diff options
Diffstat (limited to 'tests')
382 files changed, 38103 insertions, 23652 deletions
diff --git a/tests/RunSeleniumTests.php b/tests/RunSeleniumTests.php deleted file mode 100644 index 28501eaa..00000000 --- a/tests/RunSeleniumTests.php +++ /dev/null @@ -1,233 +0,0 @@ -#!/usr/bin/php -<?php -/** - * @file - * @ingroup Maintenance - * @copyright Copyright © Wikimedia Deuschland, 2009 - * @author Hallo Welt! Medienwerkstatt GmbH - * @author Markus Glaser, Dan Nessett, Priyanka Dhanda - * initial idea by Daniel Kinzler - * - * 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 - */ - -$IP = dirname( __DIR__ ); - -define( 'SELENIUMTEST', true ); - -//require_once( __DIR__ . '/../maintenance/commandLine.inc' ); -require( __DIR__ . '/../maintenance/Maintenance.php' ); - -require_once( 'PHPUnit/Runner/Version.php' ); -if( version_compare( PHPUnit_Runner_Version::id(), '3.5.0', '>=' ) ) { - # PHPUnit 3.5.0 introduced a nice autoloader based on class name - require_once( 'PHPUnit/Autoload.php' ); -} else { - # Keep the old pre PHPUnit 3.5.0 behaviour for compatibility - require_once( 'PHPUnit/TextUI/Command.php' ); -} - -require_once( 'PHPUnit/Extensions/SeleniumTestCase.php' ); -include_once( 'PHPUnit/Util/Log/JUnit.php' ); - -require_once( __DIR__ . "/selenium/SeleniumServerManager.php" ); - -class SeleniumTester extends Maintenance { - protected $selenium; - protected $serverManager; - protected $seleniumServerExecPath; - - public function __construct() { - parent::__construct(); - $this->mDescription = "Selenium Test Runner. For documentation, visit http://www.mediawiki.org/wiki/SeleniumFramework"; - $this->addOption( 'port', 'Port used by selenium server. Default: 4444', false, true ); - $this->addOption( 'host', 'Host selenium server. Default: $wgServer . $wgScriptPath', false, true ); - $this->addOption( 'testBrowser', 'The browser used during testing. Default: firefox', false, true ); - $this->addOption( 'wikiUrl', 'The Mediawiki installation to point to. Default: http://localhost', false, true ); - $this->addOption( 'username', 'The login username for sunning tests. Default: empty', false, true ); - $this->addOption( 'userPassword', 'The login password for running tests. Default: empty', false, true ); - $this->addOption( 'seleniumConfig', 'Location of the selenium config file. Default: empty', false, true ); - $this->addOption( 'list-browsers', 'List the available browsers.' ); - $this->addOption( 'verbose', 'Be noisier.' ); - $this->addOption( 'startserver', 'Start Selenium Server (on localhost) before the run.' ); - $this->addOption( 'stopserver', 'Stop Selenium Server (on localhost) after the run.' ); - $this->addOption( 'jUnitLogFile', 'Log results in a specified JUnit log file. Default: empty', false, true ); - $this->addOption( 'runAgainstGrid', 'The test will be run against a Selenium Grid. Default: false.', false, true ); - $this->deleteOption( 'dbpass' ); - $this->deleteOption( 'dbuser' ); - $this->deleteOption( 'globals' ); - $this->deleteOption( 'wiki' ); - } - - public function listBrowsers() { - $desc = "Available browsers:\n"; - - foreach ($this->selenium->getAvailableBrowsers() as $k => $v) { - $desc .= " $k => $v\n"; - } - - echo $desc; - } - - protected function startServer() { - if ( $this->seleniumServerExecPath == '' ) { - die ( "The selenium server exec path is not set in " . - "selenium_settings.ini. Cannot start server \n" . - "as requested - terminating RunSeleniumTests\n" ); - } - $this->serverManager = new SeleniumServerManager( 'true', - $this->selenium->getPort(), - $this->seleniumServerExecPath ); - switch ( $this->serverManager->start() ) { - case 'started': - break; - case 'failed': - die ( "Unable to start the Selenium Server - " . - "terminating RunSeleniumTests\n" ); - case 'running': - echo ( "Warning: The Selenium Server is " . - "already running\n" ); - break; - } - - return; - } - - protected function stopServer() { - if ( !isset ( $this->serverManager ) ) { - echo ( "Warning: Request to stop Selenium Server, but it was " . - "not stared by RunSeleniumTests\n" . - "RunSeleniumTests cannot stop a Selenium Server it " . - "did not start\n" ); - } else { - switch ( $this->serverManager->stop() ) { - case 'stopped': - break; - case 'failed': - echo ( "unable to stop the Selenium Server\n" ); - } - } - return; - } - - protected function runTests( $seleniumTestSuites = array() ) { - $result = new PHPUnit_Framework_TestResult; - $result->addListener( new SeleniumTestListener( $this->selenium->getLogger() ) ); - if ( $this->selenium->getJUnitLogFile() ) { - $jUnitListener = new PHPUnit_Util_Log_JUnit( $this->selenium->getJUnitLogFile(), true ); - $result->addListener( $jUnitListener ); - } - - foreach ( $seleniumTestSuites as $testSuiteName => $testSuiteFile ) { - require( $testSuiteFile ); - $suite = new $testSuiteName(); - $suite->setName( $testSuiteName ); - $suite->addTests(); - - try { - $suite->run( $result ); - } catch ( Testing_Selenium_Exception $e ) { - $suite->tearDown(); - throw new MWException( $e->getMessage() ); - } - } - - if ( $this->selenium->getJUnitLogFile() ) { - $jUnitListener->flush(); - } - } - - public function execute() { - global $wgServer, $wgScriptPath, $wgHooks; - - $seleniumSettings = array(); - $seleniumBrowsers = array(); - $seleniumTestSuites = array(); - - $configFile = $this->getOption( 'seleniumConfig', '' ); - if ( strlen( $configFile ) > 0 ) { - $this->output("Using Selenium Configuration file: " . $configFile . "\n"); - SeleniumConfig::getSeleniumSettings( $seleniumSettings, - $seleniumBrowsers, - $seleniumTestSuites, - $configFile ); - } elseif ( !isset( $wgHooks['SeleniumSettings'] ) ) { - $this->output("No command line, configuration file or configuration hook found.\n"); - SeleniumConfig::getSeleniumSettings( $seleniumSettings, - $seleniumBrowsers, - $seleniumTestSuites - ); - } else { - $this->output("Using 'SeleniumSettings' hook for configuration.\n"); - wfRunHooks('SeleniumSettings', array( $seleniumSettings, - $seleniumBrowsers, - $seleniumTestSuites ) ); - } - - // State for starting/stopping the Selenium server has nothing to do with the Selenium - // class. Keep this state local to SeleniumTester class. Using getOption() is clumsy, but - // the Maintenance class does not have a setOption() - if ( ! isset( $seleniumSettings['startserver'] ) ) $this->getOption( 'startserver', true ); - if ( ! isset( $seleniumSettings['stopserver'] ) ) $this->getOption( 'stopserver', true ); - if ( !isset( $seleniumSettings['seleniumserverexecpath'] ) ) $seleniumSettings['seleniumserverexecpath'] = ''; - $this->seleniumServerExecPath = $seleniumSettings['seleniumserverexecpath']; - - //set reasonable defaults if we did not find the settings - if ( !isset( $seleniumBrowsers ) ) $seleniumBrowsers = array ('firefox' => '*firefox'); - if ( !isset( $seleniumSettings['host'] ) ) $seleniumSettings['host'] = $wgServer . $wgScriptPath; - if ( !isset( $seleniumSettings['port'] ) ) $seleniumSettings['port'] = '4444'; - if ( !isset( $seleniumSettings['wikiUrl'] ) ) $seleniumSettings['wikiUrl'] = 'http://localhost'; - if ( !isset( $seleniumSettings['username'] ) ) $seleniumSettings['username'] = ''; - if ( !isset( $seleniumSettings['userPassword'] ) ) $seleniumSettings['userPassword'] = ''; - if ( !isset( $seleniumSettings['testBrowser'] ) ) $seleniumSettings['testBrowser'] = 'firefox'; - if ( !isset( $seleniumSettings['jUnitLogFile'] ) ) $seleniumSettings['jUnitLogFile'] = false; - if ( !isset( $seleniumSettings['runAgainstGrid'] ) ) $seleniumSettings['runAgainstGrid'] = false; - - // Setup Selenium class - $this->selenium = new Selenium( ); - $this->selenium->setAvailableBrowsers( $seleniumBrowsers ); - $this->selenium->setRunAgainstGrid( $this->getOption( 'runAgainstGrid', $seleniumSettings['runAgainstGrid'] ) ); - $this->selenium->setUrl( $this->getOption( 'wikiUrl', $seleniumSettings['wikiUrl'] ) ); - $this->selenium->setBrowser( $this->getOption( 'testBrowser', $seleniumSettings['testBrowser'] ) ); - $this->selenium->setPort( $this->getOption( 'port', $seleniumSettings['port'] ) ); - $this->selenium->setHost( $this->getOption( 'host', $seleniumSettings['host'] ) ); - $this->selenium->setUser( $this->getOption( 'username', $seleniumSettings['username'] ) ); - $this->selenium->setPass( $this->getOption( 'userPassword', $seleniumSettings['userPassword'] ) ); - $this->selenium->setVerbose( $this->hasOption( 'verbose' ) ); - $this->selenium->setJUnitLogFile( $this->getOption( 'jUnitLogFile', $seleniumSettings['jUnitLogFile'] ) ); - - if( $this->hasOption( 'list-browsers' ) ) { - $this->listBrowsers(); - exit(0); - } - if ( $this->hasOption( 'startserver' ) ) { - $this->startServer(); - } - - $logger = new SeleniumTestConsoleLogger; - $this->selenium->setLogger( $logger ); - - $this->runTests( $seleniumTestSuites ); - - if ( $this->hasOption( 'stopserver' ) ) { - $this->stopServer(); - } - } -} - -$maintClass = "SeleniumTester"; - -require_once( RUN_MAINTENANCE_IF_MAIN ); diff --git a/tests/TestsAutoLoader.php b/tests/TestsAutoLoader.php index 42f5f68a..00ce13c8 100644 --- a/tests/TestsAutoLoader.php +++ b/tests/TestsAutoLoader.php @@ -1,35 +1,104 @@ <?php +/** + * AutoLoader for the testing suite. + * + * 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 + * + * @file + * @ingroup Testing + */ global $wgAutoloadClasses; -$testFolder = __DIR__; +$testDir = __DIR__; $wgAutoloadClasses += array( - //PHPUnit - 'MediaWikiTestCase' => "$testFolder/phpunit/MediaWikiTestCase.php", - 'MediaWikiPHPUnitCommand' => "$testFolder/phpunit/MediaWikiPHPUnitCommand.php", - 'MediaWikiLangTestCase' => "$testFolder/phpunit/MediaWikiLangTestCase.php", - 'NewParserTest' => "$testFolder/phpunit/includes/parser/NewParserTest.php", + # tests + 'DbTestPreviewer' => "$testDir/testHelpers.inc", + 'DbTestRecorder' => "$testDir/testHelpers.inc", + 'DelayedParserTest' => "$testDir/testHelpers.inc", + 'ParserTestResult' => "$testDir/parser/ParserTestResult.php", + 'TestFileIterator' => "$testDir/testHelpers.inc", + 'TestRecorder' => "$testDir/testHelpers.inc", + 'ITestRecorder' => "$testDir/testHelpers.inc", - //includes - 'BlockTest' => "$testFolder/phpunit/includes/BlockTest.php", + # tests/phpunit + 'MediaWikiTestCase' => "$testDir/phpunit/MediaWikiTestCase.php", + 'MediaWikiPHPUnitCommand' => "$testDir/phpunit/MediaWikiPHPUnitCommand.php", + 'MediaWikiPHPUnitTestListener' => "$testDir/phpunit/MediaWikiPHPUnitTestListener.php", + 'MediaWikiLangTestCase' => "$testDir/phpunit/MediaWikiLangTestCase.php", + 'TestUser' => "$testDir/phpunit/includes/TestUser.php", - //API - 'ApiFormatTestBase' => "$testFolder/phpunit/includes/api/format/ApiFormatTestBase.php", - 'ApiTestCase' => "$testFolder/phpunit/includes/api/ApiTestCase.php", - 'TestUser' => "$testFolder/phpunit/includes/TestUser.php", - 'MockApi' => "$testFolder/phpunit/includes/api/ApiTestCase.php", - 'RandomImageGenerator' => "$testFolder/phpunit/includes/api/RandomImageGenerator.php", - 'UserWrapper' => "$testFolder/phpunit/includes/api/ApiTestCase.php", + # tests/phpunit/includes + 'BlockTest' => "$testDir/phpunit/includes/BlockTest.php", + 'RevisionStorageTest' => "$testDir/phpunit/includes/RevisionStorageTest.php", + 'WikiPageTest' => "$testDir/phpunit/includes/WikiPageTest.php", - //Selenium - 'SeleniumTestConstants' => "$testFolder/selenium/SeleniumTestConstants.php", + //db + 'ORMTableTest' => "$testDir/phpunit/includes/db/ORMTableTest.php", + 'PageORMTableForTesting' => "$testDir/phpunit/includes/db/ORMTableTest.php", + 'DatabaseTestHelper' => "$testDir/phpunit/includes/db/DatabaseTestHelper.php", - //maintenance - 'DumpTestCase' => "$testFolder/phpunit/maintenance/DumpTestCase.php", - 'BackupDumper' => "$testFolder/../maintenance/backup.inc", + # tests/phpunit/includes/api + 'ApiFormatTestBase' => "$testDir/phpunit/includes/api/format/ApiFormatTestBase.php", + 'ApiTestCase' => "$testDir/phpunit/includes/api/ApiTestCase.php", + 'ApiTestContext' => "$testDir/phpunit/includes/api/ApiTestCase.php", + 'MockApi' => "$testDir/phpunit/includes/api/ApiTestCase.php", + 'RandomImageGenerator' => "$testDir/phpunit/includes/api/RandomImageGenerator.php", + 'UserWrapper' => "$testDir/phpunit/includes/api/ApiTestCase.php", - //Generic providers - 'MediaWikiProvide' => "$testFolder/phpunit/includes/Providers.php", -); + # tests/phpunit/includes/content + 'DummyContentHandlerForTesting' => "$testDir/phpunit/includes/content/ContentHandlerTest.php", + 'DummyContentForTesting' => "$testDir/phpunit/includes/content/ContentHandlerTest.php", + 'ContentHandlerTest' => "$testDir/phpunit/includes/content/ContentHandlerTest.php", + 'JavaScriptContentTest' => "$testDir/phpunit/includes/content/JavaScriptContentTest.php", + 'TextContentTest' => "$testDir/phpunit/includes/content/TextContentTest.php", + 'WikitextContentTest' => "$testDir/phpunit/includes/content/WikitextContentTest.php", + + # tests/phpunit/includes/db + 'ORMRowTest' => "$testDir/phpunit/includes/db/ORMRowTest.php", + + # tests/phpunit/includes/parser + 'NewParserTest' => "$testDir/phpunit/includes/parser/NewParserTest.php", + 'MediaWikiParserTest' => "$testDir/phpunit/includes/parser/MediaWikiParserTest.php", + + # tests/phpunit/includes/libs + 'GenericArrayObjectTest' => "$testDir/phpunit/includes/libs/GenericArrayObjectTest.php", + + # tests/phpunit/media + 'FakeDimensionFile' => "$testDir/phpunit/includes/media/FakeDimensionFile.php", + # tests/phpunit/includes/site + 'SiteTest' => "$testDir/phpunit/includes/site/SiteTest.php", + 'TestSites' => "$testDir/phpunit/includes/site/TestSites.php", + + # tests/phpunit/mocks + 'MockFSFile' => "$testDir/phpunit/mocks/filebackend/MockFSFile.php", + 'MockFileBackend' => "$testDir/phpunit/mocks/filebackend/MockFileBackend.php", + 'MockBitmapHandler' => "$testDir/phpunit/mocks/media/MockBitmapHandler.php", + 'MockImageHandler' => "$testDir/phpunit/mocks/media/MockBitmapHandler.php", + 'MockSvgHandler' => "$testDir/phpunit/mocks/media/MockBitmapHandler.php", + + # tests/phpunit/languages + 'LanguageClassesTestCase' => "$testDir/phpunit/languages/LanguageClassesTestCase.php", + + # tests/phpunit/maintenance + 'DumpTestCase' => "$testDir/phpunit/maintenance/DumpTestCase.php", + + # tests/parser + 'ParserTest' => "$testDir/parser/parserTest.inc", + 'ParserTestParserHook' => "$testDir/parser/parserTestsParserHook.php", +); diff --git a/tests/jasmine/.htaccess b/tests/jasmine/.htaccess deleted file mode 100644 index 605d2f4c..00000000 --- a/tests/jasmine/.htaccess +++ /dev/null @@ -1 +0,0 @@ -Allow from all diff --git a/tests/jasmine/SpecRunner.html b/tests/jasmine/SpecRunner.html deleted file mode 100644 index 63d0fdfa..00000000 --- a/tests/jasmine/SpecRunner.html +++ /dev/null @@ -1,28 +0,0 @@ -<!DOCTYPE html> -<html lang="en" dir="ltr"> - <head> - <title>Jasmine Test Runner</title> - <meta charset="UTF-8" /> - <link rel="stylesheet" type="text/css" href="lib/jasmine-1.0.1/jasmine.css"> - <script src="lib/jasmine-1.0.1/jasmine.js"></script> - <script src="lib/jasmine-1.0.1/jasmine-html.js"></script> - - <!-- include source files here... --> - <script src="../../load.php?debug=true&lang=en&modules=startup&only=scripts&skin=vector&*"></script> - <script> - mw.loader.load( ['mediawiki.jqueryMsg'] ); - </script> - - <!-- insert test data files here --> - <script src="spec/mediawiki.jqueryMsg.spec.data.js"></script> - - <!-- include spec files here... --> - <script src="spec/mediawiki.jqueryMsg.spec.js"></script> - </head> -<body> - <script> - jasmine.getEnv().addReporter( new jasmine.TrivialReporter() ); - jasmine.getEnv().execute(); - </script> -</body> -</html> diff --git a/tests/jasmine/lib/jasmine-1.0.1/MIT.LICENSE b/tests/jasmine/lib/jasmine-1.0.1/MIT.LICENSE deleted file mode 100644 index 1eb9b49e..00000000 --- a/tests/jasmine/lib/jasmine-1.0.1/MIT.LICENSE +++ /dev/null @@ -1,20 +0,0 @@ -Copyright (c) 2008-2010 Pivotal Labs - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/tests/jasmine/lib/jasmine-1.0.1/jasmine-html.js b/tests/jasmine/lib/jasmine-1.0.1/jasmine-html.js deleted file mode 100644 index 81402b9c..00000000 --- a/tests/jasmine/lib/jasmine-1.0.1/jasmine-html.js +++ /dev/null @@ -1,188 +0,0 @@ -jasmine.TrivialReporter = function(doc) { - this.document = doc || document; - this.suiteDivs = {}; - this.logRunningSpecs = false; -}; - -jasmine.TrivialReporter.prototype.createDom = function(type, attrs, childrenVarArgs) { - var el = document.createElement(type); - - for (var i = 2; i < arguments.length; i++) { - var child = arguments[i]; - - if (typeof child === 'string') { - el.appendChild(document.createTextNode(child)); - } else { - if (child) { el.appendChild(child); } - } - } - - for (var attr in attrs) { - if (attr == "className") { - el[attr] = attrs[attr]; - } else { - el.setAttribute(attr, attrs[attr]); - } - } - - return el; -}; - -jasmine.TrivialReporter.prototype.reportRunnerStarting = function(runner) { - var showPassed, showSkipped; - - this.outerDiv = this.createDom('div', { className: 'jasmine_reporter' }, - this.createDom('div', { className: 'banner' }, - this.createDom('div', { className: 'logo' }, - this.createDom('a', { href: 'http://pivotal.github.com/jasmine/', target: "_blank" }, "Jasmine"), - this.createDom('span', { className: 'version' }, runner.env.versionString())), - this.createDom('div', { className: 'options' }, - "Show ", - showPassed = this.createDom('input', { id: "__jasmine_TrivialReporter_showPassed__", type: 'checkbox' }), - this.createDom('label', { "for": "__jasmine_TrivialReporter_showPassed__" }, " passed "), - showSkipped = this.createDom('input', { id: "__jasmine_TrivialReporter_showSkipped__", type: 'checkbox' }), - this.createDom('label', { "for": "__jasmine_TrivialReporter_showSkipped__" }, " skipped") - ) - ), - - this.runnerDiv = this.createDom('div', { className: 'runner running' }, - this.createDom('a', { className: 'run_spec', href: '?' }, "run all"), - this.runnerMessageSpan = this.createDom('span', {}, "Running..."), - this.finishedAtSpan = this.createDom('span', { className: 'finished-at' }, "")) - ); - - this.document.body.appendChild(this.outerDiv); - - var suites = runner.suites(); - for (var i = 0; i < suites.length; i++) { - var suite = suites[i]; - var suiteDiv = this.createDom('div', { className: 'suite' }, - this.createDom('a', { className: 'run_spec', href: '?spec=' + encodeURIComponent(suite.getFullName()) }, "run"), - this.createDom('a', { className: 'description', href: '?spec=' + encodeURIComponent(suite.getFullName()) }, suite.description)); - this.suiteDivs[suite.id] = suiteDiv; - var parentDiv = this.outerDiv; - if (suite.parentSuite) { - parentDiv = this.suiteDivs[suite.parentSuite.id]; - } - parentDiv.appendChild(suiteDiv); - } - - this.startedAt = new Date(); - - var self = this; - showPassed.onclick = function(evt) { - if (showPassed.checked) { - self.outerDiv.className += ' show-passed'; - } else { - self.outerDiv.className = self.outerDiv.className.replace(/ show-passed/, ''); - } - }; - - showSkipped.onclick = function(evt) { - if (showSkipped.checked) { - self.outerDiv.className += ' show-skipped'; - } else { - self.outerDiv.className = self.outerDiv.className.replace(/ show-skipped/, ''); - } - }; -}; - -jasmine.TrivialReporter.prototype.reportRunnerResults = function(runner) { - var results = runner.results(); - var className = (results.failedCount > 0) ? "runner failed" : "runner passed"; - this.runnerDiv.setAttribute("class", className); - //do it twice for IE - this.runnerDiv.setAttribute("className", className); - var specs = runner.specs(); - var specCount = 0; - for (var i = 0; i < specs.length; i++) { - if (this.specFilter(specs[i])) { - specCount++; - } - } - var message = "" + specCount + " spec" + (specCount == 1 ? "" : "s" ) + ", " + results.failedCount + " failure" + ((results.failedCount == 1) ? "" : "s"); - message += " in " + ((new Date().getTime() - this.startedAt.getTime()) / 1000) + "s"; - this.runnerMessageSpan.replaceChild(this.createDom('a', { className: 'description', href: '?'}, message), this.runnerMessageSpan.firstChild); - - this.finishedAtSpan.appendChild(document.createTextNode("Finished at " + new Date().toString())); -}; - -jasmine.TrivialReporter.prototype.reportSuiteResults = function(suite) { - var results = suite.results(); - var status = results.passed() ? 'passed' : 'failed'; - if (results.totalCount == 0) { // todo: change this to check results.skipped - status = 'skipped'; - } - this.suiteDivs[suite.id].className += " " + status; -}; - -jasmine.TrivialReporter.prototype.reportSpecStarting = function(spec) { - if (this.logRunningSpecs) { - this.log('>> Jasmine Running ' + spec.suite.description + ' ' + spec.description + '...'); - } -}; - -jasmine.TrivialReporter.prototype.reportSpecResults = function(spec) { - var results = spec.results(); - var status = results.passed() ? 'passed' : 'failed'; - if (results.skipped) { - status = 'skipped'; - } - var specDiv = this.createDom('div', { className: 'spec ' + status }, - this.createDom('a', { className: 'run_spec', href: '?spec=' + encodeURIComponent(spec.getFullName()) }, "run"), - this.createDom('a', { - className: 'description', - href: '?spec=' + encodeURIComponent(spec.getFullName()), - title: spec.getFullName() - }, spec.description)); - - - var resultItems = results.getItems(); - var messagesDiv = this.createDom('div', { className: 'messages' }); - for (var i = 0; i < resultItems.length; i++) { - var result = resultItems[i]; - - if (result.type == 'log') { - messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage log'}, result.toString())); - } else if (result.type == 'expect' && result.passed && !result.passed()) { - messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage fail'}, result.message)); - - if (result.trace.stack) { - messagesDiv.appendChild(this.createDom('div', {className: 'stackTrace'}, result.trace.stack)); - } - } - } - - if (messagesDiv.childNodes.length > 0) { - specDiv.appendChild(messagesDiv); - } - - this.suiteDivs[spec.suite.id].appendChild(specDiv); -}; - -jasmine.TrivialReporter.prototype.log = function() { - var console = jasmine.getGlobal().console; - if (console && console.log) { - if (console.log.apply) { - console.log.apply(console, arguments); - } else { - console.log(arguments); // ie fix: console.log.apply doesn't exist on ie - } - } -}; - -jasmine.TrivialReporter.prototype.getLocation = function() { - return this.document.location; -}; - -jasmine.TrivialReporter.prototype.specFilter = function(spec) { - var paramMap = {}; - var params = this.getLocation().search.substring(1).split('&'); - for (var i = 0; i < params.length; i++) { - var p = params[i].split('='); - paramMap[decodeURIComponent(p[0])] = decodeURIComponent(p[1]); - } - - if (!paramMap["spec"]) return true; - return spec.getFullName().indexOf(paramMap["spec"]) == 0; -}; diff --git a/tests/jasmine/lib/jasmine-1.0.1/jasmine.css b/tests/jasmine/lib/jasmine-1.0.1/jasmine.css deleted file mode 100644 index 6583fe7c..00000000 --- a/tests/jasmine/lib/jasmine-1.0.1/jasmine.css +++ /dev/null @@ -1,166 +0,0 @@ -body { - font-family: "Helvetica Neue Light", "Lucida Grande", "Calibri", "Arial", sans-serif; -} - - -.jasmine_reporter a:visited, .jasmine_reporter a { - color: #303; -} - -.jasmine_reporter a:hover, .jasmine_reporter a:active { - color: blue; -} - -.run_spec { - float:right; - padding-right: 5px; - font-size: .8em; - text-decoration: none; -} - -.jasmine_reporter { - margin: 0 5px; -} - -.banner { - color: #303; - background-color: #fef; - padding: 5px; -} - -.logo { - float: left; - font-size: 1.1em; - padding-left: 5px; -} - -.logo .version { - font-size: .6em; - padding-left: 1em; -} - -.runner.running { - background-color: yellow; -} - - -.options { - text-align: right; - font-size: .8em; -} - - - - -.suite { - border: 1px outset gray; - margin: 5px 0; - padding-left: 1em; -} - -.suite .suite { - margin: 5px; -} - -.suite.passed { - background-color: #dfd; -} - -.suite.failed { - background-color: #fdd; -} - -.spec { - margin: 5px; - padding-left: 1em; - clear: both; -} - -.spec.failed, .spec.passed, .spec.skipped { - padding-bottom: 5px; - border: 1px solid gray; -} - -.spec.failed { - background-color: #fbb; - border-color: red; -} - -.spec.passed { - background-color: #bfb; - border-color: green; -} - -.spec.skipped { - background-color: #bbb; -} - -.messages { - border-left: 1px dashed gray; - padding-left: 1em; - padding-right: 1em; -} - -.passed { - background-color: #cfc; - display: none; -} - -.failed { - background-color: #fbb; -} - -.skipped { - color: #777; - background-color: #eee; - display: none; -} - - -/*.resultMessage {*/ - /*white-space: pre;*/ -/*}*/ - -.resultMessage span.result { - display: block; - line-height: 2em; - color: black; -} - -.resultMessage .mismatch { - color: black; -} - -.stackTrace { - white-space: pre; - font-size: .8em; - margin-left: 10px; - max-height: 5em; - overflow: auto; - border: 1px inset red; - padding: 1em; - background: #eef; -} - -.finished-at { - padding-left: 1em; - font-size: .6em; -} - -.show-passed .passed, -.show-skipped .skipped { - display: block; -} - - -#jasmine_content { - position:fixed; - right: 100%; -} - -.runner { - border: 1px solid gray; - display: block; - margin: 5px 0; - padding: 2px 0 2px 10px; -} diff --git a/tests/jasmine/lib/jasmine-1.0.1/jasmine.js b/tests/jasmine/lib/jasmine-1.0.1/jasmine.js deleted file mode 100644 index 964f99ed..00000000 --- a/tests/jasmine/lib/jasmine-1.0.1/jasmine.js +++ /dev/null @@ -1,2421 +0,0 @@ -/** - * Top level namespace for Jasmine, a lightweight JavaScript BDD/spec/testing framework. - * - * @namespace - */ -var jasmine = {}; - -/** - * @private - */ -jasmine.unimplementedMethod_ = function() { - throw new Error("unimplemented method"); -}; - -/** - * Use <code>jasmine.undefined</code> instead of <code>undefined</code>, since <code>undefined</code> is just - * a plain old variable and may be redefined by somebody else. - * - * @private - */ -jasmine.undefined = jasmine.___undefined___; - -/** - * Default interval in milliseconds for event loop yields (e.g. to allow network activity or to refresh the screen with the HTML-based runner). Small values here may result in slow test running. Zero means no updates until all tests have completed. - * - */ -jasmine.DEFAULT_UPDATE_INTERVAL = 250; - -/** - * Default timeout interval in milliseconds for waitsFor() blocks. - */ -jasmine.DEFAULT_TIMEOUT_INTERVAL = 5000; - -jasmine.getGlobal = function() { - function getGlobal() { - return this; - } - - return getGlobal(); -}; - -/** - * Allows for bound functions to be compared. Internal use only. - * - * @ignore - * @private - * @param base {Object} bound 'this' for the function - * @param name {Function} function to find - */ -jasmine.bindOriginal_ = function(base, name) { - var original = base[name]; - if (original.apply) { - return function() { - return original.apply(base, arguments); - }; - } else { - // IE support - return jasmine.getGlobal()[name]; - } -}; - -jasmine.setTimeout = jasmine.bindOriginal_(jasmine.getGlobal(), 'setTimeout'); -jasmine.clearTimeout = jasmine.bindOriginal_(jasmine.getGlobal(), 'clearTimeout'); -jasmine.setInterval = jasmine.bindOriginal_(jasmine.getGlobal(), 'setInterval'); -jasmine.clearInterval = jasmine.bindOriginal_(jasmine.getGlobal(), 'clearInterval'); - -jasmine.MessageResult = function(values) { - this.type = 'log'; - this.values = values; - this.trace = new Error(); // todo: test better -}; - -jasmine.MessageResult.prototype.toString = function() { - var text = ""; - for(var i = 0; i < this.values.length; i++) { - if (i > 0) text += " "; - if (jasmine.isString_(this.values[i])) { - text += this.values[i]; - } else { - text += jasmine.pp(this.values[i]); - } - } - return text; -}; - -jasmine.ExpectationResult = function(params) { - this.type = 'expect'; - this.matcherName = params.matcherName; - this.passed_ = params.passed; - this.expected = params.expected; - this.actual = params.actual; - - this.message = this.passed_ ? 'Passed.' : params.message; - this.trace = this.passed_ ? '' : new Error(this.message); -}; - -jasmine.ExpectationResult.prototype.toString = function () { - return this.message; -}; - -jasmine.ExpectationResult.prototype.passed = function () { - return this.passed_; -}; - -/** - * Getter for the Jasmine environment. Ensures one gets created - */ -jasmine.getEnv = function() { - return jasmine.currentEnv_ = jasmine.currentEnv_ || new jasmine.Env(); -}; - -/** - * @ignore - * @private - * @param value - * @returns {Boolean} - */ -jasmine.isArray_ = function(value) { - return jasmine.isA_("Array", value); -}; - -/** - * @ignore - * @private - * @param value - * @returns {Boolean} - */ -jasmine.isString_ = function(value) { - return jasmine.isA_("String", value); -}; - -/** - * @ignore - * @private - * @param value - * @returns {Boolean} - */ -jasmine.isNumber_ = function(value) { - return jasmine.isA_("Number", value); -}; - -/** - * @ignore - * @private - * @param {String} typeName - * @param value - * @returns {Boolean} - */ -jasmine.isA_ = function(typeName, value) { - return Object.prototype.toString.apply(value) === '[object ' + typeName + ']'; -}; - -/** - * Pretty printer for expecations. Takes any object and turns it into a human-readable string. - * - * @param value {Object} an object to be outputted - * @returns {String} - */ -jasmine.pp = function(value) { - var stringPrettyPrinter = new jasmine.StringPrettyPrinter(); - stringPrettyPrinter.format(value); - return stringPrettyPrinter.string; -}; - -/** - * Returns true if the object is a DOM Node. - * - * @param {Object} obj object to check - * @returns {Boolean} - */ -jasmine.isDomNode = function(obj) { - return obj['nodeType'] > 0; -}; - -/** - * Returns a matchable 'generic' object of the class type. For use in expecations of type when values don't matter. - * - * @example - * // don't care about which function is passed in, as long as it's a function - * expect(mySpy).toHaveBeenCalledWith(jasmine.any(Function)); - * - * @param {Class} clazz - * @returns matchable object of the type clazz - */ -jasmine.any = function(clazz) { - return new jasmine.Matchers.Any(clazz); -}; - -/** - * Jasmine Spies are test doubles that can act as stubs, spies, fakes or when used in an expecation, mocks. - * - * Spies should be created in test setup, before expectations. They can then be checked, using the standard Jasmine - * expectation syntax. Spies can be checked if they were called or not and what the calling params were. - * - * A Spy has the following fields: wasCalled, callCount, mostRecentCall, and argsForCall (see docs). - * - * Spies are torn down at the end of every spec. - * - * Note: Do <b>not</b> call new jasmine.Spy() directly - a spy must be created using spyOn, jasmine.createSpy or jasmine.createSpyObj. - * - * @example - * // a stub - * var myStub = jasmine.createSpy('myStub'); // can be used anywhere - * - * // spy example - * var foo = { - * not: function(bool) { return !bool; } - * } - * - * // actual foo.not will not be called, execution stops - * spyOn(foo, 'not'); - - // foo.not spied upon, execution will continue to implementation - * spyOn(foo, 'not').andCallThrough(); - * - * // fake example - * var foo = { - * not: function(bool) { return !bool; } - * } - * - * // foo.not(val) will return val - * spyOn(foo, 'not').andCallFake(function(value) {return value;}); - * - * // mock example - * foo.not(7 == 7); - * expect(foo.not).toHaveBeenCalled(); - * expect(foo.not).toHaveBeenCalledWith(true); - * - * @constructor - * @see spyOn, jasmine.createSpy, jasmine.createSpyObj - * @param {String} name - */ -jasmine.Spy = function(name) { - /** - * The name of the spy, if provided. - */ - this.identity = name || 'unknown'; - /** - * Is this Object a spy? - */ - this.isSpy = true; - /** - * The actual function this spy stubs. - */ - this.plan = function() { - }; - /** - * Tracking of the most recent call to the spy. - * @example - * var mySpy = jasmine.createSpy('foo'); - * mySpy(1, 2); - * mySpy.mostRecentCall.args = [1, 2]; - */ - this.mostRecentCall = {}; - - /** - * Holds arguments for each call to the spy, indexed by call count - * @example - * var mySpy = jasmine.createSpy('foo'); - * mySpy(1, 2); - * mySpy(7, 8); - * mySpy.mostRecentCall.args = [7, 8]; - * mySpy.argsForCall[0] = [1, 2]; - * mySpy.argsForCall[1] = [7, 8]; - */ - this.argsForCall = []; - this.calls = []; -}; - -/** - * Tells a spy to call through to the actual implemenatation. - * - * @example - * var foo = { - * bar: function() { // do some stuff } - * } - * - * // defining a spy on an existing property: foo.bar - * spyOn(foo, 'bar').andCallThrough(); - */ -jasmine.Spy.prototype.andCallThrough = function() { - this.plan = this.originalValue; - return this; -}; - -/** - * For setting the return value of a spy. - * - * @example - * // defining a spy from scratch: foo() returns 'baz' - * var foo = jasmine.createSpy('spy on foo').andReturn('baz'); - * - * // defining a spy on an existing property: foo.bar() returns 'baz' - * spyOn(foo, 'bar').andReturn('baz'); - * - * @param {Object} value - */ -jasmine.Spy.prototype.andReturn = function(value) { - this.plan = function() { - return value; - }; - return this; -}; - -/** - * For throwing an exception when a spy is called. - * - * @example - * // defining a spy from scratch: foo() throws an exception w/ message 'ouch' - * var foo = jasmine.createSpy('spy on foo').andThrow('baz'); - * - * // defining a spy on an existing property: foo.bar() throws an exception w/ message 'ouch' - * spyOn(foo, 'bar').andThrow('baz'); - * - * @param {String} exceptionMsg - */ -jasmine.Spy.prototype.andThrow = function(exceptionMsg) { - this.plan = function() { - throw exceptionMsg; - }; - return this; -}; - -/** - * Calls an alternate implementation when a spy is called. - * - * @example - * var baz = function() { - * // do some stuff, return something - * } - * // defining a spy from scratch: foo() calls the function baz - * var foo = jasmine.createSpy('spy on foo').andCall(baz); - * - * // defining a spy on an existing property: foo.bar() calls an anonymnous function - * spyOn(foo, 'bar').andCall(function() { return 'baz';} ); - * - * @param {Function} fakeFunc - */ -jasmine.Spy.prototype.andCallFake = function(fakeFunc) { - this.plan = fakeFunc; - return this; -}; - -/** - * Resets all of a spy's the tracking variables so that it can be used again. - * - * @example - * spyOn(foo, 'bar'); - * - * foo.bar(); - * - * expect(foo.bar.callCount).toEqual(1); - * - * foo.bar.reset(); - * - * expect(foo.bar.callCount).toEqual(0); - */ -jasmine.Spy.prototype.reset = function() { - this.wasCalled = false; - this.callCount = 0; - this.argsForCall = []; - this.calls = []; - this.mostRecentCall = {}; -}; - -jasmine.createSpy = function(name) { - - var spyObj = function() { - spyObj.wasCalled = true; - spyObj.callCount++; - var args = jasmine.util.argsToArray(arguments); - spyObj.mostRecentCall.object = this; - spyObj.mostRecentCall.args = args; - spyObj.argsForCall.push(args); - spyObj.calls.push({object: this, args: args}); - return spyObj.plan.apply(this, arguments); - }; - - var spy = new jasmine.Spy(name); - - for (var prop in spy) { - spyObj[prop] = spy[prop]; - } - - spyObj.reset(); - - return spyObj; -}; - -/** - * Determines whether an object is a spy. - * - * @param {jasmine.Spy|Object} putativeSpy - * @returns {Boolean} - */ -jasmine.isSpy = function(putativeSpy) { - return putativeSpy && putativeSpy.isSpy; -}; - -/** - * Creates a more complicated spy: an Object that has every property a function that is a spy. Used for stubbing something - * large in one call. - * - * @param {String} baseName name of spy class - * @param {Array} methodNames array of names of methods to make spies - */ -jasmine.createSpyObj = function(baseName, methodNames) { - if (!jasmine.isArray_(methodNames) || methodNames.length == 0) { - throw new Error('createSpyObj requires a non-empty array of method names to create spies for'); - } - var obj = {}; - for (var i = 0; i < methodNames.length; i++) { - obj[methodNames[i]] = jasmine.createSpy(baseName + '.' + methodNames[i]); - } - return obj; -}; - -/** - * All parameters are pretty-printed and concatenated together, then written to the current spec's output. - * - * Be careful not to leave calls to <code>jasmine.log</code> in production code. - */ -jasmine.log = function() { - var spec = jasmine.getEnv().currentSpec; - spec.log.apply(spec, arguments); -}; - -/** - * Function that installs a spy on an existing object's method name. Used within a Spec to create a spy. - * - * @example - * // spy example - * var foo = { - * not: function(bool) { return !bool; } - * } - * spyOn(foo, 'not'); // actual foo.not will not be called, execution stops - * - * @see jasmine.createSpy - * @param obj - * @param methodName - * @returns a Jasmine spy that can be chained with all spy methods - */ -var spyOn = function(obj, methodName) { - return jasmine.getEnv().currentSpec.spyOn(obj, methodName); -}; - -/** - * Creates a Jasmine spec that will be added to the current suite. - * - * // TODO: pending tests - * - * @example - * it('should be true', function() { - * expect(true).toEqual(true); - * }); - * - * @param {String} desc description of this specification - * @param {Function} func defines the preconditions and expectations of the spec - */ -var it = function(desc, func) { - return jasmine.getEnv().it(desc, func); -}; - -/** - * Creates a <em>disabled</em> Jasmine spec. - * - * A convenience method that allows existing specs to be disabled temporarily during development. - * - * @param {String} desc description of this specification - * @param {Function} func defines the preconditions and expectations of the spec - */ -var xit = function(desc, func) { - return jasmine.getEnv().xit(desc, func); -}; - -/** - * Starts a chain for a Jasmine expectation. - * - * It is passed an Object that is the actual value and should chain to one of the many - * jasmine.Matchers functions. - * - * @param {Object} actual Actual value to test against and expected value - */ -var expect = function(actual) { - return jasmine.getEnv().currentSpec.expect(actual); -}; - -/** - * Defines part of a jasmine spec. Used in cominbination with waits or waitsFor in asynchrnous specs. - * - * @param {Function} func Function that defines part of a jasmine spec. - */ -var runs = function(func) { - jasmine.getEnv().currentSpec.runs(func); -}; - -/** - * Waits a fixed time period before moving to the next block. - * - * @deprecated Use waitsFor() instead - * @param {Number} timeout milliseconds to wait - */ -var waits = function(timeout) { - jasmine.getEnv().currentSpec.waits(timeout); -}; - -/** - * Waits for the latchFunction to return true before proceeding to the next block. - * - * @param {Function} latchFunction - * @param {String} optional_timeoutMessage - * @param {Number} optional_timeout - */ -var waitsFor = function(latchFunction, optional_timeoutMessage, optional_timeout) { - jasmine.getEnv().currentSpec.waitsFor.apply(jasmine.getEnv().currentSpec, arguments); -}; - -/** - * A function that is called before each spec in a suite. - * - * Used for spec setup, including validating assumptions. - * - * @param {Function} beforeEachFunction - */ -var beforeEach = function(beforeEachFunction) { - jasmine.getEnv().beforeEach(beforeEachFunction); -}; - -/** - * A function that is called after each spec in a suite. - * - * Used for restoring any state that is hijacked during spec execution. - * - * @param {Function} afterEachFunction - */ -var afterEach = function(afterEachFunction) { - jasmine.getEnv().afterEach(afterEachFunction); -}; - -/** - * Defines a suite of specifications. - * - * Stores the description and all defined specs in the Jasmine environment as one suite of specs. Variables declared - * are accessible by calls to beforeEach, it, and afterEach. Describe blocks can be nested, allowing for specialization - * of setup in some tests. - * - * @example - * // TODO: a simple suite - * - * // TODO: a simple suite with a nested describe block - * - * @param {String} description A string, usually the class under test. - * @param {Function} specDefinitions function that defines several specs. - */ -var describe = function(description, specDefinitions) { - return jasmine.getEnv().describe(description, specDefinitions); -}; - -/** - * Disables a suite of specifications. Used to disable some suites in a file, or files, temporarily during development. - * - * @param {String} description A string, usually the class under test. - * @param {Function} specDefinitions function that defines several specs. - */ -var xdescribe = function(description, specDefinitions) { - return jasmine.getEnv().xdescribe(description, specDefinitions); -}; - - -// Provide the XMLHttpRequest class for IE 5.x-6.x: -jasmine.XmlHttpRequest = (typeof XMLHttpRequest == "undefined") ? function() { - try { - return new ActiveXObject("Msxml2.XMLHTTP.6.0"); - } catch(e) { - } - try { - return new ActiveXObject("Msxml2.XMLHTTP.3.0"); - } catch(e) { - } - try { - return new ActiveXObject("Msxml2.XMLHTTP"); - } catch(e) { - } - try { - return new ActiveXObject("Microsoft.XMLHTTP"); - } catch(e) { - } - throw new Error("This browser does not support XMLHttpRequest."); -} : XMLHttpRequest; -/** - * @namespace - */ -jasmine.util = {}; - -/** - * Declare that a child class inherit it's prototype from the parent class. - * - * @private - * @param {Function} childClass - * @param {Function} parentClass - */ -jasmine.util.inherit = function(childClass, parentClass) { - /** - * @private - */ - var subclass = function() { - }; - subclass.prototype = parentClass.prototype; - childClass.prototype = new subclass; -}; - -jasmine.util.formatException = function(e) { - var lineNumber; - if (e.line) { - lineNumber = e.line; - } - else if (e.lineNumber) { - lineNumber = e.lineNumber; - } - - var file; - - if (e.sourceURL) { - file = e.sourceURL; - } - else if (e.fileName) { - file = e.fileName; - } - - var message = (e.name && e.message) ? (e.name + ': ' + e.message) : e.toString(); - - if (file && lineNumber) { - message += ' in ' + file + ' (line ' + lineNumber + ')'; - } - - return message; -}; - -jasmine.util.htmlEscape = function(str) { - if (!str) return str; - return str.replace(/&/g, '&') - .replace(/</g, '<') - .replace(/>/g, '>'); -}; - -jasmine.util.argsToArray = function(args) { - var arrayOfArgs = []; - for (var i = 0; i < args.length; i++) arrayOfArgs.push(args[i]); - return arrayOfArgs; -}; - -jasmine.util.extend = function(destination, source) { - for (var property in source) destination[property] = source[property]; - return destination; -}; - -/** - * Environment for Jasmine - * - * @constructor - */ -jasmine.Env = function() { - this.currentSpec = null; - this.currentSuite = null; - this.currentRunner_ = new jasmine.Runner(this); - - this.reporter = new jasmine.MultiReporter(); - - this.updateInterval = jasmine.DEFAULT_UPDATE_INTERVAL; - this.defaultTimeoutInterval = jasmine.DEFAULT_TIMEOUT_INTERVAL; - this.lastUpdate = 0; - this.specFilter = function() { - return true; - }; - - this.nextSpecId_ = 0; - this.nextSuiteId_ = 0; - this.equalityTesters_ = []; - - // wrap matchers - this.matchersClass = function() { - jasmine.Matchers.apply(this, arguments); - }; - jasmine.util.inherit(this.matchersClass, jasmine.Matchers); - - jasmine.Matchers.wrapInto_(jasmine.Matchers.prototype, this.matchersClass); -}; - - -jasmine.Env.prototype.setTimeout = jasmine.setTimeout; -jasmine.Env.prototype.clearTimeout = jasmine.clearTimeout; -jasmine.Env.prototype.setInterval = jasmine.setInterval; -jasmine.Env.prototype.clearInterval = jasmine.clearInterval; - -/** - * @returns an object containing jasmine version build info, if set. - */ -jasmine.Env.prototype.version = function () { - if (jasmine.version_) { - return jasmine.version_; - } else { - throw new Error('Version not set'); - } -}; - -/** - * @returns string containing jasmine version build info, if set. - */ -jasmine.Env.prototype.versionString = function() { - if (jasmine.version_) { - var version = this.version(); - return version.major + "." + version.minor + "." + version.build + " revision " + version.revision; - } else { - return "version unknown"; - } -}; - -/** - * @returns a sequential integer starting at 0 - */ -jasmine.Env.prototype.nextSpecId = function () { - return this.nextSpecId_++; -}; - -/** - * @returns a sequential integer starting at 0 - */ -jasmine.Env.prototype.nextSuiteId = function () { - return this.nextSuiteId_++; -}; - -/** - * Register a reporter to receive status updates from Jasmine. - * @param {jasmine.Reporter} reporter An object which will receive status updates. - */ -jasmine.Env.prototype.addReporter = function(reporter) { - this.reporter.addReporter(reporter); -}; - -jasmine.Env.prototype.execute = function() { - this.currentRunner_.execute(); -}; - -jasmine.Env.prototype.describe = function(description, specDefinitions) { - var suite = new jasmine.Suite(this, description, specDefinitions, this.currentSuite); - - var parentSuite = this.currentSuite; - if (parentSuite) { - parentSuite.add(suite); - } else { - this.currentRunner_.add(suite); - } - - this.currentSuite = suite; - - var declarationError = null; - try { - specDefinitions.call(suite); - } catch(e) { - declarationError = e; - } - - this.currentSuite = parentSuite; - - if (declarationError) { - this.it("encountered a declaration exception", function() { - throw declarationError; - }); - } - - return suite; -}; - -jasmine.Env.prototype.beforeEach = function(beforeEachFunction) { - if (this.currentSuite) { - this.currentSuite.beforeEach(beforeEachFunction); - } else { - this.currentRunner_.beforeEach(beforeEachFunction); - } -}; - -jasmine.Env.prototype.currentRunner = function () { - return this.currentRunner_; -}; - -jasmine.Env.prototype.afterEach = function(afterEachFunction) { - if (this.currentSuite) { - this.currentSuite.afterEach(afterEachFunction); - } else { - this.currentRunner_.afterEach(afterEachFunction); - } - -}; - -jasmine.Env.prototype.xdescribe = function(desc, specDefinitions) { - return { - execute: function() { - } - }; -}; - -jasmine.Env.prototype.it = function(description, func) { - var spec = new jasmine.Spec(this, this.currentSuite, description); - this.currentSuite.add(spec); - this.currentSpec = spec; - - if (func) { - spec.runs(func); - } - - return spec; -}; - -jasmine.Env.prototype.xit = function(desc, func) { - return { - id: this.nextSpecId(), - runs: function() { - } - }; -}; - -jasmine.Env.prototype.compareObjects_ = function(a, b, mismatchKeys, mismatchValues) { - if (a.__Jasmine_been_here_before__ === b && b.__Jasmine_been_here_before__ === a) { - return true; - } - - a.__Jasmine_been_here_before__ = b; - b.__Jasmine_been_here_before__ = a; - - var hasKey = function(obj, keyName) { - return obj != null && obj[keyName] !== jasmine.undefined; - }; - - for (var property in b) { - if (!hasKey(a, property) && hasKey(b, property)) { - mismatchKeys.push("expected has key '" + property + "', but missing from actual."); - } - } - for (property in a) { - if (!hasKey(b, property) && hasKey(a, property)) { - mismatchKeys.push("expected missing key '" + property + "', but present in actual."); - } - } - for (property in b) { - if (property == '__Jasmine_been_here_before__') continue; - if (!this.equals_(a[property], b[property], mismatchKeys, mismatchValues)) { - mismatchValues.push("'" + property + "' was '" + (b[property] ? jasmine.util.htmlEscape(b[property].toString()) : b[property]) + "' in expected, but was '" + (a[property] ? jasmine.util.htmlEscape(a[property].toString()) : a[property]) + "' in actual."); - } - } - - if (jasmine.isArray_(a) && jasmine.isArray_(b) && a.length != b.length) { - mismatchValues.push("arrays were not the same length"); - } - - delete a.__Jasmine_been_here_before__; - delete b.__Jasmine_been_here_before__; - return (mismatchKeys.length == 0 && mismatchValues.length == 0); -}; - -jasmine.Env.prototype.equals_ = function(a, b, mismatchKeys, mismatchValues) { - mismatchKeys = mismatchKeys || []; - mismatchValues = mismatchValues || []; - - for (var i = 0; i < this.equalityTesters_.length; i++) { - var equalityTester = this.equalityTesters_[i]; - var result = equalityTester(a, b, this, mismatchKeys, mismatchValues); - if (result !== jasmine.undefined) return result; - } - - if (a === b) return true; - - if (a === jasmine.undefined || a === null || b === jasmine.undefined || b === null) { - return (a == jasmine.undefined && b == jasmine.undefined); - } - - if (jasmine.isDomNode(a) && jasmine.isDomNode(b)) { - return a === b; - } - - if (a instanceof Date && b instanceof Date) { - return a.getTime() == b.getTime(); - } - - if (a instanceof jasmine.Matchers.Any) { - return a.matches(b); - } - - if (b instanceof jasmine.Matchers.Any) { - return b.matches(a); - } - - if (jasmine.isString_(a) && jasmine.isString_(b)) { - return (a == b); - } - - if (jasmine.isNumber_(a) && jasmine.isNumber_(b)) { - return (a == b); - } - - if (typeof a === "object" && typeof b === "object") { - return this.compareObjects_(a, b, mismatchKeys, mismatchValues); - } - - //Straight check - return (a === b); -}; - -jasmine.Env.prototype.contains_ = function(haystack, needle) { - if (jasmine.isArray_(haystack)) { - for (var i = 0; i < haystack.length; i++) { - if (this.equals_(haystack[i], needle)) return true; - } - return false; - } - return haystack.indexOf(needle) >= 0; -}; - -jasmine.Env.prototype.addEqualityTester = function(equalityTester) { - this.equalityTesters_.push(equalityTester); -}; -/** No-op base class for Jasmine reporters. - * - * @constructor - */ -jasmine.Reporter = function() { -}; - -//noinspection JSUnusedLocalSymbols -jasmine.Reporter.prototype.reportRunnerStarting = function(runner) { -}; - -//noinspection JSUnusedLocalSymbols -jasmine.Reporter.prototype.reportRunnerResults = function(runner) { -}; - -//noinspection JSUnusedLocalSymbols -jasmine.Reporter.prototype.reportSuiteResults = function(suite) { -}; - -//noinspection JSUnusedLocalSymbols -jasmine.Reporter.prototype.reportSpecStarting = function(spec) { -}; - -//noinspection JSUnusedLocalSymbols -jasmine.Reporter.prototype.reportSpecResults = function(spec) { -}; - -//noinspection JSUnusedLocalSymbols -jasmine.Reporter.prototype.log = function(str) { -}; - -/** - * Blocks are functions with executable code that make up a spec. - * - * @constructor - * @param {jasmine.Env} env - * @param {Function} func - * @param {jasmine.Spec} spec - */ -jasmine.Block = function(env, func, spec) { - this.env = env; - this.func = func; - this.spec = spec; -}; - -jasmine.Block.prototype.execute = function(onComplete) { - try { - this.func.apply(this.spec); - } catch (e) { - this.spec.fail(e); - } - onComplete(); -}; -/** JavaScript API reporter. - * - * @constructor - */ -jasmine.JsApiReporter = function() { - this.started = false; - this.finished = false; - this.suites_ = []; - this.results_ = {}; -}; - -jasmine.JsApiReporter.prototype.reportRunnerStarting = function(runner) { - this.started = true; - var suites = runner.topLevelSuites(); - for (var i = 0; i < suites.length; i++) { - var suite = suites[i]; - this.suites_.push(this.summarize_(suite)); - } -}; - -jasmine.JsApiReporter.prototype.suites = function() { - return this.suites_; -}; - -jasmine.JsApiReporter.prototype.summarize_ = function(suiteOrSpec) { - var isSuite = suiteOrSpec instanceof jasmine.Suite; - var summary = { - id: suiteOrSpec.id, - name: suiteOrSpec.description, - type: isSuite ? 'suite' : 'spec', - children: [] - }; - - if (isSuite) { - var children = suiteOrSpec.children(); - for (var i = 0; i < children.length; i++) { - summary.children.push(this.summarize_(children[i])); - } - } - return summary; -}; - -jasmine.JsApiReporter.prototype.results = function() { - return this.results_; -}; - -jasmine.JsApiReporter.prototype.resultsForSpec = function(specId) { - return this.results_[specId]; -}; - -//noinspection JSUnusedLocalSymbols -jasmine.JsApiReporter.prototype.reportRunnerResults = function(runner) { - this.finished = true; -}; - -//noinspection JSUnusedLocalSymbols -jasmine.JsApiReporter.prototype.reportSuiteResults = function(suite) { -}; - -//noinspection JSUnusedLocalSymbols -jasmine.JsApiReporter.prototype.reportSpecResults = function(spec) { - this.results_[spec.id] = { - messages: spec.results().getItems(), - result: spec.results().failedCount > 0 ? "failed" : "passed" - }; -}; - -//noinspection JSUnusedLocalSymbols -jasmine.JsApiReporter.prototype.log = function(str) { -}; - -jasmine.JsApiReporter.prototype.resultsForSpecs = function(specIds){ - var results = {}; - for (var i = 0; i < specIds.length; i++) { - var specId = specIds[i]; - results[specId] = this.summarizeResult_(this.results_[specId]); - } - return results; -}; - -jasmine.JsApiReporter.prototype.summarizeResult_ = function(result){ - var summaryMessages = []; - var messagesLength = result.messages.length; - for (var messageIndex = 0; messageIndex < messagesLength; messageIndex++) { - var resultMessage = result.messages[messageIndex]; - summaryMessages.push({ - text: resultMessage.type == 'log' ? resultMessage.toString() : jasmine.undefined, - passed: resultMessage.passed ? resultMessage.passed() : true, - type: resultMessage.type, - message: resultMessage.message, - trace: { - stack: resultMessage.passed && !resultMessage.passed() ? resultMessage.trace.stack : jasmine.undefined - } - }); - } - - return { - result : result.result, - messages : summaryMessages - }; -}; - -/** - * @constructor - * @param {jasmine.Env} env - * @param actual - * @param {jasmine.Spec} spec - */ -jasmine.Matchers = function(env, actual, spec, opt_isNot) { - this.env = env; - this.actual = actual; - this.spec = spec; - this.isNot = opt_isNot || false; - this.reportWasCalled_ = false; -}; - -// todo: @deprecated as of Jasmine 0.11, remove soon [xw] -jasmine.Matchers.pp = function(str) { - throw new Error("jasmine.Matchers.pp() is no longer supported, please use jasmine.pp() instead!"); -}; - -// todo: @deprecated Deprecated as of Jasmine 0.10. Rewrite your custom matchers to return true or false. [xw] -jasmine.Matchers.prototype.report = function(result, failing_message, details) { - throw new Error("As of jasmine 0.11, custom matchers must be implemented differently -- please see jasmine docs"); -}; - -jasmine.Matchers.wrapInto_ = function(prototype, matchersClass) { - for (var methodName in prototype) { - if (methodName == 'report') continue; - var orig = prototype[methodName]; - matchersClass.prototype[methodName] = jasmine.Matchers.matcherFn_(methodName, orig); - } -}; - -jasmine.Matchers.matcherFn_ = function(matcherName, matcherFunction) { - return function() { - var matcherArgs = jasmine.util.argsToArray(arguments); - var result = matcherFunction.apply(this, arguments); - - if (this.isNot) { - result = !result; - } - - if (this.reportWasCalled_) return result; - - var message; - if (!result) { - if (this.message) { - message = this.message.apply(this, arguments); - if (jasmine.isArray_(message)) { - message = message[this.isNot ? 1 : 0]; - } - } else { - var englishyPredicate = matcherName.replace(/[A-Z]/g, function(s) { return ' ' + s.toLowerCase(); }); - message = "Expected " + jasmine.pp(this.actual) + (this.isNot ? " not " : " ") + englishyPredicate; - if (matcherArgs.length > 0) { - for (var i = 0; i < matcherArgs.length; i++) { - if (i > 0) message += ","; - message += " " + jasmine.pp(matcherArgs[i]); - } - } - message += "."; - } - } - var expectationResult = new jasmine.ExpectationResult({ - matcherName: matcherName, - passed: result, - expected: matcherArgs.length > 1 ? matcherArgs : matcherArgs[0], - actual: this.actual, - message: message - }); - this.spec.addMatcherResult(expectationResult); - return jasmine.undefined; - }; -}; - - - - -/** - * toBe: compares the actual to the expected using === - * @param expected - */ -jasmine.Matchers.prototype.toBe = function(expected) { - return this.actual === expected; -}; - -/** - * toNotBe: compares the actual to the expected using !== - * @param expected - * @deprecated as of 1.0. Use not.toBe() instead. - */ -jasmine.Matchers.prototype.toNotBe = function(expected) { - return this.actual !== expected; -}; - -/** - * toEqual: compares the actual to the expected using common sense equality. Handles Objects, Arrays, etc. - * - * @param expected - */ -jasmine.Matchers.prototype.toEqual = function(expected) { - return this.env.equals_(this.actual, expected); -}; - -/** - * toNotEqual: compares the actual to the expected using the ! of jasmine.Matchers.toEqual - * @param expected - * @deprecated as of 1.0. Use not.toNotEqual() instead. - */ -jasmine.Matchers.prototype.toNotEqual = function(expected) { - return !this.env.equals_(this.actual, expected); -}; - -/** - * Matcher that compares the actual to the expected using a regular expression. Constructs a RegExp, so takes - * a pattern or a String. - * - * @param expected - */ -jasmine.Matchers.prototype.toMatch = function(expected) { - return new RegExp(expected).test(this.actual); -}; - -/** - * Matcher that compares the actual to the expected using the boolean inverse of jasmine.Matchers.toMatch - * @param expected - * @deprecated as of 1.0. Use not.toMatch() instead. - */ -jasmine.Matchers.prototype.toNotMatch = function(expected) { - return !(new RegExp(expected).test(this.actual)); -}; - -/** - * Matcher that compares the actual to jasmine.undefined. - */ -jasmine.Matchers.prototype.toBeDefined = function() { - return (this.actual !== jasmine.undefined); -}; - -/** - * Matcher that compares the actual to jasmine.undefined. - */ -jasmine.Matchers.prototype.toBeUndefined = function() { - return (this.actual === jasmine.undefined); -}; - -/** - * Matcher that compares the actual to null. - */ -jasmine.Matchers.prototype.toBeNull = function() { - return (this.actual === null); -}; - -/** - * Matcher that boolean not-nots the actual. - */ -jasmine.Matchers.prototype.toBeTruthy = function() { - return !!this.actual; -}; - - -/** - * Matcher that boolean nots the actual. - */ -jasmine.Matchers.prototype.toBeFalsy = function() { - return !this.actual; -}; - - -/** - * Matcher that checks to see if the actual, a Jasmine spy, was called. - */ -jasmine.Matchers.prototype.toHaveBeenCalled = function() { - if (arguments.length > 0) { - throw new Error('toHaveBeenCalled does not take arguments, use toHaveBeenCalledWith'); - } - - if (!jasmine.isSpy(this.actual)) { - throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.'); - } - - this.message = function() { - return [ - "Expected spy " + this.actual.identity + " to have been called.", - "Expected spy " + this.actual.identity + " not to have been called." - ]; - }; - - return this.actual.wasCalled; -}; - -/** @deprecated Use expect(xxx).toHaveBeenCalled() instead */ -jasmine.Matchers.prototype.wasCalled = jasmine.Matchers.prototype.toHaveBeenCalled; - -/** - * Matcher that checks to see if the actual, a Jasmine spy, was not called. - * - * @deprecated Use expect(xxx).not.toHaveBeenCalled() instead - */ -jasmine.Matchers.prototype.wasNotCalled = function() { - if (arguments.length > 0) { - throw new Error('wasNotCalled does not take arguments'); - } - - if (!jasmine.isSpy(this.actual)) { - throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.'); - } - - this.message = function() { - return [ - "Expected spy " + this.actual.identity + " to not have been called.", - "Expected spy " + this.actual.identity + " to have been called." - ]; - }; - - return !this.actual.wasCalled; -}; - -/** - * Matcher that checks to see if the actual, a Jasmine spy, was called with a set of parameters. - * - * @example - * - */ -jasmine.Matchers.prototype.toHaveBeenCalledWith = function() { - var expectedArgs = jasmine.util.argsToArray(arguments); - if (!jasmine.isSpy(this.actual)) { - throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.'); - } - this.message = function() { - if (this.actual.callCount == 0) { - // todo: what should the failure message for .not.toHaveBeenCalledWith() be? is this right? test better. [xw] - return [ - "Expected spy to have been called with " + jasmine.pp(expectedArgs) + " but it was never called.", - "Expected spy not to have been called with " + jasmine.pp(expectedArgs) + " but it was." - ]; - } else { - return [ - "Expected spy to have been called with " + jasmine.pp(expectedArgs) + " but was called with " + jasmine.pp(this.actual.argsForCall), - "Expected spy not to have been called with " + jasmine.pp(expectedArgs) + " but was called with " + jasmine.pp(this.actual.argsForCall) - ]; - } - }; - - return this.env.contains_(this.actual.argsForCall, expectedArgs); -}; - -/** @deprecated Use expect(xxx).toHaveBeenCalledWith() instead */ -jasmine.Matchers.prototype.wasCalledWith = jasmine.Matchers.prototype.toHaveBeenCalledWith; - -/** @deprecated Use expect(xxx).not.toHaveBeenCalledWith() instead */ -jasmine.Matchers.prototype.wasNotCalledWith = function() { - var expectedArgs = jasmine.util.argsToArray(arguments); - if (!jasmine.isSpy(this.actual)) { - throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.'); - } - - this.message = function() { - return [ - "Expected spy not to have been called with " + jasmine.pp(expectedArgs) + " but it was", - "Expected spy to have been called with " + jasmine.pp(expectedArgs) + " but it was" - ] - }; - - return !this.env.contains_(this.actual.argsForCall, expectedArgs); -}; - -/** - * Matcher that checks that the expected item is an element in the actual Array. - * - * @param {Object} expected - */ -jasmine.Matchers.prototype.toContain = function(expected) { - return this.env.contains_(this.actual, expected); -}; - -/** - * Matcher that checks that the expected item is NOT an element in the actual Array. - * - * @param {Object} expected - * @deprecated as of 1.0. Use not.toNotContain() instead. - */ -jasmine.Matchers.prototype.toNotContain = function(expected) { - return !this.env.contains_(this.actual, expected); -}; - -jasmine.Matchers.prototype.toBeLessThan = function(expected) { - return this.actual < expected; -}; - -jasmine.Matchers.prototype.toBeGreaterThan = function(expected) { - return this.actual > expected; -}; - -/** - * Matcher that checks that the expected exception was thrown by the actual. - * - * @param {String} expected - */ -jasmine.Matchers.prototype.toThrow = function(expected) { - var result = false; - var exception; - if (typeof this.actual != 'function') { - throw new Error('Actual is not a function'); - } - try { - this.actual(); - } catch (e) { - exception = e; - } - if (exception) { - result = (expected === jasmine.undefined || this.env.equals_(exception.message || exception, expected.message || expected)); - } - - var not = this.isNot ? "not " : ""; - - this.message = function() { - if (exception && (expected === jasmine.undefined || !this.env.equals_(exception.message || exception, expected.message || expected))) { - return ["Expected function " + not + "to throw", expected ? expected.message || expected : " an exception", ", but it threw", exception.message || exception].join(' '); - } else { - return "Expected function to throw an exception."; - } - }; - - return result; -}; - -jasmine.Matchers.Any = function(expectedClass) { - this.expectedClass = expectedClass; -}; - -jasmine.Matchers.Any.prototype.matches = function(other) { - if (this.expectedClass == String) { - return typeof other == 'string' || other instanceof String; - } - - if (this.expectedClass == Number) { - return typeof other == 'number' || other instanceof Number; - } - - if (this.expectedClass == Function) { - return typeof other == 'function' || other instanceof Function; - } - - if (this.expectedClass == Object) { - return typeof other == 'object'; - } - - return other instanceof this.expectedClass; -}; - -jasmine.Matchers.Any.prototype.toString = function() { - return '<jasmine.any(' + this.expectedClass + ')>'; -}; - -/** - * @constructor - */ -jasmine.MultiReporter = function() { - this.subReporters_ = []; -}; -jasmine.util.inherit(jasmine.MultiReporter, jasmine.Reporter); - -jasmine.MultiReporter.prototype.addReporter = function(reporter) { - this.subReporters_.push(reporter); -}; - -(function() { - var functionNames = [ - "reportRunnerStarting", - "reportRunnerResults", - "reportSuiteResults", - "reportSpecStarting", - "reportSpecResults", - "log" - ]; - for (var i = 0; i < functionNames.length; i++) { - var functionName = functionNames[i]; - jasmine.MultiReporter.prototype[functionName] = (function(functionName) { - return function() { - for (var j = 0; j < this.subReporters_.length; j++) { - var subReporter = this.subReporters_[j]; - if (subReporter[functionName]) { - subReporter[functionName].apply(subReporter, arguments); - } - } - }; - })(functionName); - } -})(); -/** - * Holds results for a set of Jasmine spec. Allows for the results array to hold another jasmine.NestedResults - * - * @constructor - */ -jasmine.NestedResults = function() { - /** - * The total count of results - */ - this.totalCount = 0; - /** - * Number of passed results - */ - this.passedCount = 0; - /** - * Number of failed results - */ - this.failedCount = 0; - /** - * Was this suite/spec skipped? - */ - this.skipped = false; - /** - * @ignore - */ - this.items_ = []; -}; - -/** - * Roll up the result counts. - * - * @param result - */ -jasmine.NestedResults.prototype.rollupCounts = function(result) { - this.totalCount += result.totalCount; - this.passedCount += result.passedCount; - this.failedCount += result.failedCount; -}; - -/** - * Adds a log message. - * @param values Array of message parts which will be concatenated later. - */ -jasmine.NestedResults.prototype.log = function(values) { - this.items_.push(new jasmine.MessageResult(values)); -}; - -/** - * Getter for the results: message & results. - */ -jasmine.NestedResults.prototype.getItems = function() { - return this.items_; -}; - -/** - * Adds a result, tracking counts (total, passed, & failed) - * @param {jasmine.ExpectationResult|jasmine.NestedResults} result - */ -jasmine.NestedResults.prototype.addResult = function(result) { - if (result.type != 'log') { - if (result.items_) { - this.rollupCounts(result); - } else { - this.totalCount++; - if (result.passed()) { - this.passedCount++; - } else { - this.failedCount++; - } - } - } - this.items_.push(result); -}; - -/** - * @returns {Boolean} True if <b>everything</b> below passed - */ -jasmine.NestedResults.prototype.passed = function() { - return this.passedCount === this.totalCount; -}; -/** - * Base class for pretty printing for expectation results. - */ -jasmine.PrettyPrinter = function() { - this.ppNestLevel_ = 0; -}; - -/** - * Formats a value in a nice, human-readable string. - * - * @param value - */ -jasmine.PrettyPrinter.prototype.format = function(value) { - if (this.ppNestLevel_ > 40) { - throw new Error('jasmine.PrettyPrinter: format() nested too deeply!'); - } - - this.ppNestLevel_++; - try { - if (value === jasmine.undefined) { - this.emitScalar('undefined'); - } else if (value === null) { - this.emitScalar('null'); - } else if (value === jasmine.getGlobal()) { - this.emitScalar('<global>'); - } else if (value instanceof jasmine.Matchers.Any) { - this.emitScalar(value.toString()); - } else if (typeof value === 'string') { - this.emitString(value); - } else if (jasmine.isSpy(value)) { - this.emitScalar("spy on " + value.identity); - } else if (value instanceof RegExp) { - this.emitScalar(value.toString()); - } else if (typeof value === 'function') { - this.emitScalar('Function'); - } else if (typeof value.nodeType === 'number') { - this.emitScalar('HTMLNode'); - } else if (value instanceof Date) { - this.emitScalar('Date(' + value + ')'); - } else if (value.__Jasmine_been_here_before__) { - this.emitScalar('<circular reference: ' + (jasmine.isArray_(value) ? 'Array' : 'Object') + '>'); - } else if (jasmine.isArray_(value) || typeof value == 'object') { - value.__Jasmine_been_here_before__ = true; - if (jasmine.isArray_(value)) { - this.emitArray(value); - } else { - this.emitObject(value); - } - delete value.__Jasmine_been_here_before__; - } else { - this.emitScalar(value.toString()); - } - } finally { - this.ppNestLevel_--; - } -}; - -jasmine.PrettyPrinter.prototype.iterateObject = function(obj, fn) { - for (var property in obj) { - if (property == '__Jasmine_been_here_before__') continue; - fn(property, obj.__lookupGetter__ ? (obj.__lookupGetter__(property) != null) : false); - } -}; - -jasmine.PrettyPrinter.prototype.emitArray = jasmine.unimplementedMethod_; -jasmine.PrettyPrinter.prototype.emitObject = jasmine.unimplementedMethod_; -jasmine.PrettyPrinter.prototype.emitScalar = jasmine.unimplementedMethod_; -jasmine.PrettyPrinter.prototype.emitString = jasmine.unimplementedMethod_; - -jasmine.StringPrettyPrinter = function() { - jasmine.PrettyPrinter.call(this); - - this.string = ''; -}; -jasmine.util.inherit(jasmine.StringPrettyPrinter, jasmine.PrettyPrinter); - -jasmine.StringPrettyPrinter.prototype.emitScalar = function(value) { - this.append(value); -}; - -jasmine.StringPrettyPrinter.prototype.emitString = function(value) { - this.append("'" + value + "'"); -}; - -jasmine.StringPrettyPrinter.prototype.emitArray = function(array) { - this.append('[ '); - for (var i = 0; i < array.length; i++) { - if (i > 0) { - this.append(', '); - } - this.format(array[i]); - } - this.append(' ]'); -}; - -jasmine.StringPrettyPrinter.prototype.emitObject = function(obj) { - var self = this; - this.append('{ '); - var first = true; - - this.iterateObject(obj, function(property, isGetter) { - if (first) { - first = false; - } else { - self.append(', '); - } - - self.append(property); - self.append(' : '); - if (isGetter) { - self.append('<getter>'); - } else { - self.format(obj[property]); - } - }); - - this.append(' }'); -}; - -jasmine.StringPrettyPrinter.prototype.append = function(value) { - this.string += value; -}; -jasmine.Queue = function(env) { - this.env = env; - this.blocks = []; - this.running = false; - this.index = 0; - this.offset = 0; - this.abort = false; -}; - -jasmine.Queue.prototype.addBefore = function(block) { - this.blocks.unshift(block); -}; - -jasmine.Queue.prototype.add = function(block) { - this.blocks.push(block); -}; - -jasmine.Queue.prototype.insertNext = function(block) { - this.blocks.splice((this.index + this.offset + 1), 0, block); - this.offset++; -}; - -jasmine.Queue.prototype.start = function(onComplete) { - this.running = true; - this.onComplete = onComplete; - this.next_(); -}; - -jasmine.Queue.prototype.isRunning = function() { - return this.running; -}; - -jasmine.Queue.LOOP_DONT_RECURSE = true; - -jasmine.Queue.prototype.next_ = function() { - var self = this; - var goAgain = true; - - while (goAgain) { - goAgain = false; - - if (self.index < self.blocks.length && !this.abort) { - var calledSynchronously = true; - var completedSynchronously = false; - - var onComplete = function () { - if (jasmine.Queue.LOOP_DONT_RECURSE && calledSynchronously) { - completedSynchronously = true; - return; - } - - if (self.blocks[self.index].abort) { - self.abort = true; - } - - self.offset = 0; - self.index++; - - var now = new Date().getTime(); - if (self.env.updateInterval && now - self.env.lastUpdate > self.env.updateInterval) { - self.env.lastUpdate = now; - self.env.setTimeout(function() { - self.next_(); - }, 0); - } else { - if (jasmine.Queue.LOOP_DONT_RECURSE && completedSynchronously) { - goAgain = true; - } else { - self.next_(); - } - } - }; - self.blocks[self.index].execute(onComplete); - - calledSynchronously = false; - if (completedSynchronously) { - onComplete(); - } - - } else { - self.running = false; - if (self.onComplete) { - self.onComplete(); - } - } - } -}; - -jasmine.Queue.prototype.results = function() { - var results = new jasmine.NestedResults(); - for (var i = 0; i < this.blocks.length; i++) { - if (this.blocks[i].results) { - results.addResult(this.blocks[i].results()); - } - } - return results; -}; - - -/** - * Runner - * - * @constructor - * @param {jasmine.Env} env - */ -jasmine.Runner = function(env) { - var self = this; - self.env = env; - self.queue = new jasmine.Queue(env); - self.before_ = []; - self.after_ = []; - self.suites_ = []; -}; - -jasmine.Runner.prototype.execute = function() { - var self = this; - if (self.env.reporter.reportRunnerStarting) { - self.env.reporter.reportRunnerStarting(this); - } - self.queue.start(function () { - self.finishCallback(); - }); -}; - -jasmine.Runner.prototype.beforeEach = function(beforeEachFunction) { - beforeEachFunction.typeName = 'beforeEach'; - this.before_.splice(0,0,beforeEachFunction); -}; - -jasmine.Runner.prototype.afterEach = function(afterEachFunction) { - afterEachFunction.typeName = 'afterEach'; - this.after_.splice(0,0,afterEachFunction); -}; - - -jasmine.Runner.prototype.finishCallback = function() { - this.env.reporter.reportRunnerResults(this); -}; - -jasmine.Runner.prototype.addSuite = function(suite) { - this.suites_.push(suite); -}; - -jasmine.Runner.prototype.add = function(block) { - if (block instanceof jasmine.Suite) { - this.addSuite(block); - } - this.queue.add(block); -}; - -jasmine.Runner.prototype.specs = function () { - var suites = this.suites(); - var specs = []; - for (var i = 0; i < suites.length; i++) { - specs = specs.concat(suites[i].specs()); - } - return specs; -}; - -jasmine.Runner.prototype.suites = function() { - return this.suites_; -}; - -jasmine.Runner.prototype.topLevelSuites = function() { - var topLevelSuites = []; - for (var i = 0; i < this.suites_.length; i++) { - if (!this.suites_[i].parentSuite) { - topLevelSuites.push(this.suites_[i]); - } - } - return topLevelSuites; -}; - -jasmine.Runner.prototype.results = function() { - return this.queue.results(); -}; -/** - * Internal representation of a Jasmine specification, or test. - * - * @constructor - * @param {jasmine.Env} env - * @param {jasmine.Suite} suite - * @param {String} description - */ -jasmine.Spec = function(env, suite, description) { - if (!env) { - throw new Error('jasmine.Env() required'); - } - if (!suite) { - throw new Error('jasmine.Suite() required'); - } - var spec = this; - spec.id = env.nextSpecId ? env.nextSpecId() : null; - spec.env = env; - spec.suite = suite; - spec.description = description; - spec.queue = new jasmine.Queue(env); - - spec.afterCallbacks = []; - spec.spies_ = []; - - spec.results_ = new jasmine.NestedResults(); - spec.results_.description = description; - spec.matchersClass = null; -}; - -jasmine.Spec.prototype.getFullName = function() { - return this.suite.getFullName() + ' ' + this.description + '.'; -}; - - -jasmine.Spec.prototype.results = function() { - return this.results_; -}; - -/** - * All parameters are pretty-printed and concatenated together, then written to the spec's output. - * - * Be careful not to leave calls to <code>jasmine.log</code> in production code. - */ -jasmine.Spec.prototype.log = function() { - return this.results_.log(arguments); -}; - -jasmine.Spec.prototype.runs = function (func) { - var block = new jasmine.Block(this.env, func, this); - this.addToQueue(block); - return this; -}; - -jasmine.Spec.prototype.addToQueue = function (block) { - if (this.queue.isRunning()) { - this.queue.insertNext(block); - } else { - this.queue.add(block); - } -}; - -/** - * @param {jasmine.ExpectationResult} result - */ -jasmine.Spec.prototype.addMatcherResult = function(result) { - this.results_.addResult(result); -}; - -jasmine.Spec.prototype.expect = function(actual) { - var positive = new (this.getMatchersClass_())(this.env, actual, this); - positive.not = new (this.getMatchersClass_())(this.env, actual, this, true); - return positive; -}; - -/** - * Waits a fixed time period before moving to the next block. - * - * @deprecated Use waitsFor() instead - * @param {Number} timeout milliseconds to wait - */ -jasmine.Spec.prototype.waits = function(timeout) { - var waitsFunc = new jasmine.WaitsBlock(this.env, timeout, this); - this.addToQueue(waitsFunc); - return this; -}; - -/** - * Waits for the latchFunction to return true before proceeding to the next block. - * - * @param {Function} latchFunction - * @param {String} optional_timeoutMessage - * @param {Number} optional_timeout - */ -jasmine.Spec.prototype.waitsFor = function(latchFunction, optional_timeoutMessage, optional_timeout) { - var latchFunction_ = null; - var optional_timeoutMessage_ = null; - var optional_timeout_ = null; - - for (var i = 0; i < arguments.length; i++) { - var arg = arguments[i]; - switch (typeof arg) { - case 'function': - latchFunction_ = arg; - break; - case 'string': - optional_timeoutMessage_ = arg; - break; - case 'number': - optional_timeout_ = arg; - break; - } - } - - var waitsForFunc = new jasmine.WaitsForBlock(this.env, optional_timeout_, latchFunction_, optional_timeoutMessage_, this); - this.addToQueue(waitsForFunc); - return this; -}; - -jasmine.Spec.prototype.fail = function (e) { - var expectationResult = new jasmine.ExpectationResult({ - passed: false, - message: e ? jasmine.util.formatException(e) : 'Exception' - }); - this.results_.addResult(expectationResult); -}; - -jasmine.Spec.prototype.getMatchersClass_ = function() { - return this.matchersClass || this.env.matchersClass; -}; - -jasmine.Spec.prototype.addMatchers = function(matchersPrototype) { - var parent = this.getMatchersClass_(); - var newMatchersClass = function() { - parent.apply(this, arguments); - }; - jasmine.util.inherit(newMatchersClass, parent); - jasmine.Matchers.wrapInto_(matchersPrototype, newMatchersClass); - this.matchersClass = newMatchersClass; -}; - -jasmine.Spec.prototype.finishCallback = function() { - this.env.reporter.reportSpecResults(this); -}; - -jasmine.Spec.prototype.finish = function(onComplete) { - this.removeAllSpies(); - this.finishCallback(); - if (onComplete) { - onComplete(); - } -}; - -jasmine.Spec.prototype.after = function(doAfter) { - if (this.queue.isRunning()) { - this.queue.add(new jasmine.Block(this.env, doAfter, this)); - } else { - this.afterCallbacks.unshift(doAfter); - } -}; - -jasmine.Spec.prototype.execute = function(onComplete) { - var spec = this; - if (!spec.env.specFilter(spec)) { - spec.results_.skipped = true; - spec.finish(onComplete); - return; - } - - this.env.reporter.reportSpecStarting(this); - - spec.env.currentSpec = spec; - - spec.addBeforesAndAftersToQueue(); - - spec.queue.start(function () { - spec.finish(onComplete); - }); -}; - -jasmine.Spec.prototype.addBeforesAndAftersToQueue = function() { - var runner = this.env.currentRunner(); - var i; - - for (var suite = this.suite; suite; suite = suite.parentSuite) { - for (i = 0; i < suite.before_.length; i++) { - this.queue.addBefore(new jasmine.Block(this.env, suite.before_[i], this)); - } - } - for (i = 0; i < runner.before_.length; i++) { - this.queue.addBefore(new jasmine.Block(this.env, runner.before_[i], this)); - } - for (i = 0; i < this.afterCallbacks.length; i++) { - this.queue.add(new jasmine.Block(this.env, this.afterCallbacks[i], this)); - } - for (suite = this.suite; suite; suite = suite.parentSuite) { - for (i = 0; i < suite.after_.length; i++) { - this.queue.add(new jasmine.Block(this.env, suite.after_[i], this)); - } - } - for (i = 0; i < runner.after_.length; i++) { - this.queue.add(new jasmine.Block(this.env, runner.after_[i], this)); - } -}; - -jasmine.Spec.prototype.explodes = function() { - throw 'explodes function should not have been called'; -}; - -jasmine.Spec.prototype.spyOn = function(obj, methodName, ignoreMethodDoesntExist) { - if (obj == jasmine.undefined) { - throw "spyOn could not find an object to spy upon for " + methodName + "()"; - } - - if (!ignoreMethodDoesntExist && obj[methodName] === jasmine.undefined) { - throw methodName + '() method does not exist'; - } - - if (!ignoreMethodDoesntExist && obj[methodName] && obj[methodName].isSpy) { - throw new Error(methodName + ' has already been spied upon'); - } - - var spyObj = jasmine.createSpy(methodName); - - this.spies_.push(spyObj); - spyObj.baseObj = obj; - spyObj.methodName = methodName; - spyObj.originalValue = obj[methodName]; - - obj[methodName] = spyObj; - - return spyObj; -}; - -jasmine.Spec.prototype.removeAllSpies = function() { - for (var i = 0; i < this.spies_.length; i++) { - var spy = this.spies_[i]; - spy.baseObj[spy.methodName] = spy.originalValue; - } - this.spies_ = []; -}; - -/** - * Internal representation of a Jasmine suite. - * - * @constructor - * @param {jasmine.Env} env - * @param {String} description - * @param {Function} specDefinitions - * @param {jasmine.Suite} parentSuite - */ -jasmine.Suite = function(env, description, specDefinitions, parentSuite) { - var self = this; - self.id = env.nextSuiteId ? env.nextSuiteId() : null; - self.description = description; - self.queue = new jasmine.Queue(env); - self.parentSuite = parentSuite; - self.env = env; - self.before_ = []; - self.after_ = []; - self.children_ = []; - self.suites_ = []; - self.specs_ = []; -}; - -jasmine.Suite.prototype.getFullName = function() { - var fullName = this.description; - for (var parentSuite = this.parentSuite; parentSuite; parentSuite = parentSuite.parentSuite) { - fullName = parentSuite.description + ' ' + fullName; - } - return fullName; -}; - -jasmine.Suite.prototype.finish = function(onComplete) { - this.env.reporter.reportSuiteResults(this); - this.finished = true; - if (typeof(onComplete) == 'function') { - onComplete(); - } -}; - -jasmine.Suite.prototype.beforeEach = function(beforeEachFunction) { - beforeEachFunction.typeName = 'beforeEach'; - this.before_.unshift(beforeEachFunction); -}; - -jasmine.Suite.prototype.afterEach = function(afterEachFunction) { - afterEachFunction.typeName = 'afterEach'; - this.after_.unshift(afterEachFunction); -}; - -jasmine.Suite.prototype.results = function() { - return this.queue.results(); -}; - -jasmine.Suite.prototype.add = function(suiteOrSpec) { - this.children_.push(suiteOrSpec); - if (suiteOrSpec instanceof jasmine.Suite) { - this.suites_.push(suiteOrSpec); - this.env.currentRunner().addSuite(suiteOrSpec); - } else { - this.specs_.push(suiteOrSpec); - } - this.queue.add(suiteOrSpec); -}; - -jasmine.Suite.prototype.specs = function() { - return this.specs_; -}; - -jasmine.Suite.prototype.suites = function() { - return this.suites_; -}; - -jasmine.Suite.prototype.children = function() { - return this.children_; -}; - -jasmine.Suite.prototype.execute = function(onComplete) { - var self = this; - this.queue.start(function () { - self.finish(onComplete); - }); -}; -jasmine.WaitsBlock = function(env, timeout, spec) { - this.timeout = timeout; - jasmine.Block.call(this, env, null, spec); -}; - -jasmine.util.inherit(jasmine.WaitsBlock, jasmine.Block); - -jasmine.WaitsBlock.prototype.execute = function (onComplete) { - this.env.reporter.log('>> Jasmine waiting for ' + this.timeout + ' ms...'); - this.env.setTimeout(function () { - onComplete(); - }, this.timeout); -}; -/** - * A block which waits for some condition to become true, with timeout. - * - * @constructor - * @extends jasmine.Block - * @param {jasmine.Env} env The Jasmine environment. - * @param {Number} timeout The maximum time in milliseconds to wait for the condition to become true. - * @param {Function} latchFunction A function which returns true when the desired condition has been met. - * @param {String} message The message to display if the desired condition hasn't been met within the given time period. - * @param {jasmine.Spec} spec The Jasmine spec. - */ -jasmine.WaitsForBlock = function(env, timeout, latchFunction, message, spec) { - this.timeout = timeout || env.defaultTimeoutInterval; - this.latchFunction = latchFunction; - this.message = message; - this.totalTimeSpentWaitingForLatch = 0; - jasmine.Block.call(this, env, null, spec); -}; -jasmine.util.inherit(jasmine.WaitsForBlock, jasmine.Block); - -jasmine.WaitsForBlock.TIMEOUT_INCREMENT = 10; - -jasmine.WaitsForBlock.prototype.execute = function(onComplete) { - this.env.reporter.log('>> Jasmine waiting for ' + (this.message || 'something to happen')); - var latchFunctionResult; - try { - latchFunctionResult = this.latchFunction.apply(this.spec); - } catch (e) { - this.spec.fail(e); - onComplete(); - return; - } - - if (latchFunctionResult) { - onComplete(); - } else if (this.totalTimeSpentWaitingForLatch >= this.timeout) { - var message = 'timed out after ' + this.timeout + ' msec waiting for ' + (this.message || 'something to happen'); - this.spec.fail({ - name: 'timeout', - message: message - }); - - this.abort = true; - onComplete(); - } else { - this.totalTimeSpentWaitingForLatch += jasmine.WaitsForBlock.TIMEOUT_INCREMENT; - var self = this; - this.env.setTimeout(function() { - self.execute(onComplete); - }, jasmine.WaitsForBlock.TIMEOUT_INCREMENT); - } -}; -// Mock setTimeout, clearTimeout -// Contributed by Pivotal Computer Systems, www.pivotalsf.com - -jasmine.FakeTimer = function() { - this.reset(); - - var self = this; - self.setTimeout = function(funcToCall, millis) { - self.timeoutsMade++; - self.scheduleFunction(self.timeoutsMade, funcToCall, millis, false); - return self.timeoutsMade; - }; - - self.setInterval = function(funcToCall, millis) { - self.timeoutsMade++; - self.scheduleFunction(self.timeoutsMade, funcToCall, millis, true); - return self.timeoutsMade; - }; - - self.clearTimeout = function(timeoutKey) { - self.scheduledFunctions[timeoutKey] = jasmine.undefined; - }; - - self.clearInterval = function(timeoutKey) { - self.scheduledFunctions[timeoutKey] = jasmine.undefined; - }; - -}; - -jasmine.FakeTimer.prototype.reset = function() { - this.timeoutsMade = 0; - this.scheduledFunctions = {}; - this.nowMillis = 0; -}; - -jasmine.FakeTimer.prototype.tick = function(millis) { - var oldMillis = this.nowMillis; - var newMillis = oldMillis + millis; - this.runFunctionsWithinRange(oldMillis, newMillis); - this.nowMillis = newMillis; -}; - -jasmine.FakeTimer.prototype.runFunctionsWithinRange = function(oldMillis, nowMillis) { - var scheduledFunc; - var funcsToRun = []; - for (var timeoutKey in this.scheduledFunctions) { - scheduledFunc = this.scheduledFunctions[timeoutKey]; - if (scheduledFunc != jasmine.undefined && - scheduledFunc.runAtMillis >= oldMillis && - scheduledFunc.runAtMillis <= nowMillis) { - funcsToRun.push(scheduledFunc); - this.scheduledFunctions[timeoutKey] = jasmine.undefined; - } - } - - if (funcsToRun.length > 0) { - funcsToRun.sort(function(a, b) { - return a.runAtMillis - b.runAtMillis; - }); - for (var i = 0; i < funcsToRun.length; ++i) { - try { - var funcToRun = funcsToRun[i]; - this.nowMillis = funcToRun.runAtMillis; - funcToRun.funcToCall(); - if (funcToRun.recurring) { - this.scheduleFunction(funcToRun.timeoutKey, - funcToRun.funcToCall, - funcToRun.millis, - true); - } - } catch(e) { - } - } - this.runFunctionsWithinRange(oldMillis, nowMillis); - } -}; - -jasmine.FakeTimer.prototype.scheduleFunction = function(timeoutKey, funcToCall, millis, recurring) { - this.scheduledFunctions[timeoutKey] = { - runAtMillis: this.nowMillis + millis, - funcToCall: funcToCall, - recurring: recurring, - timeoutKey: timeoutKey, - millis: millis - }; -}; - -/** - * @namespace - */ -jasmine.Clock = { - defaultFakeTimer: new jasmine.FakeTimer(), - - reset: function() { - jasmine.Clock.assertInstalled(); - jasmine.Clock.defaultFakeTimer.reset(); - }, - - tick: function(millis) { - jasmine.Clock.assertInstalled(); - jasmine.Clock.defaultFakeTimer.tick(millis); - }, - - runFunctionsWithinRange: function(oldMillis, nowMillis) { - jasmine.Clock.defaultFakeTimer.runFunctionsWithinRange(oldMillis, nowMillis); - }, - - scheduleFunction: function(timeoutKey, funcToCall, millis, recurring) { - jasmine.Clock.defaultFakeTimer.scheduleFunction(timeoutKey, funcToCall, millis, recurring); - }, - - useMock: function() { - if (!jasmine.Clock.isInstalled()) { - var spec = jasmine.getEnv().currentSpec; - spec.after(jasmine.Clock.uninstallMock); - - jasmine.Clock.installMock(); - } - }, - - installMock: function() { - jasmine.Clock.installed = jasmine.Clock.defaultFakeTimer; - }, - - uninstallMock: function() { - jasmine.Clock.assertInstalled(); - jasmine.Clock.installed = jasmine.Clock.real; - }, - - real: { - setTimeout: jasmine.getGlobal().setTimeout, - clearTimeout: jasmine.getGlobal().clearTimeout, - setInterval: jasmine.getGlobal().setInterval, - clearInterval: jasmine.getGlobal().clearInterval - }, - - assertInstalled: function() { - if (!jasmine.Clock.isInstalled()) { - throw new Error("Mock clock is not installed, use jasmine.Clock.useMock()"); - } - }, - - isInstalled: function() { - return jasmine.Clock.installed == jasmine.Clock.defaultFakeTimer; - }, - - installed: null -}; -jasmine.Clock.installed = jasmine.Clock.real; - -//else for IE support -jasmine.getGlobal().setTimeout = function(funcToCall, millis) { - if (jasmine.Clock.installed.setTimeout.apply) { - return jasmine.Clock.installed.setTimeout.apply(this, arguments); - } else { - return jasmine.Clock.installed.setTimeout(funcToCall, millis); - } -}; - -jasmine.getGlobal().setInterval = function(funcToCall, millis) { - if (jasmine.Clock.installed.setInterval.apply) { - return jasmine.Clock.installed.setInterval.apply(this, arguments); - } else { - return jasmine.Clock.installed.setInterval(funcToCall, millis); - } -}; - -jasmine.getGlobal().clearTimeout = function(timeoutKey) { - if (jasmine.Clock.installed.clearTimeout.apply) { - return jasmine.Clock.installed.clearTimeout.apply(this, arguments); - } else { - return jasmine.Clock.installed.clearTimeout(timeoutKey); - } -}; - -jasmine.getGlobal().clearInterval = function(timeoutKey) { - if (jasmine.Clock.installed.clearTimeout.apply) { - return jasmine.Clock.installed.clearInterval.apply(this, arguments); - } else { - return jasmine.Clock.installed.clearInterval(timeoutKey); - } -}; - - -jasmine.version_= { - "major": 1, - "minor": 0, - "build": 1, - "revision": 1286311016 -}; diff --git a/tests/jasmine/spec/mediawiki.jqueryMsg.spec.data.js b/tests/jasmine/spec/mediawiki.jqueryMsg.spec.data.js deleted file mode 100644 index a867f72c..00000000 --- a/tests/jasmine/spec/mediawiki.jqueryMsg.spec.data.js +++ /dev/null @@ -1,488 +0,0 @@ -// This file stores the results from the PHP parser for certain messages and arguments, -// so we can test the equivalent Javascript libraries. -// Last generated with makeLanguageSpec.php at 2011-01-28T02:04:09+00:00 - -mediaWiki.messages.set( { - "en_undelete_short": "Undelete {{PLURAL:$1|one edit|$1 edits}}", - "en_category-subcat-count": "{{PLURAL:$2|This category has only the following subcategory.|This category has the following {{PLURAL:$1|subcategory|$1 subcategories}}, out of $2 total.}}", - "fr_undelete_short": "Restaurer $1 modification{{PLURAL:$1||s}}", - "fr_category-subcat-count": "Cette cat\u00e9gorie comprend {{PLURAL:$2|la sous-cat\u00e9gorie|$2 sous-cat\u00e9gories, dont {{PLURAL:$1|celle|les $1}}}} ci-dessous.", - "ar_undelete_short": "\u0627\u0633\u062a\u0631\u062c\u0627\u0639 {{PLURAL:$1|\u062a\u0639\u062f\u064a\u0644 \u0648\u0627\u062d\u062f|\u062a\u0639\u062f\u064a\u0644\u064a\u0646|$1 \u062a\u0639\u062f\u064a\u0644\u0627\u062a|$1 \u062a\u0639\u062f\u064a\u0644|$1 \u062a\u0639\u062f\u064a\u0644\u0627}}", - "ar_category-subcat-count": "{{PLURAL:$2|\u0644\u0627 \u062a\u0635\u0627\u0646\u064a\u0641 \u0641\u0631\u0639\u064a\u0629 \u0641\u064a \u0647\u0630\u0627 \u0627\u0644\u062a\u0635\u0646\u064a\u0641|\u0647\u0630\u0627 \u0627\u0644\u062a\u0635\u0646\u064a\u0641 \u0641\u064a\u0647 \u0627\u0644\u062a\u0635\u0646\u064a\u0641 \u0627\u0644\u0641\u0631\u0639\u064a \u0627\u0644\u062a\u0627\u0644\u064a \u0641\u0642\u0637.|\u0647\u0630\u0627 \u0627\u0644\u062a\u0635\u0646\u064a\u0641 \u0641\u064a\u0647 {{PLURAL:$1||\u0647\u0630\u0627 \u0627\u0644\u062a\u0635\u0646\u064a\u0641 \u0627\u0644\u0641\u0631\u0639\u064a|\u0647\u0630\u064a\u0646 \u0627\u0644\u062a\u0635\u0646\u064a\u0641\u064a\u0646 \u0627\u0644\u0641\u0631\u0639\u064a\u064a\u0646|\u0647\u0630\u0647 \u0627\u0644$1 \u062a\u0635\u0627\u0646\u064a\u0641 \u0627\u0644\u0641\u0631\u0639\u064a\u0629|\u0647\u0630\u0647 \u0627\u0644$1 \u062a\u0635\u0646\u064a\u0641\u0627 \u0641\u0631\u0639\u064a\u0627|\u0647\u0630\u0647 \u0627\u0644$1 \u062a\u0635\u0646\u064a\u0641 \u0641\u0631\u0639\u064a}}\u060c \u0645\u0646 \u0625\u062c\u0645\u0627\u0644\u064a $2.}}", - "jp_undelete_short": "Undelete {{PLURAL:$1|one edit|$1 edits}}", - "jp_category-subcat-count": "{{PLURAL:$2|This category has only the following subcategory.|This category has the following {{PLURAL:$1|subcategory|$1 subcategories}}, out of $2 total.}}", - "zh_undelete_short": "\u6062\u590d\u88ab\u5220\u9664\u7684$1\u9879\u4fee\u8ba2", - "zh_category-subcat-count": "{{PLURAL:$2|\u672c\u5206\u7c7b\u53ea\u6709\u4e0b\u5217\u4e00\u4e2a\u5b50\u5206\u7c7b\u3002|\u672c\u5206\u7c7b\u5305\u542b\u4e0b\u5217$1\u4e2a\u5b50\u5206\u7c7b\uff0c\u5171\u6709$2\u4e2a\u5b50\u5206\u7c7b\u3002}}" -} ); -var jasmineMsgSpec = [ - { - "name": "en undelete_short 0", - "key": "en_undelete_short", - "args": [ - 0 - ], - "result": "Undelete 0 edits", - "lang": "en" - }, - { - "name": "en undelete_short 1", - "key": "en_undelete_short", - "args": [ - 1 - ], - "result": "Undelete one edit", - "lang": "en" - }, - { - "name": "en undelete_short 2", - "key": "en_undelete_short", - "args": [ - 2 - ], - "result": "Undelete 2 edits", - "lang": "en" - }, - { - "name": "en undelete_short 5", - "key": "en_undelete_short", - "args": [ - 5 - ], - "result": "Undelete 5 edits", - "lang": "en" - }, - { - "name": "en undelete_short 21", - "key": "en_undelete_short", - "args": [ - 21 - ], - "result": "Undelete 21 edits", - "lang": "en" - }, - { - "name": "en undelete_short 101", - "key": "en_undelete_short", - "args": [ - 101 - ], - "result": "Undelete 101 edits", - "lang": "en" - }, - { - "name": "en category-subcat-count 0,10", - "key": "en_category-subcat-count", - "args": [ - 0, - 10 - ], - "result": "This category has the following 0 subcategories, out of 10 total.", - "lang": "en" - }, - { - "name": "en category-subcat-count 1,1", - "key": "en_category-subcat-count", - "args": [ - 1, - 1 - ], - "result": "This category has only the following subcategory.", - "lang": "en" - }, - { - "name": "en category-subcat-count 1,2", - "key": "en_category-subcat-count", - "args": [ - 1, - 2 - ], - "result": "This category has the following subcategory, out of 2 total.", - "lang": "en" - }, - { - "name": "en category-subcat-count 3,30", - "key": "en_category-subcat-count", - "args": [ - 3, - 30 - ], - "result": "This category has the following 3 subcategories, out of 30 total.", - "lang": "en" - }, - { - "name": "fr undelete_short 0", - "key": "fr_undelete_short", - "args": [ - 0 - ], - "result": "Restaurer 0 modification", - "lang": "fr" - }, - { - "name": "fr undelete_short 1", - "key": "fr_undelete_short", - "args": [ - 1 - ], - "result": "Restaurer 1 modification", - "lang": "fr" - }, - { - "name": "fr undelete_short 2", - "key": "fr_undelete_short", - "args": [ - 2 - ], - "result": "Restaurer 2 modifications", - "lang": "fr" - }, - { - "name": "fr undelete_short 5", - "key": "fr_undelete_short", - "args": [ - 5 - ], - "result": "Restaurer 5 modifications", - "lang": "fr" - }, - { - "name": "fr undelete_short 21", - "key": "fr_undelete_short", - "args": [ - 21 - ], - "result": "Restaurer 21 modifications", - "lang": "fr" - }, - { - "name": "fr undelete_short 101", - "key": "fr_undelete_short", - "args": [ - 101 - ], - "result": "Restaurer 101 modifications", - "lang": "fr" - }, - { - "name": "fr category-subcat-count 0,10", - "key": "fr_category-subcat-count", - "args": [ - 0, - 10 - ], - "result": "Cette cat\u00e9gorie comprend 10 sous-cat\u00e9gories, dont celle ci-dessous.", - "lang": "fr" - }, - { - "name": "fr category-subcat-count 1,1", - "key": "fr_category-subcat-count", - "args": [ - 1, - 1 - ], - "result": "Cette cat\u00e9gorie comprend la sous-cat\u00e9gorie ci-dessous.", - "lang": "fr" - }, - { - "name": "fr category-subcat-count 1,2", - "key": "fr_category-subcat-count", - "args": [ - 1, - 2 - ], - "result": "Cette cat\u00e9gorie comprend 2 sous-cat\u00e9gories, dont celle ci-dessous.", - "lang": "fr" - }, - { - "name": "fr category-subcat-count 3,30", - "key": "fr_category-subcat-count", - "args": [ - 3, - 30 - ], - "result": "Cette cat\u00e9gorie comprend 30 sous-cat\u00e9gories, dont les 3 ci-dessous.", - "lang": "fr" - }, - { - "name": "ar undelete_short 0", - "key": "ar_undelete_short", - "args": [ - 0 - ], - "result": "\u0627\u0633\u062a\u0631\u062c\u0627\u0639 \u062a\u0639\u062f\u064a\u0644 \u0648\u0627\u062d\u062f", - "lang": "ar" - }, - { - "name": "ar undelete_short 1", - "key": "ar_undelete_short", - "args": [ - 1 - ], - "result": "\u0627\u0633\u062a\u0631\u062c\u0627\u0639 \u062a\u0639\u062f\u064a\u0644\u064a\u0646", - "lang": "ar" - }, - { - "name": "ar undelete_short 2", - "key": "ar_undelete_short", - "args": [ - 2 - ], - "result": "\u0627\u0633\u062a\u0631\u062c\u0627\u0639 2 \u062a\u0639\u062f\u064a\u0644\u0627\u062a", - "lang": "ar" - }, - { - "name": "ar undelete_short 5", - "key": "ar_undelete_short", - "args": [ - 5 - ], - "result": "\u0627\u0633\u062a\u0631\u062c\u0627\u0639 5 \u062a\u0639\u062f\u064a\u0644", - "lang": "ar" - }, - { - "name": "ar undelete_short 21", - "key": "ar_undelete_short", - "args": [ - 21 - ], - "result": "\u0627\u0633\u062a\u0631\u062c\u0627\u0639 21 \u062a\u0639\u062f\u064a\u0644\u0627", - "lang": "ar" - }, - { - "name": "ar undelete_short 101", - "key": "ar_undelete_short", - "args": [ - 101 - ], - "result": "\u0627\u0633\u062a\u0631\u062c\u0627\u0639 101 \u062a\u0639\u062f\u064a\u0644\u0627", - "lang": "ar" - }, - { - "name": "ar category-subcat-count 0,10", - "key": "ar_category-subcat-count", - "args": [ - 0, - 10 - ], - "result": "\u0647\u0630\u0627 \u0627\u0644\u062a\u0635\u0646\u064a\u0641 \u0641\u064a\u0647 \u060c \u0645\u0646 \u0625\u062c\u0645\u0627\u0644\u064a 10.", - "lang": "ar" - }, - { - "name": "ar category-subcat-count 1,1", - "key": "ar_category-subcat-count", - "args": [ - 1, - 1 - ], - "result": "\u0647\u0630\u0627 \u0627\u0644\u062a\u0635\u0646\u064a\u0641 \u0641\u064a\u0647 \u0627\u0644\u062a\u0635\u0646\u064a\u0641 \u0627\u0644\u0641\u0631\u0639\u064a \u0627\u0644\u062a\u0627\u0644\u064a \u0641\u0642\u0637.", - "lang": "ar" - }, - { - "name": "ar category-subcat-count 1,2", - "key": "ar_category-subcat-count", - "args": [ - 1, - 2 - ], - "result": "\u0647\u0630\u0627 \u0627\u0644\u062a\u0635\u0646\u064a\u0641 \u0641\u064a\u0647 \u0647\u0630\u0627 \u0627\u0644\u062a\u0635\u0646\u064a\u0641 \u0627\u0644\u0641\u0631\u0639\u064a\u060c \u0645\u0646 \u0625\u062c\u0645\u0627\u0644\u064a 2.", - "lang": "ar" - }, - { - "name": "ar category-subcat-count 3,30", - "key": "ar_category-subcat-count", - "args": [ - 3, - 30 - ], - "result": "\u0647\u0630\u0627 \u0627\u0644\u062a\u0635\u0646\u064a\u0641 \u0641\u064a\u0647 \u0647\u0630\u0647 \u0627\u06443 \u062a\u0635\u0627\u0646\u064a\u0641 \u0627\u0644\u0641\u0631\u0639\u064a\u0629\u060c \u0645\u0646 \u0625\u062c\u0645\u0627\u0644\u064a 30.", - "lang": "ar" - }, - { - "name": "jp undelete_short 0", - "key": "jp_undelete_short", - "args": [ - 0 - ], - "result": "Undelete 0 edits", - "lang": "jp" - }, - { - "name": "jp undelete_short 1", - "key": "jp_undelete_short", - "args": [ - 1 - ], - "result": "Undelete one edit", - "lang": "jp" - }, - { - "name": "jp undelete_short 2", - "key": "jp_undelete_short", - "args": [ - 2 - ], - "result": "Undelete 2 edits", - "lang": "jp" - }, - { - "name": "jp undelete_short 5", - "key": "jp_undelete_short", - "args": [ - 5 - ], - "result": "Undelete 5 edits", - "lang": "jp" - }, - { - "name": "jp undelete_short 21", - "key": "jp_undelete_short", - "args": [ - 21 - ], - "result": "Undelete 21 edits", - "lang": "jp" - }, - { - "name": "jp undelete_short 101", - "key": "jp_undelete_short", - "args": [ - 101 - ], - "result": "Undelete 101 edits", - "lang": "jp" - }, - { - "name": "jp category-subcat-count 0,10", - "key": "jp_category-subcat-count", - "args": [ - 0, - 10 - ], - "result": "This category has the following 0 subcategories, out of 10 total.", - "lang": "jp" - }, - { - "name": "jp category-subcat-count 1,1", - "key": "jp_category-subcat-count", - "args": [ - 1, - 1 - ], - "result": "This category has only the following subcategory.", - "lang": "jp" - }, - { - "name": "jp category-subcat-count 1,2", - "key": "jp_category-subcat-count", - "args": [ - 1, - 2 - ], - "result": "This category has the following subcategory, out of 2 total.", - "lang": "jp" - }, - { - "name": "jp category-subcat-count 3,30", - "key": "jp_category-subcat-count", - "args": [ - 3, - 30 - ], - "result": "This category has the following 3 subcategories, out of 30 total.", - "lang": "jp" - }, - { - "name": "zh undelete_short 0", - "key": "zh_undelete_short", - "args": [ - 0 - ], - "result": "\u6062\u590d\u88ab\u5220\u9664\u76840\u9879\u4fee\u8ba2", - "lang": "zh" - }, - { - "name": "zh undelete_short 1", - "key": "zh_undelete_short", - "args": [ - 1 - ], - "result": "\u6062\u590d\u88ab\u5220\u9664\u76841\u9879\u4fee\u8ba2", - "lang": "zh" - }, - { - "name": "zh undelete_short 2", - "key": "zh_undelete_short", - "args": [ - 2 - ], - "result": "\u6062\u590d\u88ab\u5220\u9664\u76842\u9879\u4fee\u8ba2", - "lang": "zh" - }, - { - "name": "zh undelete_short 5", - "key": "zh_undelete_short", - "args": [ - 5 - ], - "result": "\u6062\u590d\u88ab\u5220\u9664\u76845\u9879\u4fee\u8ba2", - "lang": "zh" - }, - { - "name": "zh undelete_short 21", - "key": "zh_undelete_short", - "args": [ - 21 - ], - "result": "\u6062\u590d\u88ab\u5220\u9664\u768421\u9879\u4fee\u8ba2", - "lang": "zh" - }, - { - "name": "zh undelete_short 101", - "key": "zh_undelete_short", - "args": [ - 101 - ], - "result": "\u6062\u590d\u88ab\u5220\u9664\u7684101\u9879\u4fee\u8ba2", - "lang": "zh" - }, - { - "name": "zh category-subcat-count 0,10", - "key": "zh_category-subcat-count", - "args": [ - 0, - 10 - ], - "result": "\u672c\u5206\u7c7b\u5305\u542b\u4e0b\u52170\u4e2a\u5b50\u5206\u7c7b\uff0c\u5171\u670910\u4e2a\u5b50\u5206\u7c7b\u3002", - "lang": "zh" - }, - { - "name": "zh category-subcat-count 1,1", - "key": "zh_category-subcat-count", - "args": [ - 1, - 1 - ], - "result": "\u672c\u5206\u7c7b\u53ea\u6709\u4e0b\u5217\u4e00\u4e2a\u5b50\u5206\u7c7b\u3002", - "lang": "zh" - }, - { - "name": "zh category-subcat-count 1,2", - "key": "zh_category-subcat-count", - "args": [ - 1, - 2 - ], - "result": "\u672c\u5206\u7c7b\u5305\u542b\u4e0b\u52171\u4e2a\u5b50\u5206\u7c7b\uff0c\u5171\u67092\u4e2a\u5b50\u5206\u7c7b\u3002", - "lang": "zh" - }, - { - "name": "zh category-subcat-count 3,30", - "key": "zh_category-subcat-count", - "args": [ - 3, - 30 - ], - "result": "\u672c\u5206\u7c7b\u5305\u542b\u4e0b\u52173\u4e2a\u5b50\u5206\u7c7b\uff0c\u5171\u670930\u4e2a\u5b50\u5206\u7c7b\u3002", - "lang": "zh" - } -]; diff --git a/tests/jasmine/spec/mediawiki.jqueryMsg.spec.js b/tests/jasmine/spec/mediawiki.jqueryMsg.spec.js deleted file mode 100644 index 46dcaa80..00000000 --- a/tests/jasmine/spec/mediawiki.jqueryMsg.spec.js +++ /dev/null @@ -1,389 +0,0 @@ -/* spec for language & message behaviour in MediaWiki */ - -mw.messages.set( { - "en_empty": "", - "en_simple": "Simple message", - "en_replace": "Simple $1 replacement", - "en_replace2": "Simple $1 $2 replacements", - "en_link": "Simple [http://example.com link to example].", - "en_link_replace": "Complex [$1 $2] behaviour.", - "en_simple_magic": "Simple {{ALOHOMORA}} message", - "en_undelete_short": "Undelete {{PLURAL:$1|one edit|$1 edits}}", - "en_undelete_empty_param": "Undelete{{PLURAL:$1|| multiple edits}}", - "en_category-subcat-count": "{{PLURAL:$2|This category has only the following subcategory.|This category has the following {{PLURAL:$1|subcategory|$1 subcategories}}, out of $2 total.}}", - "en_escape0": "Escape \\to fantasy island", - "en_escape1": "I had \\$2.50 in my pocket", - "en_escape2": "I had {{PLURAL:$1|the absolute \\|$1\\| which came out to \\$3.00 in my C:\\\\drive| some stuff}}", - "en_fail": "This should fail to {{parse", - "en_fail_magic": "There is no such magic word as {{SIETNAME}}", - "en_evil": "This has <script type='text/javascript'>window.en_evil = true;</script> tags" -} ); - -/** - * Tests - */ -( function( mw, $, undefined ) { - - describe( "mediaWiki.jqueryMsg", function() { - - describe( "basic message functionality", function() { - - it( "should return identity for empty string", function() { - var parser = new mw.jqueryMsg.parser(); - expect( parser.parse( 'en_empty' ).html() ).toEqual( '' ); - } ); - - - it( "should return identity for simple string", function() { - var parser = new mw.jqueryMsg.parser(); - expect( parser.parse( 'en_simple' ).html() ).toEqual( 'Simple message' ); - } ); - - } ); - - describe( "escaping", function() { - - it ( "should handle simple escaping", function() { - var parser = new mw.jqueryMsg.parser(); - expect( parser.parse( 'en_escape0' ).html() ).toEqual( 'Escape to fantasy island' ); - } ); - - it ( "should escape dollar signs found in ordinary text when backslashed", function() { - var parser = new mw.jqueryMsg.parser(); - expect( parser.parse( 'en_escape1' ).html() ).toEqual( 'I had $2.50 in my pocket' ); - } ); - - it ( "should handle a complicated escaping case, including escaped pipe chars in template args", function() { - var parser = new mw.jqueryMsg.parser(); - expect( parser.parse( 'en_escape2', [ 1 ] ).html() ).toEqual( 'I had the absolute |1| which came out to $3.00 in my C:\\drive' ); - } ); - - } ); - - describe( "replacing", function() { - - it ( "should handle simple replacing", function() { - var parser = new mw.jqueryMsg.parser(); - expect( parser.parse( 'en_replace', [ 'foo' ] ).html() ).toEqual( 'Simple foo replacement' ); - } ); - - it ( "should return $n if replacement not there", function() { - var parser = new mw.jqueryMsg.parser(); - expect( parser.parse( 'en_replace', [] ).html() ).toEqual( 'Simple $1 replacement' ); - expect( parser.parse( 'en_replace2', [ 'bar' ] ).html() ).toEqual( 'Simple bar $2 replacements' ); - } ); - - } ); - - describe( "linking", function() { - - it ( "should handle a simple link", function() { - var parser = new mw.jqueryMsg.parser(); - var parsed = parser.parse( 'en_link' ); - var contents = parsed.contents(); - expect( contents.length ).toEqual( 3 ); - expect( contents[0].nodeName ).toEqual( '#text' ); - expect( contents[0].nodeValue ).toEqual( 'Simple ' ); - expect( contents[1].nodeName ).toEqual( 'A' ); - expect( contents[1].getAttribute( 'href' ) ).toEqual( 'http://example.com' ); - expect( contents[1].childNodes[0].nodeValue ).toEqual( 'link to example' ); - expect( contents[2].nodeName ).toEqual( '#text' ); - expect( contents[2].nodeValue ).toEqual( '.' ); - } ); - - it ( "should replace a URL into a link", function() { - var parser = new mw.jqueryMsg.parser(); - var parsed = parser.parse( 'en_link_replace', [ 'http://example.com/foo', 'linking' ] ); - var contents = parsed.contents(); - expect( contents.length ).toEqual( 3 ); - expect( contents[0].nodeName ).toEqual( '#text' ); - expect( contents[0].nodeValue ).toEqual( 'Complex ' ); - expect( contents[1].nodeName ).toEqual( 'A' ); - expect( contents[1].getAttribute( 'href' ) ).toEqual( 'http://example.com/foo' ); - expect( contents[1].childNodes[0].nodeValue ).toEqual( 'linking' ); - expect( contents[2].nodeName ).toEqual( '#text' ); - expect( contents[2].nodeValue ).toEqual( ' behaviour.' ); - } ); - - it ( "should bind a click handler into a link", function() { - var parser = new mw.jqueryMsg.parser(); - var clicked = false; - var click = function() { clicked = true; }; - var parsed = parser.parse( 'en_link_replace', [ click, 'linking' ] ); - var contents = parsed.contents(); - expect( contents.length ).toEqual( 3 ); - expect( contents[0].nodeName ).toEqual( '#text' ); - expect( contents[0].nodeValue ).toEqual( 'Complex ' ); - expect( contents[1].nodeName ).toEqual( 'A' ); - expect( contents[1].getAttribute( 'href' ) ).toEqual( '#' ); - expect( contents[1].childNodes[0].nodeValue ).toEqual( 'linking' ); - expect( contents[2].nodeName ).toEqual( '#text' ); - expect( contents[2].nodeValue ).toEqual( ' behaviour.' ); - // determining bindings is hard in IE - var anchor = parsed.find( 'a' ); - if ( ( $.browser.mozilla || $.browser.webkit ) && anchor.click ) { - expect( clicked ).toEqual( false ); - anchor.click(); - expect( clicked ).toEqual( true ); - } - } ); - - it ( "should wrap a jquery arg around link contents -- even another element", function() { - var parser = new mw.jqueryMsg.parser(); - var clicked = false; - var click = function() { clicked = true; }; - var button = $( '<button>' ).click( click ); - var parsed = parser.parse( 'en_link_replace', [ button, 'buttoning' ] ); - var contents = parsed.contents(); - expect( contents.length ).toEqual( 3 ); - expect( contents[0].nodeName ).toEqual( '#text' ); - expect( contents[0].nodeValue ).toEqual( 'Complex ' ); - expect( contents[1].nodeName ).toEqual( 'BUTTON' ); - expect( contents[1].childNodes[0].nodeValue ).toEqual( 'buttoning' ); - expect( contents[2].nodeName ).toEqual( '#text' ); - expect( contents[2].nodeValue ).toEqual( ' behaviour.' ); - // determining bindings is hard in IE - if ( ( $.browser.mozilla || $.browser.webkit ) && button.click ) { - expect( clicked ).toEqual( false ); - parsed.find( 'button' ).click(); - expect( clicked ).toEqual( true ); - } - } ); - - - } ); - - - describe( "magic keywords", function() { - it( "should substitute magic keywords", function() { - var options = { - magic: { - 'alohomora' : 'open' - } - }; - var parser = new mw.jqueryMsg.parser( options ); - expect( parser.parse( 'en_simple_magic' ).html() ).toEqual( 'Simple open message' ); - } ); - } ); - - describe( "error conditions", function() { - it( "should return non-existent key in square brackets", function() { - var parser = new mw.jqueryMsg.parser(); - expect( parser.parse( 'en_does_not_exist' ).html() ).toEqual( '[en_does_not_exist]' ); - } ); - - - it( "should fail to parse", function() { - var parser = new mw.jqueryMsg.parser(); - expect( function() { parser.parse( 'en_fail' ); } ).toThrow( - 'Parse error at position 20 in input: This should fail to {{parse' - ); - } ); - } ); - - describe( "empty parameters", function() { - it( "should deal with empty parameters", function() { - var parser = new mw.jqueryMsg.parser(); - var ast = parser.getAst( 'en_undelete_empty_param' ); - expect( parser.parse( 'en_undelete_empty_param', [ 1 ] ).html() ).toEqual( 'Undelete' ); - expect( parser.parse( 'en_undelete_empty_param', [ 3 ] ).html() ).toEqual( 'Undelete multiple edits' ); - - } ); - } ); - - describe( "easy message interface functions", function() { - it( "should allow a global that returns strings", function() { - var gM = mw.jqueryMsg.getMessageFunction(); - // passing this through jQuery and back to string, because browsers may have subtle differences, like the case of tag names. - // a surrounding <SPAN> is needed for html() to work right - var expectedHtml = $( '<span>Complex <a href="http://example.com/foo">linking</a> behaviour.</span>' ).html(); - var result = gM( 'en_link_replace', 'http://example.com/foo', 'linking' ); - expect( typeof result ).toEqual( 'string' ); - expect( result ).toEqual( expectedHtml ); - } ); - - it( "should allow a jQuery plugin that appends to nodes", function() { - $.fn.msg = mw.jqueryMsg.getPlugin(); - var $div = $( '<div>' ).append( $( '<p>' ).addClass( 'foo' ) ); - var clicked = false; - var $button = $( '<button>' ).click( function() { clicked = true; } ); - $div.find( '.foo' ).msg( 'en_link_replace', $button, 'buttoning' ); - // passing this through jQuery and back to string, because browsers may have subtle differences, like the case of tag names. - // a surrounding <SPAN> is needed for html() to work right - var expectedHtml = $( '<span>Complex <button>buttoning</button> behaviour.</span>' ).html(); - var createdHtml = $div.find( '.foo' ).html(); - // it is hard to test for clicks with IE; also it inserts or removes spaces around nodes when creating HTML tags, depending on their type. - // so need to check the strings stripped of spaces. - if ( ( $.browser.mozilla || $.browser.webkit ) && $button.click ) { - expect( createdHtml ).toEqual( expectedHtml ); - $div.find( 'button ').click(); - expect( clicked ).toEqual( true ); - } else if ( $.browser.ie ) { - expect( createdHtml.replace( /\s/, '' ) ).toEqual( expectedHtml.replace( /\s/, '' ) ); - } - delete $.fn.msg; - } ); - - it( "jQuery plugin should escape incoming string arguments", function() { - $.fn.msg = mw.jqueryMsg.getPlugin(); - var $div = $( '<div>' ).addClass( 'foo' ); - $div.msg( 'en_replace', '<p>x</p>' ); // looks like HTML, but as a string, should be escaped. - // passing this through jQuery and back to string, because browsers may have subtle differences, like the case of tag names. - var expectedHtml = $( '<div class="foo">Simple <p>x</p> replacement</div>' ).html(); - var createdHtml = $div.html(); - expect( expectedHtml ).toEqual( createdHtml ); - delete $.fn.msg; - } ); - - - it( "jQuery plugin should never execute scripts", function() { - window.en_evil = false; - $.fn.msg = mw.jqueryMsg.getPlugin(); - var $div = $( '<div>' ); - $div.msg( 'en_evil' ); - expect( window.en_evil ).toEqual( false ); - delete $.fn.msg; - } ); - - - // n.b. this passes because jQuery already seems to strip scripts away; however, it still executes them if they are appended to any element. - it( "jQuery plugin should never emit scripts", function() { - $.fn.msg = mw.jqueryMsg.getPlugin(); - var $div = $( '<div>' ); - $div.msg( 'en_evil' ); - // passing this through jQuery and back to string, because browsers may have subtle differences, like the case of tag names. - var expectedHtml = $( '<div>This has tags</div>' ).html(); - var createdHtml = $div.html(); - expect( expectedHtml ).toEqual( createdHtml ); - console.log( 'expected: ' + expectedHtml ); - console.log( 'created: ' + createdHtml ); - delete $.fn.msg; - } ); - - - - } ); - - // The parser functions can throw errors, but let's not actually blow up for the user -- instead dump the error into the interface so we have - // a chance at fixing this - describe( "easy message interface functions with graceful failures", function() { - it( "should allow a global that returns strings, with graceful failure", function() { - var gM = mw.jqueryMsg.getMessageFunction(); - // passing this through jQuery and back to string, because browsers may have subtle differences, like the case of tag names. - // a surrounding <SPAN> is needed for html() to work right - var expectedHtml = $( '<span>en_fail: Parse error at position 20 in input: This should fail to {{parse</span>' ).html(); - var result = gM( 'en_fail' ); - expect( typeof result ).toEqual( 'string' ); - expect( result ).toEqual( expectedHtml ); - } ); - - it( "should allow a global that returns strings, with graceful failure on missing magic words", function() { - var gM = mw.jqueryMsg.getMessageFunction(); - // passing this through jQuery and back to string, because browsers may have subtle differences, like the case of tag names. - // a surrounding <SPAN> is needed for html() to work right - var expectedHtml = $( '<span>en_fail_magic: unknown operation "sietname"</span>' ).html(); - var result = gM( 'en_fail_magic' ); - expect( typeof result ).toEqual( 'string' ); - expect( result ).toEqual( expectedHtml ); - } ); - - - it( "should allow a jQuery plugin, with graceful failure", function() { - $.fn.msg = mw.jqueryMsg.getPlugin(); - var $div = $( '<div>' ).append( $( '<p>' ).addClass( 'foo' ) ); - $div.find( '.foo' ).msg( 'en_fail' ); - // passing this through jQuery and back to string, because browsers may have subtle differences, like the case of tag names. - // a surrounding <SPAN> is needed for html() to work right - var expectedHtml = $( '<span>en_fail: Parse error at position 20 in input: This should fail to {{parse</span>' ).html(); - var createdHtml = $div.find( '.foo' ).html(); - expect( createdHtml ).toEqual( expectedHtml ); - delete $.fn.msg; - } ); - - } ); - - - - - describe( "test plurals and other language-specific functions", function() { - /* copying some language definitions in here -- it's hard to make this test fast and reliable - otherwise, and we don't want to have to know the mediawiki URL from this kind of test either. - We also can't preload the langs for the test since they clobber the same namespace. - In principle Roan said it was okay to change how languages worked so that didn't happen... maybe - someday. We'd have to the same kind of importing of the default rules for most rules, or maybe - come up with some kind of subclassing scheme for languages */ - var languageClasses = { - ar: { - /** - * Arabic (العربية) language functions - */ - - convertPlural: function( count, forms ) { - forms = mw.language.preConvertPlural( forms, 6 ); - if ( count === 0 ) { - return forms[0]; - } - if ( count == 1 ) { - return forms[1]; - } - if ( count == 2 ) { - return forms[2]; - } - if ( count % 100 >= 3 && count % 100 <= 10 ) { - return forms[3]; - } - if ( count % 100 >= 11 && count % 100 <= 99 ) { - return forms[4]; - } - return forms[5]; - }, - - digitTransformTable: { - '0': '٠', // ٠ - '1': '١', // ١ - '2': '٢', // ٢ - '3': '٣', // ٣ - '4': '٤', // ٤ - '5': '٥', // ٥ - '6': '٦', // ٦ - '7': '٧', // ٧ - '8': '٨', // ٨ - '9': '٩', // ٩ - '.': '٫', // ٫ wrong table ? - ',': '٬' // ٬ - } - - }, - en: { }, - fr: { - convertPlural: function( count, forms ) { - forms = mw.language.preConvertPlural( forms, 2 ); - return ( count <= 1 ) ? forms[0] : forms[1]; - } - }, - jp: { }, - zh: { } - }; - - /* simulate how the language classes override, or don't, the standard functions in mw.language */ - $.each( languageClasses, function( langCode, rules ) { - $.each( [ 'convertPlural', 'convertNumber' ], function( i, propertyName ) { - if ( typeof rules[ propertyName ] === 'undefined' ) { - rules[ propertyName ] = mw.language[ propertyName ]; - } - } ); - } ); - - $.each( jasmineMsgSpec, function( i, test ) { - it( "should parse " + test.name, function() { - // using language override so we don't have to muck with global namespace - var parser = new mw.jqueryMsg.parser( { language: languageClasses[ test.lang ] } ); - var parsedHtml = parser.parse( test.key, test.args ).html(); - expect( parsedHtml ).toEqual( test.result ); - } ); - } ); - - } ); - - } ); -} )( window.mediaWiki, jQuery ); diff --git a/tests/jasmine/spec_makers/makeJqueryMsgSpec.php b/tests/jasmine/spec_makers/makeJqueryMsgSpec.php deleted file mode 100644 index 840da96a..00000000 --- a/tests/jasmine/spec_makers/makeJqueryMsgSpec.php +++ /dev/null @@ -1,110 +0,0 @@ -<?php - -/** - * This PHP script defines the spec that the Javascript message parser should conform to. - * - * It does this by looking up the results of various string kinds of string parsing, with various languages, - * in the current installation of MediaWiki. It then outputs a static specification, mapping expected inputs to outputs, - * which can be used with the JasmineBDD framework. This specification can then be used by simply including it into - * the SpecRunner.html file. - * - * This is similar to Michael Dale (mdale@mediawiki.org)'s parser tests, except that it doesn't look up the - * API results while doing the test, so the Jasmine run is much faster(at the cost of being out of date in rare - * circumstances. But mostly the parsing that we are doing in Javascript doesn't change much.) - * - */ - -$maintenanceDir = dirname( dirname( dirname( __DIR__ ) ) ) . '/maintenance'; - -require( "$maintenanceDir/Maintenance.php" ); - -class MakeLanguageSpec extends Maintenance { - - static $keyToTestArgs = array( - 'undelete_short' => array( - array( 0 ), - array( 1 ), - array( 2 ), - array( 5 ), - array( 21 ), - array( 101 ) - ), - 'category-subcat-count' => array( - array( 0, 10 ), - array( 1, 1 ), - array( 1, 2 ), - array( 3, 30 ) - ) - ); - - public function __construct() { - parent::__construct(); - $this->mDescription = "Create a JasmineBDD-compatible specification for message parsing"; - // add any other options here - } - - public function execute() { - list( $messages, $tests ) = $this->getMessagesAndTests(); - $this->writeJavascriptFile( $messages, $tests, "spec/mediawiki.language.parser.spec.data.js" ); - } - - private function getMessagesAndTests() { - $messages = array(); - $tests = array(); - foreach ( array( 'en', 'fr', 'ar', 'jp', 'zh' ) as $languageCode ) { - foreach ( self::$keyToTestArgs as $key => $testArgs ) { - foreach ($testArgs as $args) { - // get the raw template, without any transformations - $template = wfMessage( $key )->inLanguage( $languageCode )->plain(); - - $result = wfMessage( $key, $args )->inLanguage( $languageCode )->text(); - - // record the template, args, language, and expected result - // fake multiple languages by flattening them together - $langKey = $languageCode . '_' . $key; - $messages[ $langKey ] = $template; - $tests[] = array( - 'name' => $languageCode . " " . $key . " " . join( ",", $args ), - 'key' => $langKey, - 'args' => $args, - 'result' => $result, - 'lang' => $languageCode - ); - } - } - } - return array( $messages, $tests ); - } - - private function writeJavascriptFile( $messages, $tests, $dataSpecFile ) { - global $argv; - $arguments = count($argv) ? $argv : $_SERVER[ 'argv' ]; - - $json = new Services_JSON; - $json->pretty = true; - $javascriptPrologue = "// This file stores the results from the PHP parser for certain messages and arguments,\n" - . "// so we can test the equivalent Javascript libraries.\n" - . '// Last generated with ' . join(' ', $arguments) . ' at ' . gmdate('c') . "\n\n"; - $javascriptMessages = "mediaWiki.messages.set( " . $json->encode( $messages, true ) . " );\n"; - $javascriptTests = 'var jasmineMsgSpec = ' . $json->encode( $tests, true ) . ";\n"; - - $fp = fopen( $dataSpecFile, 'w' ); - if ( !$fp ) { - die( "couldn't open $dataSpecFile for writing" ); - } - $success = fwrite( $fp, $javascriptPrologue . $javascriptMessages . $javascriptTests ); - if ( !$success ) { - die( "couldn't write to $dataSpecFile" ); - } - $success = fclose( $fp ); - if ( !$success ) { - die( "couldn't close $dataSpecFile" ); - } - } -} - -$maintClass = "MakeLanguageSpec"; -require_once( "$maintenanceDir/doMaintenance.php" ); - - - diff --git a/tests/parser/ParserTestResult.php b/tests/parser/ParserTestResult.php new file mode 100644 index 00000000..d9ad773d --- /dev/null +++ b/tests/parser/ParserTestResult.php @@ -0,0 +1,42 @@ +<?php +/** + * @copyright Copyright © 2013, Antoine Musso + * @copyright Copyright © 2013, Wikimedia Foundation Inc. + * @license GNU GPL v2 + * + * @file + */ + +/** + * Represent the result of a parser test. + * + * @since 1.22 + */ +class ParserTestResult { + /** + * Description of the parser test. + * + * This is usually the text used to describe a parser test in the .txt + * files. It is initialized on a construction and you most probably + * never want to change it. + */ + public $description; + /** Text that was expected */ + public $expected; + /** Actual text rendered */ + public $actual; + + /** + * @param $description string A short text describing the parser test + * usually the text in the parser test .txt file. The description + * is later available using the property $description. + */ + public function __construct( $description ) { + $this->description = $description; + } + + /** Whether the test passed */ + public function isSuccess() { + return $this->expected === $this->actual; + } +} diff --git a/tests/parser/parserTest.inc b/tests/parser/parserTest.inc index 86e1e192..58ea1ed0 100644 --- a/tests/parser/parserTest.inc +++ b/tests/parser/parserTest.inc @@ -1,23 +1,27 @@ <?php -# Copyright (C) 2004, 2010 Brion Vibber <brion@pobox.com> -# http://www.mediawiki.org/ -# -# 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 - /** + * Helper code for the MediaWiki parser test suite. Some code is duplicated + * in PHPUnit's NewParserTests.php, so you'll probably want to update both + * at the same time. + * + * Copyright © 2004, 2010 Brion Vibber <brion@pobox.com> + * http://www.mediawiki.org/ + * + * 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 + * * @todo Make this more independent of the configuration (and if possible the database) * @todo document * @file @@ -29,22 +33,22 @@ */ class ParserTest { /** - * boolean $color whereas output should be colorized + * @var bool $color whereas output should be colorized */ private $color; /** - * boolean $showOutput Show test output + * @var bool $showOutput Show test output */ private $showOutput; /** - * boolean $useTemporaryTables Use temporary tables for the temporary database + * @var bool $useTemporaryTables Use temporary tables for the temporary database */ private $useTemporaryTables = true; /** - * boolean $databaseSetupDone True if the database has been set up + * @var bool $databaseSetupDone True if the database has been set up */ private $databaseSetupDone = false; @@ -61,7 +65,7 @@ class ParserTest { private $dbClone; /** - * string $oldTablePrefix Original table prefix + * @var string $oldTablePrefix Original table prefix */ private $oldTablePrefix; @@ -72,6 +76,7 @@ class ParserTest { public $regex = ""; private $savedGlobals = array(); + /** * Sets terminal colorization and diff/quick modes depending on OS and * command-line options (--color and --quick). @@ -81,14 +86,14 @@ class ParserTest { $this->color = !wfIsWindows() && Maintenance::posix_isatty( 1 ); if ( isset( $options['color'] ) ) { - switch( $options['color'] ) { - case 'no': - $this->color = false; - break; - case 'yes': - default: - $this->color = true; - break; + switch ( $options['color'] ) { + case 'no': + $this->color = false; + break; + case 'yes': + default: + $this->color = true; + break; } } @@ -100,7 +105,7 @@ class ParserTest { $this->showProgress = !isset( $options['quiet'] ); $this->showFailure = !( isset( $options['quiet'] ) - && ( isset( $options['record'] ) + && ( isset( $options['record'] ) || isset( $options['compare'] ) ) ); // redundant output $this->showOutput = isset( $options['show-output'] ); @@ -128,6 +133,7 @@ class ParserTest { } $this->runDisabled = isset( $options['run-disabled'] ); + $this->runParsoid = isset( $options['run-parsoid'] ); $this->hooks = array(); $this->functionHooks = array(); @@ -137,36 +143,38 @@ class ParserTest { static function setUp() { global $wgParser, $wgParserConf, $IP, $messageMemc, $wgMemc, $wgUser, $wgLang, $wgOut, $wgRequest, $wgStyleDirectory, $wgEnableParserCache, - $wgNamespaceAliases, $wgNamespaceProtection, $wgLocalFileRepo, + $wgExtraNamespaces, $wgNamespaceAliases, $wgNamespaceProtection, $wgLocalFileRepo, $parserMemc, $wgThumbnailScriptPath, $wgScriptPath, - $wgArticlePath, $wgStyleSheetPath, $wgScript, $wgStylePath, $wgExtensionAssetsPath, + $wgArticlePath, $wgScript, $wgStylePath, $wgExtensionAssetsPath, $wgMainCacheType, $wgMessageCacheType, $wgParserCacheType, $wgLockManagers; $wgScript = '/index.php'; $wgScriptPath = '/'; $wgArticlePath = '/wiki/$1'; - $wgStyleSheetPath = '/skins'; $wgStylePath = '/skins'; $wgExtensionAssetsPath = '/extensions'; $wgThumbnailScriptPath = false; $wgLockManagers = array( array( - 'name' => 'fsLockManager', - 'class' => 'FSLockManager', + 'name' => 'fsLockManager', + 'class' => 'FSLockManager', 'lockDirectory' => wfTempDir() . '/test-repo/lockdir', + ), array( + 'name' => 'nullLockManager', + 'class' => 'NullLockManager', ) ); $wgLocalFileRepo = array( - 'class' => 'LocalRepo', - 'name' => 'local', - 'url' => 'http://example.com/images', - 'hashLevels' => 2, + 'class' => 'LocalRepo', + 'name' => 'local', + 'url' => 'http://example.com/images', + 'hashLevels' => 2, 'transformVia404' => false, - 'backend' => new FSFileBackend( array( - 'name' => 'local-backend', + 'backend' => new FSFileBackend( array( + 'name' => 'local-backend', 'lockManager' => 'fsLockManager', 'containerPaths' => array( - 'local-public' => wfTempDir() . '/test-repo/public', - 'local-thumb' => wfTempDir() . '/test-repo/thumb', - 'local-temp' => wfTempDir() . '/test-repo/temp', + 'local-public' => wfTempDir() . '/test-repo/public', + 'local-thumb' => wfTempDir() . '/test-repo/thumb', + 'local-temp' => wfTempDir() . '/test-repo/temp', 'local-deleted' => wfTempDir() . '/test-repo/deleted', ) ) ) @@ -174,6 +182,9 @@ class ParserTest { $wgNamespaceProtection[NS_MEDIAWIKI] = 'editinterface'; $wgNamespaceAliases['Image'] = NS_FILE; $wgNamespaceAliases['Image_talk'] = NS_FILE_TALK; + # add a namespace shadowing a interwiki link, to test + # proper precedence when resolving links. (bug 51680) + $wgExtraNamespaces[100] = 'MemoryAlpha'; // XXX: tests won't run without this (for CACHE_DB) if ( $wgMainCacheType === CACHE_DB ) { @@ -201,16 +212,83 @@ class ParserTest { $wgRequest = $context->getRequest(); if ( $wgStyleDirectory === false ) { - $wgStyleDirectory = "$IP/skins"; + $wgStyleDirectory = "$IP/skins"; } + self::setupInterwikis(); } - public function setupRecorder ( $options ) { + /** + * Insert hardcoded interwiki in the lookup table. + * + * This function insert a set of well known interwikis that are used in + * the parser tests. They can be considered has fixtures are injected in + * the interwiki cache by using the 'InterwikiLoadPrefix' hook. + * Since we are not interested in looking up interwikis in the database, + * the hook completely replace the existing mechanism (hook returns false). + */ + public static function setupInterwikis() { + # Hack: insert a few Wikipedia in-project interwiki prefixes, + # for testing inter-language links + Hooks::register( 'InterwikiLoadPrefix', function ( $prefix, &$iwData ) { + static $testInterwikis = array( + 'wikipedia' => array( + 'iw_url' => 'http://en.wikipedia.org/wiki/$1', + 'iw_api' => '', + 'iw_wikiid' => '', + 'iw_local' => 0 ), + 'meatball' => array( + 'iw_url' => 'http://www.usemod.com/cgi-bin/mb.pl?$1', + 'iw_api' => '', + 'iw_wikiid' => '', + 'iw_local' => 0 ), + 'memoryalpha' => array( + 'iw_url' => 'http://www.memory-alpha.org/en/index.php/$1', + 'iw_api' => '', + 'iw_wikiid' => '', + 'iw_local' => 0 ), + 'zh' => array( + 'iw_url' => 'http://zh.wikipedia.org/wiki/$1', + 'iw_api' => '', + 'iw_wikiid' => '', + 'iw_local' => 1 ), + 'es' => array( + 'iw_url' => 'http://es.wikipedia.org/wiki/$1', + 'iw_api' => '', + 'iw_wikiid' => '', + 'iw_local' => 1 ), + 'fr' => array( + 'iw_url' => 'http://fr.wikipedia.org/wiki/$1', + 'iw_api' => '', + 'iw_wikiid' => '', + 'iw_local' => 1 ), + 'ru' => array( + 'iw_url' => 'http://ru.wikipedia.org/wiki/$1', + 'iw_api' => '', + 'iw_wikiid' => '', + 'iw_local' => 1 ), + ); + if ( array_key_exists( $prefix, $testInterwikis ) ) { + $iwData = $testInterwikis[$prefix]; + } + + // We only want to rely on the above fixtures + return false; + } );// hooks::register + } + + /** + * Remove the hardcoded interwiki lookup table. + */ + public static function tearDownInterwikis() { + Hooks::clear( 'InterwikiLoadPrefix' ); + } + + public function setupRecorder( $options ) { if ( isset( $options['record'] ) ) { $this->recorder = new DbTestRecorder( $this ); $this->recorder->version = isset( $options['setversion'] ) ? - $options['setversion'] : SpecialVersion::getVersion(); + $options['setversion'] : SpecialVersion::getVersion(); } elseif ( isset( $options['compare'] ) ) { $this->recorder = new DbTestPreviewer( $this ); } else { @@ -222,11 +300,10 @@ class ParserTest { * Remove last character if it is a newline * @group utility */ - static public function chomp( $s ) { + public static function chomp( $s ) { if ( substr( $s, -1 ) === "\n" ) { return substr( $s, 0, -1 ); - } - else { + } else { return $s; } } @@ -372,7 +449,12 @@ class ParserTest { */ public function runTestsFromFiles( $filenames ) { $ok = false; + + // be sure, ParserTest::addArticle has correct language set, + // so that system messages gets into the right language cache + $GLOBALS['wgLanguageCode'] = 'en'; $GLOBALS['wgContLang'] = Language::factory( 'en' ); + $this->recorder->start(); try { $this->setupDatabase(); @@ -385,7 +467,7 @@ class ParserTest { $this->teardownDatabase(); $this->recorder->report(); - } catch (DBError $e) { + } catch ( DBError $e ) { echo $e->getMessage(); } $this->recorder->end(); @@ -412,6 +494,9 @@ class ParserTest { /** * Get a Parser object + * + * @param string $preprocessor + * @return Parser */ function getParser( $preprocessor = null ) { global $wgParserConf; @@ -458,8 +543,7 @@ class ParserTest { if ( isset( $opts['title'] ) ) { $titleText = $opts['title']; - } - else { + } else { $titleText = 'Parser test'; } @@ -482,9 +566,10 @@ class ParserTest { } elseif ( isset( $opts['comment'] ) ) { $out = Linker::formatComment( $input, $title, $local ); } elseif ( isset( $opts['preload'] ) ) { - $out = $parser->getpreloadText( $input, $title, $options ); + $out = $parser->getPreloadText( $input, $title, $options ); } else { $output = $parser->parse( $input, $title, $options, true, true, 1337 ); + $output->setTOCEnabled( !isset( $opts['notoc'] ) ); $out = $output->getText(); if ( isset( $opts['showtitle'] ) ) { @@ -513,18 +598,23 @@ class ParserTest { } $this->teardownGlobals(); - return $this->showTestResult( $desc, $result, $out ); + + $testResult = new ParserTestResult( $desc ); + $testResult->expected = $result; + $testResult->actual = $out; + + return $this->showTestResult( $testResult ); } /** - * + * Refactored in 1.22 to use ParserTestResult */ - function showTestResult( $desc, $result, $out ) { - if ( $result === $out ) { - $this->showSuccess( $desc ); + function showTestResult( ParserTestResult $testResult ) { + if ( $testResult->isSuccess() ) { + $this->showSuccess( $testResult ); return true; } else { - $this->showFailure( $desc, $result, $out ); + $this->showFailure( $testResult ); return false; } } @@ -532,7 +622,7 @@ class ParserTest { /** * Use a regex to find out the value of an option * @param $key String: name of option val to retrieve - * @param $opts Options array to look in + * @param $opts array: Options array to look in * @param $default Mixed: default value returned if not found */ private static function getOptionValue( $key, $opts, $default ) { @@ -627,40 +717,42 @@ class ParserTest { self::getOptionValue( 'wgLinkHolderBatchSize', $opts, 1000 ); $settings = array( - 'wgServer' => 'http://Britney-Spears', + 'wgServer' => 'http://example.org', 'wgScript' => '/index.php', 'wgScriptPath' => '/', 'wgArticlePath' => '/wiki/$1', 'wgActionPaths' => array(), - 'wgLockManagers' => array( - 'name' => 'fsLockManager', - 'class' => 'FSLockManager', + 'wgLockManagers' => array( array( + 'name' => 'fsLockManager', + 'class' => 'FSLockManager', 'lockDirectory' => $this->uploadDir . '/lockdir', - ), + ), array( + 'name' => 'nullLockManager', + 'class' => 'NullLockManager', + ) ), 'wgLocalFileRepo' => array( 'class' => 'LocalRepo', 'name' => 'local', 'url' => 'http://example.com/images', 'hashLevels' => 2, 'transformVia404' => false, - 'backend' => new FSFileBackend( array( - 'name' => 'local-backend', + 'backend' => new FSFileBackend( array( + 'name' => 'local-backend', 'lockManager' => 'fsLockManager', 'containerPaths' => array( - 'local-public' => $this->uploadDir, - 'local-thumb' => $this->uploadDir . '/thumb', - 'local-temp' => $this->uploadDir . '/temp', + 'local-public' => $this->uploadDir, + 'local-thumb' => $this->uploadDir . '/thumb', + 'local-temp' => $this->uploadDir . '/temp', 'local-deleted' => $this->uploadDir . '/delete', ) ) ) ), 'wgEnableUploads' => self::getOptionValue( 'wgEnableUploads', $opts, true ), 'wgStylePath' => '/skins', - 'wgStyleSheetPath' => '/skins', 'wgSitename' => 'MediaWiki', 'wgLanguageCode' => $lang, 'wgDBprefix' => $this->db->getType() != 'oracle' ? 'parsertest_' : 'pt_', - 'wgRawHtml' => isset( $opts['rawhtml'] ), + 'wgRawHtml' => self::getOptionValue( 'wgRawHtml', $opts, false ), 'wgLang' => null, 'wgContLang' => null, 'wgNamespacesWithSubpages' => array( 0 => isset( $opts['subpage'] ) ), @@ -669,18 +761,20 @@ class ParserTest { 'wgNoFollowLinks' => true, 'wgNoFollowDomainExceptions' => array(), 'wgThumbnailScriptPath' => false, - 'wgUseImageResize' => false, + 'wgUseImageResize' => true, + 'wgSVGConverter' => 'null', + 'wgSVGConverters' => array( 'null' => 'echo "1">$output' ), 'wgLocaltimezone' => 'UTC', - 'wgAllowExternalImages' => true, + 'wgAllowExternalImages' => self::getOptionValue( 'wgAllowExternalImages', $opts, true ), 'wgUseTidy' => false, 'wgDefaultLanguageVariant' => $variant, 'wgVariantArticlePath' => false, 'wgGroupPermissions' => array( '*' => array( 'createaccount' => true, - 'read' => true, - 'edit' => true, - 'createpage' => true, - 'createtalk' => true, + 'read' => true, + 'edit' => true, + 'createpage' => true, + 'createtalk' => true, ) ), 'wgNamespaceProtection' => array( NS_MEDIAWIKI => 'editinterface' ), 'wgDefaultExternalStore' => array(), @@ -747,7 +841,7 @@ class ParserTest { $tables = array( 'user', 'user_properties', 'user_former_groups', 'page', 'page_restrictions', 'protected_titles', 'revision', 'text', 'pagelinks', 'imagelinks', 'categorylinks', 'templatelinks', 'externallinks', 'langlinks', 'iwlinks', - 'site_stats', 'hitcounter', 'ipblocks', 'image', 'oldimage', + 'site_stats', 'hitcounter', 'ipblocks', 'image', 'oldimage', 'recentchanges', 'watchlist', 'interwiki', 'logging', 'querycache', 'objectcache', 'job', 'l10n_cache', 'redirect', 'querycachetwo', 'archive', 'user_groups', 'page_props', 'category', 'msg_resource', 'msg_resource_links' @@ -810,47 +904,13 @@ class ParserTest { # Anonymous user $this->db->insert( 'user', array( - 'user_id' => 0, - 'user_name' => 'Anonymous' ) ); + 'user_id' => 0, + 'user_name' => 'Anonymous' ) ); } - # Hack: insert a few Wikipedia in-project interwiki prefixes, - # for testing inter-language links - $this->db->insert( 'interwiki', array( - array( 'iw_prefix' => 'wikipedia', - 'iw_url' => 'http://en.wikipedia.org/wiki/$1', - 'iw_api' => '', - 'iw_wikiid' => '', - 'iw_local' => 0 ), - array( 'iw_prefix' => 'meatball', - 'iw_url' => 'http://www.usemod.com/cgi-bin/mb.pl?$1', - 'iw_api' => '', - 'iw_wikiid' => '', - 'iw_local' => 0 ), - array( 'iw_prefix' => 'zh', - 'iw_url' => 'http://zh.wikipedia.org/wiki/$1', - 'iw_api' => '', - 'iw_wikiid' => '', - 'iw_local' => 1 ), - array( 'iw_prefix' => 'es', - 'iw_url' => 'http://es.wikipedia.org/wiki/$1', - 'iw_api' => '', - 'iw_wikiid' => '', - 'iw_local' => 1 ), - array( 'iw_prefix' => 'fr', - 'iw_url' => 'http://fr.wikipedia.org/wiki/$1', - 'iw_api' => '', - 'iw_wikiid' => '', - 'iw_local' => 1 ), - array( 'iw_prefix' => 'ru', - 'iw_url' => 'http://ru.wikipedia.org/wiki/$1', - 'iw_api' => '', - 'iw_wikiid' => '', - 'iw_local' => 1 ), - ) ); - # Update certain things in site_stats - $this->db->insert( 'site_stats', array( 'ss_row_id' => 1, 'ss_images' => 2, 'ss_good_articles' => 1 ) ); + $this->db->insert( 'site_stats', + array( 'ss_row_id' => 1, 'ss_images' => 2, 'ss_good_articles' => 1 ) ); # Reinitialise the LocalisationCache to match the database state Language::getLocalisationCache()->unloadAll(); @@ -858,34 +918,67 @@ class ParserTest { # Clear the message cache MessageCache::singleton()->clear(); + // Remember to update newParserTests.php after changing the below + // (and it uses a slightly different syntax just for teh lulz) $this->uploadDir = $this->setupUploadDir(); $user = User::createNew( 'WikiSysop' ); $image = wfLocalFile( Title::makeTitle( NS_FILE, 'Foobar.jpg' ) ); + # note that the size/width/height/bits/etc of the file + # are actually set by inspecting the file itself; the arguments + # to recordUpload2 have no effect. That said, we try to make things + # match up so it is less confusing to readers of the code & tests. $image->recordUpload2( '', 'Upload of some lame file', 'Some lame file', array( - 'size' => 12345, - 'width' => 1941, - 'height' => 220, - 'bits' => 24, - 'media_type' => MEDIATYPE_BITMAP, - 'mime' => 'image/jpeg', - 'metadata' => serialize( array() ), - 'sha1' => wfBaseConvert( '', 16, 36, 31 ), - 'fileExists' => true - ), $this->db->timestamp( '20010115123500' ), $user ); + 'size' => 7881, + 'width' => 1941, + 'height' => 220, + 'bits' => 8, + 'media_type' => MEDIATYPE_BITMAP, + 'mime' => 'image/jpeg', + 'metadata' => serialize( array() ), + 'sha1' => wfBaseConvert( '1', 16, 36, 31 ), + 'fileExists' => true + ), $this->db->timestamp( '20010115123500' ), $user ); + + $image = wfLocalFile( Title::makeTitle( NS_FILE, 'Thumb.png' ) ); + # again, note that size/width/height below are ignored; see above. + $image->recordUpload2( '', 'Upload of some lame thumbnail', 'Some lame thumbnail', array( + 'size' => 22589, + 'width' => 135, + 'height' => 135, + 'bits' => 8, + 'media_type' => MEDIATYPE_BITMAP, + 'mime' => 'image/png', + 'metadata' => serialize( array() ), + 'sha1' => wfBaseConvert( '2', 16, 36, 31 ), + 'fileExists' => true + ), $this->db->timestamp( '20130225203040' ), $user ); + + $image = wfLocalFile( Title::makeTitle( NS_FILE, 'Foobar.svg' ) ); + $image->recordUpload2( '', 'Upload of some lame SVG', 'Some lame SVG', array( + 'size' => 12345, + 'width' => 240, + 'height' => 180, + 'bits' => 24, + 'media_type' => MEDIATYPE_DRAWING, + 'mime' => 'image/svg+xml', + 'metadata' => serialize( array() ), + 'sha1' => wfBaseConvert( '', 16, 36, 31 ), + 'fileExists' => true + ), $this->db->timestamp( '20010115123500' ), $user ); # This image will be blacklisted in [[MediaWiki:Bad image list]] $image = wfLocalFile( Title::makeTitle( NS_FILE, 'Bad.jpg' ) ); $image->recordUpload2( '', 'zomgnotcensored', 'Borderline image', array( - 'size' => 12345, - 'width' => 320, - 'height' => 240, - 'bits' => 24, - 'media_type' => MEDIATYPE_BITMAP, - 'mime' => 'image/jpeg', - 'metadata' => serialize( array() ), - 'sha1' => wfBaseConvert( '', 16, 36, 31 ), - 'fileExists' => true - ), $this->db->timestamp( '20010115123500' ), $user ); + 'size' => 12345, + 'width' => 320, + 'height' => 240, + 'bits' => 24, + 'media_type' => MEDIATYPE_BITMAP, + 'mime' => 'image/jpeg', + 'metadata' => serialize( array() ), + 'sha1' => wfBaseConvert( '3', 16, 36, 31 ), + 'fileExists' => true + ), $this->db->timestamp( '20010115123500' ), $user ); } public function teardownDatabase() { @@ -899,7 +992,7 @@ class ParserTest { $this->databaseSetupDone = false; if ( $this->useTemporaryTables ) { - if( $this->db->getType() == 'sqlite' ) { + if ( $this->db->getType() == 'sqlite' ) { # Under SQLite the searchindex table is virtual and need # to be explicitly destroyed. See bug 29912 # See also MediaWikiTestCase::destroyDB() @@ -914,12 +1007,16 @@ class ParserTest { $tables = $this->listTables(); foreach ( $tables as $table ) { - $sql = $this->db->getType() == 'oracle' ? "DROP TABLE pt_$table DROP CONSTRAINTS" : "DROP TABLE `parsertest_$table`"; - $this->db->query( $sql ); + if ( $this->db->getType() == 'oracle' ) { + $this->db->query( "DROP TABLE pt_$table DROP CONSTRAINTS" ); + } else { + $this->db->query( "DROP TABLE `parsertest_$table`" ); + } } - if ( $this->db->getType() == 'oracle' ) + if ( $this->db->getType() == 'oracle' ) { $this->db->query( 'BEGIN FILL_WIKI_INFO; END;' ); + } $this->teardownGlobals(); } @@ -951,9 +1048,15 @@ class ParserTest { wfMkdirParents( $dir . '/3/3a', null, __METHOD__ ); copy( "$IP/skins/monobook/headbg.jpg", "$dir/3/3a/Foobar.jpg" ); + wfMkdirParents( $dir . '/e/ea', null, __METHOD__ ); + copy( "$IP/skins/monobook/wiki.png", "$dir/e/ea/Thumb.png" ); wfMkdirParents( $dir . '/0/09', null, __METHOD__ ); copy( "$IP/skins/monobook/headbg.jpg", "$dir/0/09/Bad.jpg" ); - + wfMkdirParents( $dir . '/f/ff', null, __METHOD__ ); + copy( "$IP/skins/monobook/headbg.jpg", "$dir/f/ff/Foobar.svg" ); + file_put_contents( "$dir/f/ff/Foobar.svg", + '<?xml version="1.0" encoding="utf-8"?>' . + '<svg xmlns="http://www.w3.org/2000/svg" />' ); return $dir; } @@ -964,7 +1067,7 @@ class ParserTest { private function teardownGlobals() { RepoGroup::destroySingleton(); FileBackendGroup::destroySingleton(); - LockManagerGroup::destroySingleton(); + LockManagerGroup::destroySingletons(); LinkCache::singleton()->clear(); foreach ( $this->savedGlobals as $var => $val ) { @@ -982,21 +1085,41 @@ class ParserTest { // delete the files first, then the dirs. self::deleteFiles( - array ( + array( "$dir/3/3a/Foobar.jpg", "$dir/thumb/3/3a/Foobar.jpg/180px-Foobar.jpg", "$dir/thumb/3/3a/Foobar.jpg/200px-Foobar.jpg", "$dir/thumb/3/3a/Foobar.jpg/640px-Foobar.jpg", "$dir/thumb/3/3a/Foobar.jpg/120px-Foobar.jpg", + "$dir/thumb/3/3a/Foobar.jpg/1280px-Foobar.jpg", + "$dir/thumb/3/3a/Foobar.jpg/20px-Foobar.jpg", + "$dir/thumb/3/3a/Foobar.jpg/270px-Foobar.jpg", + "$dir/thumb/3/3a/Foobar.jpg/300px-Foobar.jpg", + "$dir/thumb/3/3a/Foobar.jpg/30px-Foobar.jpg", + "$dir/thumb/3/3a/Foobar.jpg/360px-Foobar.jpg", + "$dir/thumb/3/3a/Foobar.jpg/400px-Foobar.jpg", + "$dir/thumb/3/3a/Foobar.jpg/40px-Foobar.jpg", + "$dir/thumb/3/3a/Foobar.jpg/70px-Foobar.jpg", + "$dir/thumb/3/3a/Foobar.jpg/960px-Foobar.jpg", + + "$dir/e/ea/Thumb.png", "$dir/0/09/Bad.jpg", + "$dir/f/ff/Foobar.svg", + "$dir/thumb/f/ff/Foobar.svg/180px-Foobar.svg.png", + "$dir/thumb/f/ff/Foobar.svg/360px-Foobar.svg.png", + "$dir/thumb/f/ff/Foobar.svg/langde-270px-Foobar.svg.png", + "$dir/thumb/f/ff/Foobar.svg/270px-Foobar.svg.png", + "$dir/thumb/f/ff/Foobar.svg/langde-180px-Foobar.svg.png", + "$dir/thumb/f/ff/Foobar.svg/langde-360px-Foobar.svg.png", + "$dir/math/f/a/5/fa50b8b616463173474302ca3e63586b.png", ) ); self::deleteDirs( - array ( + array( "$dir/3/3a", "$dir/3", "$dir/thumb/6/65", @@ -1004,7 +1127,13 @@ class ParserTest { "$dir/thumb/3/3a/Foobar.jpg", "$dir/thumb/3/3a", "$dir/thumb/3", - + "$dir/e/ea", + "$dir/e", + "$dir/f/ff/", + "$dir/f/", + "$dir/thumb/f/ff/Foobar.svg", + "$dir/thumb/f/ff/", + "$dir/thumb/f/", "$dir/0/09/", "$dir/0/", "$dir/thumb", @@ -1051,10 +1180,12 @@ class ParserTest { /** * Print a happy success message. * - * @param $desc String: the test name + * Refactored in 1.22 to use ParserTestResult + * + * @param $testResult ParserTestResult * @return Boolean */ - protected function showSuccess( $desc ) { + protected function showSuccess( ParserTestResult $testResult ) { if ( $this->showProgress ) { print $this->term->color( '1;32' ) . 'PASSED' . $this->term->reset() . "\n"; } @@ -1066,28 +1197,29 @@ class ParserTest { * Print a failure message and provide some explanatory output * about what went wrong if so configured. * - * @param $desc String: the test name - * @param $result String: expected HTML output - * @param $html String: actual HTML output + * Refactored in 1.22 to use ParserTestResult + * + * @param $testResult ParserTestResult * @return Boolean */ - protected function showFailure( $desc, $result, $html ) { + protected function showFailure( ParserTestResult $testResult ) { if ( $this->showFailure ) { if ( !$this->showProgress ) { # In quiet mode we didn't show the 'Testing' message before the # test, in case it succeeded. Show it now: - $this->showTesting( $desc ); + $this->showTesting( $testResult->description ); } print $this->term->color( '31' ) . 'FAILED!' . $this->term->reset() . "\n"; if ( $this->showOutput ) { - print "--- Expected ---\n$result\n--- Actual ---\n$html\n"; + print "--- Expected ---\n{$testResult->expected}\n"; + print "--- Actual ---\n{$testResult->actual}\n"; } if ( $this->showDiffs ) { - print $this->quickDiff( $result, $html ); - if ( !$this->wellFormed( $html ) ) { + print $this->quickDiff( $testResult->expected, $testResult->actual ); + if ( !$this->wellFormed( $testResult->actual ) ) { print "XML error: $this->mXmlError\n"; } } @@ -1106,7 +1238,9 @@ class ParserTest { * @param $outFileTail String: tailing for the output file name * @return String */ - protected function quickDiff( $input, $output, $inFileTail = 'expected', $outFileTail = 'actual' ) { + protected function quickDiff( $input, $output, + $inFileTail = 'expected', $outFileTail = 'actual' + ) { # Windows, or at least the fc utility, is retarded $slash = wfIsWindows() ? '\\' : '/'; $prefix = wfTempDir() . "{$slash}mwParser-" . mt_rand(); @@ -1117,14 +1251,15 @@ class ParserTest { $outfile = "$prefix-$outFileTail"; $this->dumpToFile( $output, $outfile ); - $shellInfile = wfEscapeShellArg($infile); - $shellOutfile = wfEscapeShellArg($outfile); + $shellInfile = wfEscapeShellArg( $infile ); + $shellOutfile = wfEscapeShellArg( $outfile ); global $wgDiff3; // we assume that people with diff3 also have usual diff - $diff = ( wfIsWindows() && !$wgDiff3 ) - ? `fc $shellInfile $shellOutfile` - : `diff -au $shellInfile $shellOutfile`; + $shellCommand = ( wfIsWindows() && !$wgDiff3 ) ? 'fc' : 'diff -au'; + + $diff = wfShellExec( "$shellCommand $shellInfile $shellOutfile" ); + unlink( $infile ); unlink( $outfile ); @@ -1154,7 +1289,7 @@ class ParserTest { return preg_replace( array( '/^(-.*)$/m', '/^(\+.*)$/m' ), array( $this->term->color( 34 ) . '$1' . $this->term->reset(), - $this->term->color( 31 ) . '$1' . $this->term->reset() ), + $this->term->color( 31 ) . '$1' . $this->term->reset() ), $text ); } @@ -1177,7 +1312,7 @@ class ParserTest { * @param $line Integer: the input line number, for reporting errors * @param $ignoreDuplicate Boolean: whether to silently ignore duplicate pages */ - static public function addArticle( $name, $text, $line = 'unknown', $ignoreDuplicate = '' ) { + public static function addArticle( $name, $text, $line = 'unknown', $ignoreDuplicate = '' ) { global $wgCapitalLinks; $oldCapitalLinks = $wgCapitalLinks; @@ -1203,7 +1338,7 @@ class ParserTest { } } - $page->doEdit( $text, '', EDIT_NEW ); + $page->doEditContent( ContentHandler::makeContent( $text, $title ), '', EDIT_NEW ); $wgCapitalLinks = $oldCapitalLinks; } @@ -1219,7 +1354,7 @@ class ParserTest { public function requireHook( $name ) { global $wgParser; - $wgParser->firstCallInit( ); // make sure hooks are loaded. + $wgParser->firstCallInit(); // make sure hooks are loaded. if ( isset( $wgParser->mTagHooks[$name] ) ) { $this->hooks[$name] = $wgParser->mTagHooks[$name]; @@ -1242,7 +1377,7 @@ class ParserTest { public function requireFunctionHook( $name ) { global $wgParser; - $wgParser->firstCallInit( ); // make sure hooks are loaded. + $wgParser->firstCallInit(); // make sure hooks are loaded. if ( isset( $wgParser->mFunctionHooks[$name] ) ) { $this->functionHooks[$name] = $wgParser->mFunctionHooks[$name]; @@ -1274,9 +1409,9 @@ class ParserTest { private function wellFormed( $text ) { $html = Sanitizer::hackDocType() . - '<html>' . - $text . - '</html>'; + '<html>' . + $text . + '</html>'; $parser = xml_parser_create( "UTF-8" ); @@ -1324,7 +1459,7 @@ class ParserTest { } static function getFakeTimestamp( &$parser, &$ts ) { - $ts = 123; + $ts = 123; //parsed as '1970-01-01T00:02:03Z' return true; } } diff --git a/tests/parser/parserTests.txt b/tests/parser/parserTests.txt index df057248..9658e8f8 100644 --- a/tests/parser/parserTests.txt +++ b/tests/parser/parserTests.txt @@ -21,9 +21,16 @@ # language=XXX set content language to XXX for this test # variant=XXX set the variant of language for this test (eg zh-tw) # disabled do not run test +# parsoid parsoid-only test (not run by PHP parser) +# php php-only test (not run by the parsoid parser) # showtitle make the first line the title # comment run through Linker::formatComment() instead of main parser # local format section links in edit comment text as local links +# notoc disable table of contents +# +# You can also set the following parser properties via test options: +# wgEnableUploads, wgAllowExternalImages, wgMaxTocLevel, +# wgLinkHolderBatchSize, wgRawHtml # # For testing purposes, temporary articles can created: # !!article / NAMESPACE:TITLE / !!text / ARTICLE TEXT / !!endarticle @@ -36,19 +43,19 @@ Main Page blah blah !! endarticle -!!article +!!article Template:Foo !!text FOO !!endarticle -!! article +!! article Template:Blank !! text !! endarticle !! article -Template:! +Template:pipe !! text | !! endarticle @@ -65,6 +72,80 @@ Template:inner list * item 1 !! endarticle +!! article +Template:tbl-start +!! text +{| +!! endarticle + +!! article +Template:tbl-end +!! text +|} +!! endarticle + +!! article +Template:! +!! text +| +!! endarticle + +!! article +Template:echo +!! text +{{{1}}} +!! endarticle + +!! article +Template:echo_with_span +!! text +<span>{{{1}}}</span> +!! endarticle + +!! article +Template:echo_with_div +!! text +<div>{{{1}}}</div> +!! endarticle + +!! article +Template:attr_str +!! text +{{{1}}}="{{{2}}}" +!! endarticle + +!! article +Template:table_attribs +!! text +<noinclude> +|</noinclude>style="color: red"| Foo +!! endarticle + +!! article +Template:table_cells +!! text +{{table_attribs}} || Bar || Baz +!! endarticle + +!! article +Template:image_attribs +!! text +<noinclude> +[[File:foobar.jpg|</noinclude>right|Caption text<noinclude>]]</noinclude> +!! endarticle + +!! article +A?b +!! text +Weirdo titles! +!! endarticle + +!!article +Template:Bullet +!!text +* Bar +!!endarticle + ### ### Basic tests ### @@ -108,14 +189,227 @@ baz !! end !! test +Paragraphs with newline spacing with comment lines in between +!! input +---- +a +<!--foo--> +b +---- +a +<!--foo--><!--More than 1 comment, still stripped--> +b +---- +a + <!--foo--> <!----> <!-- bar --> +b +---- +a +<!--foo--> + +b +---- +a + +<!--foo--> +b +---- +a +<!--foo--> + + +b +---- +a + + +<!--foo--> +b +---- +!! result +<hr /> +<p>a +b +</p> +<hr /> +<p>a +b +</p> +<hr /> +<p>a +b +</p> +<hr /> +<p>a +</p><p>b +</p> +<hr /> +<p>a +</p><p>b +</p> +<hr /> +<p>a +</p><p><br /> +b +</p> +<hr /> +<p>a +</p><p><br /> +b +</p> +<hr /> + +!! end + +!! test +Paragraphs with newline spacing with non-empty white-space lines in between +!! input +---- +a + +b +---- +a + + +b +---- +!! result +<hr /> +<p>a +</p><p>b +</p> +<hr /> +<p>a +</p><p><br /> +b +</p> +<hr /> + +!! end + +!! test +Paragraphs with newline spacing with non-empty mixed comment and white-space lines in between +!! input +---- +a + <!--foo--> +b +---- +a + <!--foo--><!--More than 1 comment doesn't disable stripping of this line!--> +b +---- +a + +<!--foo--> + <!--bar--> +b +---- +a + + <!--foo--> + <!--bar--> + +b +---- +!! result +<hr /> +<p>a +b +</p> +<hr /> +<p>a +b +</p> +<hr /> +<p>a +</p><p>b +</p> +<hr /> +<p>a +</p><p><br /> +b +</p> +<hr /> + +!! end + +!! test +Extra newlines: More paragraphs with indented comment +!! input +a + + <!--boo--> + +b +!!result +<p>a +</p><p><br /> +b +</p> +!!end + +!! test +Extra newlines followed by heading +!! input +a + + + +=b= +[[a]] + + +=b= +!! result +<p>a +</p><p><br /> +</p> +<h1><span class="mw-headline" id="b">b</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Parser_test&action=edit&section=1" title="Edit section: b">edit</a><span class="mw-editsection-bracket">]</span></span></h1> +<p><a href="/index.php?title=A&action=edit&redlink=1" class="new" title="A (page does not exist)">a</a> +</p><p><br /> +</p> +<h1><span class="mw-headline" id="b_2">b</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Parser_test&action=edit&section=2" title="Edit section: b">edit</a><span class="mw-editsection-bracket">]</span></span></h1> + +!! end + +!! test +Extra newlines between heading and content are swallowed +!! input +=b= + + + +[[a]] +!! result +<h1><span class="mw-headline" id="b">b</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Parser_test&action=edit&section=1" title="Edit section: b">edit</a><span class="mw-editsection-bracket">]</span></span></h1> +<p><a href="/index.php?title=A&action=edit&redlink=1" class="new" title="A (page does not exist)">a</a> +</p> +!! end + +!! test +Parsing an URL +!! input +http://fr.wikipedia.org/wiki/🍺 +<!-- EasterEgg we love beer, better be able be able to link to it --> +!! result +<p><a rel="nofollow" class="external free" href="http://fr.wikipedia.org/wiki/🍺">http://fr.wikipedia.org/wiki/🍺</a> +</p> +!! end + +!! test Simple list !! input * Item 1 * Item 2 !! result -<ul><li> Item 1 -</li><li> Item 2 -</li></ul> +<ul> +<li> Item 1 +</li> +<li> Item 2 +</li> +</ul> !! end @@ -138,25 +432,72 @@ Italics and bold * plain l'''italic''plain * plain l''''bold''' plain !! result -<ul><li> plain -</li><li> plain<i>italic</i>plain -</li><li> plain<i>italic</i>plain<i>italic</i>plain -</li><li> plain<b>bold</b>plain -</li><li> plain<b>bold</b>plain<b>bold</b>plain -</li><li> plain<i>italic</i>plain<b>bold</b>plain -</li><li> plain<b>bold</b>plain<i>italic</i>plain -</li><li> plain<i>italic<b>bold-italic</b>italic</i>plain -</li><li> plain<b>bold<i>bold-italic</i>bold</b>plain -</li><li> plain<i><b>bold-italic</b>italic</i>plain -</li><li> plain<b><i>bold-italic</i>bold</b>plain -</li><li> plain<i>italic<b>bold-italic</b></i>plain -</li><li> plain<b>bold<i>bold-italic</i></b>plain -</li><li> plain l'<i>italic</i>plain -</li><li> plain l'<b>bold</b> plain -</li></ul> +<ul> +<li> plain +</li> +<li> plain<i>italic</i>plain +</li> +<li> plain<i>italic</i>plain<i>italic</i>plain +</li> +<li> plain<b>bold</b>plain +</li> +<li> plain<b>bold</b>plain<b>bold</b>plain +</li> +<li> plain<i>italic</i>plain<b>bold</b>plain +</li> +<li> plain<b>bold</b>plain<i>italic</i>plain +</li> +<li> plain<i>italic<b>bold-italic</b>italic</i>plain +</li> +<li> plain<b>bold<i>bold-italic</i>bold</b>plain +</li> +<li> plain<i><b>bold-italic</b>italic</i>plain +</li> +<li> plain<b><i>bold-italic</i>bold</b>plain +</li> +<li> plain<i>italic<b>bold-italic</b></i>plain +</li> +<li> plain<b>bold<i>bold-italic</i></b>plain +</li> +<li> plain l'<i>italic</i>plain +</li> +<li> plain l'<b>bold</b> plain +</li> +</ul> !! end +# this example taken from the [[simple:Moon]] article (bug 47326) +!! test +Italics and possessives (1) +!! input +obtained by ''[[Lunar Prospector]]'''s gamma-ray spectrometer +!! result +<p>obtained by <i><a href="/index.php?title=Lunar_Prospector&action=edit&redlink=1" class="new" title="Lunar Prospector (page does not exist)">Lunar Prospector</a>'</i>s gamma-ray spectrometer +</p> +!! end + +# this example taken from [[en:Flaming Pie]] (bug 49926) +!! test +Italics and possessives (2) +!! input +'''''Flaming Pie''''' is ... released in 1997. In ''Flaming Pie'''s liner notes +!! result +<p><i><b>Flaming Pie</b></i> is ... released in 1997. In <i>Flaming Pie'</i>s liner notes +</p> +!! end + +# this example taken from [[en:Dictionary]] (bug 49926) +!! test +Italics and possessives (3) +!! input +The first monolingual dictionary written in a Romance language was ''Sebastián Covarrubias''' ''Tesoro de la lengua castellana o española'', published in 1611 in Madrid. In 1612 the first edition of the ''Vocabolario dell'[[Accademia della Crusca]]'', for Italian, was published. In 1690 in Rotterdam was published, posthumously, the ''Dictionnaire Universel''. +!! result +<p>The first monolingual dictionary written in a Romance language was <i>Sebastián Covarrubias'</i> <i>Tesoro de la lengua castellana o española</i>, published in 1611 in Madrid. In 1612 the first edition of the <i>Vocabolario dell'<a href="/index.php?title=Accademia_della_Crusca&action=edit&redlink=1" class="new" title="Accademia della Crusca (page does not exist)">Accademia della Crusca</a></i>, for Italian, was published. In 1690 in Rotterdam was published, posthumously, the <i>Dictionnaire Universel</i>. +</p> +!! end + + ### ### 2-quote opening sequence tests ### @@ -191,13 +532,26 @@ Italics and bold: 2-quote opening sequence: (2,4) !! test -Italics and bold: 2-quote opening sequence: (2,5) +Italics and bold: 2-quote opening sequence: (2,5) (php) +!! options +php !! input ''foo''''' !! result <p><i>foo</i> </p> !!end +# The PHP parser strips the empty tags out for giggles; parsoid doesn't. +!! test +Italics and bold: 2-quote opening sequence: (2,5) (parsoid) +!! options +parsoid +!! input +''foo''''' +!! result +<p><i>foo</i><b></b> +</p> +!!end ### @@ -235,13 +589,26 @@ Italics and bold: 3-quote opening sequence: (3,4) !! test -Italics and bold: 3-quote opening sequence: (3,5) +Italics and bold: 3-quote opening sequence: (3,5) (php) +!! options +php !! input '''foo''''' !! result <p><b>foo</b> </p> !!end +# The PHP parser strips the empty tags out for giggles; parsoid doesn't. +!! test +Italics and bold: 3-quote opening sequence: (3,5) (parsoid) +!! options +parsoid +!! input +'''foo''''' +!! result +<p><b>foo</b><i></i> +</p> +!!end ### @@ -279,13 +646,26 @@ Italics and bold: 4-quote opening sequence: (4,4) !! test -Italics and bold: 4-quote opening sequence: (4,5) +Italics and bold: 4-quote opening sequence: (4,5) (php) +!! options +php !! input ''''foo''''' !! result <p>'<b>foo</b> </p> !!end +# The PHP parser strips the empty tags out for giggles; parsoid doesn't. +!! test +Italics and bold: 4-quote opening sequence: (4,5) (parsoid) +!! options +parsoid +!! input +''''foo''''' +!! result +<p>'<b>foo</b><i></i> +</p> +!!end ### @@ -294,6 +674,7 @@ Italics and bold: 4-quote opening sequence: (4,5) !! test Italics and bold: 5-quote opening sequence: (5,2) +!! options !! input '''''foo'' !! result @@ -365,23 +746,49 @@ Italics and bold: multiple quote sequences: (2,4,4) !! test -Italics and bold: multiple quote sequences: (3,4,2) +Italics and bold: multiple quote sequences: (3,4,2) (php) +!! options +php !! input '''foo''''bar'' !! result <p><b>foo'</b>bar </p> !!end +# The PHP parser strips the empty tags out for giggles; parsoid doesn't. +!! test +Italics and bold: multiple quote sequences: (3,4,2) (parsoid) +!! options +parsoid +!! input +'''foo''''bar'' +!! result +<p><b>foo'</b>bar<i></i> +</p> +!!end !! test -Italics and bold: multiple quote sequences: (3,4,3) +Italics and bold: multiple quote sequences: (3,4,3) (php) +!! options +php !! input '''foo''''bar''' !! result <p><b>foo'</b>bar </p> !!end +# The PHP parser strips the empty tags out for giggles; parsoid doesn't. +!! test +Italics and bold: multiple quote sequences: (3,4,3) (parsoid) +!! options +parsoid +!! input +'''foo''''bar''' +!! result +<p><b>foo'</b>bar<b></b> +</p> +!!end ### ### other quote tests @@ -418,6 +825,7 @@ Italics and bold: other quote tests: (3,2,3,2) !! test Italics and bold: other quote tests: (3,2,3,3) +!! options !! input '''this is about ''foo'''s family''' !! result @@ -426,7 +834,6 @@ Italics and bold: other quote tests: (3,2,3,3) !!end - !! test Italics and bold: other quote tests: (3,(2,2),3) !! input @@ -436,6 +843,122 @@ Italics and bold: other quote tests: (3,(2,2),3) </p> !!end + +!! test +Italicized possessive +!! input +The ''[[Main Page]]'''s talk page. +!! result +<p>The <i><a href="/wiki/Main_Page" title="Main Page">Main Page</a>'</i>s talk page. +</p> +!! end + +!! test +Parsoid only: Quote balancing context should be restricted to td/th cells on the same wikitext line +(Requires tidy for PHP parser output to be fixed up) +!! options +parsoid=wt2html,wt2wt +!! input +{| +!''a!!''b +|''a||''b +|} +!! result +<table> +<tbody><tr><th><i>a</i></th><th><i>b</i></th> +<td><i>a</i></td><td><i>b</i></td></tr> +</tbody></table> +!! end + +### +### Non-html5 tags +### + +!! test +Non-html5 tags should be accepted +!! input +<center>''foo''</center> +<big>''foo''</big> +<font>''foo''</font> +<strike>''foo''</strike> +<tt>''foo''</tt> +!! result +<center><i>foo</i></center> +<p><big><i>foo</i></big> +<font><i>foo</i></font> +<strike><i>foo</i></strike> +<tt><i>foo</i></tt> +</p> +!! end + +!! test +<wbr> is valid wikitext (bug 52468) +!! input +<wbr> +!! result +<p><wbr /> +</p> +!! end + +# <strike> is HTML4, <s> is HTML4/5. +!! test +<s> or <strike> for strikethrough +!! input +<strike>strike</strike> + +<s>s</s> +!! result +<p><strike>strike</strike> +</p><p><s>s</s> +</p> +!! end + +!! test +Non-word characters don't terminate tag names (bug 17663, 40670, 52022) +!! input +<b→> doesn't work! </b> + +<bä> doesn't work! </b> + +<boo> works fine </b> + +<s.foo>foo</s> + +<s.foo>s.foo</s.foo> + +<sub-ID#1> +!! result +<p><b→> doesn't work! </b> +</p><p><bä> doesn't work! </b> +</p><p><boo> works fine </b> +</p><p><s.foo>foo</s> +</p><p><s.foo>s.foo</s.foo> +</p><p><sub-ID#1> +</p> +!! end + +### +### Special characters +### + +!! test +Bare pipe character (bug 52363) +!! input +| +!! result +<p>| +</p> +!! end + +!! test +Bare pipe character from a template (bug 52363) +!! input +{{pipe}} +!! result +<p>| +</p> +!! end + ### ### <nowiki> test cases ### @@ -482,35 +1005,79 @@ nowiki 3 *There is not nowiki. *There is <nowiki>nowiki</nowiki>. !! result -<dl><dd>There is not nowiki. -</dd><dd>There is nowiki. -</dd></dl> -<ol><li>There is not nowiki. -</li><li>There is nowiki. -</li></ol> -<ul><li>There is not nowiki. -</li><li>There is nowiki. -</li></ul> +<dl> +<dd>There is not nowiki. +</dd> +<dd>There is nowiki. +</dd> +</dl> +<ol> +<li>There is not nowiki. +</li> +<li>There is nowiki. +</li> +</ol> +<ul> +<li>There is not nowiki. +</li> +<li>There is nowiki. +</li> +</ul> !! end +!! test +Entities inside <nowiki> +!! input +<nowiki><</nowiki> +!! result +<p>< +</p> +!! end + +!! test +Entities inside template parameters +!! options +parsoid +!! input +{{echo|–}} +!! result +<p><span typeof="mw:Transclusion mw:Entity" data-mw='{"parts":[{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":"&ndash;"}},"i":0}}]}'>–</span> +</p> +!! end ### ### Comments ### !! test -Comment test 1 +Comments and Indent-Pre !! input <!-- comment 1 --> asdf + +<!-- comment 1 --> asdf <!-- comment 2 --> + +<!-- comment 1 --> asdf +<!-- comment 2 -->xyz + +<!-- comment 1 --> asdf +<!-- comment 2 --> xyz !! result <pre>asdf </pre> - +<pre>asdf +</pre> +<pre>asdf +</pre> +<p>xyz +</p> +<pre>asdf +xyz +</pre> !! end !! test -Comment test 2 +Comment test 2a !! input asdf <!-- comment 1 --> @@ -522,6 +1089,19 @@ jkl !! end !! test +Comment test 2b +!! input +asdf +<!-- comment 1 --> + +jkl +!! result +<p>asdf +</p><p>jkl +</p> +!! end + +!! test Comment test 3 !! input asdf @@ -633,6 +1213,129 @@ b </p> !! end +!! test +Comment on its own line post-expand with non-significant whitespace +!! input +a + {{blank}} <!----> +b +!! result +<p>a +</p><p>b +</p> +!! end + +### +### paragraph wrapping tests +### +!! test +No block tags +!! input +a + +b +!! result +<p>a +</p><p>b +</p> +!! end + +!! test +Block tag on one line (<div>) +!! input +a <div>foo</div> + +b +!! result +a <div>foo</div> +<p>b +</p> +!! end + +!! test +Block tag on one line (<blockquote>) +!! input +a <blockquote>foo</blockquote> + +b +!! result +a <blockquote>foo</blockquote> +<p>b +</p> +!! end + +!! test +Block tag on both lines (<div>) +!! input +a <div>foo</div> + +b <div>foo</div> +!! result +a <div>foo</div> +b <div>foo</div> + +!! end + +!! test +Block tag on both lines (<blockquote>) +!! input +a <blockquote>foo</blockquote> + +b <blockquote>foo</blockquote> +!! result +a <blockquote>foo</blockquote> +b <blockquote>foo</blockquote> + +!! end + +!! test +Multiple lines without block tags +!! input +<div>foo</div> a +b +c +d<!--foo--> e +x <div>foo</div> z +!! result +<div>foo</div> a +<p>b +c +d e +</p> +x <div>foo</div> z + +!! end + +!! test +Empty lines between lines with block tags +!! input +<div></div> + + +<div></div>a + +b +<div>a</div>b + +<div>b</div>d + + +<div>e</div> +!! result +<div></div> +<p><br /> +</p> +<div></div>a +<p>b +</p> +<div>a</div>b +<div>b</div>d +<p><br /> +</p> +<div>e</div> + +!! end + ### ### Preformatted text ### @@ -654,6 +1357,33 @@ And a <a href="/wiki/Main_Page" title="Main Page">link</a> !! end !! test +Tabs don't trigger preformatted text +!! input + This is not + preformatted text. + This is preformatted text. + So is this. +!! result +<p> This is not + preformatted text. +</p> +<pre>This is preformatted text. + So is this. +</pre> +!! end + +!! test +Ident preformatting with inline content +!! input + a + ''b'' +!! result +<pre>a +<i>b</i> +</pre> +!! end + +!! test <pre> with <nowiki> inside (compatibility with 1.6 and earlier) !! input <pre><nowiki> @@ -684,17 +1414,61 @@ Regression with preformatted in <center> !! end -# Expected output in the following test is not really expected (there should be -# <pre> in the output) -- it's only testing for well-formedness. !! test -Bug 6200: Preformatted in <blockquote> +Bug 52763: Preformatted in <blockquote> !! input <blockquote> Blah </blockquote> !! result <blockquote> - Blah +<p> Blah +</p> +</blockquote> + +!! end + +!! test +Bug 51086: Double newlines in blockquotes should be turned into paragraphs +!! input +<blockquote> +Foo + +Bar +</blockquote> +!! result +<blockquote> +<p>Foo +</p><p>Bar +</p> +</blockquote> + +!! end + +!! test +Bug 15491: <ins>/<del> in blockquote +!! input +<blockquote> +Foo <del>bar</del> <ins>baz</ins> quux +</blockquote> +!! result +<blockquote> +<p>Foo <del>bar</del> <ins>baz</ins> quux +</p> +</blockquote> + +!! end + +# Note that the p-wrapping is newline sensitive, which could be +# considered a bug: tidy will wrap only the 'Foo' in the example +# below in a <p> tag. (see comment 23-25 of bug #6200) +!! test +Bug 15491: <ins>/<del> in blockquote (2) +!! input +<blockquote>Foo <del>bar</del> <ins>baz</ins> quux +</blockquote> +!! result +<blockquote>Foo <del>bar</del> <ins>baz</ins> quux </blockquote> !! end @@ -727,6 +1501,15 @@ Bug 6200: Preformatted in <blockquote> !! end !! test +Entities inside <pre> +!! input +<pre><</pre> +!! result +<pre><</pre> + +!! end + +!! test <pre> with forbidden attribute values (bug 3202) !! input <pre width="8" style="border-width: expression(alert(document.cookie))">Narrow screen goodies</pre> @@ -787,6 +1570,700 @@ Bug 6200: Preformatted in <blockquote> </p> !! end +!! test +</pre> inside nowiki +!! input +<nowiki></pre></nowiki> +!! result +<p></pre> +</p> +!! end + +!!test +Templates: Indent-Pre: 1a. Templates that break a line should suppress <pre> +!!input + {{echo|}} +!!result + +!!end + +!!test +Templates: Indent-Pre: 1b. Templates that break a line should suppress <pre> +!!input + {{echo| +foo}} +!!result +<p>foo +</p> +!!end + +!! test +Templates: Indent-Pre: 1c: Wrapping should be based on expanded content +!! input + {{echo|a +b}} +!!result +<pre>a +</pre> +<p>b +</p> +!!end + +!! test +Templates: Indent-Pre: 1d: Wrapping should be based on expanded content +!! input + {{echo|a +b +c + d +e +}} +!!result +<pre>a +</pre> +<p>b +c +</p> +<pre>d +</pre> +<p>e +</p> +!!end + +!!test +Templates: Indent-Pre: 1e. Wrapping should be based on expanded content +!!input +{{echo| foo}} + +{{echo| foo}}{{echo| bar}} + +{{echo| foo}} +{{echo| bar}} + +{{echo|<!--cmt--> foo}} + +<!--cmt-->{{echo| foo}} + +{{echo|{{echo| }}bar}} +!!result +<pre>foo +</pre> +<pre>foo bar +</pre> +<pre>foo +bar +</pre> +<pre>foo +</pre> +<pre>foo +</pre> +<pre>bar +</pre> +!!end + +!! test +Templates: Indent-Pre: 1f: Wrapping should be based on expanded content +!! input +{{echo| }}a + +{{echo| + }}a + +{{echo| + b}} + +{{echo|a + }}b + +{{echo|a +}} b +!!result +<pre>a +</pre> +<p><br /> +</p> +<pre>a +</pre> +<p><br /> +</p> +<pre>b +</pre> +<p>a +</p> +<pre>b +</pre> +<p>a +</p> +<pre>b +</pre> +!!end + +# TODO / maybe: fix wt2wt for this +!! test +Parsoid: Don't paragraph-wrap fosterable content +!! options +parsoid=wt2html +!! input +{| +<td></td> +<td></td> + + + +|} +!! result +<table> + +<tbody> +<tr> +<td></td> + +<td></td></tr> + + + +</tbody></table> +!! end + +!! test +Parsoid: Don't paragraph-wrap fosterable content even if table syntax is unbalanced +!! options +parsoid=wt2html +!! input +{| +<td> +<td> +</td> + + + +|} +!! result +<table> + +<tbody> +<tr> +<td></td> + +<td> +</td></tr> + + + +</tbody></table> +!! end + + +#-------------------------------------------------------------------- +# Transclusion parameter whitespace stripping tests +# Behavior is different for positional and named parameters +#-------------------------------------------------------------------- +!! test +Templates: Strip leading and trailing whitespace from named-param values +!! input +{{echo|1= a }} + +{{echo|1= {{echo|b}} }} + +{{echo| 1 = + c }} + +{{echo| 1 = +* d +}} +!! result +<p>a +</p><p>b +</p><p>c +</p> +<ul> +<li> d +</li> +</ul> + +!! end + +!! test +Templates: Don't strip whitespace from positional-param values +!! input +{{echo|a }} + +{{echo|{{echo|b}} }} + +{{echo| c +}} + +{{echo| {{echo|d}} +}} + +{{echo| + e}} + +{{echo| +* f}} + +{{echo| + }}g +!! result +<p>a +</p><p>b +</p> +<pre>c +</pre> +<p><br /> +</p> +<pre>d +</pre> +<p><br /> +</p> +<pre>e +</pre> +<p><br /> +</p> +<ul> +<li> f +</li> +</ul> +<p><br /> +</p> +<pre>g +</pre> +!! end + +!! test +Templates: Handle empty comment-and-ws-only lines correctly +!! input +{{echo|foo +<!--should be ignored--> + <!--should be ignored as well--> +bar}} +!! result +<p>foo +bar +</p> +!! end + +#-------------------------------------------------------------------- +# Transclusion parameter escaping tests +#-------------------------------------------------------------------- +!! test +Templates: Parsoid parameter escaping test 1 +!! options +parsoid +!! input +{{echo|[foo]|{{echo|[bar]}}}} +!! result +<p about="#mwt1" typeof="mw:Transclusion" +data-mw='{"parts":[{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":"[foo]"},"2":{"wt":"{{echo|[bar]}}"}},"i":0}}]}'>[foo]</p> +!! end + +!! test +Parsoid: Pipes in external links in template parameter +!! options +parsoid +!! input +{{echo|[{{echo|http://example.com}} link]}} +!! result +<p><a rel="mw:ExtLink" href="http://example.com" about="#mwt31" typeof="mw:Transclusion" data-mw='{"parts":[{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":"[{{echo|http://example.com}} link]"}},"i":0}}]}'>link</a></p> +!! end + +!! test +Parsoid: pipe in transclusion parameter +!! options +parsoid +!! input +{{echo|http://foo.com/a|b}} +!! result +<p><a rel="mw:ExtLink" href="http://foo.com/a|b" about="#mwt1" +typeof="mw:Transclusion" +data-mw='{"parts":[{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":"http://foo.com/a&#124;b"}},"i":0}}]}'>http://foo.com/a|b</a></p> +!! end + +!! test +Parsoid: Pipe in external link target and content in template parameter +!! options +parsoid=html2wt,wt2wt +!! input +{{echo|[http://foo.com/a|b a|b]}} +!! result +<p><a rel="mw:ExtLink" href="http://foo.com/a|b" about="#mwt1" +typeof="mw:Transclusion" +data-mw='{"parts":[{"template":{"target":{"wt":"echo","href":"./Template:Echo"}, +"params":{"1":{"wt":"[http://foo.com/a|b a|b]"}},"i":0}}]}'>a|b</a></p> +!! end + +!! test +Templates: Dont escape already nowiki-escaped text in template parameters +!! options +parsoid=html2wt,wt2wt +!! input +{{echo|foo<nowiki>|</nowiki>bar}} +{{echo|<nowiki><div></nowiki>}} +{{echo|<nowiki></nowiki>}} +!! result +<p><span about="#mwt1" typeof="mw:Transclusion" data-mw='{"parts":[{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":"foo<nowiki>|</nowiki>bar"}},"i":0}}]}'}'>foo</span><span typeof="mw:Nowiki" about="#mwt1">|</span><span about="#mwt1">bar</span> +<span typeof="mw:Transclusion mw:Nowiki" about="#mwt2" data-mw='{"parts":[{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":"<nowiki>&lt;div&gt;</nowiki>"}},"i":0}}]}'><span typeof="mw:Entity"><</span>div<span typeof="mw:Entity">></span></span> +<span typeof="mw:Transclusion mw:Nowiki" about="#mwt3" data-mw='{"parts":[{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":"<nowiki></nowiki>"}},"i":0}}]}'></span> +</p> +!! end + +## Bug 52824 +!! test +Templates: '=' char in nested transclusions should not trigger nowiki escapes or conversion to named param +!! options +parsoid=html2wt,wt2wt +!! input +{{echo|{{echo|1=bar}}}} +!! result +<p about="#mwt1" typeof="mw:Transclusion" data-mw='{"parts":[{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":"{{echo|1=bar}}"}},"i":0}}]}'>bar</p> +!! end + +### +### Parsoid-centric tests for testing RT edge cases for pre +### + +!!test +1a. Indent-Pre and Comments +!!input + a +<!--a--> +c +!!result +<pre>a +</pre> +<p>c +</p> +!!end + +!!test +1b. Indent-Pre and Comments +!!input + a + <!--a--> +c +!!result +<pre>a +</pre> +<p>c +</p> +!!end + +!!test +1c. Indent-Pre and Comments +!!input +<!--a--> a + + <!--a--> a +!!result +<pre> a +</pre> +<pre> a +</pre> +!!end + +!!test +1d. Indent-Pre and Comments +(Pre-handler currently cannot distinguish between comment/ws order and normalizes them to [comment,ws] order) +!!input +<!--a--> a + + <!--b-->b +!!result +<pre>a +</pre> +<pre>b +</pre> +!!end + +!!test +2a. Indent-Pre and tables +!!input + {| + |- + !h1!!h2 + |foo||bar + |} +!!result +<table> + +<tr> +<th>h1</th> +<th>h2 +</th> +<td>foo</td> +<td>bar +</td></tr></table> + +!!end + +!!test +2b. Indent-Pre and tables +!!input + {| + |- +|foo +|} +!!result +<table> + +<tr> +<td>foo +</td></tr></table> + +!!end + +!!test +2c. Indent-Pre and tables (bug 42252) +!!input +{| + |+ foo + ! | bar +|} +!!result +<table> +<caption> foo +</caption> +<tr> +<th> bar +</th></tr></table> + +!!end + +!!test +3a. Indent-Pre and block tags (single-line html) +!!input + <p> foo </p> + <div> foo </div> + <blockquote> foo </blockquote> + <span> foo </span> +!!result + <p> foo </p> + <div> foo </div> + <blockquote> foo </blockquote> +<pre><span> foo </span> +</pre> +!!end + +!!test +3b. Indent-Pre and block tags (pre-content on separate line) +!!input +<p> + foo +</p> + +<div> + foo +</div> + +<center> + foo +</center> + +<blockquote> + foo +</blockquote> + +<blockquote> +<pre> +foo +</pre> +</blockquote> + +<table><tr><td> + foo +</td></tr></table> + +<ul><li> + foo +</li></ul> + +!!result +<p> + foo +</p> +<div> +<pre>foo +</pre> +</div> +<center> +<pre>foo +</pre> +</center> +<blockquote> +<p> foo +</p> +</blockquote> +<blockquote> +<pre> +foo +</pre> +</blockquote> +<table><tr><td> +<pre>foo +</pre> +</td></tr></table> +<ul><li> + foo +</li></ul> + +!!end + +!!test +4. Multiple spaces at start-of-line +!!input + <p> foo </p> + foo + {| +|foo +|} +!!result + <p> foo </p> +<pre> foo +</pre> +<table> +<tr> +<td>foo +</td></tr></table> + +!!end + +!! test +5. White-space in indent-pre +NOTE: the white-space char on 2nd line is significant +!! input + a<br/> + + b +!! result +<pre>a<br /> + +b +</pre> +!! end + +!! test +6. Pre-blocks should extend across lines with leading WS even when there is no wrappable content +!! input + a + + <!-- continue --> + b + + c + +d +!! result +<pre>a + +b +</pre> +<pre>c + +</pre> +<p>d +</p> +!! end + +!! test +7a. Indent-pre and category links +!! options +parsoid=wt2html,wt2wt +!! input + [[Category:foo]] <!-- No pre-wrapping --> +{{echo| [[Category:foo]]}} <!-- No pre-wrapping --> +!! result + <link rel="mw:WikiLink/Category" href="./Category:Foo"> <!-- No pre-wrapping --> +<span about="#mwt1" typeof="mw:Transclusion" data-mw='{"parts":[{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":" [[Category:foo]]"}},"i":0}}]}'> </span> +<link rel="mw:WikiLink/Category" href="./Category:Foo" about="#mwt1"> <!-- No pre-wrapping --> +!! end + +!! test +7b. Indent-pre and category links +!! options +parsoid=wt2html,wt2wt +!! input + [[Category:foo]] a + [[Category:foo]] {{echo|b}} +!! result +<pre> +<link rel="mw:WikiLink/Category" href="./Category:Foo"> a + +<link rel="mw:WikiLink/Category" href="./Category:Foo"> <span about="#mwt1" typeof="mw:Transclusion" data-mw='{"parts":[{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":"b"}},"i":0}}]}'>b</span></pre> +!! end + +### +### HTML-pre (some to spec PHP parser behavior and some Parsoid-RT-centric) +### + +!!test +HTML-pre: 1. embedded newlines +!!input +<pre>foo</pre> + +<pre> +foo +</pre> + +<pre> + +foo +</pre> + +<pre> + + +foo +</pre> +!!result +<pre>foo</pre> +<pre> +foo +</pre> +<pre> + +foo +</pre> +<pre> + + +foo +</pre> + +!!end + +!!test +HTML-pre: 2: indented text +!!input +<pre> + foo +</pre> +!!result +<pre> + foo +</pre> + +!!end + +!!test +HTML-pre: 3: other wikitext +!!input +<pre> +* foo +# bar += no-h = +'' no-italic '' +[[ NoLink ]] +</pre> +!!result +<pre> +* foo +# bar += no-h = +'' no-italic '' +[[ NoLink ]] +</pre> + +!!end ### ### Definition lists @@ -796,8 +2273,11 @@ Simple definition !! input ; name : Definition !! result -<dl><dt> name </dt><dd> Definition -</dd></dl> +<dl> +<dt> name </dt> +<dd> Definition +</dd> +</dl> !! end @@ -806,8 +2286,10 @@ Definition list for indentation only !! input : Indented text !! result -<dl><dd> Indented text -</dd></dl> +<dl> +<dd> Indented text +</dd> +</dl> !! end @@ -816,8 +2298,11 @@ Definition list with no space !! input ;name:Definition !! result -<dl><dt>name</dt><dd>Definition -</dd></dl> +<dl> +<dt>name</dt> +<dd>Definition +</dd> +</dl> !!end @@ -826,8 +2311,11 @@ Definition list with URL link !! input ; http://example.com/ : definition !! result -<dl><dt> <a rel="nofollow" class="external free" href="http://example.com/">http://example.com/</a> </dt><dd> definition -</dd></dl> +<dl> +<dt> <a rel="nofollow" class="external free" href="http://example.com/">http://example.com/</a> </dt> +<dd> definition +</dd> +</dl> !! end @@ -836,8 +2324,11 @@ Definition list with bracketed URL link !! input ;[http://www.example.com/ Example]:Something about it !! result -<dl><dt><a rel="nofollow" class="external text" href="http://www.example.com/">Example</a></dt><dd>Something about it -</dd></dl> +<dl> +<dt><a rel="nofollow" class="external text" href="http://www.example.com/">Example</a></dt> +<dd>Something about it +</dd> +</dl> !! end @@ -846,8 +2337,11 @@ Definition list with wikilink containing colon !! input ; [[Help:FAQ]]: The least-read page on Wikipedia !! result -<dl><dt> <a href="/index.php?title=Help:FAQ&action=edit&redlink=1" class="new" title="Help:FAQ (page does not exist)">Help:FAQ</a></dt><dd> The least-read page on Wikipedia -</dd></dl> +<dl> +<dt> <a href="/index.php?title=Help:FAQ&action=edit&redlink=1" class="new" title="Help:FAQ (page does not exist)">Help:FAQ</a></dt> +<dd> The least-read page on Wikipedia +</dd> +</dl> !! end @@ -857,8 +2351,11 @@ Definition list with news link containing colon !! input ; news:alt.wikipedia.rox: This isn't even a real newsgroup! !! result -<dl><dt> <a rel="nofollow" class="external free" href="news:alt.wikipedia.rox">news:alt.wikipedia.rox</a></dt><dd> This isn't even a real newsgroup! -</dd></dl> +<dl> +<dt> <a rel="nofollow" class="external free" href="news:alt.wikipedia.rox">news:alt.wikipedia.rox</a></dt> +<dd> This isn't even a real newsgroup! +</dd> +</dl> !! end @@ -867,8 +2364,10 @@ Malformed definition list with colon !! input ; news:alt.wikipedia.rox -- don't crash or enter an infinite loop !! result -<dl><dt> <a rel="nofollow" class="external free" href="news:alt.wikipedia.rox">news:alt.wikipedia.rox</a> -- don't crash or enter an infinite loop -</dt></dl> +<dl> +<dt> <a rel="nofollow" class="external free" href="news:alt.wikipedia.rox">news:alt.wikipedia.rox</a> -- don't crash or enter an infinite loop +</dt> +</dl> !! end @@ -877,8 +2376,11 @@ Definition lists: colon in external link text !! input ; [http://www.wikipedia2.org/ Wikipedia : The Next Generation]: OK, I made that up !! result -<dl><dt> <a rel="nofollow" class="external text" href="http://www.wikipedia2.org/">Wikipedia : The Next Generation</a></dt><dd> OK, I made that up -</dd></dl> +<dl> +<dt> <a rel="nofollow" class="external text" href="http://www.wikipedia2.org/">Wikipedia : The Next Generation</a></dt> +<dd> OK, I made that up +</dd> +</dl> !! end @@ -887,26 +2389,28 @@ Definition lists: colon in HTML attribute !! input ;<b style="display: inline">bold</b> !! result -<dl><dt><b style="display: inline">bold</b> -</dt></dl> +<dl> +<dt><b style="display: inline">bold</b> +</dt> +</dl> !! end - !! test Definition lists: self-closed tag !! input ;one<br/>two : two-line fun !! result -<dl><dt>one<br />two </dt><dd> two-line fun -</dd></dl> +<dl> +<dt>one<br />two </dt> +<dd> two-line fun +</dd> +</dl> !! end !! test Bug 11748: Literal closing tags -!! options -disabled !! input <dl> <dt>test 1</dt> @@ -921,50 +2425,79 @@ disabled <dt>test 2</dt> <dd>test test test test test</dd> </dl> + !! end !! test Definition and unordered list using wiki syntax nested in unordered list using html tags. !! input <ul><li> -; term : description +; term : description * unordered -</li> -</ul> +</li></ul> !! result <ul><li> -<dl><dt> term </dt><dd> description -</dd></dl> -<ul><li> unordered -</li></ul> +<dl> +<dt> term </dt> +<dd> description +</dd> +</dl> +<ul> +<li> unordered </li> </ul> +</li></ul> !! end !! test + Definition list with empty definition and following paragraph !! input ; term: Paragraph text !! result -<dl><dt> term</dt><dd> -</dd></dl> +<dl> +<dt> term</dt> +<dd> +</dd> +</dl> <p>Paragraph text </p> !! end !! test +Nested definition lists using html syntax +!! input +<dl><dd> +<dl> +<dd>Foo</dd> +</dl> +</dd></dl> +!! result +<dl><dd> +<dl> +<dd>Foo</dd> +</dl> +</dd></dl> + +!! end + +!! test Definition Lists: No nesting: Multiple dd's !! input ;x :a :b !! result -<dl><dt>x -</dt><dd>a -</dd><dd>b -</dd></dl> +<dl> +<dt>x +</dt> +<dd>a +</dd> +<dd>b +</dd> +</dl> !! end @@ -975,12 +2508,18 @@ Definition Lists: Indentation: Regular ::i2 :::i3 !! result -<dl><dd>i1 -<dl><dd>i2 -<dl><dd>i3 -</dd></dl> -</dd></dl> -</dd></dl> +<dl> +<dd>i1 +<dl> +<dd>i2 +<dl> +<dd>i3 +</dd> +</dl> +</dd> +</dl> +</dd> +</dl> !! end @@ -990,11 +2529,17 @@ Definition Lists: Indentation: Missing 1st level ::i2 :::i3 !! result -<dl><dd><dl><dd>i2 -<dl><dd>i3 -</dd></dl> -</dd></dl> -</dd></dl> +<dl> +<dd><dl> +<dd>i2 +<dl> +<dd>i3 +</dd> +</dl> +</dd> +</dl> +</dd> +</dl> !! end @@ -1003,13 +2548,54 @@ Definition Lists: Indentation: Multi-level indent !! input :::i3 !! result -<dl><dd><dl><dd><dl><dd>i3 -</dd></dl> -</dd></dl> -</dd></dl> +<dl> +<dd><dl> +<dd><dl> +<dd>i3 +</dd> +</dl> +</dd> +</dl> +</dd> +</dl> + +!! end +!! test +Definition Lists: Hacky use to indent tables +!! input +::{| +|foo +|bar +|} +this text +should be left alone +!! result +<dl><dd><dl><dd><table> +<tr> +<td>foo +</td> +<td>bar +</td></tr></table></dd></dl></dd></dl> +<p>this text +should be left alone +</p> !! end +# Bug 52473 +!! test +Definition Lists: Hacky use to indent tables (WS-insensitive) +!! options +parsoid +!! input +: {| +|a +|} +!! result +<dl> +<dd> <table><tr><td>a</td></tr></table> </dd> +</dl> +!! end ## The PHP parser treats : items (dd) without a corresponding ; item (dt) ## as an empty dt item. It also ignores all but the last ";" when followed ## by ":" later on. So, ";" are not ignored in ";;;t3" but are ignored in @@ -1042,13 +2628,86 @@ Definition Lists: Indentation: Multi-level indent ## ## All Parsoid only definition list tests have this difference. ## -## See also: https://bugzilla.wikimedia.org/show_bug.cgi?id=6569 +## See also: https://bugzilla.wikimedia.org/show_bug.cgi?id=6569 ## and http://lists.wikimedia.org/pipermail/wikitext-l/2011-November/000483.html !! test +Table / list interaction: indented table with lists in table contents +!! input +:{| +|- +| a +* b +|- +| c +* d +|} +!! result +<dl><dd><table> + +<tr> +<td> a +<ul> +<li> b +</li> +</ul> +</td></tr> +<tr> +<td> c +<ul> +<li> d +</li> +</ul> +</td></tr></table></dd></dl> + +!! end + +!!test +Table / list interaction: lists nested in tables nested in indented lists +!!input +:{| +| +:a +:b +| +*c +*d +|} + +*e +*f +!!result +<dl><dd><table> +<tr> +<td> +<dl> +<dd>a +</dd> +<dd>b +</dd> +</dl> +</td> +<td> +<ul> +<li>c +</li> +<li>d +</li> +</ul> +</td></tr></table></dd></dl> +<ul> +<li>e +</li> +<li>f +</li> +</ul> + +!!end + +!! test Definition Lists: Nesting: Multi-level (Parsoid only) !! options -disabled +parsoid !! input ;t1 :d1 ;;t2 ::d2 @@ -1078,7 +2737,7 @@ disabled !! test Definition Lists: Nesting: Test 2 (Parsoid only) !! options -disabled +parsoid !! input ;t1 ::d2 @@ -1098,7 +2757,7 @@ disabled !! test Definition Lists: Nesting: Test 3 (Parsoid only) !! options -disabled +parsoid !! input :;t1 ::::d2 @@ -1129,32 +2788,75 @@ Definition Lists: Nesting: Test 4 ::;t3 :::d3 !! result -<dl><dd><dl><dd><dl><dt>t3 -</dt><dd>d3 -</dd></dl> -</dd></dl> -</dd></dl> +<dl> +<dd><dl> +<dd><dl> +<dt>t3 +</dt> +<dd>d3 +</dd> +</dl> +</dd> +</dl> +</dd> +</dl> !! end +## The Parsoid team believes the following three test exposes a +## bug in the PHP parser. (Parsoid team thinks the PHP parser is +## wrong to close the <dl> after the <dt> containing the <ul>.) !! test -Definition Lists: Mixed Lists: Test 1 +Definition Lists: Mixed Lists: Test 1 (php) +!! options +php !! input :;* foo ::* bar :; baz !! result -<dl><dd><dl><dt><ul><li> foo -</li><li> bar -</li></ul> -</dt></dl> -<dl><dt> baz -</dt></dl> -</dd></dl> +<dl> +<dd><dl> +<dt><ul> +<li> foo +</li> +<li> bar +</li> +</ul> +</dt> +</dl> +<dl> +<dt> baz +</dt> +</dl> +</dd> +</dl> !! end - +!! test +Definition Lists: Mixed Lists: Test 1 (parsoid) +!! options +parsoid +!! input +:;* foo +::* bar +:; baz +!! result +<dl> +<dd><dl> +<dt><ul> +<li> foo +</li> +</ul></dt> +<dd><ul> +<li> bar +</li> +</ul></dd> +<dt> baz</dt> +</dl></dd> +</dl> +!! end !! test Definition Lists: Mixed Lists: Test 2 @@ -1162,10 +2864,15 @@ Definition Lists: Mixed Lists: Test 2 *: d1 *: d2 !! result -<ul><li><dl><dd> d1 -</dd><dd> d2 -</dd></dl> -</li></ul> +<ul> +<li><dl> +<dd> d1 +</dd> +<dd> d2 +</dd> +</dl> +</li> +</ul> !! end @@ -1176,12 +2883,21 @@ Definition Lists: Mixed Lists: Test 3 *::: d1 *::: d2 !! result -<ul><li><dl><dd><dl><dd><dl><dd> d1 -</dd><dd> d2 -</dd></dl> -</dd></dl> -</dd></dl> -</li></ul> +<ul> +<li><dl> +<dd><dl> +<dd><dl> +<dd> d1 +</dd> +<dd> d2 +</dd> +</dl> +</dd> +</dl> +</dd> +</dl> +</li> +</ul> !! end @@ -1192,10 +2908,17 @@ Definition Lists: Mixed Lists: Test 4 *;d1 :d2 *;d3 :d4 !! result -<ul><li><dl><dt>d1 </dt><dd>d2 -</dd><dt>d3 </dt><dd>d4 -</dd></dl> -</li></ul> +<ul> +<li><dl> +<dt>d1 </dt> +<dd>d2 +</dd> +<dt>d3 </dt> +<dd>d4 +</dd> +</dl> +</li> +</ul> !! end @@ -1206,11 +2929,17 @@ Definition Lists: Mixed Lists: Test 5 *:d1 *:: d2 !! result -<ul><li><dl><dd>d1 -<dl><dd> d2 -</dd></dl> -</dd></dl> -</li></ul> +<ul> +<li><dl> +<dd>d1 +<dl> +<dd> d2 +</dd> +</dl> +</dd> +</dl> +</li> +</ul> !! end @@ -1221,13 +2950,23 @@ Definition Lists: Mixed Lists: Test 6 #*:d1 #*::: d3 !! result -<ol><li><ul><li><dl><dd>d1 -<dl><dd><dl><dd> d3 -</dd></dl> -</dd></dl> -</dd></dl> -</li></ul> -</li></ol> +<ol> +<li><ul> +<li><dl> +<dd>d1 +<dl> +<dd><dl> +<dd> d3 +</dd> +</dl> +</dd> +</dl> +</dd> +</dl> +</li> +</ul> +</li> +</ol> !! end @@ -1238,10 +2977,15 @@ Definition Lists: Mixed Lists: Test 7 :* d1 :* d2 !! result -<dl><dd><ul><li> d1 -</li><li> d2 -</li></ul> -</dd></dl> +<dl> +<dd><ul> +<li> d1 +</li> +<li> d2 +</li> +</ul> +</dd> +</dl> !! end @@ -1252,12 +2996,20 @@ Definition Lists: Mixed Lists: Test 8 :* d1 ::* d2 !! result -<dl><dd><ul><li> d1 -</li></ul> -<dl><dd><ul><li> d2 -</li></ul> -</dd></dl> -</dd></dl> +<dl> +<dd><ul> +<li> d1 +</li> +</ul> +<dl> +<dd><ul> +<li> d2 +</li> +</ul> +</dd> +</dl> +</dd> +</dl> !! end @@ -1267,9 +3019,14 @@ Definition Lists: Mixed Lists: Test 9 !! input *;foo :bar !! result -<ul><li><dl><dt>foo </dt><dd>bar -</dd></dl> -</li></ul> +<ul> +<li><dl> +<dt>foo </dt> +<dd>bar +</dd> +</dl> +</li> +</ul> !! end @@ -1279,51 +3036,172 @@ Definition Lists: Mixed Lists: Test 10 !! input *#;foo :bar !! result -<ul><li><ol><li><dl><dt>foo </dt><dd>bar -</dd></dl> -</li></ol> -</li></ul> +<ul> +<li><ol> +<li><dl> +<dt>foo </dt> +<dd>bar +</dd> +</dl> +</li> +</ol> +</li> +</ul> !! end +# The Parsoid team disagrees with the PHP parser's seemingly-random +# rules regarding dd/dt on the next two tests. Parsoid is more +# consistent, and recognizes the shared nesting and keeps the +# still-open tags around until the nesting is complete. !! test -Definition Lists: Mixed Lists: Test 11 +Definition Lists: Mixed Lists: Test 11 (php) +!! options +php !! input *#*#;*;;foo :bar *#*#;boo :baz !! result -<ul><li><ol><li><ul><li><ol><li><dl><dt>foo </dt><dd><ul><li><dl><dt><dl><dt>bar -</dt></dl> -</dd></dl> -</li></ul> -</dd></dl> -<dl><dt>boo </dt><dd>baz -</dd></dl> -</li></ol> -</li></ul> -</li></ol> -</li></ul> +<ul> +<li><ol> +<li><ul> +<li><ol> +<li><dl> +<dt>foo </dt> +<dd><ul> +<li><dl> +<dt><dl> +<dt>bar +</dt> +</dl> +</dd> +</dl> +</li> +</ul> +</dd> +</dl> +<dl> +<dt>boo </dt> +<dd>baz +</dd> +</dl> +</li> +</ol> +</li> +</ul> +</li> +</ol> +</li> +</ul> !! end +!! test +Definition Lists: Mixed Lists: Test 11 (parsoid) +!! options +parsoid +!! input +*#*#;*;;foo :bar +*#*#;boo :baz +!! result +<ul> +<li> +<ol> +<li> +<ul> +<li> +<ol> +<li> +<dl> +<dt> +<ul> +<li> +<dl> +<dt> +<dl> +<dt>foo<span typeof="mw:Placeholder" data-parsoid='{"src":" "}'> </span></dt> +<dd data-parsoid='{"stx":"row"}'>bar</dd> +</dl></dt> +</dl></li> +</ul></dt> +<dt>boo<span typeof="mw:Placeholder" data-parsoid='{"src":" "}'> </span></dt> +<dd data-parsoid='{"stx":"row"}'>baz</dd> +</dl></li> +</ol></li> +</ul></li> +</ol></li> +</ul> +!! end !! test -Definition Lists: Weird Ones: Test 1 +Definition Lists: Weird Ones: Test 1 (php) +!! options +php !! input *#;*::;; foo : bar (who uses this?) !! result -<ul><li><ol><li><dl><dt> foo </dt><dd><ul><li><dl><dd><dl><dd><dl><dt><dl><dt> bar (who uses this?) -</dt></dl> -</dd></dl> -</dd></dl> -</dd></dl> -</li></ul> -</dd></dl> -</li></ol> -</li></ul> +<ul> +<li><ol> +<li><dl> +<dt> foo </dt> +<dd><ul> +<li><dl> +<dd><dl> +<dd><dl> +<dt><dl> +<dt> bar (who uses this?) +</dt> +</dl> +</dd> +</dl> +</dd> +</dl> +</dd> +</dl> +</li> +</ul> +</dd> +</dl> +</li> +</ol> +</li> +</ul> !! end +!! test +Definition Lists: Weird Ones: Test 1 (parsoid) +!! options +parsoid +!! input +*#;*::;; foo : bar (who uses this?) +!! result +<ul> +<li> +<ol> +<li> +<dl> +<dt> +<ul> +<li> +<dl> +<dd> +<dl> +<dd> +<dl> +<dt> +<dl> +<dt> foo<span typeof="mw:Placeholder" data-parsoid='{"src":" "}'> </span></dt> +<dd data-parsoid='{"stx":"row"}'> bar (who uses this?)</dd> +</dl></dt> +</dl></dd> +</dl></dd> +</dl></li> +</ul></dt> +</dl></li> +</ol></li> +</ul> +!! end ### ### External links @@ -1425,6 +3303,15 @@ second] !!end !! test +External links: Pipe char between url and text +!! input +[http://example.com | link] +!! result +<p><a rel="nofollow" class="external text" href="http://example.com">| link</a> +</p> +!!end + +!! test External links: protocol-relative URL in brackets !! input [//example.com/ Test] @@ -1479,6 +3366,17 @@ External image from https: https://meta.wikimedia.org/upload/f/f1/Ncwikicol.png !! end !! test +External image (when not allowed) +!! options +wgAllowExternalImages=0 +!! input +External image: http://meta.wikimedia.org/upload/f/f1/Ncwikicol.png +!! result +<p>External image: <a rel="nofollow" class="external free" href="http://meta.wikimedia.org/upload/f/f1/Ncwikicol.png">http://meta.wikimedia.org/upload/f/f1/Ncwikicol.png</a> +</p> +!! end + +!! test Link to non-http image, no img tag !! input Link to non-http image, no img tag: ftp://example.com/test.jpg @@ -1730,6 +3628,17 @@ External links: multiple legal whitespace is fine, Magnus. Don't break it please !! end !! test +External links: link text with spaces +!! input +[http://www.example.com a b c] +[http://www.example.com ''a'' ''b''] +!! result +<p><a rel="nofollow" class="external text" href="http://www.example.com">a b c</a> +<a rel="nofollow" class="external text" href="http://www.example.com"><i>a</i> <i>b</i></a> +</p> +!! end + +!! test External links: wiki links within external link (Bug 3695) !! input [http://example.com [[wikilink]] embedded in ext link] @@ -1751,6 +3660,15 @@ http:/example.com !! end !! test +Bracketed external links with template-generated invalid target +!! input +[{{echo|http:/example.com}} title] +!! result +<p>[http:/example.com title] +</p> +!! end + +!! test Bug 2702: Mismatched <i>, <b> and <a> tags are invalid !! input ''[http://example.com text''] @@ -1881,6 +3799,15 @@ External link containing double-single-quotes with no space separating the url f !! end !! test +External link with comments in link text +!! input +[http://www.google.com Google <!-- comment -->] +!! result +<p><a rel="nofollow" class="external text" href="http://www.google.com">Google </a> +</p> +!! end + +!! test URL-encoding in URL functions (single parameter) !! input {{localurl:Some page|amp=&}} @@ -1921,6 +3848,59 @@ http://[2404:130:0:1000::187:2]/index.php </p> !! end +!! test +Non-extlinks in brackets +!! input +[foo] +[foo bar] +[foo ''bar''] +[fool's] errand +[fool's errand] +[{{echo|foo}}] +[{{echo|foo}} bar] +[{{echo|foo}} ''bar''] +[{{echo|foo}}l's] errand +[{{echo|foo}}l's errand] +[url={{echo|foo}}] +[url=http://example.com] +!! result +<p>[foo] +[foo bar] +[foo <i>bar</i>] +[fool's] errand +[fool's errand] +[foo] +[foo bar] +[foo <i>bar</i>] +[fool's] errand +[fool's errand] +[url=foo] +[url=<a rel="nofollow" class="external free" href="http://example.com">http://example.com</a>] +</p> +!! end + +!! test +Parsoid: Percent encoding in external links +!! options +parsoid +!! input +[https://github.com/search?l=&q=ResourceLoader+%40wikimedia Search] +!! result +<p><a rel="mw:ExtLink" +href="https://github.com/search?l=&q=ResourceLoader+%40wikimedia">Search</a></p> +!! end + +!! test +Parsoid: use url link syntax for links where the content is equal the link +target +!! options +parsoid +!! input +http://example.com +!! result +<p><a rel="mw:ExtLink" href="http://example.com">http://example.com</a></p> +!! end + ### ### Quotes ### @@ -1939,7 +3919,9 @@ Normal text. '''''Bold italic text.''''' Normal text. !! test -Unclosed and unmatched quotes +Unclosed and unmatched quotes (php) +!! options +php !! input '''''Bold italic text '''with bold deactivated''' in between.''''' @@ -1974,6 +3956,47 @@ Plain ''italic'''s plain </p><p>Plain <i>italic'</i>s plain </p> !! end +# Parsoid inserts an empty bold tag pair at the end of the line, that the PHP +# parser strips. The wikitext contains just the first half of the bold +# quote pair. +!! test +Unclosed and unmatched quotes (parsoid) +!! options +parsoid +!! input +'''''Bold italic text '''with bold deactivated''' in between.''''' + +'''''Bold italic text ''with italic deactivated'' in between.''''' + +'''Bold text.. + +..spanning two paragraphs (should not work).''' + +'''Bold tag left open + +''Italic tag left open + +Normal text. + +<!-- Unmatching number of opening, closing tags: --> +'''This year''''s election ''should'' beat '''last year''''s. + +''Tom'''s car is bigger than ''Susan'''s. + +Plain ''italic'''s plain +!! result +<p><i><b>Bold italic text </b>with bold deactivated<b> in between.</b></i> +</p><p><b><i>Bold italic text </i>with italic deactivated<i> in between.</i></b> +</p><p><b>Bold text..</b> +</p><p>..spanning two paragraphs (should not work).<b></b> +</p><p><b>Bold tag left open</b> +</p><p><i>Italic tag left open</i> +</p><p>Normal text. +</p><p><b>This year'</b>s election <i>should</i> beat <b>last year'</b>s. +</p><p><i>Tom<b>s car is bigger than </b></i><b>Susan</b>s. +</p><p>Plain <i>italic'</i>s plain +</p> +!! end ### ### Tables @@ -1982,20 +4005,34 @@ Plain ''italic'''s plain ### # This should not produce <table></table> as <table><tr><td></td></tr></table> -# is the bare minimun required by the spec, see: +# is the bare minimum required by the spec, see: # http://www.w3.org/TR/xhtml-modularization/dtd_module_defs.html#a_module_Basic_Tables !! test -A table with no data. +A table with no data. (php) +!! options +php +!! input +{||} +!! result +!! end +# Parsoid team replies: empty table tags are legal in HTML5 +!! test +A table with no data. (parsoid) +!! options +parsoid !! input {||} !! result +<table></table> !! end # A table with nothing but a caption is invalid XHTML, we might want to render # this as <p>caption</p> !! test -A table with nothing but a caption -!! input +A table with nothing but a caption (php) +!! options +php +!! input {| |+ caption |} @@ -2005,13 +4042,89 @@ A table with nothing but a caption </caption><tr><td></td></tr></table> !! end +# Parsoid team replies: table with only a caption is legal in HTML5 +!! test +A table with nothing but a caption (parsoid) +!! options +parsoid +!! input +{| +|+ caption +|} +!! result +<table><caption> caption</caption></table> +!! end + +!! test +A table with caption with default-spaced attributes and a table row +!! input +{| +|+ style="color: red;" | caption1 +|- +| foo +|} +!! result +<table> +<caption style="color: red;"> caption1 +</caption> +<tr> +<td> foo +</td></tr></table> + +!! end + +!! test +A table with captions with non-default spaced attributes and a table row +!! input +{| +|+style="color: red;"|caption2 +|+ style="color: red;"| caption3 +|- +| foo +|} +!! result +<table> +<caption style="color: red;">caption2 +</caption> +<caption style="color: red;"> caption3 +</caption> +<tr> +<td> foo +</td></tr></table> + +!! end + +!! test +Table td-cell syntax variations +!! input +{| +| foo bar foo | baz +| foo bar foo || baz +| style='color:red;' | baz +| style='color:red;' || baz +|} +!! result +<table> +<tr> +<td> baz +</td> +<td> foo bar foo </td> +<td> baz +</td> +<td style="color:red;"> baz +</td> +<td> style='color:red;' </td> +<td> baz +</td></tr></table> + +!! end !! test Simple table !! input -{| +{| | 1 || 2 -|- +|- | 3 || 4 |} !! result @@ -2028,6 +4141,24 @@ Simple table !! end !! test +Simple table but with multiple dashes for row wikitext +!! input +{| +| foo +|----- +| bar +|} +!! result +<table> +<tr> +<td> foo +</td></tr> +<tr> +<td> bar +</td></tr></table> + +!! end +!! test Multiplication table !! input {| border="1" cellpadding="2" @@ -2099,15 +4230,117 @@ Multiplication table !! end !! test +Accept "||" in table headings +!! input +{| +!h1 || h2 +|} +!! result +<table> +<tr> +<th>h1 </th> +<th> h2 +</th></tr></table> + +!! end + +!! test +Accept "||" in indented table headings +!! input +:{| +!h1 || h2 +|} +!! result +<dl><dd><table> +<tr> +<th>h1 </th> +<th> h2 +</th></tr></table></dd></dl> + +!! end + +!! test +Accept empty attributes in td/th cells (td/th cells starting with leading ||) +!! input +{| +!| h1 +|| a +|} +!! result +<table> +<tr> +<th> h1 +</th> +<td> a +</td></tr></table> + +!! end + +!!test +Accept "| !" at start of line in tables (ignore !-attribute) +!!input +{| +|- +| !style="color:red" | bar +|} +!!result +<table> + +<tr> +<td> bar +</td></tr></table> + +!!end + +!!test +Allow +/- in 2nd and later cells in a row, in 1st cell when td-attrs are present, or in 1st cell when there is a space between "|" and +/- +!!input +{| +|- +|style='color:red;'|+1 +|style='color:blue;'|-1 +|- +| 1 || 2 || 3 +| 1 ||+2 ||-3 +|- +| +1 +| -1 +|} +!!result +<table> + +<tr> +<td style="color:red;">+1 +</td> +<td style="color:blue;">-1 +</td></tr> +<tr> +<td> 1 </td> +<td> 2 </td> +<td> 3 +</td> +<td> 1 </td> +<td>+2 </td> +<td>-3 +</td></tr> +<tr> +<td> +1 +</td> +<td> -1 +</td></tr></table> + +!!end + +!! test Table rowspan !! input {| border=1 -| Cell 1, row 1 -|rowspan=2| Cell 2, row 1 (and 2) -| Cell 3, row 1 -|- -| Cell 1, row 2 -| Cell 3, row 2 +| Cell 1, row 1 +|rowspan=2| Cell 2, row 1 (and 2) +| Cell 3, row 1 +|- +| Cell 1, row 2 +| Cell 3, row 2 |} !! result <table border="1"> @@ -2212,6 +4445,283 @@ Indented table markup mixed with indented pre content (proposed in bug 6200) !! end +!! test +Template-generated table cell attributes and cell content +!! input +{| +|{{table_attribs}} +| {{table_attribs}} +|} +!! result +<table> +<tr> +<td style="color: red"> Foo +</td> +<td style="color: red"> Foo +</td></tr></table> + +!! end + +!! test +Template-generated table cell attributes and cell content (2) +!! input +{| +|align=center {{table_attribs}} +|} +!! result +<table> +<tr> +<td align="center" style="color: red"> Foo +</td></tr></table> + +!! end + +!! test +Template-generated table cell attributes and cell content (3) +!! input +{| +|align=center {{table_cells}} +|} +!! result +<table> +<tr> +<td align="center" style="color: red"> Foo </td> +<td> Bar </td> +<td> Baz +</td></tr></table> + +!! end + +!! test +Table with row followed by newlines and table heading +!! input +{| +|- + +! foo +|} +!! result +<table> + + +<tr> +<th> foo +</th></tr></table> + +!! end + +!! test +Table with empty line following the start tag +!! input +{| + +|- +| foo +|} +!! result +<table> + + +<tr> +<td> foo +</td></tr></table> + +!! end + +# FIXME: Preserve the attribute properly (with an empty string as value) in +# the PHP parser. Parsoid implements the behavior below. +!! test +Table attributes with empty value +!! options +parsoid +!! input +{| +| style=| hello +|} +!! result +<table> +<tbody> +<tr> +<td style=""> hello +</td></tr></tbody></table> + +!! end + +!! test +Wikitext table with a lot of comments +!! input +{| +<!-- c0 --> +| foo +<!-- c1 --> +|- <!-- c2 --> +<!-- c3 --> +|<!-- c4 --> +<!-- c5 --> +|} +!! result +<table> +<tr> +<td> foo +</td></tr> +<tr> +<td> +</td></tr></table> + +!! end + +!! test +Wikitext table with double-line table cell +!! input +{| +|a +b +|} +!! result +<table> +<tr> +<td>a +<p>b +</p> +</td></tr></table> + +!! end + +!! test +Table cell with a single comment +!! input +{| +| <!-- c1 --> +| a +|} +!! result +<table> +<tr> +<td> +</td> +<td> a +</td></tr></table> + +!! end + +# The expected HTML structure in this test is debatable. The PHP parser does +# not parse this kind of table at all. The main focus for Parsoid is on +# round-tripping, so this output is ok for now. TODO: revisit! +!! test +Wikitext table with html-syntax row (Parsoid) +!! options +parsoid +!! input +{| +|- +<td>foo</td> +|} +!! result +<table> +<tbody> +<tr> +<td>foo</td></tr></tbody></table> +!! end + +!! test +Implicit <td> after a |- +(PHP parser relies on Tidy to add the missing <td> tags) +!! options +parsoid=wt2html,wt2wt +!! input +{| +|- +a +|} +!! result +<table> +<tr><td>a</td></tr> +</table> +!! end + +!! test +Pres should be recognized in an explicit <td> context, but not in an implicit <td> context +(PHP parser relies on Tidy to add the missing <td> tags) +!! options +parsoid=wt2html,wt2wt +!! input +{| +|- +| + a +|- + b +|} +!! result +<table> +<tbody> +<tr><td><pre>a</pre></td></tr> +<tr><td> b</td></tr> +</tbody> +</table> +!! end + +!! test +Lists should be recognized in an implicit <td> context +(PHP parser relies on Tidy to add the missing <td> tags) +!! options +parsoid=wt2html,wt2wt +!! input +{| +|- +*a +|} +!! result +<table> +<tr> +<td><ul> +<li>a</li> +</ul></td> +</tr> +</table> +!! end + +!! test +Parsoid: Round-trip tables directly followed by content (bug 51219) +!! options +parsoid=wt2html,wt2wt +!! input +{| +|foo +|} bar + +{| +|baz +|}<b>quux</b> +!! result +<table><tbody> +<tr> +<td>foo</td></tr></tbody></table> bar +<table> +<tbody> +<tr> +<td>baz</td></tr></tbody></table><b>quux</b> +!! end + +!! test +Parsoid: Default to a newline after tables in new content (bug 51219) +!! options +parsoid=html2wt +!! input +{| +|foo +|} +<nowiki> </nowiki>bar +{| +|baz +|} +'''quux''' +!! result +<table><tbody> +<tr><td>foo</td></tr></tbody></table> bar +<table><tbody> +<tr><td>baz</td></tr></tbody></table><b>quux</b> +!! end ### ### Internal links @@ -2244,6 +4754,15 @@ Piped link !! end !! test +Piped link with comment in link text +!! input +[[Main Page|The Main<!--front--> Page]] +!! result +<p><a href="/wiki/Main_Page" title="Main Page">The Main Page</a> +</p> +!! end + +!! test Broken link !! input [[Zigzagzogzagzig]] @@ -2297,6 +4816,37 @@ Link with suffix </p> !! end +!! article +prefixed article +!! text +Some text +!! endarticle + +!! test +Bug 43661: Piped links with identical prefixes +!! input +[[prefixed article|prefixed articles with spaces]] + +[[prefixed article|prefixed articlesaoeu]] + +[[Main Page|Main Page test]] +!! result +<p><a href="/wiki/Prefixed_article" title="Prefixed article">prefixed articles with spaces</a> +</p><p><a href="/wiki/Prefixed_article" title="Prefixed article">prefixed articlesaoeu</a> +</p><p><a href="/wiki/Main_Page" title="Main Page">Main Page test</a> +</p> +!! end + + +!! test +Link with HTML entity in suffix / tail +!! input +[[Main Page]]", [[Main Page]]a +!! result +<p><a href="/wiki/Main_Page" title="Main Page">Main Page</a>", <a href="/wiki/Main_Page" title="Main Page">Main Page</a>a +</p> +!! end + !! test Link with 3 brackets !! input @@ -2333,6 +4883,22 @@ Link to namespaces </p> !! end +!! article +MemoryAlpha:AlphaTest +!! text +This is an article in the MemoryAlpha namespace +(which shadows the memoryalpha interwiki link). +!! endarticle + +!! test +Namespace takes precedence over interwiki link (bug 51680) +!! input +[[MemoryAlpha:AlphaTest]] +!! result +<p><a href="/wiki/MemoryAlpha:AlphaTest" title="MemoryAlpha:AlphaTest">MemoryAlpha:AlphaTest</a> +</p> +!! end + !! test Piped link to namespace !! input @@ -2398,6 +4964,33 @@ Link containing "<#" and ">#" as a hex sequences !! end !! test +Link containing an equals sign +!! input +[[Special:BookSources/isbn=4-00-026157-6]] +!! result +<p><a href="/wiki/Special:BookSources/isbn%3D4-00-026157-6" title="Special:BookSources/isbn=4-00-026157-6">Special:BookSources/isbn=4-00-026157-6</a> +</p> +!! end + +!! article +Foo~bar +!! text +Just a test of an article title containing a tilde. +!! endarticle + +# note that links containing signatures, like [[Foo~~~~]], are +# massaged by the pre-save transform (PST) and so the tildes are never +# seen by the parser. +!! test +Link containing a tilde +!! input +[[Foo~bar]] +!! result +<p><a href="/wiki/Foo%7Ebar" title="Foo~bar">Foo~bar</a> +</p> +!! end + +!! test Link containing double-single-quotes '' (bug 4598) !! input [[Lista d''e paise d''o munno]] @@ -2493,6 +5086,18 @@ Plain link to protocol-relative URL with link text </p> !! end +!! test +Plain link to page with question mark in title +!! input +[[A?b]] + +[[A?b|Baz]] +!! result +<p><a href="/wiki/A%3Fb" title="A?b">A?b</a> +</p><p><a href="/wiki/A%3Fb" title="A?b">Baz</a> +</p> +!! end + # I'm fairly sure the expected result here is wrong. # We want these to be URL links, not pseudo-pages with URLs for titles.... @@ -2646,6 +5251,87 @@ language=kaa </p> !! end +!! article +Söfnuður +!! text +Test. +!! endarticle + +!! test +Internal link with is link prefix +!! options +language=is +!! input +Aðrir mótmælenda[[söfnuður|söfnuðir]] og +!! result +<p>Aðrir <a href="/wiki/S%C3%B6fnu%C3%B0ur" title="Söfnuður">mótmælendasöfnuðir</a> og +</p> +!! end + +!! article +Mótmælendatrú +!! text +Test. +!! endarticle + +!! test +Internal link with is link trail and link prefix +!! options +language=is +!! input +[[mótmælendatrú|xxx]]ar +[[mótmælendatrú]]ar +mótmælenda[[söfnuður]] +mótmælenda[[söfnuður|söfnuðir]] +mótmælenda[[söfnuður|söfnuðir]]xxx +!! result +<p><a href="/wiki/M%C3%B3tm%C3%A6lendatr%C3%BA" title="Mótmælendatrú">xxxar</a> +<a href="/wiki/M%C3%B3tm%C3%A6lendatr%C3%BA" title="Mótmælendatrú">mótmælendatrúar</a> +<a href="/wiki/S%C3%B6fnu%C3%B0ur" title="Söfnuður">mótmælendasöfnuður</a> +<a href="/wiki/S%C3%B6fnu%C3%B0ur" title="Söfnuður">mótmælendasöfnuðir</a> +<a href="/wiki/S%C3%B6fnu%C3%B0ur" title="Söfnuður">mótmælendasöfnuðirxxx</a> +</p> +!! end + +!! test +Parsoid link trail escaping +!! options +parsoid=html2wt,html2html +!! input +[[apple]]<nowiki/>s +!! result +<p><a rel="mw:WikiLink" href="Apple">apple</a>s</p> +!! end + +!! test +Parsoid link prefix escaping +!! options +language=is +parsoid=html2wt,html2html +!! input +Aðrir mótmælenda<nowiki/>[[söfnuður]] +!! result +<p>Aðrir mótmælenda<a rel="mw:WikiLink" href="Söfnuður">söfnuður</a></p> +!! end + +!! test +Parsoid-centric test: Whitespace in ext- and wiki-links should be preserved +!! input +[[Foo| bar]] + +[[Foo| ''bar'']] + +[http://wp.org foo] + +[http://wp.org ''foo''] +!! result +<p><a href="/index.php?title=Foo&action=edit&redlink=1" class="new" title="Foo (page does not exist)"> bar</a> +</p><p><a href="/index.php?title=Foo&action=edit&redlink=1" class="new" title="Foo (page does not exist)"> <i>bar</i></a> +</p><p><a rel="nofollow" class="external text" href="http://wp.org">foo</a> +</p><p><a rel="nofollow" class="external text" href="http://wp.org"><i>foo</i></a> +</p> +!! end + ### ### Interwiki links (see maintenance/interwiki.sql) ### @@ -2674,9 +5360,12 @@ Interwiki link encoding conversion (bug 1636) *[[Wikipedia:ro:Olteniţa]] *[[Wikipedia:ro:Olteniţa]] !! result -<ul><li><a href="http://en.wikipedia.org/wiki/ro:Olteni%C5%A3a" class="extiw" title="wikipedia:ro:Olteniţa">Wikipedia:ro:Olteniţa</a> -</li><li><a href="http://en.wikipedia.org/wiki/ro:Olteni%C5%A3a" class="extiw" title="wikipedia:ro:Olteniţa">Wikipedia:ro:Olteniţa</a> -</li></ul> +<ul> +<li><a href="http://en.wikipedia.org/wiki/ro:Olteni%C5%A3a" class="extiw" title="wikipedia:ro:Olteniţa">Wikipedia:ro:Olteniţa</a> +</li> +<li><a href="http://en.wikipedia.org/wiki/ro:Olteni%C5%A3a" class="extiw" title="wikipedia:ro:Olteniţa">Wikipedia:ro:Olteniţa</a> +</li> +</ul> !! end @@ -2689,6 +5378,12 @@ Interwiki link with fragment (bug 2130) </p> !! end + +### +### Interlanguage links +### Language links (so that searching for '### language' matches..) +### + !! test Interlanguage link !! input @@ -2735,6 +5430,199 @@ Blah blah blah </p> !! end +!! test +Parsoid-specific test: Wikilinks with should RT properly +!! options +language=ln +!! input +[[WW II]] +!!result +<p><a href="/index.php?title=WW_II&action=edit&redlink=1" class="new" title="WW II (lonkásá ezalí tɛ̂)">WW II</a> +</p> +!! end + +!! test +Parsoid bug 53221: Wikilinks should be properly entity-escaped +!! options +parsoid=html2wt +!! input +He&nbsp;llo [[Foo|He&nbsp;llo]] + +He&nbsp;llo [[He&nbsp;llo]] +!!result +<p>He&nbsp;llo <a href="Foo" rel="mw:WikiLink">He&nbsp;llo</a></p> +<p>He&nbsp;llo <a href="He&nbsp;llo" rel="mw:WikiLink">He&nbsp;llo</a></p> +!! end + +!! test +Parsoid: handle constructor well +!! options +parsoid +!! input +[[constructor]] + +[[constructor:foo]] +!! result +<p data-parsoid="{"dsr":[0,15,0,0]}"><a rel="mw:WikiLink" href="./Constructor" data-parsoid="{"stx":"simple","a":{"href":"./Constructor"},"sa":{"href":"constructor"},"dsr":[0,15,2,2]}">constructor</a></p> + + +<p data-parsoid="{"dsr":[17,36,0,0]}"><a rel="mw:WikiLink" href="./Foo" data-parsoid="{"stx":"simple","a":{"href":"./Foo"},"sa":{"href":"constructor:foo"},"dsr":[17,36,2,2]}">constructor:foo</a></p> +!! end + +!! test +Parsoid: recognize interlanguage links without a target page +!! options +parsoid +!! input +[[ko:]] +!! result +<p> +<link rel="mw:WikiLink/Language" href="http://ko.wikipedia.org/wiki/"></p> +!! end + +!! test +Parsoid: recognize interwiki links without a target page +!! options +parsoid +!! input +[[:ko:]] +!! result +<p><a rel="mw:WikiLink/Interwiki" href="http://ko.wikipedia.org/wiki/">ko:</a></p> +!! end + +### +### Redirects, Parsoid-only +### +!! test +Simple redirect to page +!! options +parsoid +!! input +#REDIRECT [[Main Page]] +!! result +<link rel="mw:PageProp/redirect" href="./Main_Page"> +!! end + +!! test +Optional colon in #REDIRECT +!! options +# the colon is archaic syntax. we support it for wt2html, but we +# don't care that it roundtrips back to the modern syntax. +parsoid=wt2html,html2html +!! input +#REDIRECT:[[Main Page]] +!! result +<link rel="mw:PageProp/redirect" href="./Main_Page"> +!! end + +!! test +Whitespace in #REDIRECT with optional colon +!! options +# the colon and gratuitous whitespace is archaic syntax. we support +# it for wt2html, but we don't care that it roundtrips back to the +# modern syntax (without extra whitespace) +parsoid=wt2html,html2html +!! input + + #REDIRECT +: +[[Main Page]] +!! result +<link rel="mw:PageProp/redirect" href="./Main_Page"> +!! end + +!! test +Piped link in #REDIRECT +!! options +# content after piped link is ignored. we support this syntax, +# but don't care that the piped link is lost when we roundtrip this. +parsoid=wt2html +!! input +#REDIRECT [[Main Page|bar]] +!! result +<link rel="mw:PageProp/redirect" href="./Main_Page"> +!! end + +!! test +Redirect to category +!! options +parsoid=wt2html +!! input +#REDIRECT [[Category:Foo]] +!! result +<link rel="mw:PageProp/redirect" href="./Category:Foo"><link rel="mw:WikiLink/Category" href="./Category:Foo"> +!! end + +!! test +Redirect to category with URL encoding +!! options +parsoid=wt2html +!! input +#REDIRECT [[Category%3AFoo]] +!! result +<link rel="mw:PageProp/redirect" href="./Category:Foo"><link rel="mw:WikiLink/Category" href="./Category:Foo"> +!! end + +!! test +Redirect to category page +!! options +parsoid=wt2html,html2html +!! input +#REDIRECT [[:Category:Foo]] +!! result +<p><a rel="mw:WikiLink" href="Category:Foo">Category:Foo</a></p> +!! end + +!! test +Redirect to image page (1) +!! options +parsoid +!! input +#REDIRECT [[File:Wiki.png]] +!! result +<link rel="mw:PageProp/redirect" href="./File:Wiki.png"> +!! end + +!! test +Redirect to image page (2) +!! options +parsoid +!! input +#REDIRECT [[Image:Wiki.png]] +!! result +<link rel="mw:PageProp/redirect" href="./File:Wiki.png"> +!! end + +!! test +Redirect to language +!! options +parsoid +!! input +#REDIRECT [[en:File:Wiki.png]] +!! result +<link rel="mw:PageProp/redirect" href="File:Wiki.png"> +!! end + +!! test +Redirect to interwiki +!! options +parsoid +!! input +#REDIRECT [[meatball:File:Wiki.png]] +!! result +<link rel="mw:PageProp/redirect" href="File:Wiki.png"> +!! end + +!! test +Non-English #REDIRECT +!! options +parsoid +language=is +!! input +#TILVÍSUN [[Main Page]] +!! result +<link rel="mw:PageProp/redirect" href="./Main_Page"> +!! end ## ## XHTML tidiness @@ -2750,6 +5638,28 @@ Blah blah blah !! end !! test +Broken br tag sanitization +!! options +php +!! input +</br> +!! result +<p></br> +</p> +!! end + +# TODO: Fix html2html mode (bug 51055)! +!! test +Parsoid: Broken br tag recognition +!! options +parsoid=wt2html +!! input +</br> +!! result +<p><br></p> +!! end + +!! test Incorrecly removing closing slashes from correctly formed XHTML !! input <br style="clear:both;" /> @@ -2758,7 +5668,7 @@ Incorrecly removing closing slashes from correctly formed XHTML </p> !! end -!! test +!! test Failing to transform badly formed HTML into correct XHTML !! input <br style="clear: left;"> @@ -2772,6 +5682,47 @@ Failing to transform badly formed HTML into correct XHTML !!end !! test +Handling html with a div self-closing tag +!! input +<div title /> +<div title/> +<div title/ > +<div title=bar /> +<div title=bar/> +<div title=bar/ > +!! result +<p><div title /> +<div title/> +</p> +<div> +<p><div title=bar /> +<div title=bar/> +</p> +<div title="bar/"></div> +</div> + +!! end + +!! test +Handling html with a br self-closing tag +!! input +<br title /> +<br title/> +<br title/ > +<br title=bar /> +<br title=bar/> +<br title=bar/ > +!! result +<p><br title="title" /> +<br title="title" /> +<br /> +<br title="bar" /> +<br title="bar" /> +<br title="bar/" /> +</p> +!! end + +!! test Horizontal ruler (should it add that extra space?) !! input <hr> @@ -2804,7 +5755,7 @@ Horizontal ruler -- eats additional dashes on the same line !! end !! test -Horizontal ruler -- does not collaps dashes on consecutive lines +Horizontal ruler -- does not collapse dashes on consecutive lines !! input ---- ---- @@ -2823,6 +5774,15 @@ Horizontal ruler -- <4 dashes render as plain text </p> !! end +!! test +Horizontal ruler -- Supports content following dashes on same line +!! input +---- Foo +!! result +<hr /> Foo + +!! end + ### ### Block-level elements ### @@ -2833,10 +5793,14 @@ Common list * item 2 *item 3 !! result -<ul><li>Common list -</li><li> item 2 -</li><li>item 3 -</li></ul> +<ul> +<li>Common list +</li> +<li> item 2 +</li> +<li>item 3 +</li> +</ul> !! end @@ -2847,10 +5811,14 @@ Numbered list #item 2 # item 3 !! result -<ol><li>Numbered list -</li><li>item 2 -</li><li> item 3 -</li></ol> +<ol> +<li>Numbered list +</li> +<li>item 2 +</li> +<li> item 3 +</li> +</ol> !! end @@ -2873,35 +5841,67 @@ Mixed list *** Level 3 #** Level 3, but ordered !! result -<ul><li>Mixed list -<ol><li> with numbers -</li></ol> -<ul><li> and bullets -</li></ul> -<ol><li> and numbers -</li></ol> -</li><li>bullets again -<ul><li>bullet level 2 -<ul><li>bullet level 3 -<ol><li>Number on level 4 -</li></ol> -</li></ul> -</li><li>bullet level 2 -<ol><li>Number on level 3 -</li><li>Number on level 3 -</li></ol> -</li></ul> -<ol><li>number level 2 -</li></ol> -</li><li>Level 1 -<ul><li><ul><li> Level 3 -</li></ul> -</li></ul> -</li></ul> -<ol><li><ul><li><ul><li> Level 3, but ordered -</li></ul> -</li></ul> -</li></ol> +<ul> +<li>Mixed list +<ol> +<li> with numbers +</li> +</ol> +<ul> +<li> and bullets +</li> +</ul> +<ol> +<li> and numbers +</li> +</ol> +</li> +<li>bullets again +<ul> +<li>bullet level 2 +<ul> +<li>bullet level 3 +<ol> +<li>Number on level 4 +</li> +</ol> +</li> +</ul> +</li> +<li>bullet level 2 +<ol> +<li>Number on level 3 +</li> +<li>Number on level 3 +</li> +</ol> +</li> +</ul> +<ol> +<li>number level 2 +</li> +</ol> +</li> +<li>Level 1 +<ul> +<li><ul> +<li> Level 3 +</li> +</ul> +</li> +</ul> +</li> +</ul> +<ol> +<li><ul> +<li><ul> +<li> Level 3, but ordered +</li> +</ul> +</li> +</ul> +</li> +</ol> !! end @@ -2911,10 +5911,14 @@ Nested lists 1 *foo **bar !! result -<ul><li>foo -<ul><li>bar -</li></ul> -</li></ul> +<ul> +<li>foo +<ul> +<li>bar +</li> +</ul> +</li> +</ul> !! end @@ -2924,10 +5928,15 @@ Nested lists 2 **foo *bar !! result -<ul><li><ul><li>foo -</li></ul> -</li><li>bar -</li></ul> +<ul> +<li><ul> +<li>foo +</li> +</ul> +</li> +<li>bar +</li> +</ul> !! end @@ -2937,10 +5946,14 @@ Nested lists 3 (first element empty) * **bar !! result -<ul><li> -<ul><li>bar -</li></ul> -</li></ul> +<ul> +<li> +<ul> +<li>bar +</li> +</ul> +</li> +</ul> !! end @@ -2950,10 +5963,15 @@ Nested lists 4 (first element empty) ** *bar !! result -<ul><li><ul><li> -</li></ul> -</li><li>bar -</li></ul> +<ul> +<li><ul> +<li> +</li> +</ul> +</li> +<li>bar +</li> +</ul> !! end @@ -2963,10 +5981,15 @@ Nested lists 5 (both elements empty) ** * !! result -<ul><li><ul><li> -</li></ul> -</li><li> -</li></ul> +<ul> +<li><ul> +<li> +</li> +</ul> +</li> +<li> +</li> +</ul> !! end @@ -2976,10 +5999,14 @@ Nested lists 6 (both elements empty) * ** !! result -<ul><li> -<ul><li> -</li></ul> -</li></ul> +<ul> +<li> +<ul> +<li> +</li> +</ul> +</li> +</ul> !! end @@ -2988,10 +6015,16 @@ Nested lists 7 (skip initial nesting levels) !! input *** foo !! result -<ul><li><ul><li><ul><li> foo -</li></ul> -</li></ul> -</li></ul> +<ul> +<li><ul> +<li><ul> +<li> foo +</li> +</ul> +</li> +</ul> +</li> +</ul> !! end @@ -3003,16 +6036,56 @@ Nested lists 8 (multiple nesting transitions) ** baz * boo !! result -<ul><li> foo -<ul><li><ul><li> bar -</li></ul> -</li><li> baz -</li></ul> -</li><li> boo -</li></ul> +<ul> +<li> foo +<ul> +<li><ul> +<li> bar +</li> +</ul> +</li> +<li> baz +</li> +</ul> +</li> +<li> boo +</li> +</ul> !! end +!! test +1. Lists with start-of-line-transparent tokens before bullets: Comments +!! input +*foo +*<!--cmt-->bar +<!--cmt-->*baz +!! result +<ul> +<li>foo +</li> +<li>bar +</li> +<li>baz +</li> +</ul> + +!! end + +!! test +2. Lists with start-of-line-transparent tokens before bullets: Template close +!! input +*foo {{echo|bar +}}*baz +!! result +<ul> +<li>foo bar +</li> +<li>baz +</li> +</ul> + +!! end !! test List items are not parsed correctly following a <pre> block (bug 785) @@ -3021,10 +6094,14 @@ List items are not parsed correctly following a <pre> block (bug 785) * <pre>bar</pre> * zar !! result -<ul><li> <pre>foo</pre> -</li><li> <pre>bar</pre> -</li><li> zar -</li></ul> +<ul> +<li> <pre>foo</pre> +</li> +<li> <pre>bar</pre> +</li> +<li> zar +</li> +</ul> !! end @@ -3043,18 +6120,30 @@ List items from template * notSOL{{inner list}} * item 2 !! result -<ul><li> item 1 -</li><li> item 2 -</li></ul> -<ul><li> item 0 -</li><li> item 1 -</li><li> item 2 -</li></ul> -<ul><li> item 0 -</li><li> notSOL -</li><li> item 1 -</li><li> item 2 -</li></ul> +<ul> +<li> item 1 +</li> +<li> item 2 +</li> +</ul> +<ul> +<li> item 0 +</li> +<li> item 1 +</li> +<li> item 2 +</li> +</ul> +<ul> +<li> item 0 +</li> +<li> notSOL +</li> +<li> item 1 +</li> +<li> item 2 +</li> +</ul> !! end @@ -3067,22 +6156,267 @@ List interrupted by empty line or heading == A heading == * Another list item !! result -<ul><li> foo -</li></ul> -<ul><li><ul><li> bar -</li></ul> -</li></ul> -<h2><span class="editsection">[<a href="/index.php?title=Parser_test&action=edit&section=1" title="Edit section: A heading">edit</a>]</span> <span class="mw-headline" id="A_heading"> A heading </span></h2> -<ul><li> Another list item -</li></ul> +<ul> +<li> foo +</li> +</ul> +<ul> +<li><ul> +<li> bar +</li> +</ul> +</li> +</ul> +<h2><span class="mw-headline" id="A_heading">A heading</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Parser_test&action=edit&section=1" title="Edit section: A heading">edit</a><span class="mw-editsection-bracket">]</span></span></h2> +<ul> +<li> Another list item +</li> +</ul> + +!!end + +!!test +Multiple list tags generated by templates +!!input +{{echo|<li>}}a +{{echo|<li>}}b +{{echo|<li>}}c +!!result +<li>a +<li>b +<li>c</li> +</li> +</li> + +!!end + +!!test +Single-comment whitespace lines dont break lists, and neither do multi-comment whitespace lines +!!input +*a +<!--This line will NOT split the list--> +*b + <!--This line will NOT split the list either--> +*c + <!--foo--> <!----> <!--This line NOT split the list either--> +*d +!!result +<ul> +<li>a +</li> +<li>b +</li> +<li>c +</li> +<li>d +</li> +</ul> + +!!end + +!!test +Replacing whitespace with tabs still doesn't break the list (gerrit 78327) +!!input +*a +<!--This line will NOT split the list--> +*b + <!--This line will NOT split the list either--> +*c + <!--foo--> <!----> <!--This line NOT split the list + either--> +*d +!!result +<ul> +<li>a +</li> +<li>b +</li> +<li>c +</li> +<li>d +</li> +</ul> + +!!end + +!!test +Test the li-hack +(Cannot test this with PHP parser since it relies on Tidy for the hack) +!!options +parsoid=wt2html,wt2wt +!!input +* foo +* <li>li-hack +* {{echo|<li>templated li-hack}} +* <!--foo--> <li> unsupported li-hack with preceding comments + +<ul> +<li><li>not a li-hack +</li> +</ul> +!!result +<ul> +<li> foo</li> +<li>li-hack</li> +<li about="#mwt1" typeof="mw:Transclusion" data-mw='{"parts":[{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":"<li>templated li-hack"}}}}]}'>templated li-hack</li> +<li> <!--foo--> </li> +<li> li-hack with preceding comments</li> +</ul> +<ul> +<li></li> +<li>not a li-hack +</li> +</ul> !!end +!! test +Parsoid: Make sure nested lists are serialized on their own line even if HTML contains no newlines +!! options +parsoid +!! input +# foo +## bar +* foo +** bar +: foo +:: bar +!! result +<ol> +<li> foo<ol> +<li> bar</li> +</ol></li> +</ol><ul> +<li> foo<ul> +<li> bar</li> +</ul></li> +</ul><dl> +<dd> foo<dl> +<dd> bar</dd> +</dl></dd> +</dl> +!! end + +!! test +Parsoid: Test of whitespace serialization with Templated bullets +!! options +parsoid +!! input +* {{bullet}} +!! result +<ul> +<li> </li><li about="#mwt1" typeof="mw:Transclusion" data-mw='{"parts":[{"template":{"target":{"wt":"bullet","href":"./Template:Bullet"},"params":{},"i":0}}]}'> Bar</li> +</ul> +!! end + +# ------------------------------------------------------------------------ +# The next set of tests are about Parsoid's ability to handle badly nested +# tags (parse, minimize scope of fixup, and roundtrip back) +# ------------------------------------------------------------------------ + +!! test +Unbalanced closing block tags break a list +(Parsoid-only since php parser generates broken html -- relies on Tidy to fix up) +!! options +parsoid +!! input +<div> +*a</div><div> +*b</div> +!! result +<div> +<ul> +<li>a +</li> +</ul></div><div> +<ul> +<li>b +</li> +</ul></div> +!! end + +!! test +Unbalanced closing non-block tags don't break a list +(Parsoid-only since php parser generates broken html -- relies on Tidy to fix up) +!! options +parsoid +!! input +<span> +*a</span><span> +*b</span> +!! result +<p><span></span> +</p> +<ul> +<li>a<span></span> +</li> +<li>b +</li> +</ul> +!! end + +!! test +Unclosed formatting tags that straddle lists are closed and reopened +(Parsoid-only since php parser generates broken html -- relies on Tidy to fix up) +!! options +parsoid +!! input +# <s> a +# b </s> +!! result +<ol> +<li> <s> a </s> +</li> +<li> <s> b </s> +</li> +</ol> +!! end + +!!test +List embedded in a non-block tag +(Ugly Parsoid output -- worth fixing; Disabled for PHP parser since it relies on Tidy) +!! options +parsoid +!!input +<small> +* foo +</small> +!!result +<p><small></small></p> +<small> +<ul> +<li> foo</li> +</ul> +</small> +<p><small></small></p> +!!end + +!! test +Table with missing opening <tr> tag +!! options +parsoid=wt2html,wt2wt +!! input +<table> +<td>foo</td> +</tr> +</table> +!! result +<table> +<tr> +<td>foo</td> +</tr> +</table> +!! end ### ### Magic Words ### +# Note that the current date is hard-coded as +# 1970-01-01T00:02:03Z (a Thursday) +# when running parser tests. The timezone is also fixed to GMT, so +# local date will be identical to current date. + !! test Magic Word: {{CURRENTDAY}} !! input @@ -3129,6 +6463,15 @@ Magic Word: {{CURRENTMONTH}} !! end !! test +Magic Word: {{CURRENTMONTH1}} +!! input +{{CURRENTMONTH1}} +!! result +<p>1 +</p> +!! end + +!! test Magic Word: {{CURRENTMONTHABBREV}} !! input {{CURRENTMONTHABBREV}} @@ -3165,6 +6508,15 @@ Magic Word: {{CURRENTTIME}} !! end !! test +Magic Word: {{CURRENTHOUR}} +!! input +{{CURRENTHOUR}} +!! result +<p>00 +</p> +!! end + +!! test Magic Word: {{CURRENTWEEK}} (@bug 4594) !! input {{CURRENTWEEK}} @@ -3183,6 +6535,66 @@ Magic Word: {{CURRENTYEAR}} !! end !! test +Magic Word: {{CURRENTTIMESTAMP}} +!! input +{{CURRENTTIMESTAMP}} +!! result +<p>19700101000203 +</p> +!! end + +!! test +Magic Words LOCAL (UTC) +!! input +* {{LOCALMONTH}} +* {{LOCALMONTH1}} +* {{LOCALMONTHNAME}} +* {{LOCALMONTHNAMEGEN}} +* {{LOCALMONTHABBREV}} +* {{LOCALDAY}} +* {{LOCALDAY2}} +* {{LOCALDAYNAME}} +* {{LOCALYEAR}} +* {{LOCALTIME}} +* {{LOCALHOUR}} +* {{LOCALWEEK}} +* {{LOCALDOW}} +* {{LOCALTIMESTAMP}} +!! result +<ul> +<li> 01 +</li> +<li> 1 +</li> +<li> January +</li> +<li> January +</li> +<li> Jan +</li> +<li> 1 +</li> +<li> 01 +</li> +<li> Thursday +</li> +<li> 1970 +</li> +<li> 00:02 +</li> +<li> 00 +</li> +<li> 1 +</li> +<li> 4 +</li> +<li> 19700101000203 +</li> +</ul> + +!! end + +!! test Magic Word: {{FULLPAGENAME}} !! options title=[[User:Ævar Arnfjörð Bjarmason]] @@ -3205,6 +6617,93 @@ title=[[User:Ævar Arnfjörð Bjarmason]] !! end !! test +Magic Word: {{TALKSPACE}} +!! options +title=[[User:Ævar Arnfjörð Bjarmason]] +!! input +{{TALKSPACE}} +!! result +<p>User talk +</p> +!! end + +!! test +Magic Word: {{TALKSPACE}}, same namespace +!! options +title=[[User talk:Ævar Arnfjörð Bjarmason]] +!! input +{{TALKSPACE}} +!! result +<p>User talk +</p> +!! end + +!! test +Magic Word: {{TALKSPACE}}, main namespace +!! options +title=[[Parser Test]] +!! input +{{TALKSPACE}} +!! result +<p>Talk +</p> +!! end + +!! test +Magic Word: {{TALKSPACEE}} +!! options +title=[[User:Ævar Arnfjörð Bjarmason]] +!! input +{{TALKSPACEE}} +!! result +<p>User_talk +</p> +!! end + +!! test +Magic Word: {{SUBJECTSPACE}} +!! options +title=[[User talk:Ævar Arnfjörð Bjarmason]] +!! input +{{SUBJECTSPACE}} +!! result +<p>User +</p> +!! end + +!! test +Magic Word: {{SUBJECTSPACE}}, same namespace +!! options +title=[[User:Ævar Arnfjörð Bjarmason]] +!! input +{{SUBJECTSPACE}} +!! result +<p>User +</p> +!! end + +!! test +Magic Word: {{SUBJECTSPACE}}, main namespace +!! options +title=[[Parser Test]] +!! input +{{SUBJECTSPACE}} +!! result + +!! end + +!! test +Magic Word: {{SUBJECTSPACEE}} +!! options +title=[[User talk:Ævar Arnfjörð Bjarmason]] +!! input +{{SUBJECTSPACEE}} +!! result +<p>User +</p> +!! end + +!! test Magic Word: {{NAMESPACE}} !! options title=[[User:Ævar Arnfjörð Bjarmason]] @@ -3238,11 +6737,121 @@ title=[[User:Ævar Arnfjörð Bjarmason]] !! end !! test +Magic Word: {{SUBPAGENAME}} +!! options +title=[[Ævar Arnfjörð Bjarmason/sub ö]] subpage +!! input +{{SUBPAGENAME}} +!! result +<p>sub ö +</p> +!! end + +!! test +Magic Word: {{SUBPAGENAMEE}} +!! options +title=[[Ævar Arnfjörð Bjarmason/sub ö]] subpage +!! input +{{SUBPAGENAMEE}} +!! result +<p>sub_%C3%B6 +</p> +!! end + +!! test +Magic Word: {{ROOTPAGENAME}} +!! options +title=[[Ævar Arnfjörð Bjarmason/sub/sub2]] subpage +!! input +{{ROOTPAGENAME}} +!! result +<p>Ævar Arnfjörð Bjarmason +</p> +!! end + +!! test +Magic Word: {{ROOTPAGENAMEE}} +!! options +title=[[Ævar Arnfjörð Bjarmason/sub/sub2]] subpage +!! input +{{ROOTPAGENAMEE}} +!! result +<p>%C3%86var_Arnfj%C3%B6r%C3%B0_Bjarmason +</p> +!! end + +!! test +Magic Word: {{BASEPAGENAME}} +!! options +title=[[Ævar Arnfjörð Bjarmason/sub]] subpage +!! input +{{BASEPAGENAME}} +!! result +<p>Ævar Arnfjörð Bjarmason +</p> +!! end + +!! test +Magic Word: {{BASEPAGENAMEE}} +!! options +title=[[Ævar Arnfjörð Bjarmason/sub]] subpage +!! input +{{BASEPAGENAMEE}} +!! result +<p>%C3%86var_Arnfj%C3%B6r%C3%B0_Bjarmason +</p> +!! end + +!! test +Magic Word: {{TALKPAGENAME}} +!! options +title=[[User:Ævar Arnfjörð Bjarmason]] +!! input +{{TALKPAGENAME}} +!! result +<p>User talk:Ævar Arnfjörð Bjarmason +</p> +!! end + +!! test +Magic Word: {{TALKPAGENAMEE}} +!! options +title=[[User:Ævar Arnfjörð Bjarmason]] +!! input +{{TALKPAGENAMEE}} +!! result +<p>User_talk:%C3%86var_Arnfj%C3%B6r%C3%B0_Bjarmason +</p> +!! end + +!! test +Magic Word: {{SUBJECTPAGENAME}} +!! options +title=[[User talk:Ævar Arnfjörð Bjarmason]] +!! input +{{SUBJECTPAGENAME}} +!! result +<p>User:Ævar Arnfjörð Bjarmason +</p> +!! end + +!! test +Magic Word: {{SUBJECTPAGENAMEE}} +!! options +title=[[User talk:Ævar Arnfjörð Bjarmason]] +!! input +{{SUBJECTPAGENAMEE}} +!! result +<p>User:%C3%86var_Arnfj%C3%B6r%C3%B0_Bjarmason +</p> +!! end + +!! test Magic Word: {{NUMBEROFFILES}} !! input {{NUMBEROFFILES}} !! result -<p>2 +<p>4 </p> !! end @@ -3320,11 +6929,20 @@ Magic Word: {{SCRIPTPATH}} !! end !! test +Magic Word: {{STYLEPATH}} +!! input +{{STYLEPATH}} +!! result +<p>/skins +</p> +!! end + +!! test Magic Word: {{SERVER}} !! input {{SERVER}} !! result -<p><a rel="nofollow" class="external free" href="http://Britney-Spears">http://Britney-Spears</a> +<p><a rel="nofollow" class="external free" href="http://example.org">http://example.org</a> </p> !! end @@ -3333,7 +6951,7 @@ Magic Word: {{SERVERNAME}} !! input {{SERVERNAME}} !! result -<p>Britney-Spears +<p>example.org </p> !! end @@ -3347,6 +6965,36 @@ Magic Word: {{SITENAME}} !! end !! test +Case-sensitive magic words, when cased differently, should just be template transclusions +!! input +{{CurrentMonth}} +{{currentday}} +{{cURreNTweEK}} +{{currentHour}} +!! result +<p><a href="/index.php?title=Template:CurrentMonth&action=edit&redlink=1" class="new" title="Template:CurrentMonth (page does not exist)">Template:CurrentMonth</a> +<a href="/index.php?title=Template:Currentday&action=edit&redlink=1" class="new" title="Template:Currentday (page does not exist)">Template:Currentday</a> +<a href="/index.php?title=Template:CURreNTweEK&action=edit&redlink=1" class="new" title="Template:CURreNTweEK (page does not exist)">Template:CURreNTweEK</a> +<a href="/index.php?title=Template:CurrentHour&action=edit&redlink=1" class="new" title="Template:CurrentHour (page does not exist)">Template:CurrentHour</a> +</p> +!! end + +!! test +Case-insensitive magic words should still work with weird casing. +!! input +{{sErVeRNaMe}} +{{LCFirst:AOEU}} +{{ucFIRST:aoeu}} +{{SERver}} +!! result +<p>example.org +aOEU +Aoeu +<a rel="nofollow" class="external free" href="http://example.org">http://example.org</a> +</p> +!! end + +!! test Namespace 1 {{ns:1}} !! input {{ns:1}} @@ -3489,7 +7137,7 @@ Magic links: RFC (bug 479) !! input RFC 822 !! result -<p><a class="external mw-magiclink-rfc" href="//tools.ietf.org/html/rfc822">RFC 822</a> +<p><a class="external mw-magiclink-rfc" rel="nofollow" href="//tools.ietf.org/html/rfc822">RFC 822</a> </p> !! end @@ -3507,7 +7155,7 @@ Magic links: PMID incorrectly converts space to underscore !! input PMID 1234 !! result -<p><a class="external mw-magiclink-pmid" href="//www.ncbi.nlm.nih.gov/pubmed/1234?dopt=Abstract">PMID 1234</a> +<p><a class="external mw-magiclink-pmid" rel="nofollow" href="//www.ncbi.nlm.nih.gov/pubmed/1234?dopt=Abstract">PMID 1234</a> </p> !! end @@ -3524,6 +7172,24 @@ Nonexistent template </p> !! end +!! test +Template with invalid target containing tags +!! input +{{a<b>b</b>|{{echo|foo}}|{{echo|a}}={{echo|b}}|a = b}} +!! result +<p>{{a<b>b</b>|foo|a=b|a = b}} +</p> +!! end + +!! test +Template with invalid target containing unclosed tag +!! input +{{a<b>|{{echo|foo}}|{{echo|a}}={{echo|b}}|a = b}} +!! result +<p>{{a<b>|foo|a=b|a = b}}</b> +</p> +!! end + !! article Template:test !! text @@ -3727,7 +7393,7 @@ Template with complex arguments !! test BUG 553: link with two variables in a piped link !! input -{| +{| |[[{{{1}}}|{{{2}}}]] |} !! result @@ -3762,6 +7428,14 @@ Template parameter as link source </p> !! end +!!test +Template-generated attribute string (k='v') +!!input +<span {{attr_str|id|v1}}>bar</span> +!!result +<p><span id="v1">bar</span> +</p> +!!end !!article Template:paramtest2 @@ -3788,8 +7462,14 @@ Main Page Template as link source !! input [[{{linktest2}}]] + +[[{{linktest2}}|Main Page]] + +[[{{linktest2}}]]Page !! result <p><a href="/wiki/Main_Page" title="Main Page">Main Page</a> +</p><p><a href="/wiki/Main_Page" title="Main Page">Main Page</a> +</p><p><a href="/wiki/Main_Page" title="Main Page">Main Page</a>Page </p> !! end @@ -3827,9 +7507,9 @@ Template from main namespace !! article Template:table !! text -{| +{| | 1 || 2 -|- +|- | 3 || 4 |} !! endarticle @@ -3882,6 +7562,20 @@ BUG 41: Template parameters shown as broken links </p> !! end +!! test +Template with targets containing wikilinks +!! input +{{[[foo]]}} + +{{[[{{echo|foo}}]]}} + +{{{{echo|[[foo}}]]}} +!! result +<p>{{<a href="/index.php?title=Foo&action=edit&redlink=1" class="new" title="Foo (page does not exist)">foo</a>}} +</p><p>{{<a href="/index.php?title=Foo&action=edit&redlink=1" class="new" title="Foo (page does not exist)">foo</a>}} +</p><p>{{[[foo}}]] +</p> +!! end !! article Template:MSGNW test @@ -3972,6 +7666,13 @@ Foo<noinclude>zar</noinclude><includeonly>bar</includeonly> !! end !! test +Un-closed <noinclude> +!! input +<noinclude> +!! result +!! end + +!! test <onlyinclude> on a page !! input <onlyinclude>Foo</onlyinclude>bar @@ -3980,6 +7681,47 @@ Foo<noinclude>zar</noinclude><includeonly>bar</includeonly> </p> !! end +!! test +Un-closed <onlyinclude> +!! input +<onlyinclude> +!! result +!! end + +!!test +Self-closed noinclude, includeonly, onlyinclude tags +!!input +<noinclude /> +<includeonly /> +<onlyinclude /> +!!result +<p><br /> +</p> +!!end + +!!test +Unbalanced includeonly and noinclude tags +!!input +{| +|a</noinclude> +|b</noinclude></noinclude> +|c</noinclude></includeonly> +|d</includeonly></includeonly> +|} +!!result +<table> +<tr> +<td>a +</td> +<td>b +</td> +<td>c</includeonly> +</td> +<td>d</includeonly></includeonly> +</td></tr></table> + +!!end + !! article Template:Includeonly section !! text @@ -3994,8 +7736,8 @@ Bug 6563: Edit link generation for section shown by <includeonly> !! input {{includeonly section}} !! result -<h2><span class="editsection">[<a href="/index.php?title=Template:Includeonly_section&action=edit&section=T-1" title="Template:Includeonly section">edit</a>]</span> <span class="mw-headline" id="Includeonly_section">Includeonly section</span></h2> -<h2><span class="editsection">[<a href="/index.php?title=Template:Includeonly_section&action=edit&section=T-2" title="Template:Includeonly section">edit</a>]</span> <span class="mw-headline" id="Section_T-1">Section T-1</span></h2> +<h2><span class="mw-headline" id="Includeonly_section">Includeonly section</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Template:Includeonly_section&action=edit&section=T-1" title="Template:Includeonly section">edit</a><span class="mw-editsection-bracket">]</span></span></h2> +<h2><span class="mw-headline" id="Section_T-1">Section T-1</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Template:Includeonly_section&action=edit&section=T-2" title="Template:Includeonly section">edit</a><span class="mw-editsection-bracket">]</span></span></h2> !! end @@ -4021,7 +7763,7 @@ Bug 6563: Edit link generation for section suppressed by <includeonly> </includeonly> ==Section 1== !! result -<h2><span class="editsection">[<a href="/index.php?title=Parser_test&action=edit&section=1" title="Edit section: Section 1">edit</a>]</span> <span class="mw-headline" id="Section_1">Section 1</span></h2> +<h2><span class="mw-headline" id="Section_1">Section 1</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Parser_test&action=edit&section=1" title="Edit section: Section 1">edit</a><span class="mw-editsection-bracket">]</span></span></h2> !! end @@ -4038,6 +7780,774 @@ section=1 ==Section 1== !! end +!! test +Un-closed <includeonly> +!! input +<includeonly> +!! result +!! end + +# TODO: test with DOM fragment reuse! +!! test +Parsoid: DOM fragment reuse +!! options +parsoid=wt2wt,wt2html +!! input +a{{echo|b<table></table>c}}d + +a{{echo|b +<table></table> +c}}d + +{{echo|a + +<table></table> + +b}} +!! result +a<span about="#mwt1" typeof="mw:Transclusion" data-mw='{"parts":[{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":"b +<table></table>c"}},"i":0}}]}'>b</span> +<table about="#mwt1"></table><span about="#mwt1">c</span>d + + +<p about="#mwt2" typeof="mw:Transclusion" data-mw='{"parts":["a",{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":"b\n<table></table>\nc"}},"i":0}},"d"]}'>ab</p><span about="#mwt2"> +</span> +<table about="#mwt2"></table><span about="#mwt2"> +</span> +<p about="#mwt2">cd</p> + + +<p about="#mwt3" typeof="mw:Transclusion" data-mw='{"parts":[{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":"a\n\n<table></table>\n\nb"}},"i":0}}]}'>a</p><span about="#mwt3"> + +</span> +<table about="#mwt3"></table><span about="#mwt3"> + +</span> +<p about="#mwt3">b</p> +!! end + +!! test +Parsoid: Merge double tds (bug 50603) +!! options +parsoid +!! input +{| +|{{echo|{{!}} foo}} +|} +!! result +<table><tbody> +<tr><td about="#mwt1" typeof="mw:Transclusion" data-mw='{"parts":["|",{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":"{{!}} foo"}},"i":0}}]}'> foo</td></tr> +</tbody></table> +!! end + +!! test +Parsoid: Merge double tds in nested transclusion content (bug 50603) +!! options +parsoid +!! input +{{echo|<div>}} +{| +|{{echo|{{!}} foo}} +|} +{{echo|</div>}} +!! result +<div about="#mwt1" typeof="mw:Transclusion" data-mw='{"parts":[{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":"<div>"}},"i":0}},"\n{|\n|",{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":"{{!}} foo"}},"i":1}},"\n|}\n",{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":"</div>"}},"i":2}}]}'> +<table><tbody> +<tr><td data-mw='{"parts":["|"]}'> foo</td></tr> +</tbody></table> +</div> +!! end + +### +### <includeonly> and <noinclude> in attributes +### +!!test +0. includeonly around the entire attribute +!!input +<span <includeonly>id="v1"</includeonly><noinclude>id="v2"</noinclude>>bar</span> +!!result +<p><span id="v2">bar</span> +</p> +!!end + +!!test +1. includeonly in html attr key +!!input +<span <noinclude>id</noinclude><includeonly>about</includeonly>="foo">bar</span> +!!result +<p><span id="foo">bar</span> +</p> +!!end + +!!test +2. includeonly in html attr value +!!input +<span id="<noinclude>v1</noinclude><includeonly>v2</includeonly>">bar</span> +<span id=<noinclude>"v1"</noinclude><includeonly>"v2"</includeonly>>bar</span> +!!result +<p><span id="v1">bar</span> +<span id="v1">bar</span> +</p> +!!end + +!!test +3. includeonly in part of an attr value +!!input +<span style="color:<noinclude>red</noinclude><includeonly>blue</includeonly>;">bar</span> +!!result +<p><span style="color:red;">bar</span> +</p> +!!end + +### +### Testing parsing of templates where a template arg +### has the same name as the template itself. +### + +!! article +Template:quote +!! text +{{{quote|{{{1}}}}}} +!! endarticle + +!!test +Templates: Template Name/Arg clash: 1. Use of positional param +!!input +{{quote|foo}} +!!result +<p>foo +</p> +!!end + +!!test +Templates: Template Name/Arg clash: 2. Use of named param +!!input +{{quote|quote=foo}} +!!result +<p>foo +</p> +!!end + +!!test +Templates: Template Name/Arg clash: 3. Use of named param with empty input +!!input +{{quote|quote}} +!!result +<p>quote +</p> +!!end + +### +### Parsoid-centric tests to stress Parsoid's ability to RT them unchanged +### + +!!test +Templates: 1. Simple use +!!input +{{echo|Foo}} +!!result +<p>Foo +</p> +!!end + +!!test +Templates: 2. Inside a block tag +!!input +<div>{{echo|Foo}}</div> +<blockquote>{{echo|Foo}}</blockquote> +!!result +<div>Foo</div> +<blockquote>Foo</blockquote> + +!!end + +!!test +Templates: P-wrapping: 1a. Templates on consecutive lines +!!input +{{echo|Foo}} +{{echo|bar}} +!!result +<p>Foo +bar +</p> +!!end + +!!test +Templates: P-wrapping: 1b. Templates on consecutive lines +!!input +Foo + +{{echo|bar}} +{{echo|baz}} +!!result +<p>Foo +</p><p>bar +baz +</p> +!!end + +!!test +Templates: P-wrapping: 1c. Templates on consecutive lines +!!input +{{echo|Foo}} +{{echo|bar}} <div>baz</div> +!!result +<p>Foo +</p> +bar <div>baz</div> + +!!end + +!!test +Templates: P-wrapping: 1d. Template preceded by comment-only line +!!options +parsoid +!!input +<!-- foo --> +{{echo|Bar}} +!!result +<!-- foo --> + +<p about="#mwt223" typeof="mw:Transclusion" data-mw='{"parts":[{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":"Bar"}},"i":0}}]}'>Bar</p> +!!end + +!!test +Templates: Inline Text: 1. Multiple tmeplate uses +!!input +{{echo|Foo}}bar{{echo|baz}} +!!result +<p>Foobarbaz +</p> +!!end + +!!test +Templates: Inline Text: 2. Back-to-back template uses +!!input +{{echo|Foo}}{{echo|bar}} +!!result +<p>Foobar +</p> +!!end + +!!test +Templates: Block Tags: 1. Multiple template uses +!!input +{{echo|<div>Foo</div>}}<div>bar</div>{{echo|<div>baz</div>}} +!!result +<div>Foo</div><div>bar</div><div>baz</div> + +!!end + +!!test +Templates: Block Tags: 2. Back-to-back template uses +!!input +{{echo|<div>Foo</div>}}{{echo|<div>bar</div>}} +!!result +<div>Foo</div><div>bar</div> + +!!end + +!!test +Templates: Links: 1. Simple example +!!input +{{echo|[[Foo|bar]]}} +!!result +<p><a href="/index.php?title=Foo&action=edit&redlink=1" class="new" title="Foo (page does not exist)">bar</a> +</p> +!!end + +!!test +Templates: Links: 2. Generation of link href +!!input +[[{{echo|Foo}}|bar]] +!!result +<p><a href="/index.php?title=Foo&action=edit&redlink=1" class="new" title="Foo (page does not exist)">bar</a> +</p> +!!end + +!!test +Templates: Links: 3. Generation of part of a link href +!!input +[[Fo{{echo|o}}|bar]] + +[[Foo{{echo|bar}}]] + +[[Foo{{echo|bar}}baz]] + +[[Foo{{echo|bar}}|bar]] + +[[:Foo{{echo|bar}}]] + +[[:Foo{{echo|bar}}|bar]] +!!result +<p><a href="/index.php?title=Foo&action=edit&redlink=1" class="new" title="Foo (page does not exist)">bar</a> +</p><p><a href="/index.php?title=Foobar&action=edit&redlink=1" class="new" title="Foobar (page does not exist)">Foobar</a> +</p><p><a href="/index.php?title=Foobarbaz&action=edit&redlink=1" class="new" title="Foobarbaz (page does not exist)">Foobarbaz</a> +</p><p><a href="/index.php?title=Foobar&action=edit&redlink=1" class="new" title="Foobar (page does not exist)">bar</a> +</p><p><a href="/index.php?title=Foobar&action=edit&redlink=1" class="new" title="Foobar (page does not exist)">Foobar</a> +</p><p><a href="/index.php?title=Foobar&action=edit&redlink=1" class="new" title="Foobar (page does not exist)">bar</a> +</p> +!!end + +!!test +Templates: Links: 4. Multiple templates generating link href +!!input +[[{{echo|F}}{{echo|o}}ob{{echo|ar}}]] +!!result +<p><a href="/index.php?title=Foobar&action=edit&redlink=1" class="new" title="Foobar (page does not exist)">Foobar</a> +</p> +!!end + +!!test +Templates: Links: 5. Generation of link text +!!input +[[Foo|{{echo|bar}}]] +!!result +<p><a href="/index.php?title=Foo&action=edit&redlink=1" class="new" title="Foo (page does not exist)">bar</a> +</p> +!!end + +!!test +Templates: Links: 5. Nested templates (only outermost template should be marked) +!!input +{{echo|[[{{echo|Foo}}|bar]]}} +!!result +<p><a href="/index.php?title=Foo&action=edit&redlink=1" class="new" title="Foo (page does not exist)">bar</a> +</p> +!!end + +!!test +Templates: HTML Tag: 1. Generation of HTML attr. key +!!input +<div {{echo|style}}="color:red;">foo</div> +!!result +<div style="color:red;">foo</div> + +!!end + +!!test +Templates: HTML Tag: 2. Generation of HTML attr. value +!!input +<div style={{echo|'color:red;'}}>foo</div> +!!result +<div style="color:red;">foo</div> + +!!end + +!!test +Templates: HTML Tag: 3. Generation of HTML attr key and value +!!input +<div {{echo|style}}={{echo|'color:red;'}}>foo</div> +!!result +<div style="color:red;">foo</div> + +!!end + +!!test +Templates: HTML Tag: 4. Generation of starting piece of HTML attr value +!!input +<div title="{{echo|This is a long title}} with just one piece templated">foo</div> +!!result +<div title="This is a long title with just one piece templated">foo</div> + +!!end + +!!test +Templates: HTML Tag: 5. Generation of middle piece of HTML attr value +!!input +<div title="This is a long title with just {{echo|one piece}} templated">foo</div> +!!result +<div title="This is a long title with just one piece templated">foo</div> + +!!end + +!!test +Templates: HTML Tag: 6. Generation of end piece of HTML attr value +!!input +<div title="This is a long title with just one piece {{echo|templated}}">foo</div> +!!result +<div title="This is a long title with just one piece templated">foo</div> + +!!end + +!!test +Templates: HTML Tag: 7. Generation of partial attribute key string +!!input +<div st{{echo|yle}}="color:red;">foo</div> +!!result +<div style="color:red;">foo</div> + +!!end + +!!test +Templates: HTML Tables: 1. Generating start of a HTML table +!!input +{{echo|<table><tr><td>foo</td>}}</tr></table> +!!result +<table><tr><td>foo</td></tr></table> + +!!end + +!!test +Templates: HTML Tables: 2a. Generating middle of a HTML table +!!input +<table><tr>{{echo|<td>foo</td>}}</tr></table> +!!result +<table><tr><td>foo</td></tr></table> + +!!end + +!!test +Templates: HTML Tables: 2b. Generating middle of a HTML table +!!input +<table>{{echo|<tr><td>foo</td></tr>}}</table> +!!result +<table><tr><td>foo</td></tr></table> + +!!end + +!!test +Templates: HTML Tables: 3. Generating end of a HTML table +!!input +<table><tr>{{echo|<td>foo</td></tr></table>}} +!!result +<table><tr><td>foo</td></tr></table> + +!!end + +!!test +Templates: HTML Tables: 4a. Generating a single tag of a HTML table +!!input +{{echo|<table>}}<tr><td>foo</td></tr></table> +!!result +<table><tr><td>foo</td></tr></table> + +!!end + +!!test +Templates: HTML Tables: 4b. Generating a single tag of a HTML table +!!input +<table>{{echo|<tr>}}<td>foo</td></tr></table> +!!result +<table><tr><td>foo</td></tr></table> + +!!end + +!!test +Templates: HTML Tables: 4c. Generating a single tag of a HTML table +!!input +<table><tr>{{echo|<td>}}foo</td></tr></table> +!!result +<table><tr><td>foo</td></tr></table> + +!!end + +!!test +Templates: HTML Tables: 4d. Generating a single tag of a HTML table +!!input +<table><tr><td>foo{{echo|</td>}}</tr></table> +!!result +<table><tr><td>foo</td></tr></table> + +!!end + +!!test +Templates: HTML Tables: 4e. Generating a single tag of a HTML table +!!input +<table><tr><td>foo</td>{{echo|</tr>}}</table> +!!result +<table><tr><td>foo</td></tr></table> + +!!end + +!!test +Templates: HTML Tables: 4f. Generating a single tag of a HTML table +!!input +<table><tr><td>foo</td></tr>{{echo|</table>}} +!!result +<table><tr><td>foo</td></tr></table> + +!!end + +!!test +Templates: HTML Tables: 5. Proper fostering of categories from inside +!!options +parsoid=wt2html,wt2wt +!!input +<table>[[Category:foo1]]<tr><td>foo</td></tr></table> +<!--Two categories (Bug 50330)--> +<table>[[Category:bar1]][[Category:bar2]]<tr><td>foo</td></tr></table> +!!result +<link rel="mw:WikiLink/Category" href="./Category:Foo1"><table><tbody><tr><td>foo</td></tr></tbody></table> +<!--Two categories (Bug 50330)--> +<link rel="mw:WikiLink/Category" href="./Category:Bar1"><link rel="mw:WikiLink/Category" href="./Category:Bar2"><table><tbody><tr><td>foo</td></tr></tbody></table> +!!end + +!!test +Templates: Wiki Tables: 1a. Fostering of entire template content +!!input +{| +{{echo|a}} +|} +!!result +<table> +a +<tr><td></td></tr></table> + +!!end + +!!test +Templates: Wiki Tables: 1b. Fostering of entire template content +!!input +{| +{{echo|<div>}} +foo +{{echo|</div>}} +|} +!!result +<table> +<div> +<p>foo +</p> +</div> +<tr><td></td></tr></table> + +!!end + +!!test +Templates: Wiki Tables: 2. Fostering of partial template content +!!input +{| +{{echo|a +<div>b</div>}} +|} +!!result +<table> +a +<div>b</div> +<tr><td></td></tr></table> + +!!end + +!!test +Templates: Wiki Tables: 3. td-content via multiple templates +!!input +{| +{{echo|{{pipe}}a}}{{echo|b}} +|} +!!result +<table> +<tr> +<td>ab +</td></tr></table> + +!!end + +!!test +Templates: Wiki Tables: 4. Templated tags, no content +!!input +{{tbl-start}} +{{tbl-end}} +!!result +<table> +<tr><td></td></tr></table> + +!!end + +!!test +Templates: Wiki Tables: 5. Templated tags, regular td-tags +!!input +{{tbl-start}} +|foo +{{tbl-end}} +!!result +<table> +<tr> +<td>foo +</td></tr></table> + +!!end + +!!test +Templates: Wiki Tables: 6. Templated tags, templated td-tags +!!input +{{tbl-start}} +{{!}}foo +{{tbl-end}} +!!result +<table> +<tr> +<td>foo +</td></tr></table> + +!!end + +!!test +Templates: Lists: Multi-line list-items via templates +!!input +*{{echo|a {{nonexistent| +unused}}}} +*{{echo|b {{nonexistent| +unused}}}} +!!result +<ul> +<li>a <a href="/index.php?title=Template:Nonexistent&action=edit&redlink=1" class="new" title="Template:Nonexistent (page does not exist)">Template:Nonexistent</a> +</li> +<li>b <a href="/index.php?title=Template:Nonexistent&action=edit&redlink=1" class="new" title="Template:Nonexistent (page does not exist)">Template:Nonexistent</a> +</li> +</ul> + +!!end + +!!test +Templates: Ugly nesting: 1. Quotes opened/closed across templates (echo) +!!input +{{echo|''a}}{{echo|b''c''d}}{{echo|''e}} +!!result +<p><i>ab</i>c<i>d</i>e +</p> +!!end + +!!test +Templates: Ugly nesting: 2. Quotes opened/closed across templates (echo_with_span) +(PHP parser generates misnested html) +!! options +parsoid +!!input +{{echo_with_span|''a}}{{echo_with_span|b''c''d}}{{echo_with_span|''e}} +!!result +<p><span about="#mwt1" typeof="mw:Transclusion" data-mw="{"parts":[{"template":{"target":{"wt":"echo_with_span","href":"./Template:Echo_with_span"},"params":{"1":{"wt":"''a"}},"i":0}}]}"><i>a</i></span><i about="#mwt2" typeof="mw:Transclusion" data-mw="{"parts":[{"template":{"target":{"wt":"echo_with_span","href":"./Template:Echo_with_span"},"params":{"1":{"wt":"b''c''d"}},"i":0}},{"template":{"target":{"wt":"echo_with_span","href":"./Template:Echo_with_span"},"params":{"1":{"wt":"''e"}},"i":1}}]}"><span>b</span></i><span about="#mwt2">c</span><i about="#mwt2">d<span></span></i><span about="#mwt2">e</span></p> +!!end + +!!test +Templates: Ugly nesting: 3. Quotes opened/closed across templates (echo_with_div) +(PHP parser generates misnested html; Parsoid html2wt mode adds newlines between {{echo}}s) +!! options +parsoid=wt2html,wt2wt +!!input +{{echo_with_div|''a}}{{echo_with_div|b''c''d}}{{echo_with_div|''e}} +!!result +<div about="#mwt1" typeof="mw:Transclusion" data-mw="{"parts":[{"template":{"target":{"wt":"echo_with_div","href":"./Template:Echo_with_div"},"params":{"1":{"wt":"''a"}},"i":0}}]}"><i>a</i></div> +<div about="#mwt2" typeof="mw:Transclusion" data-mw="{"parts":[{"template":{"target":{"wt":"echo_with_div","href":"./Template:Echo_with_div"},"params":{"1":{"wt":"b''c''d"}},"i":0}}]}"><i>b</i>c<i>d</i></div> +<div about="#mwt3" typeof="mw:Transclusion" data-mw="{"parts":[{"template":{"target":{"wt":"echo_with_div","href":"./Template:Echo_with_div"},"params":{"1":{"wt":"''e"}},"i":0}}]}">e</div> +!!end + +!!test +Templates: Ugly nesting: 4. Divs opened/closed across templates +!!input +a<div>b{{echo|c</div>d}}e +!!result +a<div>bc</div>de + +!!end + +!!test +Templates: Ugly templates: 1. Navbox template parses badly leading to table misnesting +(Parsoid-centric) +!! options +parsoid +!!input +{| +|{{echo|foo</table>}} +|bar +|} +!!result +<table about="#mwt1" typeof="mw:Transclusion" data-mw='{"parts":["{|\n|",{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":"foo</table>"}},"i":0}},"\n|bar\n|}"]}'> + +<tbody> +<tr> +<td>foo</td></tr></tbody></table><span about="#mwt1"> +</span><span about="#mwt1">bar</span><span about="#mwt1"> +</span> +!!end + +!!test +Templates: Ugly templates: 2. Navbox template parses badly leading to table misnesting +(Parsoid-centric) +!! options +parsoid +!!input +<table> + <tr> + <td> + <table> + <tr> + <td>1. {{echo|foo </table>}}</td> + <td> bar </td> + <td>2. {{echo|baz </table>}}</td> + </tr> + <tr> + <td>abc</td> + </tr> + </table> + </td> + </tr> + <tr> + <td>xyz</td> + </tr> +</table> +!!result +<table about="#mwt2" typeof="mw:Transclusion" data-mw='{"parts":["<table>\n <tr>\n <td>\n <table>\n <tr>\n <td>1. ",{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":"foo </table>"}},"i":0}},"</td>\n <td> bar </td>\n <td>2. ",{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":"baz </table>"}},"i":1}},"</td>\n </tr>\n <tr>\n <td>abc</td>\n </tr>\n </table>\n </td>\n </tr>\n <tr>\n <td>xyz</td>\n </tr>\n</table>"]}'> + <tbody><tr> + <td> + <table> + <tbody><tr> + <td>1. foo </td></tr></tbody></table></td> + <td> bar </td> + <td>2. baz </td></tr></tbody></table><span about="#mwt2"> + </span><span about="#mwt2"> + </span><span about="#mwt2"> + </span><span about="#mwt2">abc</span><span about="#mwt2"> + </span><span about="#mwt2"> + </span><span about="#mwt2"> + </span><span about="#mwt2"> + </span><span about="#mwt2"> + </span><span about="#mwt2"> + </span><span about="#mwt2">xyz</span><span about="#mwt2"> + </span><span about="#mwt2"> +</span> +!!end + +!! test +Templates: Ugly templates: 3. newline-only template parameter +!! input +foo {{echo| +}} +!! result +<p>foo +</p> +!! end + +# This looks like a bug: a single newline triggers p/br for some reason. +!! test +Templates: Ugly templates: 4. newline-only template parameter inconsistency +!! input +{{echo| +}} +!! result +<p><br /> +</p> +!! end + + +!!test +Parser Functions: 1. Simple example +!!input +{{uc:foo}} +!!result +<p>FOO +</p> +!!end + +!!test +Parser Functions: 2. Nested use (only outermost should be marked up) +!!input +{{uc:{{lc:FOO}}}} +!!result +<p>FOO +</p> +!!end + ### ### Pre-save transform tests ### @@ -4156,10 +8666,13 @@ wiki<nowiki>nowiki<!--nowiki</nowiki>wiki wiki<nowiki>nowiki<!--nowiki</nowiki>wiki !!end +# Leading @ in this template definition works around a limitation +# in parsoid's parserTests which otherwise strips the <span> from the +# result (confusing it for a template wrapper) !! article Template:dangerous !!text -<span onmouseover="alert('crap')">Oh no</span> +@<span onmouseover="alert('crap')">Oh no</span> !!endarticle !!test @@ -4167,7 +8680,7 @@ Template:dangerous !! input {{Template:dangerous}} !! result -<p><span>Oh no</span> +<p>@<span>Oh no</span> </p> !! end @@ -4556,6 +9069,62 @@ But not inside includeonly <includeonly>{{subst:Foo}}</includeonly> !! end +!! test +Parsoid: Recognize nowiki with trailing space in tags +!! options +parsoid=wt2html +!! input +<nowiki ><div>[[foo]]</nowiki > + +a<nowiki / >b + +c<nowiki />d + +e<nowiki/ >f +!! result +<p><span typeof="mw:Nowiki"><div>[[foo]]</span></p> +<p>ab</p> +<p>cd</p> +<p>ef</p> +!! end + +!! test +Parsoid: Recognize nowiki with odd capitalization +!! options +parsoid=wt2html +!! input +<noWikI ><div>[[foo]]</Nowiki > +!! result +<p><span typeof="mw:Nowiki"><div>[[foo]]</span></p> +!! end + + +!! test +Parsoid: Escape nowiki with trailing space in tags +!! options +parsoid=html2wt +!! input +<nowiki > foo </nowiki > + +a<nowiki />b + +c<nowiki/ >d +!! result +<p><nowiki > foo </nowiki ></p> +<p>a<nowiki />b</p> +<p>c<nowiki/ >d</p> +!! end + +!! test +Parsoid: Escape weird noWikI capitalizations +!! options +parsoid=html2wt +!! input +<noWikI > foo </NoWikI > +!! result +<p><noWikI > foo </NoWikI ></p> +!! end + ### ### Message transform tests ### @@ -4624,9 +9193,9 @@ Special:RecentChanges/param !! options msg !! input -{{#special:foobarnonexistent}} +{{#special:foobar nonexistent}} !! result -No such special page +Special:Foobar nonexistent !! end !! test @@ -4654,16 +9223,21 @@ Special:RecentChanges/param !! options msg !! input -{{#speciale:foobarnonexistent}} +{{#speciale:foobar nonexistent}} !! result -No_such_special_page +Special:Foobar_nonexistent !! end ### ### Images ### +### For Parsoid-specific tests, see +#### http://www.mediawiki.org/wiki/Parsoid/MediaWiki_DOM_spec#Images + !! test -Simple image +Simple image (php) +!! options +php !! input [[Image:foobar.jpg]] !! result @@ -4672,16 +9246,20 @@ Simple image !! end !! test -Right-aligned image +Simple image (parsoid) +!! options +parsoid=wt2html !! input -[[Image:foobar.jpg|right]] +[[Image:foobar.jpg]] !! result -<div class="floatright"><a href="/wiki/File:Foobar.jpg" class="image"><img alt="Foobar.jpg" src="http://example.com/images/3/3a/Foobar.jpg" width="1941" height="220" /></a></div> - +<p><span class="mw-default-size" typeof="mw:Image"><a href="File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" height="220" width="1941"></a></span> +</p> !! end !! test -Simple image (using File: namespace, now canonical) +Simple image (using File: namespace, now canonical) (php) +!! options +php !! input [[File:foobar.jpg]] !! result @@ -4690,32 +9268,245 @@ Simple image (using File: namespace, now canonical) !! end !! test -Image with caption +Simple image (using File: namespace, now canonical) (parsoid) +!! options +parsoid +!! input +[[File:Foobar.jpg]] +!! result +<p><span class="mw-default-size" typeof="mw:Image"><a href="File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" height="220" width="1941"></a></span> +</p> +!! end + +!! test +Right-aligned image (php) +!! options +php +!! input +[[Image:foobar.jpg|right]] +!! result +<div class="floatright"><a href="/wiki/File:Foobar.jpg" class="image"><img alt="Foobar.jpg" src="http://example.com/images/3/3a/Foobar.jpg" width="1941" height="220" /></a></div> + +!! end + +!! test +Right-aligned image (parsoid) +!! options +parsoid +!! input +[[File:Foobar.jpg|right]] +!! result +<figure class="mw-default-size mw-halign-right" typeof="mw:Image"><a href="File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" height="220" width="1941"></a></figure> +!! end + +!! test +Image with caption (php) +!! options +php +!! input +[[File:Foobar.jpg|right|Caption text]] +!! result +<div class="floatright"><a href="/wiki/File:Foobar.jpg" class="image" title="Caption text"><img alt="Caption text" src="http://example.com/images/3/3a/Foobar.jpg" width="1941" height="220" /></a></div> + +!! end + +!! test +Image with caption (parsoid) +!! options +parsoid +!! input +[[File:Foobar.jpg|right|Caption text]] +!! result +<figure class="mw-default-size mw-halign-right" typeof="mw:Image"><a href="File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" height="220" width="1941"></a><figcaption>Caption text</figcaption></figure> +!! end + +!! test +Image with empty attribute (php) +!! options +php +!! input +[[File:Foobar.jpg|right||Caption text]] +!! result +<div class="floatright"><a href="/wiki/File:Foobar.jpg" class="image" title="Caption text"><img alt="Caption text" src="http://example.com/images/3/3a/Foobar.jpg" width="1941" height="220" /></a></div> + +!! end + +!! test +Image with empty attribute (parsoid) +!! options +parsoid=wt2html +!! input +[[File:Foobar.jpg|right||Caption text]] +!! result +<figure class="mw-default-size mw-halign-right" typeof="mw:Image"><a href="File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" height="220" width="1941"></a><figcaption>Caption text</figcaption></figure> +!! end + +!! test +Image with attributes from template (php) +!! options +php !! input -[[Image:foobar.jpg|right|Caption text]] +[[File:Foobar.jpg|{{image_attribs}}]] !! result <div class="floatright"><a href="/wiki/File:Foobar.jpg" class="image" title="Caption text"><img alt="Caption text" src="http://example.com/images/3/3a/Foobar.jpg" width="1941" height="220" /></a></div> !! end !! test -Image with link parameter, wiki target +Image with attributes from template (parsoid) +!! options +parsoid !! input -[[Image:foobar.jpg|link=Target page]] +[[File:Foobar.jpg|{{image_attribs}}]] !! result -<p><a href="/wiki/Target_page" title="Target page"><img alt="Foobar.jpg" src="http://example.com/images/3/3a/Foobar.jpg" width="1941" height="220" /></a> +<figure class="mw-default-size mw-halign-right" typeof="mw:Image"><a href="File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" height="220" width="1941"></a><figcaption>Caption text</figcaption></figure> +!! end + +!! test +Image with link tails (php) +!! options +php +!! input +123[[File:Foobar.jpg]]456 +123[[File:Foobar.jpg|right]]456 +123[[File:Foobar.jpg|thumb]]456 +!! result +<p>123<a href="/wiki/File:Foobar.jpg" class="image"><img alt="Foobar.jpg" src="http://example.com/images/3/3a/Foobar.jpg" width="1941" height="220" /></a>456 </p> +123<div class="floatright"><a href="/wiki/File:Foobar.jpg" class="image"><img alt="Foobar.jpg" src="http://example.com/images/3/3a/Foobar.jpg" width="1941" height="220" /></a></div>456 +123<div class="thumb tright"><div class="thumbinner" style="width:182px;"><a href="/wiki/File:Foobar.jpg" class="image"><img alt="Foobar.jpg" src="http://example.com/images/thumb/3/3a/Foobar.jpg/180px-Foobar.jpg" width="180" height="20" class="thumbimage" srcset="http://example.com/images/thumb/3/3a/Foobar.jpg/270px-Foobar.jpg 1.5x, http://example.com/images/thumb/3/3a/Foobar.jpg/360px-Foobar.jpg 2x" /></a> <div class="thumbcaption"><div class="magnify"><a href="/wiki/File:Foobar.jpg" class="internal" title="Enlarge"><img src="/skins/common/images/magnify-clip.png" width="15" height="11" alt="" /></a></div></div></div></div>456 + !! end !! test -Image with link parameter, URL target +Image with link tails (parsoid) +!! options +parsoid !! input -[[Image:foobar.jpg|link=http://example.com/]] +123[[File:Foobar.jpg]]456 +123[[File:Foobar.jpg|right]]456 +123[[File:Foobar.jpg|thumb]]456 +!! result +<p>123<span class="mw-default-size" typeof="mw:Image"><a href="File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" height="220" width="1941"></a></span>456</p> +123<figure class="mw-default-size mw-halign-right" typeof="mw:Image"><a href="File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" height="220" width="1941"></a></figure>456 +123<figure class="mw-default-size" typeof="mw:Image/Thumb"><a href="File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/180px-Foobar.jpg" height="20" width="180"></a></figure>456 +!! end + +!! test +Image with multiple captions -- only last one is accepted (php) +!! options +php +!! input +[[File:Foobar.jpg|right|Caption1 - ignored|[[Caption2]] - ignored|Caption3 - accepted]] +!! result +<div class="floatright"><a href="/wiki/File:Foobar.jpg" class="image" title="Caption3 - accepted"><img alt="Caption3 - accepted" src="http://example.com/images/3/3a/Foobar.jpg" width="1941" height="220" /></a></div> + +!! end + +!! test +Image with multiple captions -- only last one is accepted (parsoid) +!! options +parsoid +!! input +[[File:Foobar.jpg|right|Caption1 - ignored|[[Caption2]] - ignored|Caption3 - accepted]] +!! result +<figure class="mw-default-size mw-halign-right" typeof="mw:Image"><a href="File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" height="220" width="1941"></a><figcaption>Caption3 - accepted</figcaption></figure> +!! end + +!! test +Image with width attribute at different positions (php) +!! options +php +!! input +[[File:Foobar.jpg|200px|right|Caption]] +[[File:Foobar.jpg|right|200px|Caption]] +[[File:Foobar.jpg|right|Caption|200px]] +!! result +<div class="floatright"><a href="/wiki/File:Foobar.jpg" class="image" title="Caption"><img alt="Caption" src="http://example.com/images/thumb/3/3a/Foobar.jpg/200px-Foobar.jpg" width="200" height="23" srcset="http://example.com/images/thumb/3/3a/Foobar.jpg/300px-Foobar.jpg 1.5x, http://example.com/images/thumb/3/3a/Foobar.jpg/400px-Foobar.jpg 2x" /></a></div> +<div class="floatright"><a href="/wiki/File:Foobar.jpg" class="image" title="Caption"><img alt="Caption" src="http://example.com/images/thumb/3/3a/Foobar.jpg/200px-Foobar.jpg" width="200" height="23" srcset="http://example.com/images/thumb/3/3a/Foobar.jpg/300px-Foobar.jpg 1.5x, http://example.com/images/thumb/3/3a/Foobar.jpg/400px-Foobar.jpg 2x" /></a></div> +<div class="floatright"><a href="/wiki/File:Foobar.jpg" class="image" title="Caption"><img alt="Caption" src="http://example.com/images/thumb/3/3a/Foobar.jpg/200px-Foobar.jpg" width="200" height="23" srcset="http://example.com/images/thumb/3/3a/Foobar.jpg/300px-Foobar.jpg 1.5x, http://example.com/images/thumb/3/3a/Foobar.jpg/400px-Foobar.jpg 2x" /></a></div> + +!! end + +!! test +Image with width attribute at different positions (parsoid) +!! options +parsoid +!! input +[[File:Foobar.jpg|200px|right|Caption]] +[[File:Foobar.jpg|right|200px|Caption]] +[[File:Foobar.jpg|right|Caption|200px]] +!! result +<figure class="mw-halign-right" typeof="mw:Image"><a href="File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/200px-Foobar.jpg" height="23" width="200"></a><figcaption>Caption</figcaption></figure> +<figure class="mw-halign-right" typeof="mw:Image"><a href="File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/200px-Foobar.jpg" height="23" width="200"></a><figcaption>Caption</figcaption></figure> +<figure class="mw-halign-right" typeof="mw:Image"><a href="File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/200px-Foobar.jpg" height="23" width="200"></a><figcaption>Caption</figcaption></figure> +!! end + +!! test +Image with link parameter, wiki target (php) +!! options +php +!! input +[[File:Foobar.jpg|link=Main Page]] +!! result +<p><a href="/wiki/Main_Page" title="Main Page"><img alt="Foobar.jpg" src="http://example.com/images/3/3a/Foobar.jpg" width="1941" height="220" /></a> +</p> +!! end + +!! test +Image with link parameter, wiki target (parsoid) +!! options +parsoid +!! input +[[File:Foobar.jpg|link=Main Page]] +!! result +<p><span class="mw-default-size" typeof="mw:Image"><a href="Main_Page"><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" height="220" width="1941"></a></span></p> +!! end + +!! test +Image with link parameter, URL target (php) +!! options +php +!! input +[[File:Foobar.jpg|link=http://example.com/]] !! result <p><a href="http://example.com/" rel="nofollow"><img alt="Foobar.jpg" src="http://example.com/images/3/3a/Foobar.jpg" width="1941" height="220" /></a> </p> !! end +# parsoid bug 49293 (part 1) +!! test +Image with link parameter, URL target (parsoid) +!! options +parsoid +!! input +[[File:Foobar.jpg|link=http://example.com/]] +!! result +<p><span class="mw-default-size" typeof="mw:Image"><a href="http://example.com/"><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" height="220" width="1941"></a></span></p> +!! end + +!! test +Image with link parameter, protocol-less URL target (php) +!! options +php +!! input +[[File:Foobar.jpg|link=//example.com/]] +!! result +<p><a href="//example.com/" rel="nofollow"><img alt="Foobar.jpg" src="http://example.com/images/3/3a/Foobar.jpg" width="1941" height="220" /></a> +</p> +!! end + +# parsoid bug 49293 (part 2) +!! test +Image with link parameter, protocol-less URL target (parsoid) +!! options +parsoid +!! input +[[File:Foobar.jpg|link=//example.com/]] +!! result +<p><span class="mw-default-size" typeof="mw:Image"><a href="//example.com/"><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" height="220" width="1941"></a></span></p> +!! end + !! test Image with link parameter, wgExternalLinkTarget !! input @@ -4761,38 +9552,131 @@ wgExternalLinkTarget='foobar' !! end !! test -Image with empty link parameter +Image with empty link parameter (php) +!! options +php !! input -[[Image:foobar.jpg|link=]] +[[File:Foobar.jpg|link=]] !! result <p><img alt="Foobar.jpg" src="http://example.com/images/3/3a/Foobar.jpg" width="1941" height="220" /> </p> !! end !! test -Image with link parameter (wiki target) and unnamed parameter +Image with empty link parameter (parsoid) +!! options +parsoid +!! input +[[File:Foobar.jpg|link=]] +!! result +<p><span class="mw-default-size" typeof="mw:Image"><span><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" height="220" width="1941"></span></span></p> +!! end + +!! test +Image with link parameter (wiki target) and unnamed parameter (php) +!! options +php !! input -[[Image:foobar.jpg|link=Target page|Title]] +[[File:Foobar.jpg|link=Main Page|Title]] !! result -<p><a href="/wiki/Target_page" title="Title"><img alt="Title" src="http://example.com/images/3/3a/Foobar.jpg" width="1941" height="220" /></a> +<p><a href="/wiki/Main_Page" title="Title"><img alt="Title" src="http://example.com/images/3/3a/Foobar.jpg" width="1941" height="220" /></a> </p> !! end !! test -Image with link parameter (URL target) and unnamed parameter +Image with link parameter (wiki target) and unnamed parameter (parsoid) +!! options +parsoid !! input -[[Image:foobar.jpg|link=http://example.com/|Title]] +[[File:Foobar.jpg|link=Main Page|Title]] +!! result +<p><span class="mw-default-size" typeof="mw:Image" data-mw='{"caption":"Title"}'><a href="Main_Page"><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" height="220" width="1941"></a></span></p> +!! end + +!! test +Image with link parameter (URL target) and unnamed parameter (php) +!! options +php +!! input +[[File:Foobar.jpg|link=http://example.com/|Title]] !! result <p><a href="http://example.com/" title="Title" rel="nofollow"><img alt="Title" src="http://example.com/images/3/3a/Foobar.jpg" width="1941" height="220" /></a> </p> !! end !! test +Image with link parameter (URL target) and unnamed parameter (parsoid) +!! options +parsoid +!! input +[[File:Foobar.jpg|link=http://example.com/|Title]] +!! result +<p><span class="mw-default-size" typeof="mw:Image" data-mw='{"caption":"Title"}'><a href="http://example.com/"><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" height="220" width="1941"></a></span></p> +!! end + +!! test Thumbnail image with link parameter +!! options +php !! input [[Image:foobar.jpg|thumb|link=http://example.com/|Title]] !! result -<div class="thumb tright"><div class="thumbinner" style="width:182px;"><a href="http://example.com/"><img alt="" src="http://example.com/images/3/3a/Foobar.jpg" width="180" height="20" class="thumbimage" /></a> <div class="thumbcaption"><div class="magnify"><a href="/wiki/File:Foobar.jpg" class="internal" title="Enlarge"><img src="/skins/common/images/magnify-clip.png" width="15" height="11" alt="" /></a></div>Title</div></div></div> +<div class="thumb tright"><div class="thumbinner" style="width:182px;"><a href="http://example.com/"><img alt="" src="http://example.com/images/thumb/3/3a/Foobar.jpg/180px-Foobar.jpg" width="180" height="20" class="thumbimage" srcset="http://example.com/images/thumb/3/3a/Foobar.jpg/270px-Foobar.jpg 1.5x, http://example.com/images/thumb/3/3a/Foobar.jpg/360px-Foobar.jpg 2x" /></a> <div class="thumbcaption"><div class="magnify"><a href="/wiki/File:Foobar.jpg" class="internal" title="Enlarge"><img src="/skins/common/images/magnify-clip.png" width="15" height="11" alt="" /></a></div>Title</div></div></div> + +!! end + +!! test +Manually-specified thumbnail image +!! options +php +!! input +[[Image:Foobar.jpg|thumb=Thumb.png|Title]] +!! result +<div class="thumb tright"><div class="thumbinner" style="width:137px;"><a href="/wiki/File:Foobar.jpg"><img alt="" src="http://example.com/images/e/ea/Thumb.png" width="135" height="135" class="thumbimage" /></a> <div class="thumbcaption"><div class="magnify"><a href="/wiki/File:Foobar.jpg" class="internal" title="Enlarge"><img src="/skins/common/images/magnify-clip.png" width="15" height="11" alt="" /></a></div>Title</div></div></div> + +!! end + +!! test +Manually-specified thumbnail image with explicit link to wiki page +!! options +php +!! input +[[Image:Foobar.jpg|thumb=Thumb.png|link=Main Page|Title]] +!! result +<div class="thumb tright"><div class="thumbinner" style="width:137px;"><a href="/wiki/Main_Page" title="Main Page"><img alt="" src="http://example.com/images/e/ea/Thumb.png" width="135" height="135" class="thumbimage" /></a> <div class="thumbcaption"><div class="magnify"><a href="/wiki/File:Foobar.jpg" class="internal" title="Enlarge"><img src="/skins/common/images/magnify-clip.png" width="15" height="11" alt="" /></a></div>Title</div></div></div> + +!! end + +!! test +Manually-specified thumbnail image with explicit link to url +!! options +php +!! input +[[Image:Foobar.jpg|thumb=Thumb.png|link=http://example.com|Title]] +!! result +<div class="thumb tright"><div class="thumbinner" style="width:137px;"><a href="http://example.com"><img alt="" src="http://example.com/images/e/ea/Thumb.png" width="135" height="135" class="thumbimage" /></a> <div class="thumbcaption"><div class="magnify"><a href="/wiki/File:Foobar.jpg" class="internal" title="Enlarge"><img src="/skins/common/images/magnify-clip.png" width="15" height="11" alt="" /></a></div>Title</div></div></div> + +!! end + +!! test +Manually-specified thumbnail image with explicit no link +!! options +php +!! input +[[Image:Foobar.jpg|thumb=Thumb.png|link=|Title]] +!! result +<div class="thumb tright"><div class="thumbinner" style="width:137px;"><img alt="" src="http://example.com/images/e/ea/Thumb.png" width="135" height="135" class="thumbimage" /> <div class="thumbcaption"><div class="magnify"><a href="/wiki/File:Foobar.jpg" class="internal" title="Enlarge"><img src="/skins/common/images/magnify-clip.png" width="15" height="11" alt="" /></a></div>Title</div></div></div> + +!! end + +!! test +Manually-specified thumbnail image with explicit link and alt text +!! options +php +!! input +[[Image:Foobar.jpg|thumb=Thumb.png|link=Main Page|alt=alttext|Title]] +!! result +<div class="thumb tright"><div class="thumbinner" style="width:137px;"><a href="/wiki/Main_Page" title="Main Page"><img alt="alttext" src="http://example.com/images/e/ea/Thumb.png" width="135" height="135" class="thumbimage" /></a> <div class="thumbcaption"><div class="magnify"><a href="/wiki/File:Foobar.jpg" class="internal" title="Enlarge"><img src="/skins/common/images/magnify-clip.png" width="15" height="11" alt="" /></a></div>Title</div></div></div> !! end @@ -4866,7 +9750,7 @@ Thumbnail image caption with a free URL !! input [[Image:foobar.jpg|thumb|http://example.com]] !! result -<div class="thumb tright"><div class="thumbinner" style="width:182px;"><a href="/wiki/File:Foobar.jpg" class="image"><img alt="" src="http://example.com/images/3/3a/Foobar.jpg" width="180" height="20" class="thumbimage" /></a> <div class="thumbcaption"><div class="magnify"><a href="/wiki/File:Foobar.jpg" class="internal" title="Enlarge"><img src="/skins/common/images/magnify-clip.png" width="15" height="11" alt="" /></a></div><a rel="nofollow" class="external free" href="http://example.com">http://example.com</a></div></div></div> +<div class="thumb tright"><div class="thumbinner" style="width:182px;"><a href="/wiki/File:Foobar.jpg" class="image"><img alt="" src="http://example.com/images/thumb/3/3a/Foobar.jpg/180px-Foobar.jpg" width="180" height="20" class="thumbimage" srcset="http://example.com/images/thumb/3/3a/Foobar.jpg/270px-Foobar.jpg 1.5x, http://example.com/images/thumb/3/3a/Foobar.jpg/360px-Foobar.jpg 2x" /></a> <div class="thumbcaption"><div class="magnify"><a href="/wiki/File:Foobar.jpg" class="internal" title="Enlarge"><img src="/skins/common/images/magnify-clip.png" width="15" height="11" alt="" /></a></div><a rel="nofollow" class="external free" href="http://example.com">http://example.com</a></div></div></div> !! end @@ -4875,7 +9759,37 @@ Thumbnail image caption with a free URL and explicit alt !! input [[Image:foobar.jpg|thumb|http://example.com|alt=Alteration]] !! result -<div class="thumb tright"><div class="thumbinner" style="width:182px;"><a href="/wiki/File:Foobar.jpg" class="image"><img alt="Alteration" src="http://example.com/images/3/3a/Foobar.jpg" width="180" height="20" class="thumbimage" /></a> <div class="thumbcaption"><div class="magnify"><a href="/wiki/File:Foobar.jpg" class="internal" title="Enlarge"><img src="/skins/common/images/magnify-clip.png" width="15" height="11" alt="" /></a></div><a rel="nofollow" class="external free" href="http://example.com">http://example.com</a></div></div></div> +<div class="thumb tright"><div class="thumbinner" style="width:182px;"><a href="/wiki/File:Foobar.jpg" class="image"><img alt="Alteration" src="http://example.com/images/thumb/3/3a/Foobar.jpg/180px-Foobar.jpg" width="180" height="20" class="thumbimage" srcset="http://example.com/images/thumb/3/3a/Foobar.jpg/270px-Foobar.jpg 1.5x, http://example.com/images/thumb/3/3a/Foobar.jpg/360px-Foobar.jpg 2x" /></a> <div class="thumbcaption"><div class="magnify"><a href="/wiki/File:Foobar.jpg" class="internal" title="Enlarge"><img src="/skins/common/images/magnify-clip.png" width="15" height="11" alt="" /></a></div><a rel="nofollow" class="external free" href="http://example.com">http://example.com</a></div></div></div> + +!! end + +!! test +SVG thumbnails with no language set +!! options +!! input +[[File:Foobar.svg|thumb|width=200]] +!! result +<div class="thumb tright"><div class="thumbinner" style="width:182px;"><a href="/wiki/File:Foobar.svg" class="image"><img alt="" src="http://example.com/images/thumb/f/ff/Foobar.svg/180px-Foobar.svg.png" width="180" height="180" class="thumbimage" srcset="http://example.com/images/thumb/f/ff/Foobar.svg/270px-Foobar.svg.png 1.5x, http://example.com/images/thumb/f/ff/Foobar.svg/360px-Foobar.svg.png 2x" /></a> <div class="thumbcaption"><div class="magnify"><a href="/wiki/File:Foobar.svg" class="internal" title="Enlarge"><img src="/skins/common/images/magnify-clip.png" width="15" height="11" alt="" /></a></div>width=200</div></div></div> + +!! end + +!! test +SVG thumbnails with language de +!! options +!! input +[[File:Foobar.svg|thumb|width=200|lang=de]] +!! result +<div class="thumb tright"><div class="thumbinner" style="width:182px;"><a href="/index.php?title=File:Foobar.svg&lang=de" class="image"><img alt="" src="http://example.com/images/thumb/f/ff/Foobar.svg/langde-180px-Foobar.svg.png" width="180" height="180" class="thumbimage" srcset="http://example.com/images/thumb/f/ff/Foobar.svg/langde-270px-Foobar.svg.png 1.5x, http://example.com/images/thumb/f/ff/Foobar.svg/langde-360px-Foobar.svg.png 2x" /></a> <div class="thumbcaption"><div class="magnify"><a href="/wiki/File:Foobar.svg" class="internal" title="Enlarge"><img src="/skins/common/images/magnify-clip.png" width="15" height="11" alt="" /></a></div>width=200</div></div></div> + +!! end + +!! test +SVG thumbnails with invalid language code +!! options +!! input +[[File:Foobar.svg|thumb|width=200|lang=invalid.language.code]] +!! result +<div class="thumb tright"><div class="thumbinner" style="width:182px;"><a href="/wiki/File:Foobar.svg" class="image"><img alt="" src="http://example.com/images/thumb/f/ff/Foobar.svg/180px-Foobar.svg.png" width="180" height="180" class="thumbimage" srcset="http://example.com/images/thumb/f/ff/Foobar.svg/270px-Foobar.svg.png 1.5x, http://example.com/images/thumb/f/ff/Foobar.svg/360px-Foobar.svg.png 2x" /></a> <div class="thumbcaption"><div class="magnify"><a href="/wiki/File:Foobar.svg" class="internal" title="Enlarge"><img src="/skins/common/images/magnify-clip.png" width="15" height="11" alt="" /></a></div>lang=invalid.language.code</div></div></div> !! end @@ -4884,7 +9798,7 @@ BUG 1887: A ISBN with a thumbnail !! input [[Image:foobar.jpg|thumb|ISBN 1235467890]] !! result -<div class="thumb tright"><div class="thumbinner" style="width:182px;"><a href="/wiki/File:Foobar.jpg" class="image"><img alt="" src="http://example.com/images/3/3a/Foobar.jpg" width="180" height="20" class="thumbimage" /></a> <div class="thumbcaption"><div class="magnify"><a href="/wiki/File:Foobar.jpg" class="internal" title="Enlarge"><img src="/skins/common/images/magnify-clip.png" width="15" height="11" alt="" /></a></div><a href="/wiki/Special:BookSources/1235467890" class="internal mw-magiclink-isbn">ISBN 1235467890</a></div></div></div> +<div class="thumb tright"><div class="thumbinner" style="width:182px;"><a href="/wiki/File:Foobar.jpg" class="image"><img alt="" src="http://example.com/images/thumb/3/3a/Foobar.jpg/180px-Foobar.jpg" width="180" height="20" class="thumbimage" srcset="http://example.com/images/thumb/3/3a/Foobar.jpg/270px-Foobar.jpg 1.5x, http://example.com/images/thumb/3/3a/Foobar.jpg/360px-Foobar.jpg 2x" /></a> <div class="thumbcaption"><div class="magnify"><a href="/wiki/File:Foobar.jpg" class="internal" title="Enlarge"><img src="/skins/common/images/magnify-clip.png" width="15" height="11" alt="" /></a></div><a href="/wiki/Special:BookSources/1235467890" class="internal mw-magiclink-isbn">ISBN 1235467890</a></div></div></div> !! end @@ -4893,7 +9807,7 @@ BUG 1887: A RFC with a thumbnail !! input [[Image:foobar.jpg|thumb|This is RFC 12354]] !! result -<div class="thumb tright"><div class="thumbinner" style="width:182px;"><a href="/wiki/File:Foobar.jpg" class="image"><img alt="" src="http://example.com/images/3/3a/Foobar.jpg" width="180" height="20" class="thumbimage" /></a> <div class="thumbcaption"><div class="magnify"><a href="/wiki/File:Foobar.jpg" class="internal" title="Enlarge"><img src="/skins/common/images/magnify-clip.png" width="15" height="11" alt="" /></a></div>This is <a class="external mw-magiclink-rfc" href="//tools.ietf.org/html/rfc12354">RFC 12354</a></div></div></div> +<div class="thumb tright"><div class="thumbinner" style="width:182px;"><a href="/wiki/File:Foobar.jpg" class="image"><img alt="" src="http://example.com/images/thumb/3/3a/Foobar.jpg/180px-Foobar.jpg" width="180" height="20" class="thumbimage" srcset="http://example.com/images/thumb/3/3a/Foobar.jpg/270px-Foobar.jpg 1.5x, http://example.com/images/thumb/3/3a/Foobar.jpg/360px-Foobar.jpg 2x" /></a> <div class="thumbcaption"><div class="magnify"><a href="/wiki/File:Foobar.jpg" class="internal" title="Enlarge"><img src="/skins/common/images/magnify-clip.png" width="15" height="11" alt="" /></a></div>This is <a class="external mw-magiclink-rfc" rel="nofollow" href="//tools.ietf.org/html/rfc12354">RFC 12354</a></div></div></div> !! end @@ -4902,7 +9816,7 @@ BUG 1887: A mailto link with a thumbnail !! input [[Image:foobar.jpg|thumb|Please mailto:nobody@example.com]] !! result -<div class="thumb tright"><div class="thumbinner" style="width:182px;"><a href="/wiki/File:Foobar.jpg" class="image"><img alt="" src="http://example.com/images/3/3a/Foobar.jpg" width="180" height="20" class="thumbimage" /></a> <div class="thumbcaption"><div class="magnify"><a href="/wiki/File:Foobar.jpg" class="internal" title="Enlarge"><img src="/skins/common/images/magnify-clip.png" width="15" height="11" alt="" /></a></div>Please <a rel="nofollow" class="external free" href="mailto:nobody@example.com">mailto:nobody@example.com</a></div></div></div> +<div class="thumb tright"><div class="thumbinner" style="width:182px;"><a href="/wiki/File:Foobar.jpg" class="image"><img alt="" src="http://example.com/images/thumb/3/3a/Foobar.jpg/180px-Foobar.jpg" width="180" height="20" class="thumbimage" srcset="http://example.com/images/thumb/3/3a/Foobar.jpg/270px-Foobar.jpg 1.5x, http://example.com/images/thumb/3/3a/Foobar.jpg/360px-Foobar.jpg 2x" /></a> <div class="thumbcaption"><div class="magnify"><a href="/wiki/File:Foobar.jpg" class="internal" title="Enlarge"><img src="/skins/common/images/magnify-clip.png" width="15" height="11" alt="" /></a></div>Please <a rel="nofollow" class="external free" href="mailto:nobody@example.com">mailto:nobody@example.com</a></div></div></div> !! end @@ -4975,7 +9889,7 @@ Image caption containing another image !! input [[Image:Foobar.jpg|thumb|This is a caption with another [[Image:icon.png|image]] inside it!]] !! result -<div class="thumb tright"><div class="thumbinner" style="width:182px;"><a href="/wiki/File:Foobar.jpg" class="image"><img alt="" src="http://example.com/images/3/3a/Foobar.jpg" width="180" height="20" class="thumbimage" /></a> <div class="thumbcaption"><div class="magnify"><a href="/wiki/File:Foobar.jpg" class="internal" title="Enlarge"><img src="/skins/common/images/magnify-clip.png" width="15" height="11" alt="" /></a></div>This is a caption with another <a href="/index.php?title=Special:Upload&wpDestFile=Icon.png" class="new" title="File:Icon.png">image</a> inside it!</div></div></div> +<div class="thumb tright"><div class="thumbinner" style="width:182px;"><a href="/wiki/File:Foobar.jpg" class="image"><img alt="" src="http://example.com/images/thumb/3/3a/Foobar.jpg/180px-Foobar.jpg" width="180" height="20" class="thumbimage" srcset="http://example.com/images/thumb/3/3a/Foobar.jpg/270px-Foobar.jpg 1.5x, http://example.com/images/thumb/3/3a/Foobar.jpg/360px-Foobar.jpg 2x" /></a> <div class="thumbcaption"><div class="magnify"><a href="/wiki/File:Foobar.jpg" class="internal" title="Enlarge"><img src="/skins/common/images/magnify-clip.png" width="15" height="11" alt="" /></a></div>This is a caption with another <a href="/index.php?title=Special:Upload&wpDestFile=Icon.png" class="new" title="File:Icon.png">image</a> inside it!</div></div></div> !! end @@ -4989,13 +9903,22 @@ Image caption containing a newline </p> !!end +!!test +Parsoid: Image caption containing leading space +(The leading space should not trigger nowiki escaping in wt2wt mode) +!! input +[[Image:Foobar.jpg|thumb| bar]] +!! result +<div class="thumb tright"><div class="thumbinner" style="width:182px;"><a href="/wiki/File:Foobar.jpg" class="image"><img alt="" src="http://example.com/images/thumb/3/3a/Foobar.jpg/180px-Foobar.jpg" width="180" height="20" class="thumbimage" srcset="http://example.com/images/thumb/3/3a/Foobar.jpg/270px-Foobar.jpg 1.5x, http://example.com/images/thumb/3/3a/Foobar.jpg/360px-Foobar.jpg 2x" /></a> <div class="thumbcaption"><div class="magnify"><a href="/wiki/File:Foobar.jpg" class="internal" title="Enlarge"><img src="/skins/common/images/magnify-clip.png" width="15" height="11" alt="" /></a></div>bar</div></div></div> + +!!end !! test Bug 3090: External links other than http: in image captions !! input [[Image:Foobar.jpg|thumb|200px|This caption has [irc://example.net irc] and [https://example.com Secure] ext links in it.]] !! result -<div class="thumb tright"><div class="thumbinner" style="width:202px;"><a href="/wiki/File:Foobar.jpg" class="image"><img alt="" src="http://example.com/images/3/3a/Foobar.jpg" width="200" height="23" class="thumbimage" /></a> <div class="thumbcaption"><div class="magnify"><a href="/wiki/File:Foobar.jpg" class="internal" title="Enlarge"><img src="/skins/common/images/magnify-clip.png" width="15" height="11" alt="" /></a></div>This caption has <a rel="nofollow" class="external text" href="irc://example.net">irc</a> and <a rel="nofollow" class="external text" href="https://example.com">Secure</a> ext links in it.</div></div></div> +<div class="thumb tright"><div class="thumbinner" style="width:202px;"><a href="/wiki/File:Foobar.jpg" class="image"><img alt="" src="http://example.com/images/thumb/3/3a/Foobar.jpg/200px-Foobar.jpg" width="200" height="23" class="thumbimage" srcset="http://example.com/images/thumb/3/3a/Foobar.jpg/300px-Foobar.jpg 1.5x, http://example.com/images/thumb/3/3a/Foobar.jpg/400px-Foobar.jpg 2x" /></a> <div class="thumbcaption"><div class="magnify"><a href="/wiki/File:Foobar.jpg" class="internal" title="Enlarge"><img src="/skins/common/images/magnify-clip.png" width="15" height="11" alt="" /></a></div>This caption has <a rel="nofollow" class="external text" href="irc://example.net">irc</a> and <a rel="nofollow" class="external text" href="https://example.com">Secure</a> ext links in it.</div></div></div> !! end @@ -5008,7 +9931,38 @@ Custom class </p> !! end -!! article +!! test +Localized image handling (1). +!! options +language=es +!! input +[[Archivo:Foobar.jpg|izquierda|enlace=foo|caption]] +!! result +<div class="floatleft"><a href="/wiki/Foo" title="caption"><img alt="caption" src="http://example.com/images/3/3a/Foobar.jpg" width="1941" height="220" /></a></div> + +!! end + +!! test +Localized image handling (2). +!! options +language=es +!! input +[[Archivo:Foobar.jpg|miniatura|izquierda|enlace=foo|caption]] +!! result +<div class="thumb tleft"><div class="thumbinner" style="width:182px;"><a href="/wiki/Foo" title="Foo"><img alt="" src="http://example.com/images/thumb/3/3a/Foobar.jpg/180px-Foobar.jpg" width="180" height="20" class="thumbimage" srcset="http://example.com/images/thumb/3/3a/Foobar.jpg/270px-Foobar.jpg 1.5x, http://example.com/images/thumb/3/3a/Foobar.jpg/360px-Foobar.jpg 2x" /></a> <div class="thumbcaption"><div class="magnify"><a href="/wiki/Archivo:Foobar.jpg" class="internal" title="Aumentar"><img src="/skins/common/images/magnify-clip.png" width="15" height="11" alt="" /></a></div>caption</div></div></div> + +!! end + +!! test +"border", "frameless" and "class" attributes on an image. +!! input +[[File:Foobar.jpg|frameless|border|class=extra|caption]] +!! result +<p><a href="/wiki/File:Foobar.jpg" class="image" title="caption"><img alt="caption" src="http://example.com/images/thumb/3/3a/Foobar.jpg/180px-Foobar.jpg" width="180" height="20" class="extra thumbborder" srcset="http://example.com/images/thumb/3/3a/Foobar.jpg/270px-Foobar.jpg 1.5x, http://example.com/images/thumb/3/3a/Foobar.jpg/360px-Foobar.jpg 2x" /></a> +</p> +!! end + +!! article File:Barfoo.jpg !! text #REDIRECT [[File:Barfoo.jpg]] @@ -5034,6 +9988,152 @@ wgEnableUploads=0 </p> !! end +# Parsoid-specific testing for images +# http://www.mediawiki.org/wiki/Parsoid/MediaWiki_DOM_spec#Images +# Currently imperfect due to a flaw in the Parsoid testrunner +# Work in progress +# THESE TESTS SHOULD BE MOVED UP and merged with the php-specific +# image tests. + +!! test +Parsoid-specific image handling - simple image with size and middle alignment +!! options +parsoid +!! input +[[Image:Foobar.jpg|50px|middle]] +!! result +<p> +<span class="mw-valign-middle" typeof="mw:Image"> +<a href="File:Foobar.jpg"> +<img resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/50px-Foobar.jpg" height="6" width="50"> +</a> +</span> +</p> +!! end + +!! test +Parsoid-specific image handling - simple image with both sizes, a baseline alignment, and a caption +!! options +parsoid +!! input +[[Image:Foobar.jpg|500x10px|baseline|caption]] +!! result +<p> +<span class="mw-valign-baseline" typeof="mw:Image" data-mw="{"caption":"caption"}"> +<a href="File:Foobar.jpg"> +<img resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/89px-Foobar.jpg" height="10" width="89"> +</a> +</span> +</p> +!! end + +!! test +Parsoid-specific image handling - simple image with border and size spec +!! options +parsoid +!! input +[[Image:Foobar.jpg|50px|border|caption]] +!! result +<p> +<span class="mw-image-border" typeof="mw:Image" data-mw="{"caption":"caption"}"> +<a href="File:Foobar.jpg"> +<img resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/50px-Foobar.jpg" height="6" width="50"> +</a> +</span> +</p> +!! end + +!! test +Parsoid-specific image handling - thumbnail with halign, valign, and caption +!! options +parsoid +!! input +[[Image:Foobar.jpg|thumb|left|baseline|caption content]] +!! result +<figure class="mw-default-size mw-halign-left mw-valign-baseline" typeof="mw:Image/Thumb"> +<a href="File:Foobar.jpg"> +<img resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/180px-Foobar.jpg" height="21" width="180" /> +</a> +<figcaption>caption content</figcaption> +</figure> +!! end + +!! test +Parsoid-specific image handling - thumbnail with specific size, halign, valign, and caption +!! options +parsoid +!! input +[[Image:Foobar.jpg|thumb|50x50px|right|middle|caption]] +!! result +<figure class="mw-halign-right mw-valign-middle" typeof="mw:Image/Thumb"> +<a href="File:Foobar.jpg"> +<img resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/50px-Foobar.jpg" height="6" width="50" /> +</a> +<figcaption>caption</figcaption> +</figure> +!! end + +!! test +Parsoid-specific image handling - framed image with specific size and caption +!! options +parsoid +!! input +[[Image:Foobar.jpg|500x50px|frame|caption]] +!! result +<figure typeof="mw:Image/Frame"> +<a href="File:Foobar.jpg"> +<img resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/442px-Foobar.jpg" height="50" width="442" /> +</a> +<figcaption>caption</figcaption> +</figure> +!! end + +!! test +Parsoid-specific image handling - framed image with specific size, halign, valign, and caption +!! options +parsoid +!! input +[[Image:Foobar.jpg|500x50px|frame|left|baseline|caption]] +!! result +<figure class="mw-halign-left mw-valign-baseline" typeof="mw:Image/Frame"> +<a href="File:Foobar.jpg"> +<img resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/442px-Foobar.jpg" height="50" width="442" /> +</a> +<figcaption>caption</figcaption> +</figure> +!! end + +!! test +Parsoid-specific image handling - frameless image with specific size, border, and caption +!! options +parsoid +!! input +[[Image:Foobar.jpg|frameless|500x50px|border|caption]] +!! result +<p> +<span class="mw-image-border" typeof="mw:Image/Frameless" data-mw="{"caption":"caption"}"> +<a href="File:Foobar.jpg"> +<img resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/442px-Foobar.jpg" height="50" width="442" /> +</a> +</p> +!! end + +#!! test +#Parsoid-specific image handling - simple image with a formatted caption +#!! options +#parsoid +#!! input +#[[Image:Foobar.jpg|<table><tr><td>a</td><td>b</td></tr><tr><td>c</td></tr></table>]] +#!! result +#<p> +#<span typeof="mw:Image"> +#<a class="mw-default-size" href="Image:Foobar.jpg"> +#<img alt="Foobar.jpg" class="mw-default-size" src="http://example.com/images/3/3a/Foobar.jpg" height="220" width="1941"> +#</a> +#<span>abc</span> +#</span> +#</p> + ### ### Subpages @@ -5066,6 +10166,38 @@ subpage title=[[Subpage test]] </p> !! end +# TODO: make this PHP-parser compatible! +!! test +Relative subpage noslash link +!! options +parsoid=wt2wt,wt2html,html2html +subpage title=[[Subpage test/1/2/3/4]] +!!input +[[../../subpage/]] + +[[../../subpage]] +!! result +<p><a rel="mw:WikiLink" href="Subpage_test/1/2/subpage/">subpage</a></p> +<p><a rel="mw:WikiLink" href="Subpage_test/1/2/subpage">Subpage_test/1/2/subpage</a></p> +!! end + +# TODO: make this PHP-parser compatible! +!! test +Parsoid: dot-slash prefixed wikilinks +!! options +parsoid=wt2wt,wt2html,html2html +!!input +[[./foo]] + +[[././bar]] + +[[././baz/]] +!! result +<p><a rel="mw:WikiLink" href="./Foo">foo</a></p> +<p><a rel="mw:WikiLink" href="./Bar">bar</a></p> +<p><a rel="mw:WikiLink" href="./Baz/">baz/</a></p> +!! end + !! test Disabled subpages !! input @@ -5123,6 +10255,185 @@ PAGESINCATEGORY invalid title fatal (r33546 fix) </p> !! end +!! test +Category with different sort key +!! options +cat +!! input +[[Category:MediaWiki User's Guide|Foo]] +!! result +<a href="/wiki/Category:MediaWiki_User%27s_Guide" title="Category:MediaWiki User's Guide">MediaWiki User's Guide</a> +!! end + +!! test +Category with identical sort key +!! options +cat +!! input +[[Category:MediaWiki User's Guide|MediaWiki User's Guide]] +!! result +<a href="/wiki/Category:MediaWiki_User%27s_Guide" title="Category:MediaWiki User's Guide">MediaWiki User's Guide</a> +!! end + +!! test +Category with empty sort key +!! options +cat +pst +!! input +[[Category:MediaWiki User's Guide|]] +!! result +[[Category:MediaWiki User's Guide|MediaWiki User's Guide]] +!! end + +!! test +Category with empty sort key and parentheses +!! options +cat +pst +!! input +[[Category:Foo (bar)|]] +!! result +[[Category:Foo (bar)|Foo]] +!! end + +!! test +Category with link tail +!! options +cat +pst +!! input +123[[Category:Foo]]456 +!! result +123[[Category:Foo]]456 +!! end + +!! test +Category with template +!! options +cat +pst +!! input +[[Category:{{echo|Foo}}]] +!! result +[[Category:{{echo|Foo}}]] +!! end + +!! test +Category with template in sort key +!! options +cat +pst +!! input +[[Category:Foo|{{echo|Bar}}]] +!! result +[[Category:Foo|{{echo|Bar}}]] +!! end + +!! test +Category with template in sort key and title +!! options +cat +pst +!! input +[[Category:{{echo|Foo}}|{{echo|Bar}}]] +!! result +[[Category:{{echo|Foo}}|{{echo|Bar}}]] +!! end + +!! test +Category / paragraph interactions +!! input +Foo [[Category:Baz]] Bar + +Foo [[Category:Baz]] +Bar + +Foo +[[Category:Baz]] +Bar + +Foo +[[Category:Baz]] Bar + +Foo +[[Category:Baz]] + [[Category:Baz]] +[[Category:Baz]] +Bar + +[[Category:Baz]] + [[Category:Baz]] +[[Category:Baz]] + +[[Category:Baz]] + {{echo|[[Category:Baz]]}} +[[Category:Baz]] +!! result +<p>Foo Bar +</p><p>Foo +Bar +</p><p>Foo +Bar +</p><p>Foo Bar +</p><p>Foo +Bar +</p> +!! end + +!! test +Parsoid: Serialize link to category page with colon escape +!! options +parsoid +!! input + +[[:Category:Foo]] +[[:Category:Foo|Bar]] +!! result +<p> +<a rel="mw:WikiLink" href="Category:Foo">Category:Foo</a> +<a rel="mw:WikiLink" href="Category:Foo">Bar</a> +</p> +!! end + +!! test +Parsoid: Link prefix/suffixes aren't applied to category links +!! options +parsoid=wt2html,wt2wt,html2html +language=is +!! input +x[[Category:Foo]]y +!! result +<p>x<link rel="mw:WikiLink/Category" href="Category:Foo">y</p> +!! end + +!! test +Parsoid: Serialize link to file page with colon escape +!! options +parsoid +!! input + +[[:File:Foo.png]] +[[:File:Foo.png|Bar]] +!! result +<p> +<a rel="mw:WikiLink" href="File:Foo.png">File:Foo.png</a> +<a rel="mw:WikiLink" href="File:Foo.png">Bar</a> +</p> +!! end + +!! test +Parsoid: Serialize a genuine category link without colon escape +!! options +parsoid +!! input +[[Category:Foo]] +[[Category:Foo|Bar]] +!! result +<link rel="mw:WikiLink/Category" href="Category:Foo"> +<link rel="mw:WikiLink/Category" href="Category:Foo#Bar"> +!! end + ### ### Inter-language links ### @@ -5138,6 +10449,19 @@ ill es:Alimento fr:Nourriture zh:食品 !! end +!! test +Duplicate interlanguage links (bug 24502) +!! options +ill +!! input +[[es:1]] +[[es:2]] +[[fr:1]] +[[fr:2]] +!! result +es:1 fr:1 +!! end + ### ### Sections ### @@ -5152,13 +10476,13 @@ More ===Smaller headline=== Blah blah !! result -<h2><span class="editsection">[<a href="/index.php?title=Parser_test&action=edit&section=1" title="Edit section: Headline 1">edit</a>]</span> <span class="mw-headline" id="Headline_1"> Headline 1 </span></h2> +<h2><span class="mw-headline" id="Headline_1">Headline 1</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Parser_test&action=edit&section=1" title="Edit section: Headline 1">edit</a><span class="mw-editsection-bracket">]</span></span></h2> <p>Some text </p> -<h2><span class="editsection">[<a href="/index.php?title=Parser_test&action=edit&section=2" title="Edit section: Headline 2">edit</a>]</span> <span class="mw-headline" id="Headline_2">Headline 2</span></h2> +<h2><span class="mw-headline" id="Headline_2">Headline 2</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Parser_test&action=edit&section=2" title="Edit section: Headline 2">edit</a><span class="mw-editsection-bracket">]</span></span></h2> <p>More </p> -<h3><span class="editsection">[<a href="/index.php?title=Parser_test&action=edit&section=3" title="Edit section: Smaller headline">edit</a>]</span> <span class="mw-headline" id="Smaller_headline">Smaller headline</span></h3> +<h3><span class="mw-headline" id="Smaller_headline">Smaller headline</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Parser_test&action=edit&section=3" title="Edit section: Smaller headline">edit</a><span class="mw-editsection-bracket">]</span></span></h3> <p>Blah blah </p> !! end @@ -5175,7 +10499,7 @@ Section headings with TOC Some text ===Another headline=== !! result -<table id="toc" class="toc"><tr><td><div id="toctitle"><h2>Contents</h2></div> +<div id="toc" class="toc"><div id="toctitle"><h2>Contents</h2></div> <ul> <li class="toclevel-1 tocsection-1"><a href="#Headline_1"><span class="tocnumber">1</span> <span class="toctext">Headline 1</span></a> <ul> @@ -5196,22 +10520,23 @@ Some text </ul> </li> </ul> -</td></tr></table> -<h2><span class="editsection">[<a href="/index.php?title=Parser_test&action=edit&section=1" title="Edit section: Headline 1">edit</a>]</span> <span class="mw-headline" id="Headline_1"> Headline 1 </span></h2> -<h3><span class="editsection">[<a href="/index.php?title=Parser_test&action=edit&section=2" title="Edit section: Subheadline 1">edit</a>]</span> <span class="mw-headline" id="Subheadline_1"> Subheadline 1 </span></h3> -<h5><span class="editsection">[<a href="/index.php?title=Parser_test&action=edit&section=3" title="Edit section: Skipping a level">edit</a>]</span> <span class="mw-headline" id="Skipping_a_level"> Skipping a level </span></h5> -<h6><span class="editsection">[<a href="/index.php?title=Parser_test&action=edit&section=4" title="Edit section: Skipping a level">edit</a>]</span> <span class="mw-headline" id="Skipping_a_level_2"> Skipping a level </span></h6> -<h2><span class="editsection">[<a href="/index.php?title=Parser_test&action=edit&section=5" title="Edit section: Headline 2">edit</a>]</span> <span class="mw-headline" id="Headline_2"> Headline 2 </span></h2> +</div> + +<h2><span class="mw-headline" id="Headline_1">Headline 1</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Parser_test&action=edit&section=1" title="Edit section: Headline 1">edit</a><span class="mw-editsection-bracket">]</span></span></h2> +<h3><span class="mw-headline" id="Subheadline_1">Subheadline 1</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Parser_test&action=edit&section=2" title="Edit section: Subheadline 1">edit</a><span class="mw-editsection-bracket">]</span></span></h3> +<h5><span class="mw-headline" id="Skipping_a_level">Skipping a level</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Parser_test&action=edit&section=3" title="Edit section: Skipping a level">edit</a><span class="mw-editsection-bracket">]</span></span></h5> +<h6><span class="mw-headline" id="Skipping_a_level_2">Skipping a level</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Parser_test&action=edit&section=4" title="Edit section: Skipping a level">edit</a><span class="mw-editsection-bracket">]</span></span></h6> +<h2><span class="mw-headline" id="Headline_2">Headline 2</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Parser_test&action=edit&section=5" title="Edit section: Headline 2">edit</a><span class="mw-editsection-bracket">]</span></span></h2> <p>Some text </p> -<h3><span class="editsection">[<a href="/index.php?title=Parser_test&action=edit&section=6" title="Edit section: Another headline">edit</a>]</span> <span class="mw-headline" id="Another_headline">Another headline</span></h3> +<h3><span class="mw-headline" id="Another_headline">Another headline</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Parser_test&action=edit&section=6" title="Edit section: Another headline">edit</a><span class="mw-editsection-bracket">]</span></span></h3> !! end # perl -e 'print "="x$_," Level $_ heading","="x$_,"\n" for 1..10' !! test Handling of sections up to level 6 and beyond -!! input +!! input = Level 1 Heading= == Level 2 Heading== === Level 3 Heading=== @@ -5223,7 +10548,7 @@ Handling of sections up to level 6 and beyond ========= Level 9 Heading========= ========== Level 10 Heading========== !! result -<table id="toc" class="toc"><tr><td><div id="toctitle"><h2>Contents</h2></div> +<div id="toc" class="toc"><div id="toctitle"><h2>Contents</h2></div> <ul> <li class="toclevel-1 tocsection-1"><a href="#Level_1_Heading"><span class="tocnumber">1</span> <span class="toctext">Level 1 Heading</span></a> <ul> @@ -5251,17 +10576,18 @@ Handling of sections up to level 6 and beyond </ul> </li> </ul> -</td></tr></table> -<h1><span class="editsection">[<a href="/index.php?title=Parser_test&action=edit&section=1" title="Edit section: Level 1 Heading">edit</a>]</span> <span class="mw-headline" id="Level_1_Heading"> Level 1 Heading</span></h1> -<h2><span class="editsection">[<a href="/index.php?title=Parser_test&action=edit&section=2" title="Edit section: Level 2 Heading">edit</a>]</span> <span class="mw-headline" id="Level_2_Heading"> Level 2 Heading</span></h2> -<h3><span class="editsection">[<a href="/index.php?title=Parser_test&action=edit&section=3" title="Edit section: Level 3 Heading">edit</a>]</span> <span class="mw-headline" id="Level_3_Heading"> Level 3 Heading</span></h3> -<h4><span class="editsection">[<a href="/index.php?title=Parser_test&action=edit&section=4" title="Edit section: Level 4 Heading">edit</a>]</span> <span class="mw-headline" id="Level_4_Heading"> Level 4 Heading</span></h4> -<h5><span class="editsection">[<a href="/index.php?title=Parser_test&action=edit&section=5" title="Edit section: Level 5 Heading">edit</a>]</span> <span class="mw-headline" id="Level_5_Heading"> Level 5 Heading</span></h5> -<h6><span class="editsection">[<a href="/index.php?title=Parser_test&action=edit&section=6" title="Edit section: Level 6 Heading">edit</a>]</span> <span class="mw-headline" id="Level_6_Heading"> Level 6 Heading</span></h6> -<h6><span class="editsection">[<a href="/index.php?title=Parser_test&action=edit&section=7" title="Edit section: = Level 7 Heading=">edit</a>]</span> <span class="mw-headline" id=".3D_Level_7_Heading.3D">= Level 7 Heading=</span></h6> -<h6><span class="editsection">[<a href="/index.php?title=Parser_test&action=edit&section=8" title="Edit section: == Level 8 Heading==">edit</a>]</span> <span class="mw-headline" id=".3D.3D_Level_8_Heading.3D.3D">== Level 8 Heading==</span></h6> -<h6><span class="editsection">[<a href="/index.php?title=Parser_test&action=edit&section=9" title="Edit section: === Level 9 Heading===">edit</a>]</span> <span class="mw-headline" id=".3D.3D.3D_Level_9_Heading.3D.3D.3D">=== Level 9 Heading===</span></h6> -<h6><span class="editsection">[<a href="/index.php?title=Parser_test&action=edit&section=10" title="Edit section: ==== Level 10 Heading====">edit</a>]</span> <span class="mw-headline" id=".3D.3D.3D.3D_Level_10_Heading.3D.3D.3D.3D">==== Level 10 Heading====</span></h6> +</div> + +<h1><span class="mw-headline" id="Level_1_Heading">Level 1 Heading</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Parser_test&action=edit&section=1" title="Edit section: Level 1 Heading">edit</a><span class="mw-editsection-bracket">]</span></span></h1> +<h2><span class="mw-headline" id="Level_2_Heading">Level 2 Heading</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Parser_test&action=edit&section=2" title="Edit section: Level 2 Heading">edit</a><span class="mw-editsection-bracket">]</span></span></h2> +<h3><span class="mw-headline" id="Level_3_Heading">Level 3 Heading</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Parser_test&action=edit&section=3" title="Edit section: Level 3 Heading">edit</a><span class="mw-editsection-bracket">]</span></span></h3> +<h4><span class="mw-headline" id="Level_4_Heading">Level 4 Heading</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Parser_test&action=edit&section=4" title="Edit section: Level 4 Heading">edit</a><span class="mw-editsection-bracket">]</span></span></h4> +<h5><span class="mw-headline" id="Level_5_Heading">Level 5 Heading</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Parser_test&action=edit&section=5" title="Edit section: Level 5 Heading">edit</a><span class="mw-editsection-bracket">]</span></span></h5> +<h6><span class="mw-headline" id="Level_6_Heading">Level 6 Heading</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Parser_test&action=edit&section=6" title="Edit section: Level 6 Heading">edit</a><span class="mw-editsection-bracket">]</span></span></h6> +<h6><span class="mw-headline" id=".3D_Level_7_Heading.3D">= Level 7 Heading=</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Parser_test&action=edit&section=7" title="Edit section: = Level 7 Heading=">edit</a><span class="mw-editsection-bracket">]</span></span></h6> +<h6><span class="mw-headline" id=".3D.3D_Level_8_Heading.3D.3D">== Level 8 Heading==</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Parser_test&action=edit&section=8" title="Edit section: == Level 8 Heading==">edit</a><span class="mw-editsection-bracket">]</span></span></h6> +<h6><span class="mw-headline" id=".3D.3D.3D_Level_9_Heading.3D.3D.3D">=== Level 9 Heading===</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Parser_test&action=edit&section=9" title="Edit section: === Level 9 Heading===">edit</a><span class="mw-editsection-bracket">]</span></span></h6> +<h6><span class="mw-headline" id=".3D.3D.3D.3D_Level_10_Heading.3D.3D.3D.3D">==== Level 10 Heading====</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Parser_test&action=edit&section=10" title="Edit section: ==== Level 10 Heading====">edit</a><span class="mw-editsection-bracket">]</span></span></h6> !! end @@ -5275,7 +10601,7 @@ TOC regression (bug 9764) == title 2 == === title 2.1 === !! result -<table id="toc" class="toc"><tr><td><div id="toctitle"><h2>Contents</h2></div> +<div id="toc" class="toc"><div id="toctitle"><h2>Contents</h2></div> <ul> <li class="toclevel-1 tocsection-1"><a href="#title_1"><span class="tocnumber">1</span> <span class="toctext">title 1</span></a> <ul> @@ -5293,13 +10619,14 @@ TOC regression (bug 9764) </ul> </li> </ul> -</td></tr></table> -<h2><span class="editsection">[<a href="/index.php?title=Parser_test&action=edit&section=1" title="Edit section: title 1">edit</a>]</span> <span class="mw-headline" id="title_1"> title 1 </span></h2> -<h3><span class="editsection">[<a href="/index.php?title=Parser_test&action=edit&section=2" title="Edit section: title 1.1">edit</a>]</span> <span class="mw-headline" id="title_1.1"> title 1.1 </span></h3> -<h4><span class="editsection">[<a href="/index.php?title=Parser_test&action=edit&section=3" title="Edit section: title 1.1.1">edit</a>]</span> <span class="mw-headline" id="title_1.1.1"> title 1.1.1 </span></h4> -<h3><span class="editsection">[<a href="/index.php?title=Parser_test&action=edit&section=4" title="Edit section: title 1.2">edit</a>]</span> <span class="mw-headline" id="title_1.2"> title 1.2 </span></h3> -<h2><span class="editsection">[<a href="/index.php?title=Parser_test&action=edit&section=5" title="Edit section: title 2">edit</a>]</span> <span class="mw-headline" id="title_2"> title 2 </span></h2> -<h3><span class="editsection">[<a href="/index.php?title=Parser_test&action=edit&section=6" title="Edit section: title 2.1">edit</a>]</span> <span class="mw-headline" id="title_2.1"> title 2.1 </span></h3> +</div> + +<h2><span class="mw-headline" id="title_1">title 1</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Parser_test&action=edit&section=1" title="Edit section: title 1">edit</a><span class="mw-editsection-bracket">]</span></span></h2> +<h3><span class="mw-headline" id="title_1.1">title 1.1</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Parser_test&action=edit&section=2" title="Edit section: title 1.1">edit</a><span class="mw-editsection-bracket">]</span></span></h3> +<h4><span class="mw-headline" id="title_1.1.1">title 1.1.1</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Parser_test&action=edit&section=3" title="Edit section: title 1.1.1">edit</a><span class="mw-editsection-bracket">]</span></span></h4> +<h3><span class="mw-headline" id="title_1.2">title 1.2</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Parser_test&action=edit&section=4" title="Edit section: title 1.2">edit</a><span class="mw-editsection-bracket">]</span></span></h3> +<h2><span class="mw-headline" id="title_2">title 2</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Parser_test&action=edit&section=5" title="Edit section: title 2">edit</a><span class="mw-editsection-bracket">]</span></span></h2> +<h3><span class="mw-headline" id="title_2.1">title 2.1</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Parser_test&action=edit&section=6" title="Edit section: title 2.1">edit</a><span class="mw-editsection-bracket">]</span></span></h3> !! end @@ -5315,7 +10642,7 @@ wgMaxTocLevel=3 == title 2 == === title 2.1 === !! result -<table id="toc" class="toc"><tr><td><div id="toctitle"><h2>Contents</h2></div> +<div id="toc" class="toc"><div id="toctitle"><h2>Contents</h2></div> <ul> <li class="toclevel-1 tocsection-1"><a href="#title_1"><span class="tocnumber">1</span> <span class="toctext">title 1</span></a> <ul> @@ -5329,13 +10656,14 @@ wgMaxTocLevel=3 </ul> </li> </ul> -</td></tr></table> -<h2><span class="editsection">[<a href="/index.php?title=Parser_test&action=edit&section=1" title="Edit section: title 1">edit</a>]</span> <span class="mw-headline" id="title_1"> title 1 </span></h2> -<h3><span class="editsection">[<a href="/index.php?title=Parser_test&action=edit&section=2" title="Edit section: title 1.1">edit</a>]</span> <span class="mw-headline" id="title_1.1"> title 1.1 </span></h3> -<h4><span class="editsection">[<a href="/index.php?title=Parser_test&action=edit&section=3" title="Edit section: title 1.1.1">edit</a>]</span> <span class="mw-headline" id="title_1.1.1"> title 1.1.1 </span></h4> -<h3><span class="editsection">[<a href="/index.php?title=Parser_test&action=edit&section=4" title="Edit section: title 1.2">edit</a>]</span> <span class="mw-headline" id="title_1.2"> title 1.2 </span></h3> -<h2><span class="editsection">[<a href="/index.php?title=Parser_test&action=edit&section=5" title="Edit section: title 2">edit</a>]</span> <span class="mw-headline" id="title_2"> title 2 </span></h2> -<h3><span class="editsection">[<a href="/index.php?title=Parser_test&action=edit&section=6" title="Edit section: title 2.1">edit</a>]</span> <span class="mw-headline" id="title_2.1"> title 2.1 </span></h3> +</div> + +<h2><span class="mw-headline" id="title_1">title 1</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Parser_test&action=edit&section=1" title="Edit section: title 1">edit</a><span class="mw-editsection-bracket">]</span></span></h2> +<h3><span class="mw-headline" id="title_1.1">title 1.1</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Parser_test&action=edit&section=2" title="Edit section: title 1.1">edit</a><span class="mw-editsection-bracket">]</span></span></h3> +<h4><span class="mw-headline" id="title_1.1.1">title 1.1.1</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Parser_test&action=edit&section=3" title="Edit section: title 1.1.1">edit</a><span class="mw-editsection-bracket">]</span></span></h4> +<h3><span class="mw-headline" id="title_1.2">title 1.2</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Parser_test&action=edit&section=4" title="Edit section: title 1.2">edit</a><span class="mw-editsection-bracket">]</span></span></h3> +<h2><span class="mw-headline" id="title_2">title 2</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Parser_test&action=edit&section=5" title="Edit section: title 2">edit</a><span class="mw-editsection-bracket">]</span></span></h2> +<h3><span class="mw-headline" id="title_2.1">title 2.1</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Parser_test&action=edit&section=6" title="Edit section: title 2.1">edit</a><span class="mw-editsection-bracket">]</span></span></h3> !! end @@ -5350,7 +10678,7 @@ wgMaxTocLevel=3 ====Section 1.1.1.1==== ==Section 2== !! result -<table id="toc" class="toc"><tr><td><div id="toctitle"><h2>Contents</h2></div> +<div id="toc" class="toc"><div id="toctitle"><h2>Contents</h2></div> <ul> <li class="toclevel-1 tocsection-1"><a href="#Section_1"><span class="tocnumber">1</span> <span class="toctext">Section 1</span></a> <ul> @@ -5359,12 +10687,13 @@ wgMaxTocLevel=3 </li> <li class="toclevel-1 tocsection-5"><a href="#Section_2"><span class="tocnumber">2</span> <span class="toctext">Section 2</span></a></li> </ul> -</td></tr></table> -<h2><span class="editsection">[<a href="/index.php?title=Parser_test&action=edit&section=1" title="Edit section: Section 1">edit</a>]</span> <span class="mw-headline" id="Section_1">Section 1</span></h2> -<h3><span class="editsection">[<a href="/index.php?title=Parser_test&action=edit&section=2" title="Edit section: Section 1.1">edit</a>]</span> <span class="mw-headline" id="Section_1.1">Section 1.1</span></h3> -<h4><span class="editsection">[<a href="/index.php?title=Parser_test&action=edit&section=3" title="Edit section: Section 1.1.1">edit</a>]</span> <span class="mw-headline" id="Section_1.1.1">Section 1.1.1</span></h4> -<h4><span class="editsection">[<a href="/index.php?title=Parser_test&action=edit&section=4" title="Edit section: Section 1.1.1.1">edit</a>]</span> <span class="mw-headline" id="Section_1.1.1.1">Section 1.1.1.1</span></h4> -<h2><span class="editsection">[<a href="/index.php?title=Parser_test&action=edit&section=5" title="Edit section: Section 2">edit</a>]</span> <span class="mw-headline" id="Section_2">Section 2</span></h2> +</div> + +<h2><span class="mw-headline" id="Section_1">Section 1</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Parser_test&action=edit&section=1" title="Edit section: Section 1">edit</a><span class="mw-editsection-bracket">]</span></span></h2> +<h3><span class="mw-headline" id="Section_1.1">Section 1.1</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Parser_test&action=edit&section=2" title="Edit section: Section 1.1">edit</a><span class="mw-editsection-bracket">]</span></span></h3> +<h4><span class="mw-headline" id="Section_1.1.1">Section 1.1.1</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Parser_test&action=edit&section=3" title="Edit section: Section 1.1.1">edit</a><span class="mw-editsection-bracket">]</span></span></h4> +<h4><span class="mw-headline" id="Section_1.1.1.1">Section 1.1.1.1</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Parser_test&action=edit&section=4" title="Edit section: Section 1.1.1.1">edit</a><span class="mw-editsection-bracket">]</span></span></h4> +<h2><span class="mw-headline" id="Section_2">Section 2</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Parser_test&action=edit&section=5" title="Edit section: Section 2">edit</a><span class="mw-editsection-bracket">]</span></span></h2> !! end @@ -5375,8 +10704,8 @@ Resolving duplicate section names == Foo bar == == Foo bar == !! result -<h2><span class="editsection">[<a href="/index.php?title=Parser_test&action=edit&section=1" title="Edit section: Foo bar">edit</a>]</span> <span class="mw-headline" id="Foo_bar"> Foo bar </span></h2> -<h2><span class="editsection">[<a href="/index.php?title=Parser_test&action=edit&section=2" title="Edit section: Foo bar">edit</a>]</span> <span class="mw-headline" id="Foo_bar_2"> Foo bar </span></h2> +<h2><span class="mw-headline" id="Foo_bar">Foo bar</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Parser_test&action=edit&section=1" title="Edit section: Foo bar">edit</a><span class="mw-editsection-bracket">]</span></span></h2> +<h2><span class="mw-headline" id="Foo_bar_2">Foo bar</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Parser_test&action=edit&section=2" title="Edit section: Foo bar">edit</a><span class="mw-editsection-bracket">]</span></span></h2> !! end @@ -5386,8 +10715,8 @@ Resolving duplicate section names with differing case (bug 10721) == Foo bar == == Foo Bar == !! result -<h2><span class="editsection">[<a href="/index.php?title=Parser_test&action=edit&section=1" title="Edit section: Foo bar">edit</a>]</span> <span class="mw-headline" id="Foo_bar"> Foo bar </span></h2> -<h2><span class="editsection">[<a href="/index.php?title=Parser_test&action=edit&section=2" title="Edit section: Foo Bar">edit</a>]</span> <span class="mw-headline" id="Foo_Bar_2"> Foo Bar </span></h2> +<h2><span class="mw-headline" id="Foo_bar">Foo bar</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Parser_test&action=edit&section=1" title="Edit section: Foo bar">edit</a><span class="mw-editsection-bracket">]</span></span></h2> +<h2><span class="mw-headline" id="Foo_Bar_2">Foo Bar</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Parser_test&action=edit&section=2" title="Edit section: Foo Bar">edit</a><span class="mw-editsection-bracket">]</span></span></h2> !! end @@ -5406,10 +10735,10 @@ __NOTOC__ {{sections}} ==Section 4== !! result -<h2><span class="editsection">[<a href="/index.php?title=Parser_test&action=edit&section=1" title="Edit section: Section 0">edit</a>]</span> <span class="mw-headline" id="Section_0">Section 0</span></h2> -<h3><span class="editsection">[<a href="/index.php?title=Template:Sections&action=edit&section=T-1" title="Template:Sections">edit</a>]</span> <span class="mw-headline" id="Section_1">Section 1</span></h3> -<h2><span class="editsection">[<a href="/index.php?title=Template:Sections&action=edit&section=T-2" title="Template:Sections">edit</a>]</span> <span class="mw-headline" id="Section_2">Section 2</span></h2> -<h2><span class="editsection">[<a href="/index.php?title=Parser_test&action=edit&section=2" title="Edit section: Section 4">edit</a>]</span> <span class="mw-headline" id="Section_4">Section 4</span></h2> +<h2><span class="mw-headline" id="Section_0">Section 0</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Parser_test&action=edit&section=1" title="Edit section: Section 0">edit</a><span class="mw-editsection-bracket">]</span></span></h2> +<h3><span class="mw-headline" id="Section_1">Section 1</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Template:Sections&action=edit&section=T-1" title="Template:Sections">edit</a><span class="mw-editsection-bracket">]</span></span></h3> +<h2><span class="mw-headline" id="Section_2">Section 2</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Template:Sections&action=edit&section=T-2" title="Template:Sections">edit</a><span class="mw-editsection-bracket">]</span></span></h2> +<h2><span class="mw-headline" id="Section_4">Section 4</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Parser_test&action=edit&section=2" title="Edit section: Section 4">edit</a><span class="mw-editsection-bracket">]</span></span></h2> !! end @@ -5420,8 +10749,8 @@ __NOEDITSECTION__ ==Section 1== ==Section 2== !! result -<h2> <span class="mw-headline" id="Section_1">Section 1</span></h2> -<h2> <span class="mw-headline" id="Section_2">Section 2</span></h2> +<h2><span class="mw-headline" id="Section_1">Section 1</span></h2> +<h2><span class="mw-headline" id="Section_2">Section 2</span></h2> !! end @@ -5430,7 +10759,7 @@ Link inside a section heading !! input ==Section with a [[Main Page|link]] in it== !! result -<h2><span class="editsection">[<a href="/index.php?title=Parser_test&action=edit&section=1" title="Edit section: Section with a link in it">edit</a>]</span> <span class="mw-headline" id="Section_with_a_link_in_it">Section with a <a href="/wiki/Main_Page" title="Main Page">link</a> in it</span></h2> +<h2><span class="mw-headline" id="Section_with_a_link_in_it">Section with a <a href="/wiki/Main_Page" title="Main Page">link</a> in it</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Parser_test&action=edit&section=1" title="Edit section: Section with a link in it">edit</a><span class="mw-editsection-bracket">]</span></span></h2> !! end @@ -5442,7 +10771,7 @@ __TOC__ === title 1.1 === == title 2 == !! result -<table id="toc" class="toc"><tr><td><div id="toctitle"><h2>Contents</h2></div> +<div id="toc" class="toc"><div id="toctitle"><h2>Contents</h2></div> <ul> <li class="toclevel-1 tocsection-1"><a href="#title_1"><span class="tocnumber">1</span> <span class="toctext">title 1</span></a> <ul> @@ -5451,10 +10780,11 @@ __TOC__ </li> <li class="toclevel-1 tocsection-3"><a href="#title_2"><span class="tocnumber">2</span> <span class="toctext">title 2</span></a></li> </ul> -</td></tr></table> -<h2><span class="editsection">[<a href="/index.php?title=Parser_test&action=edit&section=1" title="Edit section: title 1">edit</a>]</span> <span class="mw-headline" id="title_1"> title 1 </span></h2> -<h3><span class="editsection">[<a href="/index.php?title=Parser_test&action=edit&section=2" title="Edit section: title 1.1">edit</a>]</span> <span class="mw-headline" id="title_1.1"> title 1.1 </span></h3> -<h2><span class="editsection">[<a href="/index.php?title=Parser_test&action=edit&section=3" title="Edit section: title 2">edit</a>]</span> <span class="mw-headline" id="title_2"> title 2 </span></h2> +</div> + +<h2><span class="mw-headline" id="title_1">title 1</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Parser_test&action=edit&section=1" title="Edit section: title 1">edit</a><span class="mw-editsection-bracket">]</span></span></h2> +<h3><span class="mw-headline" id="title_1.1">title 1.1</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Parser_test&action=edit&section=2" title="Edit section: title 1.1">edit</a><span class="mw-editsection-bracket">]</span></span></h3> +<h2><span class="mw-headline" id="title_2">title 2</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Parser_test&action=edit&section=3" title="Edit section: title 2">edit</a><span class="mw-editsection-bracket">]</span></span></h2> !! end @@ -5468,7 +10798,7 @@ http://example.com [[Image:foobar.jpg]] !!end !! test -Short headings with trailing space should match behaviour of Parser::doHeadings (bug 19910) +Short headings with trailing space should match behavior of Parser::doHeadings (bug 19910) !! input === The line above must have a trailing space! @@ -5476,10 +10806,10 @@ The line above must have a trailing space! --> <!-- --> But just in case it doesn't... !! result -<h1><span class="editsection">[<a href="/index.php?title=Parser_test&action=edit&section=1" title="Edit section: =">edit</a>]</span> <span class="mw-headline" id=".3D">=</span></h1> +<h1><span class="mw-headline" id=".3D">=</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Parser_test&action=edit&section=1" title="Edit section: =">edit</a><span class="mw-editsection-bracket">]</span></span></h1> <p>The line above must have a trailing space! </p> -<h1><span class="editsection">[<a href="/index.php?title=Parser_test&action=edit&section=2" title="Edit section: =">edit</a>]</span> <span class="mw-headline" id=".3D_2">=</span></h1> +<h1><span class="mw-headline" id=".3D_2">=</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Parser_test&action=edit&section=2" title="Edit section: =">edit</a><span class="mw-editsection-bracket">]</span></span></h1> <p>But just in case it doesn't... </p> !! end @@ -5506,7 +10836,7 @@ section 5 !! result <p>The tooltips shall not show entities to the user (ie. be double escaped) </p> -<table id="toc" class="toc"><tr><td><div id="toctitle"><h2>Contents</h2></div> +<div id="toc" class="toc"><div id="toctitle"><h2>Contents</h2></div> <ul> <li class="toclevel-1 tocsection-1"><a href="#text_.3E_text"><span class="tocnumber">1</span> <span class="toctext">text > text</span></a></li> <li class="toclevel-1 tocsection-2"><a href="#text_.3C_text"><span class="tocnumber">2</span> <span class="toctext">text < text</span></a></li> @@ -5514,20 +10844,21 @@ section 5 <li class="toclevel-1 tocsection-4"><a href="#text_.27_text"><span class="tocnumber">4</span> <span class="toctext">text ' text</span></a></li> <li class="toclevel-1 tocsection-5"><a href="#text_.22_text"><span class="tocnumber">5</span> <span class="toctext">text " text</span></a></li> </ul> -</td></tr></table> -<h2><span class="editsection">[<a href="/index.php?title=Parser_test&action=edit&section=1" title="Edit section: text > text">edit</a>]</span> <span class="mw-headline" id="text_.3E_text"> text > text </span></h2> +</div> + +<h2><span class="mw-headline" id="text_.3E_text">text > text</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Parser_test&action=edit&section=1" title="Edit section: text > text">edit</a><span class="mw-editsection-bracket">]</span></span></h2> <p>section 1 </p> -<h2><span class="editsection">[<a href="/index.php?title=Parser_test&action=edit&section=2" title="Edit section: text < text">edit</a>]</span> <span class="mw-headline" id="text_.3C_text"> text < text </span></h2> +<h2><span class="mw-headline" id="text_.3C_text">text < text</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Parser_test&action=edit&section=2" title="Edit section: text < text">edit</a><span class="mw-editsection-bracket">]</span></span></h2> <p>section 2 </p> -<h2><span class="editsection">[<a href="/index.php?title=Parser_test&action=edit&section=3" title="Edit section: text & text">edit</a>]</span> <span class="mw-headline" id="text_.26_text"> text & text </span></h2> +<h2><span class="mw-headline" id="text_.26_text">text & text</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Parser_test&action=edit&section=3" title="Edit section: text & text">edit</a><span class="mw-editsection-bracket">]</span></span></h2> <p>section 3 </p> -<h2><span class="editsection">[<a href="/index.php?title=Parser_test&action=edit&section=4" title="Edit section: text ' text">edit</a>]</span> <span class="mw-headline" id="text_.27_text"> text ' text </span></h2> +<h2><span class="mw-headline" id="text_.27_text">text ' text</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Parser_test&action=edit&section=4" title="Edit section: text ' text">edit</a><span class="mw-editsection-bracket">]</span></span></h2> <p>section 4 </p> -<h2><span class="editsection">[<a href="/index.php?title=Parser_test&action=edit&section=5" title="Edit section: text " text">edit</a>]</span> <span class="mw-headline" id="text_.22_text"> text " text </span></h2> +<h2><span class="mw-headline" id="text_.22_text">text " text</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Parser_test&action=edit&section=5" title="Edit section: text " text">edit</a><span class="mw-editsection-bracket">]</span></span></h2> <p>section 5 </p> !! end @@ -5541,18 +10872,59 @@ Headers with excess '=' characters =''italic'' heading== ==''italic'' heading= !! result -<table id="toc" class="toc"><tr><td><div id="toctitle"><h2>Contents</h2></div> +<div id="toc" class="toc"><div id="toctitle"><h2>Contents</h2></div> <ul> <li class="toclevel-1 tocsection-1"><a href="#foo.3D"><span class="tocnumber">1</span> <span class="toctext">foo=</span></a></li> <li class="toclevel-1 tocsection-2"><a href="#.3Dfoo"><span class="tocnumber">2</span> <span class="toctext">=foo</span></a></li> <li class="toclevel-1 tocsection-3"><a href="#italic_heading.3D"><span class="tocnumber">3</span> <span class="toctext"><i>italic</i> heading=</span></a></li> <li class="toclevel-1 tocsection-4"><a href="#.3Ditalic_heading"><span class="tocnumber">4</span> <span class="toctext">=<i>italic</i> heading</span></a></li> </ul> -</td></tr></table> -<h1><span class="editsection">[<a href="/index.php?title=Parser_test&action=edit&section=1" title="Edit section: foo=">edit</a>]</span> <span class="mw-headline" id="foo.3D">foo=</span></h1> -<h1><span class="editsection">[<a href="/index.php?title=Parser_test&action=edit&section=2" title="Edit section: =foo">edit</a>]</span> <span class="mw-headline" id=".3Dfoo">=foo</span></h1> -<h1><span class="editsection">[<a href="/index.php?title=Parser_test&action=edit&section=3" title="Edit section: italic heading=">edit</a>]</span> <span class="mw-headline" id="italic_heading.3D"><i>italic</i> heading=</span></h1> -<h1><span class="editsection">[<a href="/index.php?title=Parser_test&action=edit&section=4" title="Edit section: =italic heading">edit</a>]</span> <span class="mw-headline" id=".3Ditalic_heading">=<i>italic</i> heading</span></h1> +</div> + +<h1><span class="mw-headline" id="foo.3D">foo=</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Parser_test&action=edit&section=1" title="Edit section: foo=">edit</a><span class="mw-editsection-bracket">]</span></span></h1> +<h1><span class="mw-headline" id=".3Dfoo">=foo</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Parser_test&action=edit&section=2" title="Edit section: =foo">edit</a><span class="mw-editsection-bracket">]</span></span></h1> +<h1><span class="mw-headline" id="italic_heading.3D"><i>italic</i> heading=</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Parser_test&action=edit&section=3" title="Edit section: italic heading=">edit</a><span class="mw-editsection-bracket">]</span></span></h1> +<h1><span class="mw-headline" id=".3Ditalic_heading">=<i>italic</i> heading</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Parser_test&action=edit&section=4" title="Edit section: =italic heading">edit</a><span class="mw-editsection-bracket">]</span></span></h1> + +!! end + +!! test +HTML headers vs TOC (bug 23393) +(__NOEDITSECTION__ for clearer output, doesn't matter here) +!! input +<h1>Header 1</h1> +== Header 1.1 == +== Header 1.2 == + +<h1>Header 2 +</h1> +== Header 2.1 == +== Header 2.2 == +__NOEDITSECTION__ +!! result +<div id="toc" class="toc"><div id="toctitle"><h2>Contents</h2></div> +<ul> +<li class="toclevel-1"><a href="#Header_1"><span class="tocnumber">1</span> <span class="toctext">Header 1</span></a> +<ul> +<li class="toclevel-2 tocsection-1"><a href="#Header_1.1"><span class="tocnumber">1.1</span> <span class="toctext">Header 1.1</span></a></li> +<li class="toclevel-2 tocsection-2"><a href="#Header_1.2"><span class="tocnumber">1.2</span> <span class="toctext">Header 1.2</span></a></li> +</ul> +</li> +<li class="toclevel-1"><a href="#Header_2"><span class="tocnumber">2</span> <span class="toctext">Header 2</span></a> +<ul> +<li class="toclevel-2 tocsection-3"><a href="#Header_2.1"><span class="tocnumber">2.1</span> <span class="toctext">Header 2.1</span></a></li> +<li class="toclevel-2 tocsection-4"><a href="#Header_2.2"><span class="tocnumber">2.2</span> <span class="toctext">Header 2.2</span></a></li> +</ul> +</li> +</ul> +</div> + +<h1><span class="mw-headline" id="Header_1">Header 1</span></h1> +<h2><span class="mw-headline" id="Header_1.1">Header 1.1</span></h2> +<h2><span class="mw-headline" id="Header_1.2">Header 1.2</span></h2> +<h1><span class="mw-headline" id="Header_2">Header 2</span></h1> +<h2><span class="mw-headline" id="Header_2.1">Header 2.1</span></h2> +<h2><span class="mw-headline" id="Header_2.2">Header 2.2</span></h2> !! end @@ -5594,6 +10966,10 @@ Namespaced link must have a title (bad fragment version) !!end +### +### HTML tags and HTML attributes +### + !! test div with no attributes !! input @@ -5639,6 +11015,49 @@ div with illegal double attributes !!end +# FIXME: produce empty string instead of "class" in the PHP parser, following +# the HTML5 spec. +!! test +div with empty attribute value, space before equals +!! options +parsoid +!! input +<div class =>HTML rocks</div> +!! result +<div class="">HTML rocks</div> + +!! end + +# The PHP parser escapes the opening brace to { for some reason, so +# disabled this test for it. +!! test +div with braces in attribute value +!! options +parsoid +!! input +<div title="{}">Foo</div> +!! result +<div title="{}">Foo</div> +!! end + +# This it very inconsistent in the PHP parser: it returns +# class="class" if there is a space between the name and the equal sign (see +# 'div with empty attribute value, space before equals'), but strips the +# attribute completely if the space is missing. We hope that not much content +# depends on this, so are implementing the behavior below in Parsoid for +# consistencies' sake. Disabled for the PHP parser. +# FIXME: fix this behavior in the PHP parser? +!! test +div with empty attribute value, no space before equals +!! options +parsoid +!! input +<div class=>HTML rocks</div> +!! result +<div class="">HTML rocks</div> + +!! end + !! test HTML multiple attributes correction !! input @@ -5671,6 +11090,23 @@ DIV IN UPPERCASE !!end +!! test +Non-ASCII pseudo-tags are rendered as text +!! input +<khyô> +!! result +<p><khyô> +</p> +!! end + +!! test +Pseudo-tag with URL 'name' renders as url link +!! input +<http://example.com/> +!! result +<p><<a rel="nofollow" class="external free" href="http://example.com/">http://example.com/</a>> +</p> +!! end !! test text with amp in the middle of nowhere @@ -5691,6 +11127,15 @@ I always thought é was a cute letter. !! end !! test +text with entity-escaped character entity-like string: eacute +!! input +I always thought &eacute; was a cute letter. +!! result +<p>I always thought &eacute; was a cute letter. +</p> +!! end + +!! test text with undefined character entity: xacute !! input I always thought &xacute; was a cute letter. @@ -5701,6 +11146,81 @@ I always thought &xacute; was a cute letter. ### +### Nesting tests (see bug 41545, 50604, 51081) +### + +# This test case is fixed in Parsoid by domino 1.0.12. (bug 50604) +# Note that html2wt is considerably more difficult if we use <b> in +# the test case, instead of <big> +!! test +Ensure that HTML adoption agency algorithm is properly implemented. +!! input +<big>X<big>Y</big>Z</big> +!! result +<p><big>X<big>Y</big>Z</big> +</p> +!! end + +# This was bug 41545 in the PHP parser. +!! test +Nesting of <kbd> +!! input +<kbd>X<kbd>Y</kbd>Z</kbd> +!! result +<p><kbd>X<kbd>Y</kbd>Z</kbd> +</p> +!! end + +# The following cases were bug 51081 in the PHP parser. +# Note that there are some other nestable tags (b, i, etc) which are +# not covered; see bug 51081 for discussion. +!! test +Nesting of <em> +!! input +<em>X<em>Y</em>Z</em> +!! result +<p><em>X<em>Y</em>Z</em> +</p> +!! end + +!! test +Nesting of <strong> +!! input +<strong>X<strong>Y</strong>Z</strong> +!! result +<p><strong>X<strong>Y</strong>Z</strong> +</p> +!! end + +!! test +Nesting of <q> +!! input +<q>X<q>Y</q>Z</q> +!! result +<p><q>X<q>Y</q>Z</q> +</p> +!! end + +!! test +Nesting of <ruby> +!! input +<ruby>X<ruby>Y</ruby>Z</ruby> +!! result +<p><ruby>X<ruby>Y</ruby>Z</ruby> +</p> +!! end + +!! test +Nesting of <bdo> +!! input +<bdo>X<bdo>Y</bdo>Z</bdo> +!! result +<p><bdo>X<bdo>Y</bdo>Z</bdo> +</p> +!! end + + +### ### Media links ### @@ -6054,6 +11574,17 @@ Bug 3244: HTML attribute safety (extension; unsafe) !! end +!! test +Opera -o-link CSS +!! input +<div +title="data:text/html,<img src=1 onerror=alert(1)>" +style="-o-link:attr(title);-o-link-source:current">X</div> +!! result +<div title="data:text/html,<img src=1 onerror=alert(1)>" style="/* insecure input */">X</div> + +!! end + # More MSIE fun discovered by Tom Gilder !! test @@ -6092,6 +11623,70 @@ MSIE CSS safety test: comment in expression !! end +!! test +CSS safety test: vertical tab +!! input +<p style="font-size: 100px; background-image:url\b(https://www.google.com/images/srpr/logo6w.png)">A</p> +!! result +<p style="/* invalid control char */">A</p> + +!! end + +!! test +MSIE CSS safety test: Fullwidth +!! input +<p style="font-size: 100px; color: expression((title='XSSed'),'red')">A</p> +<div style="top:EXPRESSION(alert())">B</div> +!! result +<p style="/* insecure input */">A</p> +<div style="/* insecure input */">B</div> + +!! end + +!! test +MSIE CSS safety test: IPA extensions +!! input +<div style="background-image:uʀʟ(javascript:alert())">A</div> +<p style="font-size: 100px; color: expʀessɪoɴ((title='XSSed'),'red')">B</p> +!! result +<div style="/* insecure input */">A</div> +<p style="/* insecure input */">B</p> + +!! end + +!! test +MSIE CSS safety test: sup/sub script +!! input +<div style="background-image:url⁽javascript:alert())">A</div> +<div style="background-image:url₍javascript:alert())">B</div> +<p style="font-size: 100px; color: expressioⁿ((title='XSSed'),'red')">C</p> +!! result +<div style="/* insecure input */">A</div> +<div style="/* insecure input */">B</div> +<p style="/* insecure input */">C</p> + +!! end + +!! test +MSIE CSS safety test: Repetition markers +!! input +<p style="font-size: 100px; color: expres〱ion((title='XSSed'),'red')">A</p> +<p style="font-size: 100px; color: expresゝion((title='XSSed'),'red')">B</p> +<p style="font-size: 100px; color: expresーion((title='XSSed'),'red')">C</p> +<p style="font-size: 100px; color: expresヽion((title='XSSed'),'red')">D</p> +<p style="font-size: 100px; color: expresﹽion((title='XSSed'),'red')">E</p> +<p style="font-size: 100px; color: expresﹼion((title='XSSed'),'red')">F</p> +<p style="font-size: 100px; color: expresーion((title='XSSed'),'red')">G</p> +!! result +<p style="/* insecure input */">A</p> +<p style="/* insecure input */">B</p> +<p style="/* insecure input */">C</p> +<p style="/* insecure input */">D</p> +<p style="/* insecure input */">E</p> +<p style="/* insecure input */">F</p> +<p style="/* insecure input */">G</p> + +!! end !! test Table attribute legitimate extension @@ -6175,7 +11770,7 @@ Expansion of multi-line templates in attribute values (bug 6255 sanity check 2) !! end ### -### Parser hooks (see maintenance/parserTestsParserHook.php for the <tag> extension) +### Parser hooks (see tests/parser/parserTestsParserHook.php for the <tag> extension) ### !! test Parser hook: empty input @@ -6183,9 +11778,9 @@ Parser hook: empty input <tag></tag> !! result <pre> -string(0) "" -array(0) { -} +'' +array ( +) </pre> !! end @@ -6197,8 +11792,8 @@ Parser hook: empty input using terminated empty elements !! result <pre> NULL -array(0) { -} +array ( +) </pre> !! end @@ -6210,8 +11805,8 @@ Parser hook: empty input using terminated empty elements (space before) !! result <pre> NULL -array(0) { -} +array ( +) </pre> !! end @@ -6222,9 +11817,9 @@ Parser hook: basic input <tag>input</tag> !! result <pre> -string(5) "input" -array(0) { -} +'input' +array ( +) </pre> !! end @@ -6236,9 +11831,9 @@ Parser hook: case insensitive <TAG>input</TAG> !! result <pre> -string(5) "input" -array(0) { -} +'input' +array ( +) </pre> !! end @@ -6250,9 +11845,9 @@ Parser hook: case insensitive, redux <TaG>input</TAg> !! result <pre> -string(5) "input" -array(0) { -} +'input' +array ( +) </pre> !! end @@ -6265,9 +11860,9 @@ noxml <tag><tag></tag></tag> !! result <pre> -string(5) "<tag>" -array(0) { -} +'<tag>' +array ( +) </pre></tag> !! end @@ -6278,17 +11873,13 @@ Parser hook: basic arguments <tag width=200 height = "100" depth = '50' square></tag> !! result <pre> -string(0) "" -array(4) { - ["width"]=> - string(3) "200" - ["height"]=> - string(3) "100" - ["depth"]=> - string(2) "50" - ["square"]=> - string(6) "square" -} +'' +array ( + 'width' => '200', + 'height' => '100', + 'depth' => '50', + 'square' => 'square', +) </pre> !! end @@ -6299,11 +11890,10 @@ Parser hook: argument containing a forward slash (bug 5344) <tag filename='/tmp/bla'></tag> !! result <pre> -string(0) "" -array(1) { - ["filename"]=> - string(8) "/tmp/bla" -} +'' +array ( + 'filename' => '/tmp/bla', +) </pre> !! end @@ -6315,10 +11905,9 @@ Parser hook: empty input using terminated empty elements (bug 2374) !! result <pre> NULL -array(1) { - ["foo"]=> - string(3) "bar" -} +array ( + 'foo' => 'bar', +) </pre>text !! end @@ -6333,16 +11922,12 @@ other stuff !! result <pre> NULL -array(4) { - ["width"]=> - string(3) "200" - ["height"]=> - string(3) "100" - ["depth"]=> - string(2) "50" - ["square"]=> - string(6) "square" -} +array ( + 'width' => '200', + 'height' => '100', + 'depth' => '50', + 'square' => 'square', +) </pre> <p>other stuff </tag> @@ -6350,7 +11935,7 @@ array(4) { !! end ### -### (see maintenance/parserTestsStaticParserHook.php for the <statictag> extension) +### (see tests/parser/parserTestsParserHook.php for the <statictag> extension) ### !! test @@ -6468,6 +12053,30 @@ Something need to be done. foo-2 ? !! end !! test +Sanitizer: Validating that <meta> and <link> work, but only for Microdata +!! input +<div itemscope> + <meta itemprop="hello" content="world"> + <meta http-equiv="refresh" content="5"> + <meta itemprop="hello" http-equiv="refresh" content="5"> + <link itemprop="hello" href="{{SERVER}}"> + <link rel="stylesheet" href="{{SERVER}}"> + <link rel="stylesheet" itemprop="hello" href="{{SERVER}}"> +</div> +!! result +<div itemscope="itemscope"> +<p> <meta itemprop="hello" content="world" /> + <meta http-equiv="refresh" content="5"> + <meta itemprop="hello" content="5" /> +</p> + <link itemprop="hello" href="http://example.org" /> + <link rel="stylesheet" href="<a rel="nofollow" class="external free" href="http://example.org">http://example.org</a>"> + <link itemprop="hello" href="http://example.org" /> +</div> + +!! end + +!! test Language converter: output gets cut off unexpectedly (bug 5757) !! options language=zh @@ -6558,8 +12167,10 @@ disabled !! result <ul> <li>One -</li><li>Two -</li></ul> +</li> +<li>Two +</li> +</ul> !! end @@ -6590,8 +12201,10 @@ disabled !! result <ol> <li>One -</li><li>Two -</li></ol> +</li> +<li>Two +</li> +</ol> !! end @@ -6636,12 +12249,16 @@ disabled !! result <ul> <li>One -</li><li>Two: +</li> +<li>Two: <ul> <li>Sub-one -</li><li>Sub-two -</li></ul> -</li></ul> +</li> +<li>Sub-two +</li> +</ul> +</li> +</ul> !! end @@ -6686,21 +12303,27 @@ disabled !! result <ol> <li>One -</li><li>Two: +</li> +<li>Two: <ol> <li>Sub-one -</li><li>Sub-two -</li></ol> -</li></ol> +</li> +<li>Sub-two +</li> +</ol> +</li> +</ol> !! end !! test HTML ordered list item with parameters oddity !! input -<ol><li id="fragment">One</li></ol> +<ol><li id="fragment">One</li> +</ol> !! result -<ol><li id="fragment">One</li></ol> +<ol><li id="fragment">One</li> +</ol> !! end @@ -6752,12 +12375,13 @@ Fuzz testing: Parser14 == onmouseover= == http://__TOC__ !! result -<h2><span class="editsection">[<a href="/index.php?title=Parser_test&action=edit&section=1" title="Edit section: onmouseover=">edit</a>]</span> <span class="mw-headline" id="onmouseover.3D"> onmouseover= </span></h2> -http://<table id="toc" class="toc"><tr><td><div id="toctitle"><h2>Contents</h2></div> +<h2><span class="mw-headline" id="onmouseover.3D">onmouseover=</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Parser_test&action=edit&section=1" title="Edit section: onmouseover=">edit</a><span class="mw-editsection-bracket">]</span></span></h2> +http://<div id="toc" class="toc"><div id="toctitle"><h2>Contents</h2></div> <ul> <li class="toclevel-1 tocsection-1"><a href="#onmouseover.3D"><span class="tocnumber">1</span> <span class="toctext">onmouseover=</span></a></li> </ul> -</td></tr></table> +</div> + !! end @@ -6767,7 +12391,7 @@ Fuzz testing: Parser14-table ==a== {| STYLE=__TOC__ !! result -<h2><span class="editsection">[<a href="/index.php?title=Parser_test&action=edit&section=1" title="Edit section: a">edit</a>]</span> <span class="mw-headline" id="a">a</span></h2> +<h2><span class="mw-headline" id="a">a</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Parser_test&action=edit&section=1" title="Edit section: a">edit</a><span class="mw-editsection-bracket">]</span></span></h2> <table style="__TOC__"> <tr><td></td></tr> </table> @@ -6913,7 +12537,7 @@ Fuzz testing: image with bogus manual thumbnail !!input [[Image:foobar.jpg|thumbnail= ]] !!result -<div class="thumb tright"><div class="thumbinner" style="width:1943px;">Error creating thumbnail: <div class="thumbcaption"></div></div></div> +<div class="thumb tright"><div class="thumbinner" style="width:182px;">Error creating thumbnail: <div class="thumbcaption"></div></div></div> !!end @@ -7420,7 +13044,7 @@ section=2 !! end -# Formerly testing for bug 2587, now resolved by the use of unmarked sections +# Formerly testing for bug 2587, now resolved by the use of unmarked sections # instead of respecting commented sections !! test Section extraction prefixed by comment (section 1) @@ -7445,7 +13069,7 @@ section=2 !!end -# Formerly testing for bug 2607, now resolved by the use of unmarked sections +# Formerly testing for bug 2607, now resolved by the use of unmarked sections # instead of respecting HTML-style headings !! test Section extraction, mixed wiki and html (section 1) @@ -7492,7 +13116,7 @@ section=1 ==marked== !!end -# Test behaviour of bug 19910 +# Test behavior of bug 19910 !! test Sectiion with all-equals !! options @@ -7924,18 +13548,34 @@ Handling of 
 in URLs !! input **irc://
a !! result -<ul><li><ul><li><a rel="nofollow" class="external free" href="irc://%0Aa">irc://%0Aa</a> -</li></ul> -</li></ul> +<ul> +<li><ul> +<li><a rel="nofollow" class="external free" href="irc://%0Aa">irc://%0Aa</a> +</li> +</ul> +</li> +</ul> !!end !! test -5 quotes, code coverage +1 line +5 quotes, code coverage +1 line (php) +!! options +php !! input ''''' !! result !! end +# The PHP parser strips the empty tags out for giggles; parsoid doesn't. +!! test +5 quotes, code coverage +1 line (parsoid) +!! options +parsoid +!! input +''''' +!! result +<p><b><i></i></b></p> +!! end !! test Special:Search page linking. @@ -7948,43 +13588,78 @@ Special:Search page linking. !! test Say the magic word +!! options +title=[[Parser test]] !! input * {{PAGENAME}} +* {{PAGENAMEE}} +* {{FULLPAGENAME}} +* {{FULLPAGENAMEE}} * {{BASEPAGENAME}} +* {{BASEPAGENAMEE}} * {{SUBPAGENAME}} * {{SUBPAGENAMEE}} -* {{BASEPAGENAME}} -* {{BASEPAGENAMEE}} +* {{ROOTPAGENAME}} +* {{ROOTPAGENAMEE}} * {{TALKPAGENAME}} * {{TALKPAGENAMEE}} * {{SUBJECTPAGENAME}} * {{SUBJECTPAGENAMEE}} * {{NAMESPACEE}} * {{NAMESPACE}} +* {{NAMESPACENUMBER}} * {{TALKSPACE}} * {{TALKSPACEE}} * {{SUBJECTSPACE}} * {{SUBJECTSPACEE}} * {{Dynamic|{{NUMBEROFUSERS}}|{{NUMBEROFPAGES}}|{{CURRENTVERSION}}|{{CONTENTLANGUAGE}}|{{DIRECTIONMARK}}|{{CURRENTTIMESTAMP}}|{{NUMBEROFARTICLES}}}} !! result -<ul><li> Parser test -</li><li> Parser test -</li><li> Parser test -</li><li> Parser_test -</li><li> Parser test -</li><li> Parser_test -</li><li> Talk:Parser test -</li><li> Talk:Parser_test -</li><li> Parser test -</li><li> Parser_test -</li><li> -</li><li> -</li><li> Talk -</li><li> Talk -</li><li> -</li><li> -</li><li> <a href="/index.php?title=Template:Dynamic&action=edit&redlink=1" class="new" title="Template:Dynamic (page does not exist)">Template:Dynamic</a> -</li></ul> +<ul> +<li> Parser test +</li> +<li> Parser_test +</li> +<li> Parser test +</li> +<li> Parser_test +</li> +<li> Parser test +</li> +<li> Parser_test +</li> +<li> Parser test +</li> +<li> Parser_test +</li> +<li> Parser test +</li> +<li> Parser_test +</li> +<li> Talk:Parser test +</li> +<li> Talk:Parser_test +</li> +<li> Parser test +</li> +<li> Parser_test +</li> +<li> +</li> +<li> +</li> +<li> 0 +</li> +<li> Talk +</li> +<li> Talk +</li> +<li> +</li> +<li> +</li> +<li> <a href="/index.php?title=Template:Dynamic&action=edit&redlink=1" class="new" title="Template:Dynamic (page does not exist)">Template:Dynamic</a> +</li> +</ul> !! end ### Note: Above tests excludes the "{{NUMBEROFADMINS}}" magic word because it generates a MySQL error when included. @@ -8003,40 +13678,40 @@ image4 |300px| centre * image6 </gallery> !! result -<ul class="gallery"> +<ul class="gallery mw-gallery-traditional"> <li class="gallerybox" style="width: 155px"><div style="width: 155px"> - <div style="height: 150px;">Image1.png</div> + <div class="thumb" style="height: 150px;">Image1.png</div> <div class="gallerytext"> </div> </div></li> <li class="gallerybox" style="width: 155px"><div style="width: 155px"> - <div style="height: 150px;">Image2.gif</div> + <div class="thumb" style="height: 150px;">Image2.gif</div> <div class="gallerytext"> <p>|||| </p> </div> </div></li> <li class="gallerybox" style="width: 155px"><div style="width: 155px"> - <div style="height: 150px;">Image3</div> + <div class="thumb" style="height: 150px;">Image3</div> <div class="gallerytext"> </div> </div></li> <li class="gallerybox" style="width: 155px"><div style="width: 155px"> - <div style="height: 150px;">Image4</div> + <div class="thumb" style="height: 150px;">Image4</div> <div class="gallerytext"> <p>300px| centre </p> </div> </div></li> <li class="gallerybox" style="width: 155px"><div style="width: 155px"> - <div style="height: 150px;">Image5.svg</div> + <div class="thumb" style="height: 150px;">Image5.svg</div> <div class="gallerytext"> <p><a rel="nofollow" class="external free" href="http://///////">http://///////</a> </p> </div> </div></li> <li class="gallerybox" style="width: 155px"><div style="width: 155px"> - <div style="height: 150px;">* image6</div> + <div class="thumb" style="height: 150px;">* image6</div> <div class="gallerytext"> </div> </div></li> @@ -8055,34 +13730,34 @@ image:foobar.jpg image:foobar.jpg|Blabla|alt=This is a foo-bar.|blabla. </gallery> !! result -<ul class="gallery" style="max-width: 226px;_width: 226px;"> +<ul class="gallery mw-gallery-traditional" style="max-width: 226px;_width: 226px;"> <li class='gallerycaption'>Foo <a href="/wiki/Main_Page" title="Main Page">Main Page</a></li> <li class="gallerybox" style="width: 105px"><div style="width: 105px"> - <div style="height: 70px;">Nonexistant.jpg</div> + <div class="thumb" style="height: 70px;">Nonexistant.jpg</div> <div class="gallerytext"> <p>caption </p> </div> </div></li> <li class="gallerybox" style="width: 105px"><div style="width: 105px"> - <div style="height: 70px;">Nonexistant.jpg</div> + <div class="thumb" style="height: 70px;">Nonexistant.jpg</div> <div class="gallerytext"> </div> </div></li> <li class="gallerybox" style="width: 105px"><div style="width: 105px"> - <div class="thumb" style="width: 100px;"><div style="margin:31px auto;"><a href="/wiki/File:Foobar.jpg" class="image"><img alt="" src="http://example.com/images/3/3a/Foobar.jpg" width="70" height="8" /></a></div></div> + <div class="thumb" style="width: 100px;"><div style="margin:31px auto;"><a href="/wiki/File:Foobar.jpg" class="image"><img alt="" src="http://example.com/images/thumb/3/3a/Foobar.jpg/70px-Foobar.jpg" width="70" height="8" /></a></div></div> <div class="gallerytext"> <p>some <b>caption</b> <a href="/wiki/Main_Page" title="Main Page">Main Page</a> </p> </div> </div></li> <li class="gallerybox" style="width: 105px"><div style="width: 105px"> - <div class="thumb" style="width: 100px;"><div style="margin:31px auto;"><a href="/wiki/File:Foobar.jpg" class="image"><img alt="Foobar.jpg" src="http://example.com/images/3/3a/Foobar.jpg" width="70" height="8" /></a></div></div> + <div class="thumb" style="width: 100px;"><div style="margin:31px auto;"><a href="/wiki/File:Foobar.jpg" class="image"><img alt="Foobar.jpg" src="http://example.com/images/thumb/3/3a/Foobar.jpg/70px-Foobar.jpg" width="70" height="8" /></a></div></div> <div class="gallerytext"> </div> </div></li> <li class="gallerybox" style="width: 105px"><div style="width: 105px"> - <div class="thumb" style="width: 100px;"><div style="margin:31px auto;"><a href="/wiki/File:Foobar.jpg" class="image"><img alt="This is a foo-bar." src="http://example.com/images/3/3a/Foobar.jpg" width="70" height="8" /></a></div></div> + <div class="thumb" style="width: 100px;"><div style="margin:31px auto;"><a href="/wiki/File:Foobar.jpg" class="image"><img alt="This is a foo-bar." src="http://example.com/images/thumb/3/3a/Foobar.jpg/70px-Foobar.jpg" width="70" height="8" /></a></div></div> <div class="gallerytext"> <p>Blabla|blabla. </p> @@ -8100,16 +13775,16 @@ File:foobar.jpg|[[File:foobar.jpg|20px|desc|alt=inneralt]]|alt=galleryalt File:foobar.jpg|{{Test|unamedParam|alt=param}}|alt=galleryalt </gallery> !! result -<ul class="gallery"> +<ul class="gallery mw-gallery-traditional"> <li class="gallerybox" style="width: 155px"><div style="width: 155px"> - <div class="thumb" style="width: 150px;"><div style="margin:68px auto;"><a href="/wiki/File:Foobar.jpg" class="image"><img alt="galleryalt" src="http://example.com/images/3/3a/Foobar.jpg" width="120" height="14" /></a></div></div> + <div class="thumb" style="width: 150px;"><div style="margin:68px auto;"><a href="/wiki/File:Foobar.jpg" class="image"><img alt="galleryalt" src="http://example.com/images/thumb/3/3a/Foobar.jpg/120px-Foobar.jpg" width="120" height="14" /></a></div></div> <div class="gallerytext"> -<p><a href="/wiki/File:Foobar.jpg" class="image" title="desc"><img alt="inneralt" src="http://example.com/images/3/3a/Foobar.jpg" width="20" height="2" /></a> +<p><a href="/wiki/File:Foobar.jpg" class="image" title="desc"><img alt="inneralt" src="http://example.com/images/thumb/3/3a/Foobar.jpg/20px-Foobar.jpg" width="20" height="2" srcset="http://example.com/images/thumb/3/3a/Foobar.jpg/30px-Foobar.jpg 1.5x, http://example.com/images/thumb/3/3a/Foobar.jpg/40px-Foobar.jpg 2x" /></a> </p> </div> </div></li> <li class="gallerybox" style="width: 155px"><div style="width: 155px"> - <div class="thumb" style="width: 150px;"><div style="margin:68px auto;"><a href="/wiki/File:Foobar.jpg" class="image"><img alt="galleryalt" src="http://example.com/images/3/3a/Foobar.jpg" width="120" height="14" /></a></div></div> + <div class="thumb" style="width: 150px;"><div style="margin:68px auto;"><a href="/wiki/File:Foobar.jpg" class="image"><img alt="galleryalt" src="http://example.com/images/thumb/3/3a/Foobar.jpg/120px-Foobar.jpg" width="120" height="14" /></a></div></div> <div class="gallerytext"> <p>This is a test template </p> @@ -8129,9 +13804,9 @@ image:foobar.jpg|some '''caption''' [[Main Page]] File:Foobar.jpg </gallery> !! result -<ul class="gallery"> +<ul class="gallery mw-gallery-traditional"> <li class="gallerybox" style="width: 155px"><div style="width: 155px"> - <div style="height: 150px;">Nonexistant.jpg</div> + <div class="thumb" style="height: 150px;">Nonexistant.jpg</div> <div class="gallerytext"> <p><a href="/wiki/File:Nonexistant.jpg" title="File:Nonexistant.jpg">Nonexistant.jpg</a><br /> caption @@ -8139,14 +13814,14 @@ caption </div> </div></li> <li class="gallerybox" style="width: 155px"><div style="width: 155px"> - <div style="height: 150px;">Nonexistant.jpg</div> + <div class="thumb" style="height: 150px;">Nonexistant.jpg</div> <div class="gallerytext"> <p><a href="/wiki/File:Nonexistant.jpg" title="File:Nonexistant.jpg">Nonexistant.jpg</a><br /> </p> </div> </div></li> <li class="gallerybox" style="width: 155px"><div style="width: 155px"> - <div class="thumb" style="width: 150px;"><div style="margin:68px auto;"><a href="/wiki/File:Foobar.jpg" class="image"><img alt="" src="http://example.com/images/3/3a/Foobar.jpg" width="120" height="14" /></a></div></div> + <div class="thumb" style="width: 150px;"><div style="margin:68px auto;"><a href="/wiki/File:Foobar.jpg" class="image"><img alt="" src="http://example.com/images/thumb/3/3a/Foobar.jpg/120px-Foobar.jpg" width="120" height="14" /></a></div></div> <div class="gallerytext"> <p><a href="/wiki/File:Foobar.jpg" title="File:Foobar.jpg">Foobar.jpg</a><br /> some <b>caption</b> <a href="/wiki/Main_Page" title="Main Page">Main Page</a> @@ -8154,7 +13829,7 @@ some <b>caption</b> <a href="/wiki/Main_Page" title="Main Page">Main Page</a> </div> </div></li> <li class="gallerybox" style="width: 155px"><div style="width: 155px"> - <div class="thumb" style="width: 150px;"><div style="margin:68px auto;"><a href="/wiki/File:Foobar.jpg" class="image"><img alt="Foobar.jpg" src="http://example.com/images/3/3a/Foobar.jpg" width="120" height="14" /></a></div></div> + <div class="thumb" style="width: 150px;"><div style="margin:68px auto;"><a href="/wiki/File:Foobar.jpg" class="image"><img alt="Foobar.jpg" src="http://example.com/images/thumb/3/3a/Foobar.jpg/120px-Foobar.jpg" width="120" height="14" /></a></div></div> <div class="gallerytext"> <p><a href="/wiki/File:Foobar.jpg" title="File:Foobar.jpg">Foobar.jpg</a><br /> </p> @@ -8174,24 +13849,24 @@ image:foobar.jpg foobar.jpg </gallery> !! result -<ul class="gallery"> +<ul class="gallery mw-gallery-traditional"> <li class="gallerybox" style="width: 155px"><div style="width: 155px"> - <div style="height: 150px;">Nonexistant.jpg</div> + <div class="thumb" style="height: 150px;">Nonexistant.jpg</div> <div class="gallerytext"> </div> </div></li> <li class="gallerybox" style="width: 155px"><div style="width: 155px"> - <div style="height: 150px;">Nonexistant.jpg</div> + <div class="thumb" style="height: 150px;">Nonexistant.jpg</div> <div class="gallerytext"> </div> </div></li> <li class="gallerybox" style="width: 155px"><div style="width: 155px"> - <div class="thumb" style="width: 150px;"><div style="margin:68px auto;"><a href="/wiki/File:Foobar.jpg" class="image"><img alt="Foobar.jpg" src="http://example.com/images/3/3a/Foobar.jpg" width="120" height="14" /></a></div></div> + <div class="thumb" style="width: 150px;"><div style="margin:68px auto;"><a href="/wiki/File:Foobar.jpg" class="image"><img alt="Foobar.jpg" src="http://example.com/images/thumb/3/3a/Foobar.jpg/120px-Foobar.jpg" width="120" height="14" /></a></div></div> <div class="gallerytext"> </div> </div></li> <li class="gallerybox" style="width: 155px"><div style="width: 155px"> - <div class="thumb" style="width: 150px;"><div style="margin:68px auto;"><a href="/wiki/File:Foobar.jpg" class="image"><img alt="Foobar.jpg" src="http://example.com/images/3/3a/Foobar.jpg" width="120" height="14" /></a></div></div> + <div class="thumb" style="width: 150px;"><div style="margin:68px auto;"><a href="/wiki/File:Foobar.jpg" class="image"><img alt="Foobar.jpg" src="http://example.com/images/thumb/3/3a/Foobar.jpg/120px-Foobar.jpg" width="120" height="14" /></a></div></div> <div class="gallerytext"> </div> </div></li> @@ -8228,7 +13903,7 @@ HTML Hex character encoding mixed case !! test __FORCETOC__ override -!! input +!! input __NEWSECTIONLINK__ __FORCETOC__ !! result @@ -8277,12 +13952,12 @@ Double RFC !! input RFC RFC 1234 !! result -<p>RFC <a class="external mw-magiclink-rfc" href="//tools.ietf.org/html/rfc1234">RFC 1234</a> +<p>RFC <a class="external mw-magiclink-rfc" rel="nofollow" href="//tools.ietf.org/html/rfc1234">RFC 1234</a> </p> !! end !! test -Double RFC with a wiki link +Double RFC with a wiki link !! input RFC [[RFC 1234]] !! result @@ -8295,7 +13970,7 @@ RFC code coverage !! input RFC 983 987 !! result -<p><a class="external mw-magiclink-rfc" href="//tools.ietf.org/html/rfc983">RFC 983</a> 987 +<p><a class="external mw-magiclink-rfc" rel="nofollow" href="//tools.ietf.org/html/rfc983">RFC 983</a> 987 </p> !! end @@ -8322,7 +13997,7 @@ Width + Height sized image (using px) (height is ignored) !! input [[Image:foobar.jpg|640x480px]] !! result -<p><a href="/wiki/File:Foobar.jpg" class="image"><img alt="Foobar.jpg" src="http://example.com/images/3/3a/Foobar.jpg" width="640" height="73" /></a> +<p><a href="/wiki/File:Foobar.jpg" class="image"><img alt="Foobar.jpg" src="http://example.com/images/thumb/3/3a/Foobar.jpg/640px-Foobar.jpg" width="640" height="73" srcset="http://example.com/images/thumb/3/3a/Foobar.jpg/960px-Foobar.jpg 1.5x, http://example.com/images/thumb/3/3a/Foobar.jpg/1280px-Foobar.jpg 2x" /></a> </p> !!end @@ -8331,7 +14006,7 @@ Width-sized image (using px, no following whitespace) !! input [[Image:foobar.jpg|640px]] !! result -<p><a href="/wiki/File:Foobar.jpg" class="image"><img alt="Foobar.jpg" src="http://example.com/images/3/3a/Foobar.jpg" width="640" height="73" /></a> +<p><a href="/wiki/File:Foobar.jpg" class="image"><img alt="Foobar.jpg" src="http://example.com/images/thumb/3/3a/Foobar.jpg/640px-Foobar.jpg" width="640" height="73" srcset="http://example.com/images/thumb/3/3a/Foobar.jpg/960px-Foobar.jpg 1.5x, http://example.com/images/thumb/3/3a/Foobar.jpg/1280px-Foobar.jpg 2x" /></a> </p> !!end @@ -8340,7 +14015,7 @@ Width-sized image (using px, with following whitespace - test regression from r3 !! input [[Image:foobar.jpg|640px ]] !! result -<p><a href="/wiki/File:Foobar.jpg" class="image"><img alt="Foobar.jpg" src="http://example.com/images/3/3a/Foobar.jpg" width="640" height="73" /></a> +<p><a href="/wiki/File:Foobar.jpg" class="image"><img alt="Foobar.jpg" src="http://example.com/images/thumb/3/3a/Foobar.jpg/640px-Foobar.jpg" width="640" height="73" srcset="http://example.com/images/thumb/3/3a/Foobar.jpg/960px-Foobar.jpg 1.5x, http://example.com/images/thumb/3/3a/Foobar.jpg/1280px-Foobar.jpg 2x" /></a> </p> !!end @@ -8349,7 +14024,7 @@ Width-sized image (using px, with preceding whitespace - test regression from r3 !! input [[Image:foobar.jpg| 640px]] !! result -<p><a href="/wiki/File:Foobar.jpg" class="image"><img alt="Foobar.jpg" src="http://example.com/images/3/3a/Foobar.jpg" width="640" height="73" /></a> +<p><a href="/wiki/File:Foobar.jpg" class="image"><img alt="Foobar.jpg" src="http://example.com/images/thumb/3/3a/Foobar.jpg/640px-Foobar.jpg" width="640" height="73" srcset="http://example.com/images/thumb/3/3a/Foobar.jpg/960px-Foobar.jpg 1.5x, http://example.com/images/thumb/3/3a/Foobar.jpg/1280px-Foobar.jpg 2x" /></a> </p> !!end @@ -8372,13 +14047,25 @@ disabled !! input :;;;:: !! result -<dl><dd><dl><dt><dl><dt><dl><dt><dl><dd><dl><dd> -</dd></dl> -</dd></dl> -</dt></dl> -</dt></dl> -</dt></dl> -</dd></dl> +<dl> +<dd><dl> +<dt><dl> +<dt><dl> +<dt><dl> +<dd><dl> +<dd> +</dd> +</dl> +</dd> +</dl> +</dt> +</dl> +</dt> +</dl> +</dt> +</dl> +</dd> +</dl> !!end @@ -8389,7 +14076,7 @@ Images with the "|" character in the comment !! input [[image:Foobar.jpg|thumb|An [http://test/?param1=|left|¶m2=|x external] URL]] !! result -<div class="thumb tright"><div class="thumbinner" style="width:182px;"><a href="/wiki/File:Foobar.jpg" class="image"><img alt="" src="http://example.com/images/3/3a/Foobar.jpg" width="180" height="20" class="thumbimage" /></a> <div class="thumbcaption"><div class="magnify"><a href="/wiki/File:Foobar.jpg" class="internal" title="Enlarge"><img src="/skins/common/images/magnify-clip.png" width="15" height="11" alt="" /></a></div>An <a rel="nofollow" class="external text" href="http://test/?param1=%7Cleft%7C&param2=%7Cx">external</a> URL</div></div></div> +<div class="thumb tright"><div class="thumbinner" style="width:182px;"><a href="/wiki/File:Foobar.jpg" class="image"><img alt="" src="http://example.com/images/thumb/3/3a/Foobar.jpg/180px-Foobar.jpg" width="180" height="20" class="thumbimage" srcset="http://example.com/images/thumb/3/3a/Foobar.jpg/270px-Foobar.jpg 1.5x, http://example.com/images/thumb/3/3a/Foobar.jpg/360px-Foobar.jpg 2x" /></a> <div class="thumbcaption"><div class="magnify"><a href="/wiki/File:Foobar.jpg" class="internal" title="Enlarge"><img src="/skins/common/images/magnify-clip.png" width="15" height="11" alt="" /></a></div>An <a rel="nofollow" class="external text" href="http://test/?param1=%7Cleft%7C&param2=%7Cx">external</a> URL</div></div></div> !!end @@ -8405,7 +14092,7 @@ Images with the "|" character in the comment !! test HTML with raw HTML ($wgRawHtml==true) !! options -rawhtml +wgRawHtml=1 !! input <html><script>alert(1);</script></html> !! result @@ -8474,6 +14161,45 @@ subpage title=[[Subpage test/L1/L2/L3]] </p> !! end +!! article +Subpage test/L1/L2/L3Sibling +!! text +Sibling article +!! endarticle + +!! test +Transclusion of a sibling page (one level up) +!! options +subpage title=[[Subpage test/L1/L2/L3]] +!! input +{{../L3Sibling}} +!! result +<p>Sibling article +</p> +!! end + +!! test +Transclusion of a child page +!! options +subpage title=[[Subpage test/L1/L2]] +!! input +{{/L3Sibling}} +!! result +<p>Sibling article +</p> +!! end + +!! test +Non-transclusion because of too many up levels +!! options +subpage title=[[Subpage test/L1/L2/L3]] +!! input +{{../../../../More than parent}} +!! result +<p>{{../../../../More than parent}} +</p> +!! end + !! test Definition list code coverage !! input @@ -8481,10 +14207,17 @@ Definition list code coverage ; title : def ;title: def !! result -<dl><dt> title  </dt><dd> def -</dd><dt> title </dt><dd> def -</dd><dt>title</dt><dd> def -</dd></dl> +<dl> +<dt> title  </dt> +<dd> def +</dd> +<dt> title </dt> +<dd> def +</dd> +<dt>title</dt> +<dd> def +</dd> +</dl> !! end @@ -8548,7 +14281,7 @@ Inclusion of !userCanEdit() content !! input {{MediaWiki:Fake}} !! result -<h2><span class="editsection">[<a href="/index.php?title=MediaWiki:Fake&action=edit&section=T-1" title="MediaWiki:Fake">edit</a>]</span> <span class="mw-headline" id="header">header</span></h2> +<h2><span class="mw-headline" id="header">header</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=MediaWiki:Fake&action=edit&section=T-1" title="MediaWiki:Fake">edit</a><span class="mw-editsection-bracket">]</span></span></h2> !! end @@ -8563,7 +14296,7 @@ Out-of-order TOC heading levels =====5===== ==2== !! result -<table id="toc" class="toc"><tr><td><div id="toctitle"><h2>Contents</h2></div> +<div id="toc" class="toc"><div id="toctitle"><h2>Contents</h2></div> <ul> <li class="toclevel-1 tocsection-1"><a href="#2"><span class="tocnumber">1</span> <span class="toctext">2</span></a> <ul> @@ -8578,13 +14311,14 @@ Out-of-order TOC heading levels </ul> </li> </ul> -</td></tr></table> -<h2><span class="editsection">[<a href="/index.php?title=Parser_test&action=edit&section=1" title="Edit section: 2">edit</a>]</span> <span class="mw-headline" id="2">2</span></h2> -<h6><span class="editsection">[<a href="/index.php?title=Parser_test&action=edit&section=2" title="Edit section: 6">edit</a>]</span> <span class="mw-headline" id="6">6</span></h6> -<h3><span class="editsection">[<a href="/index.php?title=Parser_test&action=edit&section=3" title="Edit section: 3">edit</a>]</span> <span class="mw-headline" id="3">3</span></h3> -<h1><span class="editsection">[<a href="/index.php?title=Parser_test&action=edit&section=4" title="Edit section: 1">edit</a>]</span> <span class="mw-headline" id="1">1</span></h1> -<h5><span class="editsection">[<a href="/index.php?title=Parser_test&action=edit&section=5" title="Edit section: 5">edit</a>]</span> <span class="mw-headline" id="5">5</span></h5> -<h2><span class="editsection">[<a href="/index.php?title=Parser_test&action=edit&section=6" title="Edit section: 2">edit</a>]</span> <span class="mw-headline" id="2_2">2</span></h2> +</div> + +<h2><span class="mw-headline" id="2">2</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Parser_test&action=edit&section=1" title="Edit section: 2">edit</a><span class="mw-editsection-bracket">]</span></span></h2> +<h6><span class="mw-headline" id="6">6</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Parser_test&action=edit&section=2" title="Edit section: 6">edit</a><span class="mw-editsection-bracket">]</span></span></h6> +<h3><span class="mw-headline" id="3">3</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Parser_test&action=edit&section=3" title="Edit section: 3">edit</a><span class="mw-editsection-bracket">]</span></span></h3> +<h1><span class="mw-headline" id="1">1</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Parser_test&action=edit&section=4" title="Edit section: 1">edit</a><span class="mw-editsection-bracket">]</span></span></h1> +<h5><span class="mw-headline" id="5">5</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Parser_test&action=edit&section=5" title="Edit section: 5">edit</a><span class="mw-editsection-bracket">]</span></span></h5> +<h2><span class="mw-headline" id="2_2">2</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Parser_test&action=edit&section=6" title="Edit section: 2">edit</a><span class="mw-editsection-bracket">]</span></span></h2> !! end @@ -8691,14 +14425,11 @@ anchorencode encodes like the TOC generator: (bug 18431) {{anchorencode: _ +:.3A%3A&&]] }} __NOEDITSECTION__ !! result -<h3> <span class="mw-headline" id=".2B:.3A.253A.26.26.5D.5D"> _ +:.3A%3A&&]] </span></h3> +<h3><span class="mw-headline" id=".2B:.3A.253A.26.26.5D.5D">_ +:.3A%3A&&]]</span></h3> <p>.2B:.3A.253A.26.26.5D.5D </p> !! end -# Expected output in the following test is not necessarily expected (there -# should probably be <p> tags inside the <blockquote> in the output) -- it's -# only testing for well-formedness. !! test Bug 6200: blockquotes and paragraph formatting !! input @@ -8711,7 +14442,8 @@ bar baz !! result <blockquote> -foo +<p>foo +</p> </blockquote> <p>bar </p> @@ -8740,6 +14472,27 @@ bar </pre> !! end +!!test +Parsing of overlapping (improperly nested) inline html tags (PHP parser) +!!options +php +!!input +<span><s>x</span></s> +!!result +<p><span><s>x</span></s></span> +</p> +!!end + +!!test +Parsing of overlapping (improperly nested) inline html tags (Parsoid) +!!options +parsoid +!!input +<span><s>x</span></s> +!!result +<p><span><s>x</s></span><s></s> +</p> +!!end ### ### Language variants related tests @@ -8755,6 +14508,33 @@ Both [[Dunav]] and [[Дунав]] are names for this river. </p> !!end +!! article +Дуна +!! text +content +!! endarticle + +!! test +Link to another existing title shouldn't be parsed as self-link even if it's a variant of this title +!! options +title=[[Duna]] language=sr +!! input +[[Дуна]] is not a self-link while [[Duna]] and [[Dуна]] are still self-links. +!! result +<p><a href="/wiki/%D0%94%D1%83%D0%BD%D0%B0" title="Дуна">Дуна</a> is not a self-link while <strong class="selflink">Duna</strong> and <strong class="selflink">Dуна</strong> are still self-links. +</p> +!! end + +!! test +Link to a section of a variant of this title shouldn't be parsed as self-link +!! options +title=[[Duna]] language=sr +!! input +[[Dуна]] is a self-link while [[Dunа#Foo]] and [[Dуна#Foo]] are not self-links. +!! result +<p><strong class="selflink">Dуна</strong> is a self-link while <a href="/wiki/%D0%94%D1%83%D0%BD%D0%B0" title="Дуна">Dunа#Foo</a> and <a href="/wiki/%D0%94%D1%83%D0%BD%D0%B0" title="Дуна">Dуна#Foo</a> are not self-links. +</p> +!! end !! test Link to pages in language variants @@ -8827,6 +14607,29 @@ language=sr cat !! end +!! article +Category:分类 +!! text +blah +!! endarticle + +!! article +Category:分類 +!! text +blah +!! endarticle + +!! test +Don't convert blue categorylinks to another variant (bug 33210) +!! options +language=zh cat +!! input +[[A]][[Category:分类]] +!! result +<a href="/wiki/Category:%E5%88%86%E7%B1%BB" title="Category:分类">分类</a> +!! end + + !! test Stripping -{}- tags (language variants) !! options @@ -8882,7 +14685,7 @@ language=sr variant=sr-ec !! input == -{Naslov}- == !! result -<h2><span class="editsection">[<a href="/index.php?title=Parser_test&action=edit&section=1" title="Уредите одељак „Naslov“">уреди</a>]</span> <span class="mw-headline" id="-.7BNaslov.7D-"> Naslov </span></h2> +<h2><span class="mw-headline" id="-.7BNaslov.7D-">Naslov</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Parser_test&action=edit&section=1" title="Уредите одељак „Naslov“">уреди</a><span class="mw-editsection-bracket">]</span></span></h2> !! end @@ -8900,6 +14703,20 @@ language=zh variant=zh-tw !! test +Conversion around HTML tags +!! options +language=sr variant=sr-ec +!! input +-{H|span=>sr-ec:script;title=>sr-ec:src;}- +<span title="La-{sr-el:L;sr-ec:C;}-tin">ski</span> +!! result +<p> +<span title="ЛаCтин">ски</span> +</p> +!! end + + +!! test Explicit session-wise language variant mapping (A flag and - flag) !! options language=zh variant=zh-tw @@ -8954,6 +14771,20 @@ China !! end !! test +Recursive conversion of alt and title attrs shouldn't clear converter state +!! options +language=zh variant=zh-cn showtitle +!! input +-{H|zh-cn:Exclamation;zh-tw:exclamation;}- +Should be stripped-{T|zh-cn:China;zh-tw:Taiwan}-<span title="exclamation">!</span> +!! result +China +<p> +Should be stripped<span title="Exclamation">!</span> +</p> +!! end + +!! test Bug 24072: more test on conversion rule for title !! options language=zh variant=zh-tw showtitle @@ -8968,6 +14799,30 @@ This won't take interferes with the title rule. !! end !! test +Partly disable title conversion if variant == main language code +!! options +language=zh variant=zh title=[[ZH]] showtitle +!! input +-{T|zh-cn:CN;zh-tw:TW}- +!! result +ZH +<p> +</p> +!! end + +!! test +Partly disable title conversion if variant == main language code, more +!! options +language=zh variant=zh title=[[ZH]] showtitle +!! input +-{T|TW}- +!! result +ZH +<p> +</p> +!! end + +!! test Raw output of variant escape tags (R flag) !! options language=zh variant=zh-tw @@ -8990,6 +14845,29 @@ Nested: -{zh-hans:Hi -{zh-cn:China;zh-sg:Singapore;}-;zh-hant:Hello -{zh-tw:Taiw !! end !! test +Proper conversion of text in external links +!! options +language=sr variant=sr-ec +!! input +http://www.google.com +gopher://www.google.com +[http://www.google.com http://www.google.com] +[gopher://www.google.com gopher://www.google.com] +[https://www.google.com irc://www.google.com] +[ftp://www.google.com www.google.com/ftp://dir] +[//www.google.com www.google.com] +!! result +<p><a rel="nofollow" class="external free" href="http://www.google.com">http://www.google.com</a> +<a rel="nofollow" class="external free" href="gopher://www.google.com">gopher://www.google.com</a> +<a rel="nofollow" class="external free" href="http://www.google.com">http://www.google.com</a> +<a rel="nofollow" class="external free" href="gopher://www.google.com">gopher://www.google.com</a> +<a rel="nofollow" class="external text" href="https://www.google.com">irc://www.google.com</a> +<a rel="nofollow" class="external text" href="ftp://www.google.com">www.гоогле.цом/фтп://дир</a> +<a rel="nofollow" class="external text" href="//www.google.com">www.гоогле.цом</a> +</p> +!! end + +!! test Do not convert roman numbers to language variants !! options language=sr variant=sr-ec @@ -9002,7 +14880,7 @@ Fridrih IV je car. !! test Unclosed language converter markup "-{" -!! options +!! options language=sr !! input -{T|hello @@ -9017,25 +14895,60 @@ Don't convert raw rule "-{R|=>}-" to "=>" language=sr !! input -{R|=>}- -!! result +!! result <p>=> </p> !!end -!!article -Template:Bullet -!!text -* Bar -!!endarticle - !! test Bug 529: Uncovered bullet !! input * Foo {{bullet}} !! result -<ul><li> Foo -</li><li> Bar -</li></ul> +<ul> +<li> Foo +</li> +<li> Bar +</li> +</ul> + +!! end + +# Plain MediaWiki does not remove empty lists, but tidy actually does. +# Templates in Wikipedia rely on this behavior, as tidy has always been +# enabled there. These tests are normally run *without* tidy, so specify the +# full output here. +# To test realistic parsing behavior, apply a tidy-like transformation to both +# the expected output and your parser's output. +!! test +Bug 529: Uncovered bullet leaving empty list, normally removed by tidy +!! input +******* Foo {{bullet}} +!! result +<ul> +<li><ul> +<li><ul> +<li><ul> +<li><ul> +<li><ul> +<li><ul> +<li> Foo +</li> +</ul> +</li> +</ul> +</li> +</ul> +</li> +</ul> +</li> +</ul> +</li> +</ul> +</li> +<li> Bar +</li> +</ul> !! end @@ -9067,9 +14980,12 @@ Bug 529: Uncovered bullet in parser function result !! input * Foo {{lc:{{bullet}} }} !! result -<ul><li> Foo -</li><li> bar -</li></ul> +<ul> +<li> Foo +</li> +<li> bar +</li> +</ul> !! end @@ -9125,7 +15041,7 @@ Morwen/13: Unclosed link followed by heading !! result <p>[[link </p> -<h2><span class="editsection">[<a href="/index.php?title=Parser_test&action=edit&section=1" title="Edit section: heading">edit</a>]</span> <span class="mw-headline" id="heading">heading</span></h2> +<h2><span class="mw-headline" id="heading">heading</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Parser_test&action=edit&section=1" title="Edit section: heading">edit</a><span class="mw-editsection-bracket">]</span></span></h2> !! end @@ -9137,7 +15053,7 @@ HHP2.1: Heuristics for headings in preprocessor parenthetical structures !! result <p>{{foo| </p> -<h1> <span class="mw-headline" id="heading">heading</span></h1> +<h1><span class="mw-headline" id="heading">heading</span></h1> !! end @@ -9149,7 +15065,7 @@ HHP2.2: Heuristics for headings in preprocessor parenthetical structures !! result <p>{{foo| </p> -<h2><span class="editsection">[<a href="/index.php?title=Parser_test&action=edit&section=1" title="Edit section: heading">edit</a>]</span> <span class="mw-headline" id="heading">heading</span></h2> +<h2><span class="mw-headline" id="heading">heading</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Parser_test&action=edit&section=1" title="Edit section: heading">edit</a><span class="mw-editsection-bracket">]</span></span></h2> !! end @@ -9239,8 +15155,6 @@ B</strong> # Bug 6200: <blockquote> should behave like <div> with respect to line breaks !! test Bug 6200: paragraphs inside blockquotes (no extra line breaks) -!! options -disabled !! input <blockquote>Line one @@ -9253,8 +15167,6 @@ Line two</blockquote> !! test Bug 6200: paragraphs inside blockquotes (extra line break on open) -!! options -disabled !! input <blockquote> Line one @@ -9270,8 +15182,6 @@ Line two</blockquote> !! test Bug 6200: paragraphs inside blockquotes (extra line break on close) -!! options -disabled !! input <blockquote>Line one @@ -9287,8 +15197,6 @@ Line two !! test Bug 6200: paragraphs inside blockquotes (extra line break on open and close) -!! options -disabled !! input <blockquote> Line one @@ -9383,7 +15291,7 @@ Free external link invading image caption !! input [[Image:Foobar.jpg|thumb|http://x|hello]] !! result -<div class="thumb tright"><div class="thumbinner" style="width:182px;"><a href="/wiki/File:Foobar.jpg" class="image"><img alt="" src="http://example.com/images/3/3a/Foobar.jpg" width="180" height="20" class="thumbimage" /></a> <div class="thumbcaption"><div class="magnify"><a href="/wiki/File:Foobar.jpg" class="internal" title="Enlarge"><img src="/skins/common/images/magnify-clip.png" width="15" height="11" alt="" /></a></div>hello</div></div></div> +<div class="thumb tright"><div class="thumbinner" style="width:182px;"><a href="/wiki/File:Foobar.jpg" class="image"><img alt="" src="http://example.com/images/thumb/3/3a/Foobar.jpg/180px-Foobar.jpg" width="180" height="20" class="thumbimage" srcset="http://example.com/images/thumb/3/3a/Foobar.jpg/270px-Foobar.jpg 1.5x, http://example.com/images/thumb/3/3a/Foobar.jpg/360px-Foobar.jpg 2x" /></a> <div class="thumbcaption"><div class="magnify"><a href="/wiki/File:Foobar.jpg" class="internal" title="Enlarge"><img src="/skins/common/images/magnify-clip.png" width="15" height="11" alt="" /></a></div>hello</div></div></div> !! end @@ -9416,17 +15324,6 @@ Multibyte character in padright </p> !! end -!! test -Formatted date -!! config -wgUseDynamicDates=1 -!! input -[[2009-03-24]] -!! result -<p><span class="mw-formatted-date" title="2009-03-24"><a href="/index.php?title=2009&action=edit&redlink=1" class="new" title="2009 (page does not exist)">2009</a>-<a href="/index.php?title=March_24&action=edit&redlink=1" class="new" title="March 24 (page does not exist)">03-24</a></span> -</p> -!!end - !!test formatdate parser function !!input @@ -9446,17 +15343,6 @@ formatdate parser function, with default format !! end !! test -Linked date with autoformatting disabled -!! config -wgUseDynamicDates=false -!! input -[[2009-03-24]] -!! result -<p><a href="/index.php?title=2009-03-24&action=edit&redlink=1" class="new" title="2009-03-24 (page does not exist)">2009-03-24</a> -</p> -!! end - -!! test Spacing of numbers in formatted dates !! input {{#formatdate:January 15}} @@ -9466,17 +15352,6 @@ Spacing of numbers in formatted dates !! end !! test -Spacing of numbers in formatted dates (linked) -!! config -wgUseDynamicDates=true -!! input -[[January 15]] -!! result -<p><span class="mw-formatted-date" title="01-15"><a href="/index.php?title=January_15&action=edit&redlink=1" class="new" title="January 15 (page does not exist)">January 15</a></span> -</p> -!! end - -!! test formatdate parser function, with default format and on a page of which the content language is always English and different from the wiki content language !! options language=nl title=[[MediaWiki:Common.css]] @@ -9542,7 +15417,7 @@ comment title=[[Main Page]] !! input pre-comment text /* External links */ removed bogus entries !! result -pre-comment text - <a href="/wiki/Main_Page#External_links" title="Main Page">→</a><span dir="auto"><span class="autocomment">External links: </span> removed bogus entries</span> +pre-comment text <a href="/wiki/Main_Page#External_links" title="Main Page">→</a><span dir="auto"><span class="autocomment">External links: </span> removed bogus entries</span> !!end !! test @@ -9772,7 +15647,41 @@ Screen <p>this is not the the title </p> !! end - + +!! test +Verify that displaytitle handles inline CSS styles (bug 26547) - rejected value +!! options +showtitle +title=[[Screen]] +!! config +wgAllowDisplayTitle=true +wgRestrictDisplayTitle=true +!! input +this is not the the title +{{DISPLAYTITLE:<span style="display: none;">s</span>creen}} +!! result +<span style="/* attempt to bypass $wgRestrictDisplayTitle */">s</span>creen +<p>this is not the the title +</p> +!! end + +!! test +Verify that displaytitle handles inline CSS styles (bug 26547) - accepted value +!! options +showtitle +title=[[Screen]] +!! config +wgAllowDisplayTitle=true +wgRestrictDisplayTitle=true +!! input +this is not the the title +{{DISPLAYTITLE:<span style="color: red;">s</span>creen}} +!! result +<span style="color: red;">s</span>creen +<p>this is not the the title +</p> +!! end + !! test preload: check <noinclude> and <includeonly> !! options @@ -9782,7 +15691,7 @@ Hello <noinclude>cruel</noinclude><includeonly>kind</includeonly> world. !! result Hello kind world. !! end - + !! test preload: check <onlyinclude> !! options @@ -9814,7 +15723,7 @@ preload !! end !! test -Play a bit with r67090 and bug 3158 +Play a bit with r67090 and bug 3158 !! options disabled !! input @@ -9997,29 +15906,31 @@ title=[[Main Page]] __TOC__ == ''Lost'' episodes == !! result -<table id="toc" class="toc"><tr><td><div id="toctitle"><h2>Contents</h2></div> +<div id="toc" class="toc"><div id="toctitle"><h2>Contents</h2></div> <ul> <li class="toclevel-1 tocsection-1"><a href="#Lost_episodes"><span class="tocnumber">1</span> <span class="toctext"><i>Lost</i> episodes</span></a></li> </ul> -</td></tr></table> -<h2><span class="editsection">[<a href="/index.php?title=Main_Page&action=edit&section=1" title="Edit section: Lost episodes">edit</a>]</span> <span class="mw-headline" id="Lost_episodes"> <i>Lost</i> episodes </span></h2> +</div> + +<h2><span class="mw-headline" id="Lost_episodes"><i>Lost</i> episodes</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Main_Page&action=edit&section=1" title="Edit section: Lost episodes">edit</a><span class="mw-editsection-bracket">]</span></span></h2> !! end !! test -Bug 26375: TOC with bold +Bug 26375: TOC with bold !! options title=[[Main Page]] !! input __TOC__ == '''should be bold''' then normal text == !! result -<table id="toc" class="toc"><tr><td><div id="toctitle"><h2>Contents</h2></div> +<div id="toc" class="toc"><div id="toctitle"><h2>Contents</h2></div> <ul> <li class="toclevel-1 tocsection-1"><a href="#should_be_bold_then_normal_text"><span class="tocnumber">1</span> <span class="toctext"><b>should be bold</b> then normal text</span></a></li> </ul> -</td></tr></table> -<h2><span class="editsection">[<a href="/index.php?title=Main_Page&action=edit&section=1" title="Edit section: should be bold then normal text">edit</a>]</span> <span class="mw-headline" id="should_be_bold_then_normal_text"> <b>should be bold</b> then normal text </span></h2> +</div> + +<h2><span class="mw-headline" id="should_be_bold_then_normal_text"><b>should be bold</b> then normal text</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Main_Page&action=edit&section=1" title="Edit section: should be bold then normal text">edit</a><span class="mw-editsection-bracket">]</span></span></h2> !! end @@ -10031,12 +15942,13 @@ title=[[Main Page]] __TOC__ == Image [[Image:foobar.jpg]] == !! result -<table id="toc" class="toc"><tr><td><div id="toctitle"><h2>Contents</h2></div> +<div id="toc" class="toc"><div id="toctitle"><h2>Contents</h2></div> <ul> <li class="toclevel-1 tocsection-1"><a href="#Image"><span class="tocnumber">1</span> <span class="toctext">Image</span></a></li> </ul> -</td></tr></table> -<h2><span class="editsection">[<a href="/index.php?title=Main_Page&action=edit&section=1" title="Edit section: Image">edit</a>]</span> <span class="mw-headline" id="Image"> Image <a href="/wiki/File:Foobar.jpg" class="image"><img alt="Foobar.jpg" src="http://example.com/images/3/3a/Foobar.jpg" width="1941" height="220" /></a> </span></h2> +</div> + +<h2><span class="mw-headline" id="Image">Image <a href="/wiki/File:Foobar.jpg" class="image"><img alt="Foobar.jpg" src="http://example.com/images/3/3a/Foobar.jpg" width="1941" height="220" /></a></span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Main_Page&action=edit&section=1" title="Edit section: Image">edit</a><span class="mw-editsection-bracket">]</span></span></h2> !! end @@ -10048,12 +15960,13 @@ title=[[Main Page]] __TOC__ == <blockquote>Quote</blockquote> == !! result -<table id="toc" class="toc"><tr><td><div id="toctitle"><h2>Contents</h2></div> +<div id="toc" class="toc"><div id="toctitle"><h2>Contents</h2></div> <ul> <li class="toclevel-1 tocsection-1"><a href="#Quote"><span class="tocnumber">1</span> <span class="toctext">Quote</span></a></li> </ul> -</td></tr></table> -<h2><span class="editsection">[<a href="/index.php?title=Main_Page&action=edit&section=1" title="Edit section: Quote">edit</a>]</span> <span class="mw-headline" id="Quote"> <blockquote>Quote</blockquote> </span></h2> +</div> + +<h2><span class="mw-headline" id="Quote"><blockquote>Quote</blockquote></span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Main_Page&action=edit&section=1" title="Edit section: Quote">edit</a><span class="mw-editsection-bracket">]</span></span></h2> !! end @@ -10067,12 +15980,13 @@ __TOC__ <small>Hanc marginis exiguitas non caperet.</small> QED !! result -<table id="toc" class="toc"><tr><td><div id="toctitle"><h2>Contents</h2></div> +<div id="toc" class="toc"><div id="toctitle"><h2>Contents</h2></div> <ul> <li class="toclevel-1 tocsection-1"><a href="#Proof:_2_.3C_3"><span class="tocnumber">1</span> <span class="toctext">Proof: 2 < 3</span></a></li> </ul> -</td></tr></table> -<h2><span class="editsection">[<a href="/index.php?title=Main_Page&action=edit&section=1" title="Edit section: Proof: 2 < 3">edit</a>]</span> <span class="mw-headline" id="Proof:_2_.3C_3"> Proof: 2 < 3 </span></h2> +</div> + +<h2><span class="mw-headline" id="Proof:_2_.3C_3">Proof: 2 < 3</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Main_Page&action=edit&section=1" title="Edit section: Proof: 2 < 3">edit</a><span class="mw-editsection-bracket">]</span></span></h2> <p><small>Hanc marginis exiguitas non caperet.</small> QED </p> @@ -10086,14 +16000,15 @@ __TOC__ == <i>Foo</i> <blockquote>Bar</blockquote> == !! result -<table id="toc" class="toc"><tr><td><div id="toctitle"><h2>Contents</h2></div> +<div id="toc" class="toc"><div id="toctitle"><h2>Contents</h2></div> <ul> <li class="toclevel-1 tocsection-1"><a href="#Foo_Bar"><span class="tocnumber">1</span> <span class="toctext"><i>Foo</i> <b>Bar</b></span></a></li> <li class="toclevel-1 tocsection-2"><a href="#Foo_Bar_2"><span class="tocnumber">2</span> <span class="toctext"><i>Foo</i> Bar</span></a></li> </ul> -</td></tr></table> -<h2><span class="editsection">[<a href="/index.php?title=Parser_test&action=edit&section=1" title="Edit section: Foo Bar">edit</a>]</span> <span class="mw-headline" id="Foo_Bar"> <i>Foo</i> <b>Bar</b> </span></h2> -<h2><span class="editsection">[<a href="/index.php?title=Parser_test&action=edit&section=2" title="Edit section: Foo Bar">edit</a>]</span> <span class="mw-headline" id="Foo_Bar_2"> <i>Foo</i> <blockquote>Bar</blockquote> </span></h2> +</div> + +<h2><span class="mw-headline" id="Foo_Bar"><i>Foo</i> <b>Bar</b></span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Parser_test&action=edit&section=1" title="Edit section: Foo Bar">edit</a><span class="mw-editsection-bracket">]</span></span></h2> +<h2><span class="mw-headline" id="Foo_Bar_2"><i>Foo</i> <blockquote>Bar</blockquote></span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Parser_test&action=edit&section=2" title="Edit section: Foo Bar">edit</a><span class="mw-editsection-bracket">]</span></span></h2> !! end @@ -10105,14 +16020,47 @@ __TOC__ == <sup class="a > b">Evilbye</sup> == !! result -<table id="toc" class="toc"><tr><td><div id="toctitle"><h2>Contents</h2></div> +<div id="toc" class="toc"><div id="toctitle"><h2>Contents</h2></div> <ul> <li class="toclevel-1 tocsection-1"><a href="#Hello"><span class="tocnumber">1</span> <span class="toctext"><sup>Hello</sup></span></a></li> <li class="toclevel-1 tocsection-2"><a href="#b.22.3EEvilbye"><span class="tocnumber">2</span> <span class="toctext"><sup> b">Evilbye</sup></span></a></li> </ul> -</td></tr></table> -<h2><span class="editsection">[<a href="/index.php?title=Parser_test&action=edit&section=1" title="Edit section: Hello">edit</a>]</span> <span class="mw-headline" id="Hello"> <sup class="in-h2">Hello</sup> </span></h2> -<h2><span class="editsection">[<a href="/index.php?title=Parser_test&action=edit&section=2" title="Edit section: b">Evilbye">edit</a>]</span> <span class="mw-headline" id="b.22.3EEvilbye"> <sup> b">Evilbye</sup> </span></h2> +</div> + +<h2><span class="mw-headline" id="Hello"><sup class="in-h2">Hello</sup></span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Parser_test&action=edit&section=1" title="Edit section: Hello">edit</a><span class="mw-editsection-bracket">]</span></span></h2> +<h2><span class="mw-headline" id="b.22.3EEvilbye"><sup> b">Evilbye</sup></span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Parser_test&action=edit&section=2" title="Edit section: b">Evilbye">edit</a><span class="mw-editsection-bracket">]</span></span></h2> + +!! end + +!! test +span tags with directionality in TOC +!! input +__TOC__ +== <span dir="ltr">C++</span> == + +== <span dir="rtl">זבנג!</span> == + +== <span style="font-style: italic">The attributes on these span tags must be deleted from the TOC</span> == + +== <span style="font-style: italic" dir="ltr">All attributes on these span tags must be deleted from the TOC</span> == + +== <span dir="ltr" style="font-style: italic">Attributes after dir on these span tags must be deleted from the TOC</span> == +!! result +<div id="toc" class="toc"><div id="toctitle"><h2>Contents</h2></div> +<ul> +<li class="toclevel-1 tocsection-1"><a href="#C.2B.2B"><span class="tocnumber">1</span> <span class="toctext"><span dir="ltr">C++</span></span></a></li> +<li class="toclevel-1 tocsection-2"><a href="#.D7.96.D7.91.D7.A0.D7.92.21"><span class="tocnumber">2</span> <span class="toctext"><span dir="rtl">זבנג!</span></span></a></li> +<li class="toclevel-1 tocsection-3"><a href="#The_attributes_on_these_span_tags_must_be_deleted_from_the_TOC"><span class="tocnumber">3</span> <span class="toctext"><span>The attributes on these span tags must be deleted from the TOC</span></span></a></li> +<li class="toclevel-1 tocsection-4"><a href="#All_attributes_on_these_span_tags_must_be_deleted_from_the_TOC"><span class="tocnumber">4</span> <span class="toctext"><span>All attributes on these span tags must be deleted from the TOC</span></span></a></li> +<li class="toclevel-1 tocsection-5"><a href="#Attributes_after_dir_on_these_span_tags_must_be_deleted_from_the_TOC"><span class="tocnumber">5</span> <span class="toctext"><span dir="ltr">Attributes after dir on these span tags must be deleted from the TOC</span></span></a></li> +</ul> +</div> + +<h2><span class="mw-headline" id="C.2B.2B"><span dir="ltr">C++</span></span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Parser_test&action=edit&section=1" title="Edit section: C++">edit</a><span class="mw-editsection-bracket">]</span></span></h2> +<h2><span class="mw-headline" id=".D7.96.D7.91.D7.A0.D7.92.21"><span dir="rtl">זבנג!</span></span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Parser_test&action=edit&section=2" title="Edit section: זבנג!">edit</a><span class="mw-editsection-bracket">]</span></span></h2> +<h2><span class="mw-headline" id="The_attributes_on_these_span_tags_must_be_deleted_from_the_TOC"><span style="font-style: italic">The attributes on these span tags must be deleted from the TOC</span></span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Parser_test&action=edit&section=3" title="Edit section: The attributes on these span tags must be deleted from the TOC">edit</a><span class="mw-editsection-bracket">]</span></span></h2> +<h2><span class="mw-headline" id="All_attributes_on_these_span_tags_must_be_deleted_from_the_TOC"><span style="font-style: italic" dir="ltr">All attributes on these span tags must be deleted from the TOC</span></span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Parser_test&action=edit&section=4" title="Edit section: All attributes on these span tags must be deleted from the TOC">edit</a><span class="mw-editsection-bracket">]</span></span></h2> +<h2><span class="mw-headline" id="Attributes_after_dir_on_these_span_tags_must_be_deleted_from_the_TOC"><span dir="ltr" style="font-style: italic">Attributes after dir on these span tags must be deleted from the TOC</span></span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Parser_test&action=edit&section=5" title="Edit section: Attributes after dir on these span tags must be deleted from the TOC">edit</a><span class="mw-editsection-bracket">]</span></span></h2> !! end @@ -10129,7 +16077,7 @@ title=[[Main Page]] !! input {{int:Bug32057}} !! result -<h2><span class="editsection">[<a href="/index.php?title=Main_Page&action=edit&section=1" title="Edit section: Headline text">edit</a>]</span> <span class="mw-headline" id="Headline_text"> Headline text </span></h2> +<h2><span class="mw-headline" id="Headline_text">Headline text</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Main_Page&action=edit&section=1" title="Edit section: Headline text">edit</a><span class="mw-editsection-bracket">]</span></span></h2> !! end @@ -10176,6 +16124,19 @@ Strip marker in formatNum !! end !! test +Check noCommafy in formatNum +!! options +language=be-tarask +!! input +{{formatnum:123456.78}} +{{formatnum:123456.78|NOSEP}} +!! result +<p>123 456,78 +123456.78 +</p> +!! end + +!! test Strip marker in grammar !! options language=fi @@ -10218,7 +16179,7 @@ nowiki inside link inside heading (bug 18295) !! input ==[[foo|x<nowiki>y</nowiki>z]]== !! result -<h2><span class="editsection">[<a href="/index.php?title=Parser_test&action=edit&section=1" title="Edit section: xyz">edit</a>]</span> <span class="mw-headline" id="xyz"><a href="/index.php?title=Foo&action=edit&redlink=1" class="new" title="Foo (page does not exist)">xyz</a></span></h2> +<h2><span class="mw-headline" id="xyz"><a href="/index.php?title=Foo&action=edit&redlink=1" class="new" title="Foo (page does not exist)">xyz</a></span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Parser_test&action=edit&section=1" title="Edit section: xyz">edit</a><span class="mw-editsection-bracket">]</span></span></h2> !! end @@ -10257,9 +16218,9 @@ Gallery override link with WikiLink (bug 34852) File:foobar.jpg|caption|alt=galleryalt|link=InterWikiLink </gallery> !! result -<ul class="gallery"> +<ul class="gallery mw-gallery-traditional"> <li class="gallerybox" style="width: 155px"><div style="width: 155px"> - <div class="thumb" style="width: 150px;"><div style="margin:68px auto;"><a href="/wiki/InterWikiLink"><img alt="galleryalt" src="http://example.com/images/3/3a/Foobar.jpg" width="120" height="14" /></a></div></div> + <div class="thumb" style="width: 150px;"><div style="margin:68px auto;"><a href="/wiki/InterWikiLink"><img alt="galleryalt" src="http://example.com/images/thumb/3/3a/Foobar.jpg/120px-Foobar.jpg" width="120" height="14" /></a></div></div> <div class="gallerytext"> <p>caption </p> @@ -10276,9 +16237,9 @@ Gallery override link with absolute external link (bug 34852) File:foobar.jpg|caption|alt=galleryalt|link=http://www.example.org </gallery> !! result -<ul class="gallery"> +<ul class="gallery mw-gallery-traditional"> <li class="gallerybox" style="width: 155px"><div style="width: 155px"> - <div class="thumb" style="width: 150px;"><div style="margin:68px auto;"><a href="http://www.example.org"><img alt="galleryalt" src="http://example.com/images/3/3a/Foobar.jpg" width="120" height="14" /></a></div></div> + <div class="thumb" style="width: 150px;"><div style="margin:68px auto;"><a href="http://www.example.org"><img alt="galleryalt" src="http://example.com/images/thumb/3/3a/Foobar.jpg/120px-Foobar.jpg" width="120" height="14" /></a></div></div> <div class="gallerytext"> <p>caption </p> @@ -10295,9 +16256,9 @@ Gallery override link with malicious javascript (bug 34852) File:foobar.jpg|caption|alt=galleryalt|link=" onclick="alert('malicious javascript code!'); </gallery> !! result -<ul class="gallery"> +<ul class="gallery mw-gallery-traditional"> <li class="gallerybox" style="width: 155px"><div style="width: 155px"> - <div class="thumb" style="width: 150px;"><div style="margin:68px auto;"><a href="/wiki/%22_onclick%3D%22alert(%27malicious_javascript_code!%27);"><img alt="galleryalt" src="http://example.com/images/3/3a/Foobar.jpg" width="120" height="14" /></a></div></div> + <div class="thumb" style="width: 150px;"><div style="margin:68px auto;"><a href="/wiki/%22_onclick%3D%22alert(%27malicious_javascript_code!%27);"><img alt="galleryalt" src="http://example.com/images/thumb/3/3a/Foobar.jpg/120px-Foobar.jpg" width="120" height="14" /></a></div></div> <div class="gallerytext"> <p>caption </p> @@ -10308,6 +16269,23 @@ File:foobar.jpg|caption|alt=galleryalt|link=" onclick="alert('malicious javascri !! end !!test +Gallery with invalid title as link (bug 43964) +!! input +<gallery> +File:foobar.jpg|link=< +</gallery> +!! result +<ul class="gallery mw-gallery-traditional"> + <li class="gallerybox" style="width: 155px"><div style="width: 155px"> + <div class="thumb" style="width: 150px;"><div style="margin:68px auto;"><a href="/wiki/File:Foobar.jpg" class="image"><img alt="Foobar.jpg" src="http://example.com/images/thumb/3/3a/Foobar.jpg/120px-Foobar.jpg" width="120" height="14" /></a></div></div> + <div class="gallerytext"> + </div> + </div></li> +</ul> + +!! end + +!!test Language parser function !! input {{#language:ar}} @@ -10328,6 +16306,19 @@ abc !! end !!test +Special parser function +!! input +{{#special:RandomPage}} +{{#special:BaDtItLe}} +{{#special:Foobar}} +!! result +<p>Special:Random +Special:Badtitle +Special:Foobar +</p> +!! end + +!!test Bug 34939 - Case insensitive link parsing ([HttP://]) !! input [HttP://MediaWiki.Org/] @@ -10354,6 +16345,2243 @@ HttP://MediaWiki.Org/ </p> !! end +!!test +Disable TOC +!! options +notoc +!! input +Lead +== Section 1 == +== Section 2 == +== Section 3 == +== Section 4 == +== Section 5 == +!! result +<p>Lead +</p> + +<h2><span class="mw-headline" id="Section_1">Section 1</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Parser_test&action=edit&section=1" title="Edit section: Section 1">edit</a><span class="mw-editsection-bracket">]</span></span></h2> +<h2><span class="mw-headline" id="Section_2">Section 2</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Parser_test&action=edit&section=2" title="Edit section: Section 2">edit</a><span class="mw-editsection-bracket">]</span></span></h2> +<h2><span class="mw-headline" id="Section_3">Section 3</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Parser_test&action=edit&section=3" title="Edit section: Section 3">edit</a><span class="mw-editsection-bracket">]</span></span></h2> +<h2><span class="mw-headline" id="Section_4">Section 4</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Parser_test&action=edit&section=4" title="Edit section: Section 4">edit</a><span class="mw-editsection-bracket">]</span></span></h2> +<h2><span class="mw-headline" id="Section_5">Section 5</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Parser_test&action=edit&section=5" title="Edit section: Section 5">edit</a><span class="mw-editsection-bracket">]</span></span></h2> + +!! end + + +### +### Parsoids-specific tests +### Parsoid-PHP parser incompatibilities +### +!!test +1. SOL-sensitive wikitext tokens as template-args +!!options +parsoid=wt2html,wt2wt +!!input +{{echo|*a}} +{{echo|#a}} +{{echo|:a}} +!!result +<span about="#mwt1" typeof="mw:Transclusion"> +</span><ul about="#mwt1"><li>a</li> +</ul> +<span about="#mwt2" typeof="mw:Transclusion"> +</span><ol about="#mwt2"><li>a</li> +</ol> +<span about="#mwt3" typeof="mw:Transclusion"> +</span><dl about="#mwt3"><dd>a</dd> +</dl> +!!end + +#### ---------------------------------------------------------------- +#### Parsoid-only testing of Parsoid's impl of <ref> and <references> +#### tags. Parsoid's output for these tags differs from that of the +#### PHP parser. +#### ---------------------------------------------------------------- + +!!test +Ref: 1. ref-location should be replaced with an index span +!!options +parsoid +!!input +A <ref>foo</ref> +B <ref name="x">foo</ref> +C <ref name="y" /> +!!result +<p>A <span about="#mwt1" class="reference" data-mw='{"name":"ref","body":{"html":"foo"},"attrs":{}}' id="cite_ref-1-0" rel="dc:references" typeof="mw:Extension/ref"><a href="#cite_note-1">[1]</a></span> +B <span about="#mwt2" class="reference" data-mw='{"name":"ref","body":{"html":"foo"},"attrs":{"name":"x"}}' id="cite_ref-x-2-0" rel="dc:references" typeof="mw:Extension/ref"><a href="#cite_note-x-2">[2]</a></span> +C <span about="#mwt3" class="reference" data-mw='{"name":"ref","attrs":{"name":"y"}}' id="cite_ref-y-3-0" rel="dc:references" typeof="mw:Extension/ref"><a href="#cite_note-y-3">[3]</a></span></p> +!!end + +!!test +Ref: 2. ref-tags with identical names should all get the same index +!!options +parsoid +!!input +A <ref name="x">foo</ref> +B <ref name="x" /> +!!result +<p>A <span about="#mwt1" class="reference" data-mw='{"name":"ref","body":{"html":"foo"},"attrs":{"name":"x"}}' id="cite_ref-x-1-0" rel="dc:references" typeof="mw:Extension/ref"><a href="#cite_note-x-1">[1]</a></span> +B <span about="#mwt2" class="reference" data-mw='{"name":"ref","attrs":{"name":"x"}}' id="cite_ref-x-1-1" rel="dc:references" typeof="mw:Extension/ref"><a href="#cite_note-x-1">[1]</a></span></p> +!!end + +!!test +Ref: 3. spaces in ref-names should be ignored +!!options +parsoid +!!input +A <ref name="x">foo</ref> +B <ref name=" x " /> +C <ref name= x /> +!!result +<p>A <span about="#mwt1" class="reference" data-mw='{"name":"ref","body":{"html":"foo"},"attrs":{"name":"x"}}' id="cite_ref-x-1-0" rel="dc:references" typeof="mw:Extension/ref"><a href="#cite_note-x-1">[1]</a></span> +B <span about="#mwt2" class="reference" data-mw='{"name":"ref","attrs":{"name":"x"}}' id="cite_ref-x-1-1" rel="dc:references" typeof="mw:Extension/ref"><a href="#cite_note-x-1">[1]</a></span> +C <span about="#mwt3" class="reference" data-mw='{"name":"ref","attrs":{"name":"x"}}' id="cite_ref-x-1-2" rel="dc:references" typeof="mw:Extension/ref"><a href="#cite_note-x-1">[1]</a></span></p> +!!end + +!!test +Ref: 4. 'constructor' should be accepted as a valid ref-name +(NOTE: constructor is a predefined property in JS and constructor as a ref-name can clash with it if not handled properly) +!!options +parsoid +!!input +A <ref name="constructor">foo</ref> +!!result +<p>A <span about="#mwt1" class="reference" data-mw='{"name":"ref","body":{"html":"foo"},"attrs":{"name":"constructor"}}' id="cite_ref-constructor-1-0" rel="dc:references" typeof="mw:Extension/ref"><a href="#cite_note-constructor-1">[1]</a></span></p> +!!end + +!!test +Ref: 5. body should accept generic wikitext +!!options +parsoid +!!input +A <ref> + This is a '''[[bolded link]]''' and this is a {{echo|transclusion}} +</ref> + +<references /> +!!result +<p>A <span about="#mwt1" class="reference" data-mw='{"name":"ref","body":{"html":"This is a <b data-parsoid=\"{&quot;dsr&quot;:[19,40,3,3]}\"><a rel=\"mw:WikiLink\" href=\"./Bolded_link\" data-parsoid=\"{&quot;stx&quot;:&quot;simple&quot;,&quot;a&quot;:{&quot;href&quot;:&quot;./Bolded_link&quot;},&quot;sa&quot;:{&quot;href&quot;:&quot;bolded link&quot;},&quot;dsr&quot;:[22,37,2,2]}\">bolded link</a></b> and this is a <span about=\"#mwt5\" typeof=\"mw:Transclusion\" data-mw=\"{&quot;parts&quot;:[{&quot;template&quot;:{&quot;target&quot;:{&quot;wt&quot;:&quot;echo&quot;,&quot;href&quot;:&quot;./Template:Echo&quot;},&quot;params&quot;:{&quot;1&quot;:{&quot;wt&quot;:&quot;transclusion&quot;}},&quot;i&quot;:0}}]}\" data-parsoid=\"{&quot;dsr&quot;:[55,76,null,null],&quot;pi&quot;:[[{&quot;k&quot;:&quot;1&quot;,&quot;spc&quot;:[&quot;&quot;,&quot;&quot;,&quot;&quot;,&quot;&quot;]}]]}\">transclusion</span>\n"},"attrs":{}}' id="cite_ref-1-0" rel="dc:references" typeof="mw:Extension/ref"><a href="#cite_note-1">[1]</a></span></p> + +<ol class="references" typeof="mw:Extension/references" about="#mwt2" data-mw='{"name":"references","attrs":{}}'> +<li about="#cite_note-1" id="cite_note-1"><span rel="mw:referencedBy"><a href="#cite_ref-1-0">↑</a></span> This is a <b><a rel="mw:WikiLink" href="./Bolded_link">bolded link</a></b> and this is a <span about="#mwt3" typeof="mw:Transclusion" data-mw='{"parts":[{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":"transclusion"}},"i":0}}]}'>transclusion</span> +</li> +</ol> +!!end + +!!test +Ref: 6. indent-pres should not be output in ref-body +!!options +parsoid +!!input +A <ref> + foo + bar + baz +</ref> + +<references /> +!!result +<p>A <span about="#mwt1" class="reference" data-mw='{"name":"ref","body":{"html":"foo\n bar\n baz\n"},"attrs":{}}' id="cite_ref-1-0" rel="dc:references" typeof="mw:Extension/ref"><a href="#cite_note-1">[1]</a></span></p> + +<ol class="references" typeof="mw:Extension/references" about="#mwt3" data-mw='{"name":"references","attrs":{}}'> +<li about="#cite_note-1" id="cite_note-1"><span rel="mw:referencedBy"><a href="#cite_ref-1-0">↑</a></span> foo + bar + baz +</li> +</ol> +!!end + +!!test +Ref: 7. No p-wrapping in ref-body +!!options +parsoid +!!input +A <ref> +foo + +bar + + +baz + + + +booz +</ref> + +<references /> +!!result +<p>A <span about="#mwt1" class="reference" data-mw='{"name":"ref","body":{"html":"foo\n\nbar\n\n\nbaz\n\n\n\nbooz\n"},"attrs":{}}' id="cite_ref-1-0" rel="dc:references" typeof="mw:Extension/ref"><a href="#cite_note-1">[1]</a></span></p> + +<ol about="#mwt2" class="references" typeof="mw:Extension/references" data-mw='{"name":"references","attrs":{}}'> +<li about="#cite_note-1" id="cite_note-1"><span rel="mw:referencedBy"><a href="#cite_ref-1-0">↑</a></span> foo + +bar + + +baz + + + +booz +</li> +</ol> +!!end + +!!test +Ref: 8. transclusion wikitext has lower precedence +!!options +parsoid +!!input +A <ref> foo {{echo|</ref> B C}} + +<references /> +!!result +<p>A <span about="#mwt1" class="reference" data-mw='{"name":"ref","body":{"html":"foo <span typeof=\"mw:Nowiki\" data-parsoid=\"{&quot;src&quot;:&quot;{{&quot;,&quot;dsr&quot;:[12,14,0,0]}\">{{</span>echo|"},"attrs":{}}' id="cite_ref-1-0" rel="dc:references" typeof="mw:Extension/ref"><a href="#cite_note-1">[1]</a></span> B C<span typeof="mw:Nowiki">}}</span></p> + +<ol about="#mwt2" class="references" typeof="mw:Extension/references" data-mw='{"name":"references","attrs":{}}'> +<li about="#cite_note-1" id="cite_note-1"><span rel="mw:referencedBy"><a href="#cite_ref-1-0">↑</a></span> foo <span typeof="mw:Nowiki">{{</span>echo|</li> +</ol> +!!end + +!!test +Ref: 9. unclosed comments should not leak out of ref-body +!!options +parsoid +!!input +A <ref> foo <!--</ref> B C + +<references /> +!!result +<p>A <span about="#mwt1" class="reference" data-mw='{"name":"ref","body":{"html":"foo <!---->"},"attrs":{}}' id="cite_ref-1-0" rel="dc:references" typeof="mw:Extension/ref"><a href="#cite_note-1">[1]</a></span> B C</p> + +<ol about="#mwt2" class="references" typeof="mw:Extension/references" data-mw='{"name":"references","attrs":{}}'> +<li about="#cite_note-1" id="cite_note-1"><span rel="mw:referencedBy"><a href="#cite_ref-1-0">↑</a></span> foo <!----></li> +</ol> +!!end + +!!test +Ref: 10. Unclosed HTML tags should not leak out of ref-body +!!options +parsoid +!!input +A <ref> <b> foo </ref> B C + +<references /> +!!result +<p>A <span about="#mwt1" class="reference" data-mw='{"name":"ref","body":{"html":"<b data-parsoid=\"{&quot;stx&quot;:&quot;html&quot;,&quot;autoInsertedEnd&quot;:true,&quot;dsr&quot;:[8,16,3,0]}\"> foo </b>"},"attrs":{}}' id="cite_ref-1-0" rel="dc:references" typeof="mw:Extension/ref"><a href="#cite_note-1">[1]</a></span> B C</p> + +<ol about="#mwt2" class="references" typeof="mw:Extension/references" data-mw='{"name":"references","attrs":{}}'> +<li about="#cite_note-1" id="cite_note-1"><span rel="mw:referencedBy"><a href="#cite_ref-1-0">↑</a></span> <b> foo </b></li> +</ol> +!!end + +!!test +Ref: 11. ref-tags acts like an inline element wrt P-wrapping +!!options +parsoid +!!input +A <ref>foo</ref> B +C <ref>bar</ref> D +!!result +<p>A <span about="#mwt1" class="reference" data-mw='{"name":"ref","body":{"html":"foo"},"attrs":{}}' id="cite_ref-1-0" rel="dc:references" typeof="mw:Extension/ref"><a href="#cite_note-1">[1]</a></span> B +C <span about="#mwt2" class="reference" data-mw='{"name":"ref","body":{"html":"bar"},"attrs":{}}' id="cite_ref-2-0" rel="dc:references" typeof="mw:Extension/ref"><a href="#cite_note-2">[2]</a></span> D</p> +!!end + +!!test +Ref: 12. ref-tags act as trailing newline migration barrier +!!options +parsoid +!!input +<!--the newline at the end of this line moves out of the p-tag-->a + +b<!--the newline at the end of this line stays inside the p-tag--> <ref /> +<ref /> + +c +!!result +<p><!--the newline at the end of this line moves out of the p-tag-->a</p> + + +<p>b<!--the newline at the end of this line stays inside the p-tag--> <span about="#mwt1" class="reference" data-mw='{"name":"ref","attrs":{}}' id="cite_ref-1-0" rel="dc:references" typeof="mw:Extension/ref"><a href="#cite_note-1">[1]</a></span> +<span about="#mwt2" class="reference" data-mw='{"name":"ref","attrs":{}}' id="cite_ref-2-0" rel="dc:references" typeof="mw:Extension/ref"><a href="#cite_note-2">[2]</a></span></p> + + +<p>c</p> +!!end + +!!test +Ref: 13. ref-tags are not SOL-transparent and block indent-pres +!!options +parsoid +!!input +<ref>foo</ref> A +<ref>bar +</ref> B +!!result +<p><span about="#mwt1" class="reference" data-mw='{"name":"ref","body":{"html":"foo"},"attrs":{}}' id="cite_ref-1-0" rel="dc:references" typeof="mw:Extension/ref"><a href="#cite_note-1">[1]</a></span> A +<span about="#mwt2" class="reference" data-mw='{"name":"ref","body":{"html":"bar\n"},"attrs":{}}' id="cite_ref-2-0" rel="dc:references" typeof="mw:Extension/ref"><a href="#cite_note-2">[2]</a></span> B</p> +!!end + +!!test +Ref: 14. A nested ref-tag should be emitted as plain text +!!options +parsoid +!!input +<ref>foo <ref>bar</ref> baz</ref> + +<references /> +!!result +<p><span about="#mwt1" class="reference" data-mw="{"name":"ref","body":{"html":"foo &lt;ref&gt;bar&lt;/ref&gt; baz"},"attrs":{}}" id="cite_ref-1-0" rel="dc:references" typeof="mw:Extension/ref"><a href="#cite_note-1">[1]</a></span></p> + +<ol class="references" typeof="mw:Extension/references" about="#mwt2" data-mw="{"name":"references","attrs":{}}"> +<li about="#cite_note-1" id="cite_note-1" data-parsoid="{}"><span rel="mw:referencedBy"><a href="#cite_ref-1-0">↑</a></span> foo <ref>bar</ref> baz</li> +</ol> +!!end + +!!test +Ref: 15. ref-tags with identical names should get identical indexes +!!options +parsoid +!!input +A1 <ref name="a">foo</ref> A2 <ref name="a" /> +B1 <ref name="b" /> B2 <ref name="b">bar</ref> + +<references /> +!!result +<p>A1 <span about="#mwt3" class="reference" data-mw='{"name":"ref","body":{"html":"foo"},"attrs":{"name":"a"}}' id="cite_ref-a-1-0" rel="dc:references" typeof="mw:Extension/ref"><a href="#cite_note-a-1">[1]</a></span> A2 <span about="#mwt4" class="reference" data-mw='{"name":"ref","attrs":{"name":"a"}}' id="cite_ref-a-1-1" rel="dc:references" typeof="mw:Extension/ref"><a href="#cite_note-a-1">[1]</a></span> +B1 <span about="#mwt7" class="reference" data-mw='{"name":"ref","attrs":{"name":"b"}}' id="cite_ref-b-2-0" rel="dc:references" typeof="mw:Extension/ref"><a href="#cite_note-b-2">[2]</a></span> B2 <span about="#mwt8" class="reference" data-mw='{"name":"ref","body":{"html":"bar"},"attrs":{"name":"b"}}' id="cite_ref-b-2-1" rel="dc:references" typeof="mw:Extension/ref"><a href="#cite_note-b-2">[2]</a></span></p> + +<ol about="#mwt10" class="references" typeof="mw:Extension/references" data-mw='{"name":"references","attrs":{}}'><li about="#cite_note-a-1" id="cite_note-a-1"><span rel="mw:referencedBy">↑ <a href="#cite_ref-a-1-0">1.0</a> <a href="#cite_ref-a-1-1">1.1</a></span> foo</li><li about="#cite_note-b-2" id="cite_note-b-2"><span rel="mw:referencedBy">↑ <a href="#cite_ref-b-2-0">2.0</a> <a href="#cite_ref-b-2-1">2.1</a></span> bar</li> +</ol> +!!end + +!!test +References: 1. references tag without any refs should be handled properly +!!options +parsoid +!!input +<references /> +!!result +<ol about="#mwt2" class="references" typeof="mw:Extension/references" data-mw='{"name":"references","attrs":{}}'></ol> +!!end + +!!test +References: 2. references tag with group only outputs references from that group +!!options +parsoid +!!input +A <ref group="a">foo</ref> +B <ref group="b">bar</ref> + +<references group='a' /> +!!result +<p>A <span about="#mwt2" class="reference" data-mw='{"name":"ref","body":{"html":"foo"},"attrs":{"group":"a"}}' id="cite_ref-1-0" rel="dc:references" typeof="mw:Extension/ref"><a href="#cite_note-1">[a 1]</a></span> +B <span about="#mwt4" class="reference" data-mw='{"name":"ref","body":{"html":"bar"},"attrs":{"group":"b"}}' id="cite_ref-1-0" rel="dc:references" typeof="mw:Extension/ref"><a href="#cite_note-1">[b 1]</a></span></p> + +<ol about="#mwt6" class="references" typeof="mw:Extension/references" data-mw='{"name":"references","attrs":{"group":"a"}}'><li about="#cite_note-1" id="cite_note-1"><span rel="mw:referencedBy"><a href="#cite_ref-1-0">↑</a></span> foo</li> +</ol> +!!end + +!!test +References: 3. ref list should be cleared after processing references +!!options +parsoid +!!input +A <ref>foo</ref> + +<references /> + +B <ref>bar</ref> + +<references /> +!!result +<p>A <span about="#mwt2" class="reference" data-mw='{"name":"ref","body":{"html":"foo"},"attrs":{}}' id="cite_ref-1-0" rel="dc:references" typeof="mw:Extension/ref"><a href="#cite_note-1">[1]</a></span></p> + +<ol about="#mwt4" class="references" typeof="mw:Extension/references" data-mw='{"name":"references","attrs":{}}'><li about="#cite_note-1" id="cite_note-1"><span rel="mw:referencedBy"><a href="#cite_ref-1-0">↑</a></span> foo</li> +</ol> + +<p>B <span about="#mwt6" class="reference" data-mw='{"name":"ref","body":{"html":"bar"},"attrs":{}}' id="cite_ref-1-0" rel="dc:references" typeof="mw:Extension/ref"><a href="#cite_note-1">[1]</a></span></p> + +<ol about="#mwt8" class="references" typeof="mw:Extension/references" data-mw='{"name":"references","attrs":{}}'><li about="#cite_note-1" id="cite_note-1"><span rel="mw:referencedBy"><a href="#cite_ref-1-0">↑</a></span> bar</li> +</ol> +!!end + +!!test +References: 4. only referenced group should be cleared after processing references +!!options +parsoid +!!input +A <ref group="a">afoo</ref> +B <ref>bfoo</ref> + +<references group="a"/> + +C <ref>cfoo</ref> + +<references /> +!!result +<p>A <span about="#mwt2" class="reference" data-mw='{"name":"ref","body":{"html":"afoo"},"attrs":{"group":"a"}}' id="cite_ref-1-0" rel="dc:references" typeof="mw:Extension/ref"><a href="#cite_note-1">[a 1]</a></span> +B <span about="#mwt4" class="reference" data-mw='{"name":"ref","body":{"html":"bfoo"},"attrs":{}}' id="cite_ref-1-0" rel="dc:references" typeof="mw:Extension/ref" data-parsoid='{"src":"<ref>bfoo</ref>","dsr":[30,45,5,6]}'><a href="#cite_note-1">[1]</a></span></p> + +<ol about="#mwt6" class="references" typeof="mw:Extension/references" data-mw='{"name":"references","attrs":{"group":"a"}}'><li about="#cite_note-1" id="cite_note-1"><span rel="mw:referencedBy"><a href="#cite_ref-1-0">↑</a></span> afoo</li> +</ol> + +<p>C <span about="#mwt8" class="reference" data-mw='{"name":"ref","body":{"html":"cfoo"},"attrs":{}}' id="cite_ref-2-0" rel="dc:references" typeof="mw:Extension/ref"><a href="#cite_note-2">[2]</a></span></p> + +<ol about="#mwt10" class="references" typeof="mw:Extension/references" data-mw='{"name":"references","attrs":{}}'><li about="#cite_note-1" id="cite_note-1"><span rel="mw:referencedBy"><a href="#cite_ref-1-0">↑</a></span> bfoo</li><li about="#cite_note-2" id="cite_note-2"><span rel="mw:referencedBy"><a href="#cite_ref-2-0">↑</a></span> cfoo</li> +</ol> +!!end + +!!test +References: 5. ref tags in references should be processed while ignoring all other content +!!options +parsoid +!!input +A <ref name="a" /> +B <ref name="b">bar</ref> + +<references> +<ref name="a">foo</ref> +This should just get lost. +</references> +!!result +<p>A <span about="#mwt2" class="reference" data-mw='{"name":"ref","attrs":{"name":"a"}}' id="cite_ref-a-1-0" rel="dc:references" typeof="mw:Extension/ref"><a href="#cite_note-a-1">[1]</a></span> +B <span about="#mwt4" class="reference" data-mw='{"name":"ref","body":{"html":"bar"},"attrs":{"name":"b"}}' id="cite_ref-b-2-0" rel="dc:references" typeof="mw:Extension/ref"><a href="#cite_note-b-2">[2]</a></span></p> + +<ol class="references" typeof="mw:Extension/references" about="#mwt6" data-mw='{"name":"references","body":{"extsrc":"<ref name=\"a\">foo</ref>\nThis should just get lost.","html":"\n<span about=\"#mwt8\" class=\"reference\" data-mw=\"{&quot;name&quot;:&quot;ref&quot;,&quot;body&quot;:{&quot;html&quot;:&quot;foo&quot;},&quot;attrs&quot;:{&quot;name&quot;:&quot;a&quot;}}\" rel=\"dc:references\" typeof=\"mw:Extension/ref\"><a href=\"#cite_note-a-1\">[1]</a></span>\n"},"attrs":{}}'><li about="#cite_note-a-1" id="cite_note-a-1"><span rel="mw:referencedBy"><a href="#cite_ref-a-1-0">↑</a></span> foo</li><li about="#cite_note-b-2" id="cite_note-b-2"><span rel="mw:referencedBy"><a href="#cite_ref-b-2-0">↑</a></span> bar</li> +</ol> +!!end + +!!test +References: 6. <references /> from a transclusion +!!options +parsoid +!!input +{{echo|<references />}} +!!result +<ol class="references" about="#mwt2" typeof="mw:Transclusion" data-mw='{"parts":[{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":"<references />"}},"i":0}}]}'></ol> +!!end + +!! test +References: 7. Multiple references tags (one without and one with nested refs) should be correctly handled +!! options +parsoid +!! input +A <ref>foo bar for a</ref> +B <ref name="b" /> + +<references /> + +<references> +<ref name="b">foo</ref> +</references> +!! result +<p>A <span about="#mwt2" class="reference" data-mw='{"name":"ref","body":{"html":"foo bar for a"},"attrs":{}}' id="cite_ref-1-0" rel="dc:references" typeof="mw:Extension/ref"><a href="#cite_note-1">[1]</a></span> +B <span about="#mwt4" class="reference" data-mw='{"name":"ref","attrs":{"name":"b"}}' id="cite_ref-b-2-0" rel="dc:references" typeof="mw:Extension/ref"><a href="#cite_note-b-2">[2]</a></span></p> + +<ol class="references" typeof="mw:Extension/references" about="#mwt6" data-mw='{"name":"references","attrs":{}}'> +<li about="#cite_note-1" id="cite_note-1"><span rel="mw:referencedBy"><a href="#cite_ref-1-0">↑</a></span> foo bar for a</li> +<li about="#cite_note-b-2" id="cite_note-b-2"><span rel="mw:referencedBy"><a href="#cite_ref-b-2-0">↑</a></span> </li> +</ol> + +<ol class="references" typeof="mw:Extension/references" about="#mwt8" data-mw='{"name":"references","body":{"extsrc":"<ref name=\"b\">foo</ref>","html":"\n<span about=\"#mwt10\" class=\"reference\" data-mw=\"{&quot;name&quot;:&quot;ref&quot;,&quot;body&quot;:{&quot;html&quot;:&quot;foo&quot;},&quot;attrs&quot;:{&quot;name&quot;:&quot;b&quot;}}\" rel=\"dc:references\" typeof=\"mw:Extension/ref\"><a href=\"#cite_note-b-1\">[1]</a></span>\n"},"attrs":{}}'> +<li about="#cite_note-b-1" id="cite_note-b-1"><span rel="mw:referencedBy">↑</span> foo</li> +</ol> +!! end + +#### ---------------------------------------------------------------- +#### The following section of tests are primarily to test +#### wikitext escaping capabilities of Parsoid. Given that +#### escaping can be done any number of ways, the wikitext (input) +#### is always adjusted to reflect how Parsoid adds nowiki +#### escape tags. +#### +#### We are marking several tests as parsoid-only since the +#### HTML in the result section is different from what the +#### PHP parser generates for it. +#### ---------------------------------------------------------------- + + +#### --------------- Headings --------------- +#### 0. Unnested +#### 1. Nested inside html <h1>=foo=</h1> +#### 2. Outside heading nest on a single line <h1>foo</h1>*bar +#### 3. Nested inside html with wikitext split by html tags +#### 4. No escape needed +#### 5. Empty headings <h1></h1> +#### 6. Heading chars in SOL context +#### ---------------------------------------- +!! test +Headings: 0. Unnested +!! options +parsoid +!! input +<nowiki>=foo=</nowiki> + +<nowiki> =foo= </nowiki> +<!--cmt--> +<nowiki>=foo=</nowiki> + +=foo''a''<nowiki>=</nowiki> +!! result +<p><span typeof="mw:Nowiki">=foo=</span></p> + +<p><span typeof="mw:Nowiki"> =foo= </span> +<!--cmt--> +<span typeof="mw:Nowiki">=foo=</span></p> + +<p>=foo<i>a</i><span typeof="mw:Nowiki">=</span></p> +!!end + +!! test +Headings: 1. Nested inside html +!! options +parsoid +!! input +=<nowiki>=foo=</nowiki>= + +==<nowiki>=foo=</nowiki>== + +===<nowiki>=foo=</nowiki>=== + +====<nowiki>=foo=</nowiki>==== + +=====<nowiki>=foo=</nowiki>===== + +======<nowiki>=foo=</nowiki>====== +!! result +<h1><span typeof="mw:Nowiki">=foo=</span></h1> +<h2><span typeof="mw:Nowiki">=foo=</span></h2> +<h3><span typeof="mw:Nowiki">=foo=</span></h3> +<h4><span typeof="mw:Nowiki">=foo=</span></h4> +<h5><span typeof="mw:Nowiki">=foo=</span></h5> +<h6><span typeof="mw:Nowiki">=foo=</span></h6> +!!end + +!! test +Headings: 2. Outside heading nest on a single line <h1>foo</h1>*bar +!! options +parsoid +!! input +=foo= +<nowiki>*</nowiki>bar + +=foo= +=bar + +=foo= +<nowiki>=bar=</nowiki> +!! result +<h1>foo</h1>*bar +<h1>foo</h1>=bar +<h1>foo</h1>=bar= +!!end + +!! test +Headings: 3. Nested inside html with wikitext split by html tags +!! options +parsoid +!! input +=='''bold'''<nowiki>foo=</nowiki>= +!! result +<h1>=<b>bold</b><span typeof="mw:Nowiki">foo=</span></h1> +!!end + +!! test +Headings: 4a. No escaping needed (testing just h1 and h2) +!! options +parsoid +!! input +==foo= + +=foo== + += =foo= = + +==foo= bar= + +===foo== + +==foo=== + +=''=''foo== + +=<nowiki>=</nowiki>= +!! result +<h1>=foo</h1> +<h1>foo=</h1> +<h1> =foo= </h1> +<h1>=foo= bar</h1> +<h2>=foo</h2> +<h2>foo=</h2> +<h1><i>=</i>foo=</h1> +<h1><span typeof="mw:Nowiki">=</span></h1> +!!end + +!! test +Headings: 4b. No escaping needed (inside p-tags) +!! options +parsoid +!! input +=== +=foo= x +=foo= <s></s> +!! result +<p>=== +=foo= x +=foo= <s></s> +</p> +!!end + +!! test +Headings: 5. Empty headings +!! options +parsoid +!! input +=<nowiki/>= + +==<nowiki/>== + +===<nowiki/>=== + +====<nowiki/>==== + +=====<nowiki/>===== + +======<nowiki/>====== +!! result +<h1></h1> +<h2></h2> +<h3></h3> +<h4></h4> +<h5></h5> +<h6></h6> +!!end + +!! test +Headings: 6a. Heading chars in SOL context (with trailing spaces) +!! options +parsoid +!! input +<nowiki>=a=</nowiki> + +<nowiki>=a=</nowiki> + +<nowiki>=a=</nowiki> + +<nowiki>=a=</nowiki> +!! result +<p>=a=</p> +<p>=a= </p> +<p>=a= </p> +<p>=a= </p> +!!end + +!! test +Headings: 6b. Heading chars in SOL context (with trailing newlines) +!! options +parsoid +!! input +<nowiki>=a= +b</nowiki> + +<nowiki>=a= +b</nowiki> + +<nowiki>=a= +b</nowiki> + +<nowiki>=a= +b</nowiki> +!! result +<p>=a= +b</p> +<p>=a= +b</p> +<p>=a= +b</p> +<p>=a= +b</p> +</p> +!!end + +!! test +Headings: 6c. Heading chars in SOL context (leading newline break) +!! options +parsoid +!! input +a +<nowiki>=b=</nowiki> +!! result +<p>a +=b=</p> +!!end + +!! test +Headings: 6d. Heading chars in SOL context (with interspersed comments) +!! options +parsoid +!! input +<!--c0--><nowiki>=a=</nowiki> +<!--c1--> +<nowiki>=a=</nowiki> <!--c2--> <!--c3--> +!! result +<p><!--c0-->=a=</p> +<p><!--c1-->=a= <!--c2--> <!--c3--></p> +!!end + +!! test +Headings: 6d. Heading chars in SOL context (No escaping needed) +!! options +parsoid=html2wt +!! input +=a=<div>b</div> +!! result +=a=<div>b</div> +!!end + +#### --------------- Lists --------------- +#### 0. Outside nests (*foo, etc.) +#### 1. Nested inside html <ul><li>*foo</li></ul> +#### 2. Inside definition lists +#### 3. Only bullets at start should be escaped +#### 4. No escapes needed +#### 5. No unnecessary escapes +#### 6. Escape bullets in SOL position +#### 7. Escape bullets in a multi-line context +#### ---------------------------------------- + +!! test +Lists: 0. Outside nests +!! input +<nowiki>*</nowiki>foo + +<nowiki>#</nowiki>foo +!! result +<p>*foo +</p><p>#foo +</p> +!!end + +!! test +Lists: 1. Nested inside html +!! input +*<nowiki>*foo</nowiki> + +*<nowiki>#foo</nowiki> + +*<nowiki>:foo</nowiki> + +*<nowiki>;foo</nowiki> + +#<nowiki>*foo</nowiki> + +#<nowiki>#foo</nowiki> + +#<nowiki>:foo</nowiki> + +#<nowiki>;foo</nowiki> +!! result +<ul> +<li>*foo +</li> +</ul> +<ul> +<li>#foo +</li> +</ul> +<ul> +<li>:foo +</li> +</ul> +<ul> +<li>;foo +</li> +</ul> +<ol> +<li>*foo +</li> +</ol> +<ol> +<li>#foo +</li> +</ol> +<ol> +<li>:foo +</li> +</ol> +<ol> +<li>;foo +</li> +</ol> + +!!end + +!! test +Lists: 2. Inside definition lists +!! input +;<nowiki>;foo</nowiki> + +;<nowiki>:foo</nowiki> + +;<nowiki>:foo</nowiki> +:bar + +:<nowiki>:foo</nowiki> +!! result +<dl> +<dt>;foo +</dt> +</dl> +<dl> +<dt>:foo +</dt> +</dl> +<dl> +<dt>:foo +</dt> +<dd>bar +</dd> +</dl> +<dl> +<dd>:foo +</dd> +</dl> + +!!end + +!! test +Lists: 3. Only bullets at start of text should be escaped +!! input +*<nowiki>*foo*bar</nowiki> + +*<nowiki>*foo</nowiki>''it''*bar +!! result +<ul> +<li>*foo*bar +</li> +</ul> +<ul> +<li>*foo<i>it</i>*bar +</li> +</ul> + +!!end + +!! test +Lists: 4. No escapes needed +!! options +parsoid +!! input +*foo*bar + +*''foo''*bar + +*[[Foo]]: bar +!! result +<ul> +<li>foo*bar +</li> +</ul> +<ul> +<li><i>foo</i>*bar +</li> +</ul> +<ul> +<li><a rel="mw:WikiLink" href="Foo">Foo</a>: bar +</li> +</ul> +!!end + +!! test +Lists: 5. No unnecessary escapes +!! input +* bar <span><nowiki>[[foo]]</nowiki></span> + +*=bar <span><nowiki>[[foo]]</nowiki></span> + +*[[bar <span><nowiki>[[foo]]</nowiki></span> + +*]]bar <span><nowiki>[[foo]]</nowiki></span> + +*=bar <span>foo]]</span>= + +* <s></s>: a +!! result +<ul> +<li> bar <span>[[foo]]</span> +</li> +</ul> +<ul> +<li>=bar <span>[[foo]]</span> +</li> +</ul> +<ul> +<li>[[bar <span>[[foo]]</span> +</li> +</ul> +<ul> +<li>]]bar <span>[[foo]]</span> +</li> +</ul> +<ul> +<li>=bar <span>foo]]</span>= +</li> +</ul> +<ul> +<li> <s></s>: a +</li> +</ul> + +!!end + +!! test +Lists: 6. Escape bullets in SOL position +!! options +parsoid +!! input +<!--cmt--><nowiki>*foo</nowiki> +!! result +<p><!--cmt--><span typeof="mw:Nowiki">*foo</span></p> +!!end + +!! test +Lists: 7. Escape bullets in a multi-line context +!! input +a +<nowiki>*</nowiki>b +!! result +<p>a +*b +</p> +!!end + +#### --------------- HRs --------------- +#### 1. Single line +#### ----------------------------------- + +!! test +HRs: 1. Single line +!! options +parsoid +!! input +----<nowiki>----</nowiki> +----=foo= +----*foo +!! result +<hr><span typeof="mw:Nowiki">----</span> +<hr>=foo= +<hr>*foo +!! end + +#### --------------- Tables --------------- +#### 1a. Simple example +#### 1b. No escaping needed (!foo) +#### 1c. No escaping needed (|foo) +#### 1d. No escaping needed (|}foo) +#### +#### 2a. Nested in td (<td>foo|bar</td>) +#### 2b. Nested in td (<td>foo||bar</td>) +#### 2c. Nested in td -- no escaping needed(<td>foo!!bar</td>) +#### +#### 3a. Nested in th (<th>foo!bar</th>) +#### 3b. Nested in th (<th>foo!!bar</th>) +#### 3c. Nested in th -- no escaping needed(<th>foo||bar</th>) +#### +#### 4a. Escape - +#### 4b. Escape + +#### 4c. No escaping needed +#### -------------------------------------- + +!! test +Tables: 1a. Simple example +!! input +<nowiki>{| +|}</nowiki> +!! result +<p>{| +|} +</p> +!! end + +!! test +Tables: 1b. No escaping needed +!! input +!foo +!! result +<p>!foo +</p> +!! end + +!! test +Tables: 1c. No escaping needed +!! input +|foo +!! result +<p>|foo +</p> +!! end + +!! test +Tables: 1d. No escaping needed +!! input +|}foo +!! result +<p>|}foo +</p> +!! end + +!! test +Tables: 2a. Nested in td +!! options +parsoid +!! input +{| +|<nowiki>foo|bar</nowiki> +|} +!! result +<table><tbody><tr> +<td><span typeof="mw:Nowiki">foo|bar</span></td></tr></tbody></table> +!! end + +!! test +Tables: 2b. Nested in td +!! options +parsoid +!! input +{| +|<nowiki>foo||bar</nowiki> +|''it''<nowiki>foo||bar</nowiki> +|} +!! result +<table><tbody><tr> +<td><span typeof="mw:Nowiki">foo||bar</span></td> +<td><i>it</i><span typeof="mw:Nowiki">foo||bar</span></td></tr></tbody></table> +!! end + +!! test +Tables: 2c. Nested in td -- no escaping needed +!! options +parsoid +!! input +{| +|foo!!bar +|} +!! result +<table><tbody><tr><td>foo!!bar +</td></tr></tbody></table> + +!! end + +!! test +Tables: 3a. Nested in th +!! options +parsoid +!! input +{| +!foo!bar +|} +!! result +<table><tbody><tr><th>foo!bar +</th></tr></tbody></table> + +!! end + +!! test +Tables: 3b. Nested in th +!! options +parsoid +!! input +{| +!<nowiki>foo!!bar</nowiki> +|} +!! result +<table> +<tbody><tr><th><span typeof="mw:Nowiki">foo!!bar</span></th></tr> +</tbody></table> +!! end + +!! test +Tables: 3c. Nested in th -- no escaping needed +!! options +parsoid +!! input +{| +!<nowiki>foo||bar</nowiki> +|} +!! result +<table><tbody><tr> +<th><span typeof="mw:Nowiki">foo||bar</span></th></tr></tbody></table> +!! end + +!! test +Tables: 4a. Escape - +!! options +parsoid +!! input +{| +!-bar +|- +|<nowiki>-bar</nowiki> +|} +!! result +<table><tbody> +<tr><th>-bar</th></tr> +<tr> +<td><span typeof="mw:Nowiki">-bar</span></td></tr></tbody></table> +!! end + +!! test +Tables: 4b. Escape + +!! options +parsoid +!! input +{| +!+bar +|- +|<nowiki>+bar</nowiki> +|} +!! result +<table><tbody> +<tr><th>+bar</th></tr> +<tr> +<td><span typeof="mw:Nowiki">+bar</span></td></tr></tbody></table> +!! end + +!! test +Tables: 4c. No escaping needed +!! options +parsoid +!! input +{| +|foo-bar +|foo+bar +|- +|''foo''-bar +|''foo''+bar +|- +|foo +bar|baz ++bar +-bar +|} +!! result +<table><tbody> +<tr><td>foo-bar</td><td>foo+bar</td></tr> +<tr><td><i>foo</i>-bar</td><td><i>foo</i>+bar</td></tr> +<tr><td>foo +<p>bar|baz ++bar +-bar</p></td></tr> +</tbody></table> +!! end + +### SSS FIXME: Disabled right now because accurate html2wt +### on this snippet requires data-parsoid flags that we've +### stripped out of these tests. We should scheme how we +### we want to handle these kind of tests that require +### data-parsoid flags for accurate html2wt serialization + +!! test +Tables: 4d. No escaping needed +!! options +disabled +!! input +{| +||+1 +||-2 +|} +!! result +<table> +<tr> +<td>+1 +</td> +<td>-2 +</td></tr></table> + +!! end + +#### --------------- Links ---------------- +#### 1. Quote marks in link text +#### 2. Wikilinks: Escapes needed +#### 3. Wikilinks: No escapes needed +#### 4. Extlinks: Escapes needed +#### 5. Extlinks: No escapes needed +#### -------------------------------------- +!! test +Links 1. Quote marks in link text +!! options +parsoid +!! input +[[Foo|Foo<nowiki>''boo''</nowiki>]] +!! result +<a rel="mw:WikiLink" href="Foo">Foo''boo''</a> +!! end + +!! test +Links 2. WikiLinks: Escapes needed +!! options +parsoid +!! input +[[Foo|<nowiki>[Foobar]</nowiki>]] +[[Foo|<nowiki>Foobar]</nowiki>]] +[[Foo|x [Foobar] x]] +[[Foo|<nowiki>x [http://google.com g] x</nowiki>]] +[[Foo|<nowiki>[[Bar]]</nowiki>]] +[[Foo|<nowiki>x [[Bar]] x</nowiki>]] +[[Foo|<nowiki>|Bar</nowiki>]] +[[Foo|<nowiki>]]bar</nowiki>]] +[[Foo|<nowiki>[[bar</nowiki>]] +[[Foo|<nowiki>x ]] y [[ z</nowiki>]] +!! result +<a href="Foo" rel="mw:WikiLink">[Foobar]</a> +<a href="Foo" rel="mw:WikiLink">Foobar]</a> +<a href="Foo" rel="mw:WikiLink">x [Foobar] x</a> +<a href="Foo" rel="mw:WikiLink">x [http://google.com g] x</a> +<a href="Foo" rel="mw:WikiLink">[[Bar]]</a> +<a href="Foo" rel="mw:WikiLink">x [[Bar]] x</a> +<a href="Foo" rel="mw:WikiLink">|Bar</a> +<a href="Foo" rel="mw:WikiLink">]]bar</a> +<a href="Foo" rel="mw:WikiLink">[[bar</a> +<a href="Foo" rel="mw:WikiLink">x ]] y [[ z</a> +!! end + +!! test +Links 3. WikiLinks: No escapes needed +!! options +parsoid +!! input +[[Foo|[Foobar]] +[[Foo|foo|bar]] +!! result +<a href="Foo" rel="mw:WikiLink">[Foobar</a> +<a href="Foo" rel="mw:WikiLink">foo|bar</a> +!! end + +!! test +Links 4. ExtLinks: Escapes needed +!! options +parsoid +!! input +[http://google.com <nowiki>[google]</nowiki>] +[http://google.com <nowiki>google]</nowiki>] +!! result +<a href="http://google.com" rel="mw:ExtLink">[google]</a> +<a href="http://google.com" rel="mw:ExtLink">google]</a> +!! end + +!! test +Links 5. ExtLinks: No escapes needed +!! options +parsoid +!! input +[http://google.com [google] +!! result +<a href="http://google.com" rel="mw:ExtLink">[google</a> +!! end + +#### --------------- Quotes --------------- +#### 1. Quotes inside <b> and <i> +#### 2. Link fragments separated by <i> and <b> tags +#### 3. Link fragments inside <i> and <b> +#### 4. No escaping needed +#### -------------------------------------- +!! test +1. Quotes inside <b> and <i> +!! options +parsoid=html2wt,wt2wt +!! input +''<nowiki>'foo'</nowiki>'' +''<nowiki>''foo''</nowiki>'' +''<nowiki>'''foo'''</nowiki>'' +''foo''<nowiki/>'s +'''<nowiki>'foo'</nowiki>''' +'''<nowiki>''foo''</nowiki>''' +'''<nowiki>'''foo'''</nowiki>''' +'''<nowiki>foo'</nowiki>''<nowiki>bar'</nowiki>''baz''' +'''foo'''<nowiki/>'s +'''foo'' +''foo''<nowiki/>' +'<nowiki/>''foo''<nowiki/>' +''''foo''' +'''foo'''<nowiki/>' +'<nowiki/>'''foo'''<nowiki/>' +!! result +<p><i>'foo'</i> +<i>''foo''</i> +<i>'''foo'''</i> +<i>foo</i>'s +<b>'foo'</b> +<b>''foo''</b> +<b>'''foo'''</b> +<b>foo'<i>bar'</i>baz</b> +<b>foo</b>'s +'<i>foo</i> +<i>foo</i>' +'<i>foo</i>' +'<b>foo</b> +<b>foo</b>' +'<b>foo</b>'</p> +!! end + +!! test +2. Link fragments separated by <i> and <b> tags +!! input +[[''foo''<nowiki>hello]]</nowiki> + +[['''foo'''<nowiki>hello]]</nowiki> +!! result +<p>[[<i>foo</i>hello]] +</p><p>[[<b>foo</b>hello]] +</p> +!! end + +!! test +3. Link fragments inside <i> and <b> +(FIXME: Escaping one or both of [[ and ]] is also acceptable -- + this is one of the shortcomings of this format) +!! input +''[[foo''<nowiki>]]</nowiki> + +'''[[foo'''<nowiki>]]</nowiki> +!! result +<p><i>[[foo</i>]] +</p><p><b>[[foo</b>]] +</p> +!! end + +!! test +4. No escaping needed +!! input +'<span>''bar''</span>' +'<span>'''bar'''</span>' +!! result +<p>'<span><i>bar</i></span>' +'<span><b>bar</b></span>' +</p> +!! end + +#### ----------- Paragraphs --------------- +#### 1. No unnecessary escapes +#### -------------------------------------- + +!! test +1. No unnecessary escapes +!! input +bar <span><nowiki>[[foo]]</nowiki></span> + +=bar <span><nowiki>[[foo]]</nowiki></span> + +[[bar <span><nowiki>[[foo]]</nowiki></span> + +]]bar <span><nowiki>[[foo]]</nowiki></span> + +=bar <span>foo]]</span><nowiki>=</nowiki> +!! result +<p>bar <span>[[foo]]</span> +</p><p>=bar <span>[[foo]]</span> +</p><p>[[bar <span>[[foo]]</span> +</p><p>]]bar <span>[[foo]]</span> +</p><p>=bar <span>foo]]</span>= +</p> +!!end + +#### ----------------------- PRE -------------------------- +#### 1. Leading whitespace in SOL context should be escaped +#### ------------------------------------------------------ +!! test +1. Leading whitespace in SOL context should be escaped +!! options +parsoid +!! input +<nowiki> </nowiki>a + +<nowiki> </nowiki> a + +<nowiki> </nowiki>a(tab) + +<nowiki> </nowiki> a +<!--cmt--> +<nowiki> </nowiki> a + +a +<nowiki> </nowiki>b + +a +<nowiki> </nowiki>b + +a +<nowiki> </nowiki> b +!! result +<p> a</p> +<p> a</p> +<p> a(tab)</p> +<p> a</p> +<p><!--cmt--> a</p> +<p>a + b</p> +<p>a + b</p> +<p>a + b</p> +!! end + +#### --------------- Behavior Switches -------------------- +!! test +1. Valid behavior switches should be escaped +!! options +parsoid=html2wt +!! input +<nowiki>__TOC__</nowiki> +!! result +__TOC__ +!! end + +!! test +2. Invalid behavior switches should not be escaped +!! options +parsoid=html2wt +!! input +__TOO__ +__|__ +!! result +__TOO__ +__|__ +!! end + +#### --------------- HTML tags --------------- +#### 1. a tags +#### 2. other tags +#### 3. multi-line html tag +#### ----------------------------------------- +!! test +1. a tags +!! options +parsoid +!! input +<a href="http://google.com">google</a> +!! result +<a href="http://google.com">google</a> +!! end + +!! test +2. other tags +!! input +<nowiki><div>foo</div> +<div style="color:red">foo</div></nowiki> +!! result +<p><div>foo</div> +<div style="color:red">foo</div> +</p> +!! end + +!! test +3. multi-line html tag +!! input +<nowiki><div +>foo</div +></nowiki> +!! result +<p><div +>foo</div +> +</p> +!! end + +!! test +4. extension tags +!! input +<nowiki><ref>foo</ref></nowiki> +!! result +<p><ref>foo</ref> +</p> +!! end + +#### --------------- Others --------------- +!! test +Escaping nowikis +!! input +<nowiki>foo</nowiki> +!! result +<p><nowiki>foo</nowiki> +</p> +!! end + +## The quote-char in the input is necessary for triggering the bug +!! test +(Bug 52035) Nowiki-escaping should not get tripped by " :" in text +!! options +parsoid=wt2wt,html2wt +!! input +foo's bar : +!! result +<p>foo's bar :</p> +!! end + +!! test + +Tag-like HTML structures are passed through as text +!! input +<x y> + +<x.y> + +<x-y> + +1>2 + +x<y + +a>b + +1<d e>f +!! result +<p><x y> +</p><p><x.y> +</p><p><x-y> +</p><p>1>2 +</p><p>x<y +</p><p>a>b +</p><p>1<d e>f +</p> +!! end + + +# This was a bug in the PHP parser (see bug 17663 and its dups, +# https://bugzilla.wikimedia.org/show_bug.cgi?id=17663) +!! test +Tag names followed by punctuation should not be recognized as tags +!! input +<s.ome> text +!! result +<p><s.ome> text +</p> +!! end + +!! test +HTML tag with necessary entities in attributes +!! input +<span title="&amp;">foo</span> +!! result +<p><span title="&amp;">foo</span> +</p> +!! end + +!! test +HTML tag with 'unnecessary' entity encoding in attributes +!! input +<span title="&">foo</span> +!! result +<p><span title="&">foo</span> +</p> +!! end + +!! test +HTML tag with broken attribute value quoting +!! input +<span title="Hello world>Foo</span> +!! result +<p><span>Foo</span> +</p> +!! end + +!! test +Parsoid-only: HTML tag with broken attribute value quoting +!! options +parsoid +!! input +<span title="Hello world>Foo</span> +!! result +<p><span title="Hello world">Foo</span> +</p> +!! end + +!! test +Table with broken attribute value quoting +!! input +{| +| title="Hello world|Foo +|} +!! result +<table> +<tr> +<td>Foo +</td></tr></table> + +!! end + +!! test +Table with broken attribute value quoting on consecutive lines +!! input +{| +| title="Hello world|Foo +| style="color:red|Bar +|} +!! result +<table> +<tr> +<td>Foo +</td> +<td>Bar +</td></tr></table> + +!! end + +!! test +Parsoid-only: Table with broken attribute value quoting on consecutive lines +!! options +parsoid +!! input +{| +| title="Hello world|Foo +| style="color:red|Bar +|} +!! result +<table><tbody> +<tr> +<td title="Hello world">Foo +</td><td style="color: red">Bar +</td></tr></tbody></table> + +!! end + +!! test +Parsoid-only: Don't wrap broken template tags in <nowiki> on wt2wt (Bug 42353) +!! options +parsoid +!! input +{{}} +!! result +{{}} +!! end + +!! test +Parsoid-only: Don't wrap broken template tags in <nowiki> on wt2wt (Bug 42353) +!! options +parsoid +!! input +}}{{ +!! result +}}{{ +!! end + +!!test +Accept empty td cell attribute +!!input +{| +| align="center" | foo || | +|} +!!result +<table> +<tr> +<td align="center"> foo </td> +<td> +</td></tr></table> + +!!end + +!!test +Non-empty attributes in th-cells +!!input +{| +! Foo !! style="color: red" | Bar +|} +!!result +<table> +<tr> +<th> Foo </th> +<th style="color: red"> Bar +</th></tr></table> + +!!end + +!!test +Accept empty attributes in th-cells +!!input +{| +!| foo !!| bar +|} +!!result +<table> +<tr> +<th> foo </th> +<th> bar +</th></tr></table> + +!!end + +!!test +Empty table rows go away +!!input +{| +| Hello +| there +|- class="foo" +|- +|} +!! result +<table> +<tr> +<td> Hello +</td> +<td> there +</td></tr> + +</table> + +!! end + +### +### Parsoid-centric tests for testing RTing of inter-element separators +### Edge cases not tested by existing parser tests and specific to +### Parsoid-specific serialization strategies. +### + +!!test +RT-ed inter-element separators should be valid separators +!!input +{| +|- [[foo]] +|} +!!result +<table> + +</table> + +!!end + +!!test +Trailing newlines in a deep dom-subtree that ends a wikitext line should be migrated out +(Parsoid-only since PHP parser relies on Tidy for correct output) +!!options +parsoid +!!input +{| +|<small>foo +bar +|} + +{| +|<small>foo<small> +|} +!!result +!!end + +!!test +Empty TD followed by TD with tpl-generated attribute +!!input +{| +|- +| +|{{echo|style='color:red'}}|foo +|} +!!result +<table> + +<tr> +<td> +</td> +<td>foo +</td></tr></table> + +!!end + +!!test +Indented table with an empty td +!!input + {| + |- + | + |foo + |} +!!result +<table> + +<tr> +<td> +</td> +<td>foo +</td></tr></table> + +!!end + +!!test +Empty TR followed by a template-generated TR +(Parsoid-specific since PHP parser doesn't handle this mixed tbl-wikitext) +!!options +parsoid +!!input +{| +|- +{{echo|<tr><td>foo</td></tr>}} +|} +!!result +<table> +<tbody> +<tr></tr> +<tr about="#mwt1" typeof="mw:Transclusion" data-mw='{"parts":[{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":"<tr><td>foo</td></tr>"}},"i":0}}]}'> +<td>foo</td></tr> +</tbody></table> +!!end + +## PHP and parsoid output differ for this, and since this is primarily +## for testing Parsoid's serializer, marking this Parsoid only +!!test +Empty TR followed by mixed-ws-comment line should RT correctly +!!options +parsoid +!!input +{| +|- + <!--c--> +|- +<!--c--> <!--d--> +|} +!!result +<table> +<tbody> +<tr></tr> + <!--c--> +<tr> +<!--c--> </tr><!--d--> +</tbody></table> + +!!end + +!!test +Multi-line image caption generated by templates with/without trailing newlines +!!options +parsoid +!!input +[[File:foo.jpg|thumb|300px|foo\n{{echo|A}}\n{{echo|B}}\n{{echo|C}}]] +[[File:foo.jpg|thumb|300px|foo\n{{echo|A}}\n{{echo|B}}\n{{echo|C}}\n\n]] +!!result +<div class="thumb tright"><div class="thumbinner" style="width:182px;"><a href="/index.php?title=Special:Upload&wpDestFile=Foo.jpg" class="new" title="File:Foo.jpg">File:Foo.jpg</a> <div class="thumbcaption">foo\nA\nB\nC</div></div></div> +<div class="thumb tright"><div class="thumbinner" style="width:182px;"><a href="/index.php?title=Special:Upload&wpDestFile=Foo.jpg" class="new" title="File:Foo.jpg">File:Foo.jpg</a> <div class="thumbcaption">foo\nA\nB\nC\n\n</div></div></div> + +!!end + +## PHP emits broken html for this, and since this is primarily +## a Parsoid serializer test, marking this Parsoid only +!!test +Improperly nested inline or quotes tags with whitespace in between +!!options +parsoid +!!input +<span> <s>x</span> </s> +''' ''x''' '' +!!result +<p><span> <s>x</s></span><s> </s> +<b> <i>x</i></b><i> </i> +</p> +!!end + +!!test +Encapsulate protected attributes from wt +!!options +parsoid +!!input +<div typeof="mw:placeholder stuff" data-parsoid="weird" data-parsoid-other="no" about="time" rel="mw:true">foo</div> +!!result +<body><div data-x-typeof="mw:placeholder stuff" data-x-data-parsoid="weird" data-x-data-parsoid-other="no" data-x-about="time" data-x-rel="mw:true">foo</div> +</body> +!!end + +## Currently the p-wrapper is fragile in how adds / removes transformations. +## Having nested or stray pre tags results in the attempt to add duplicates, +## causing an assertion fail. This test tries to prevent that situation. +!!test +Ensure ParagraphWrapper can deal with stray closing pre tags +!!options +parsoid=wt2html +!!input +plain text</pre> +!!result +plain text +!!end + +!!test +Ensure fostered text content is wrapped in spans +!!options +parsoid=wt2html +!!input +<table>hi</table><table>ho</table> +!!result +<span>hi</span> +<table></table> +<span>ho</span> +<table></table> +!!end + +!!test +Encapsulation properly handles null DSR information from foster box +!!options +parsoid=wt2html,wt2wt +!!input +{{echo|<table>foo<tr><td>bar</td></tr></table>}} +!!result +<span typeof="mw:Transclusion" data-mw="{"parts":[{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":" +<table>foo +<tr> +<td>bar</td></tr></table>"}},"i":0}}]}">foo</span> +<table> +<tbody> +<tr> +<td>bar</td></tr></tbody></table> +!!end + +!!test +1. Encapsulate foster-parented transclusion content +!!options +parsoid=wt2wt,wt2html +!!input +<table>{{echo|foo<tr><td>bar</td></tr>}}</table> +!!result +<span typeof="mw:Transclusion" data-mw="{"parts":[" +<table>",{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":"foo +<tr> +<td>bar</td></tr>"}},"i":0}},"</table>"]}">foo</span> +<table> +<tbody> +<tr> +<td>bar</td></tr></tbody></table> +!!end + +!!test +2. Encapsulate foster-parented transclusion content +!!options +parsoid=wt2wt,wt2html +!!input +<table><div>{{echo|foo}}</div><tr><td>bar</td></tr></table> +!!result +<div typeof="mw:Transclusion" data-mw="{"parts":[" +<table> +<div>",{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":"foo"}},"i":0}},"</div> +<tr> +<td>bar</td></tr></table>"]}">foo</div> +<table> +<tbody> +<tr> +<td>bar</td></tr></tbody></table> +!!end + +!!test +3. Encapsulate foster-parented transclusion content +!!options +parsoid=wt2wt,wt2html +!!input +<table><div><p>{{echo|foo</p></div><tr><td>}}bar</td></tr></table> +!!result +<div typeof="mw:Transclusion" data-mw="{"parts":[" +<table> +<div> +<p>",{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":"foo</p></div> +<tr> +<td>"}},"i":0}},"bar</td></tr></table>"]}"> +<p>foo</p></div> +<table> +<tbody> +<tr> +<td>bar</td></tr></tbody></table> +!!end + +!!test +4. Encapsulate foster-parented transclusion content +!!options +parsoid=wt2wt,wt2html +!!input +<table><div><p>{{echo|foo</p></div><tr><td>}}bar</td></tr></table> +!!result +<div typeof="mw:Transclusion" data-mw="{"parts":[" +<table> +<div> +<p>",{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":"foo</p></div> +<tr> +<td>"}},"i":0}},"bar</td></tr></table>"]}"> +<p>foo</p></div> +<table> +<tbody> +<tr> +<td>bar</td></tr></tbody></table> +!!end + +!!test +5. Encapsulate foster-parented transclusion content +!!options +parsoid=wt2wt,wt2html +!!input +<table><tr><td><div><p>{{echo|foo</p></div></td>foo}}</tr></table> +!!result +<span typeof="mw:Transclusion" data-mw="{"parts":[" +<table> +<tr> +<td> +<div> +<p>",{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":"foo</p></div></td>foo"}},"i":0}},"</tr></table>"]}">foo</span> +<table> +<tbody> +<tr> +<td> +<div> +<p>foo</p></div></td></tr></tbody></table> +!!end + +!!test +6. Encapsulate foster-parented transclusion content +!!options +parsoid=wt2wt,wt2html +!!input +<table><tr><td><div><p>{{echo|foo</p></div></td>foo</tr></table>}}<p>ok</p> +!!result +<span typeof="mw:Transclusion" data-mw="{"parts":[" +<table> +<tr> +<td> +<div> +<p>",{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":"foo</p></div></td>foo</tr></table>"}},"i":0}}]}">foo</span> +<table> +<tbody> +<tr> +<td> +<div> +<p>foo</p></div></td></tr></tbody></table> +<p>ok</p> +!!end + +!!test +7. Encapsulate foster-parented transclusion content +!!options +parsoid=wt2wt,wt2html +!!input +<table>{{echo|<p>foo</p>}}<td>bar</td></table> +!!result +<p typeof="mw:Transclusion" data-mw="{"parts":[" +<table>",{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":" +<p>foo</p>"}},"i":0}}," +<td>bar</td></table>"]}">foo</p> +<table> +<tbody> +<tr> +<td>bar</td></tr></tbody></table> +!!end + +!!test +8. Encapsulate foster-parented transclusion content +!!options +parsoid=wt2wt,wt2html +!!input +{{echo|a +}}{|{{echo|style='color:red'}} +|- +|b +|} +!!result +<p typeof="mw:Transclusion" data-mw="{"parts":[{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":"a\n"}},"i":0}}]}">a</p><span typeof="mw:Transclusion" data-mw="{"parts":["{|",{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"style":{"wt":"'color:red'"}},"i":0}},"\n|-\n|b\n|}"]}">{{{1}}}</span> +<table> +<tbody> +<tr> +<td>b</td></tr></tbody></table> +!!end + +# ----------------------------------------------------------------- +# The following section of tests are primarily to spec requirements +# around serialization of new/edited content. +# +# All these tests are marked Parsoid html2wt and html2html only +# ---------------------------------------------------------------- + +!! test +Image: Modifying size of an image +!! options +parsoid=html2wt +!! input +[[Image:Wiki.png|230x230px]] +!! result +<p data-parsoid='{"dsr":[0,24,0,0]}'><span typeof="mw:Image" data-parsoid='{"optList":[{"ck":"width","ak":"100px"}],"cacheKey":"[[Image:Wiki.png|100px]]","img":{"h":115,"w":100,"wdset":true},"dsr":[0,24,null,null]}'><a href="./File:Wiki.png" data-parsoid='{"a":{"href":"./File:Wiki.png"}}'><img resource="./File:Wiki.png" src="//upload.wikimedia.org/wikipedia/en/thumb/b/bc/Wiki.png/100px-Wiki.png" height="230" width="200" data-parsoid='{"a":{"resource":"./File:Wiki.png"},"sa":{"resource":"Image:Wiki.png"}}'></a></span></p> +!!end + +!! test +Image: New block level image should have \n before and after +!! options +parsoid=html2wt +!! input +123 +[[File:Wiki.png|right|thumb|150x150px]] +456 +!! result +<p>123</p><figure typeof="mw:Image/Thumb" class="mw-halign-right"><a href="./File:Wiki.png"><img src="http://192.168.142.128/mw/images/thumb/b/bc/Wiki.png/131px-Wiki.png" width="131" height="150" resource="./File:Wiki.png"></a></figure><p>456</p> +!!end + +# Wacky -- the leading newline in input is required because +# that is what the serializer emits. To be fixed. Not fixing +# the test because this test is required to test serialization of +# new content and preferred whitespace style. +!! test +Lists: Serialize correctly even when list content is wrapped in p-tags (like VE does) +!! options +parsoid=html2wt +!! input + +* foo +!! result +<ul> +<li><p>foo</p></li> +</ul> +!! end + +# Wacky -- the leading newline in input is required because +# that is what the serializer emits. To be fixed. Not fixing +# the test because this test is required to test serialization of +# new content and preferred whitespace style. +!! test +Lists: Add space after bullets +!! options +parsoid=html2wt +!! input + +* foo +* bar +* <span> baz</span> +!! result +<ul> +<li>foo</li> +<li> bar</li> +<li><span> baz</span></li> +</ul> +!! end + +!! test +Parsoid: Serialize positional parameters with = in them as named parameter +!! options +parsoid=html2wt +!! input +{{echo|1 = f=oo}} + +{{echo|1 = f=oo|2 = bar}} + +<!--Orig params with data-parsoid has heuristics for handling = chars--> +<!--FIXME: But maybe the heuristic needs fixing to apply to new params as well--> +{{echo|<nowiki>f=oo</nowiki>|bar}} +!! result +<p about="#mwt1" typeof="mw:Transclusion" +data-mw='{"parts":[{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":"f=oo"}},"i":0}}]}'>foo</p> + +<p about="#mwt1" typeof="mw:Transclusion" +data-mw='{"parts":[{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":"f=oo"}, "2":{"wt":"bar"}},"i":0}}]}'>foo</p> + +<!--Orig params with data-parsoid has heuristics for handling = chars--> +<!--FIXME: But maybe the heuristic needs fixing to apply to new params as well--> +<p data-parsoid='{"pi":[[{"k":"1","spc":["","","",""]},{"k":"2","spc":["","","",""]}]]}' about="#mwt1" typeof="mw:Transclusion" data-mw='{"parts":[{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":"f=oo"},"2":{"wt":"bar"}},"i":0}}]}'>foo</p> +!! end + +!! test +Parsoid: Correctly serialize block-node children when they are a combination of text and p-nodes +!! options +parsoid=html2wt +!! input +<div>a +b +</div> +<div>a +b +</div> +<div> +a + +b +</div> +!! result +<div>a<p>b</p></div> +<div>a +<p>b</p></div> +<div> +a +<p>b</p></div> +!! end + +#----------------------------- +# I/B quote minimization tests +#----------------------------- + +!! test +1. I/B quote minimization: wikitext-only tags should be combined +!! options +parsoid=html2wt +!! input +''AB'' + +'''AB''' + +''A'''B''''' + +'''A''B''''' + +'''A''BC''D''' + +'''''AB''''' + +'''''AB''''' + +'''''AB''''' +!! result +<p><i>A</i><i>B</i></p> +<p><b>A</b><b>B</b></p> +<p><i>A</i><b><i>B</i></b></p> +<p><b>A</b><i><b>B</b></i></p> +<p><b>A</b><i><b>B</b><b>C</b></i><b>D</b></p> +<p><i><b>A</b></i><i><b>B</b></i></p> +<p><i><b>A</b></i><b><i>B</i></b></p> +<p><b><i>A</i></b><i><b>B</b></i></p> +!! end + +!! test +2. I/B quote minimization: wikitext and html tags should not be combined +!! options +parsoid=html2wt +!! input +''A''<i>B</i> + +''A'''''<i>B</i>''' +!! result +<p><i>A</i><i data-parsoid='{"stx":"html"}'>B</i></p> +<p><i>A</i><b><i data-parsoid='{"stx":"html"}'>B</i></b></p> +!! end + +!! test +3. I/B quote minimization: templated content stops minimization +!! options +parsoid=html2wt +!! input +''A''{{echo|''B''}} + +''A''{{echo|'''''B'''''}} +!! result +<p><i>A</i><i about="#mwt1" typeof="mw:Transclusion" data-mw='{"parts":[{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":"''B''"}},"i":0}}]}'>B</i> +<p><i>A</i><b about="#mwt1" typeof="mw:Transclusion" data-mw='{"parts":[{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":"'''''B'''''"}},"i":0}}]}'><i>B</i></b> +!! end + +!! test +4. I/B quote minimization: new content should be mimimized with adjacent old content +!! options +parsoid=html2wt +!! input +''AB'' + +'''AB''' + +''A'''B''''' +!! result +<p><i>A</i><i data-parsoid='{}'>B</i></p> +<p><b data-parsoid='{}'>A</b><b>B</b></p> +<p><i>A</i><b data-parsoid='{}'><i data-parsoid='{}'>B</i></b></p> +!! end + +# ----------------------------------------------------------------- +# End of section for Parsoid-only html2wt tests for serialization +# of new content +# ----------------------------------------------------------------- TODO: more images diff --git a/tests/parser/parserTestsParserHook.php b/tests/parser/parserTestsParserHook.php index 24d852c5..c8b3e897 100644 --- a/tests/parser/parserTestsParserHook.php +++ b/tests/parser/parserTestsParserHook.php @@ -34,23 +34,19 @@ class ParserTestParserHook { } static function dumpHook( $in, $argv ) { - ob_start(); - var_dump( - $in, - $argv - ); - $ret = ob_get_clean(); - - return "<pre>\n$ret</pre>"; + return "<pre>\n" . + var_export( $in, true ) . "\n" . + var_export( $argv, true ) . "\n" . + "</pre>"; } static function staticTagHook( $in, $argv, $parser ) { - if ( ! count( $argv ) ) { + if ( !count( $argv ) ) { $parser->static_tag_buf = $in; return ''; } elseif ( count( $argv ) === 1 && isset( $argv['action'] ) - && $argv['action'] === 'flush' && $in === null ) - { + && $argv['action'] === 'flush' && $in === null + ) { // Clear the buffer, we probably don't need to if ( isset( $parser->static_tag_buf ) ) { $tmp = $parser->static_tag_buf; @@ -59,12 +55,12 @@ class ParserTestParserHook { } $parser->static_tag_buf = null; return $tmp; - } else - // wtf? + } else { // wtf? return "\nCall this extension as <statictag>string</statictag> or as" . " <statictag action=flush/>, not in any other way.\n" . "text: " . var_export( $in, true ) . "\n" . "argv: " . var_export( $argv, true ) . "\n"; + } } } diff --git a/tests/parser/preprocess/All_system_messages.expected b/tests/parser/preprocess/All_system_messages.expected index 897c5fb0..078d8f0d 100644 --- a/tests/parser/preprocess/All_system_messages.expected +++ b/tests/parser/preprocess/All_system_messages.expected @@ -1239,27 +1239,6 @@ diff </td><td> <template lineStart="1"><title>int:Difference</title></template> </td></tr><tr><td> -[http://tl.wiktionary.org/w/wiki.phtml?title=MediaWiki:Disambiguations&action=edit disambiguations]<br> -[[MediaWiki_talk:Disambiguations|Talk]] -</td><td> -Disambiguation pages -</td><td> -<template lineStart="1"><title>int:Disambiguations</title></template> -</td></tr><tr><td> -[http://tl.wiktionary.org/w/wiki.phtml?title=MediaWiki:Disambiguationspage&action=edit disambiguationspage]<br> -[[MediaWiki_talk:Disambiguationspage|Talk]] -</td><td> -Wiktionary:Links_to_disambiguating_pages -</td><td> -<template lineStart="1"><title>int:Disambiguationspage</title></template> -</td></tr><tr><td> -[http://tl.wiktionary.org/w/wiki.phtml?title=MediaWiki:Disambiguationstext&action=edit disambiguationstext]<br> -[[MediaWiki_talk:Disambiguationstext|Talk]] -</td><td> -The following pages link to a &lt;i&gt;disambiguation page&lt;/i&gt;. They should link to the appropriate topic instead.&lt;br /&gt;A page is treated as dismbiguation if it is linked from $1.&lt;br /&gt;Links from other namespaces are &lt;i&gt;not&lt;/i&gt; listed here. -</td><td> -<template lineStart="1"><title>int:Disambiguationstext</title></template> -</td></tr><tr><td> [http://tl.wiktionary.org/w/wiki.phtml?title=MediaWiki:Disclaimerpage&action=edit disclaimerpage]<br> [[MediaWiki_talk:Disclaimerpage|Talk]] </td><td> diff --git a/tests/parser/preprocess/All_system_messages.txt b/tests/parser/preprocess/All_system_messages.txt index fc10d7cf..3c30da94 100644 --- a/tests/parser/preprocess/All_system_messages.txt +++ b/tests/parser/preprocess/All_system_messages.txt @@ -1239,27 +1239,6 @@ diff </td><td> {{int:Difference}} </td></tr><tr><td> -[http://tl.wiktionary.org/w/wiki.phtml?title=MediaWiki:Disambiguations&action=edit disambiguations]<br> -[[MediaWiki_talk:Disambiguations|Talk]] -</td><td> -Disambiguation pages -</td><td> -{{int:Disambiguations}} -</td></tr><tr><td> -[http://tl.wiktionary.org/w/wiki.phtml?title=MediaWiki:Disambiguationspage&action=edit disambiguationspage]<br> -[[MediaWiki_talk:Disambiguationspage|Talk]] -</td><td> -Wiktionary:Links_to_disambiguating_pages -</td><td> -{{int:Disambiguationspage}} -</td></tr><tr><td> -[http://tl.wiktionary.org/w/wiki.phtml?title=MediaWiki:Disambiguationstext&action=edit disambiguationstext]<br> -[[MediaWiki_talk:Disambiguationstext|Talk]] -</td><td> -The following pages link to a <i>disambiguation page</i>. They should link to the appropriate topic instead.<br />A page is treated as dismbiguation if it is linked from $1.<br />Links from other namespaces are <i>not</i> listed here. -</td><td> -{{int:Disambiguationstext}} -</td></tr><tr><td> [http://tl.wiktionary.org/w/wiki.phtml?title=MediaWiki:Disclaimerpage&action=edit disclaimerpage]<br> [[MediaWiki_talk:Disclaimerpage|Talk]] </td><td> diff --git a/tests/parser/preprocess/NestedTemplates.expected b/tests/parser/preprocess/NestedTemplates.expected new file mode 100644 index 00000000..645626df --- /dev/null +++ b/tests/parser/preprocess/NestedTemplates.expected @@ -0,0 +1,90 @@ +<root><template><title>vorlage</title></template> + +<tplarg lineStart="1"><title>argument</title></tplarg> + +Nach [[:meta:Help:Expansion#XML parse tree]] +{<tplarg><title>vorlagenname</title></tplarg>} +<template lineStart="1"><title> <template><title>vorlagenname</title></template></title></template> +<template lineStart="1"><title><template><title>vorlagenname</title></template> </title></template> +<template lineStart="1"><title><template><title>vorlagenname</title></template>erweiterung</title></template> + +<template lineStart="1"><title><tplarg><title>vorlagenname</title></tplarg></title></template> +<tplarg lineStart="1"><title> <template><title>vorlagenname</title></template></title></tplarg> +<template lineStart="1"><title> <tplarg><title>vorlagenname</title></tplarg></title></template> +<tplarg lineStart="1"><title><template><title>vorlagenname</title></template> </title></tplarg> +<template lineStart="1"><title><tplarg><title>vorlagenname</title></tplarg> </title></template> + +nur etwas erweitert +<tplarg lineStart="1"><title><tplarg><title>vorlagenname</title></tplarg></title></tplarg> +<tplarg lineStart="1"><title> <tplarg><title>vorlagenname</title></tplarg></title></tplarg> +<tplarg lineStart="1"><title><tplarg><title>vorlagenname</title></tplarg> </title></tplarg> +<template lineStart="1"><title> {<tplarg><title>vorlagenname</title></tplarg></title></template>} +{<tplarg><title> <template><title>vorlagenname</title></template></title></tplarg>} +<template lineStart="1"><title> <template><title> <template><title>vorlagenname</title></template></title></template></title></template> +{<tplarg><title> <template><title>vorlagenname</title></template>} </title></tplarg> +{<template><title><tplarg><title>vorlagenname</title></tplarg>} </title></template> +{<tplarg><title><template><title>vorlagenname</title></template> </title></tplarg>} +<template lineStart="1"><title> <template><title><template><title>vorlagenname</title></template> </title></template></title></template> +<tplarg lineStart="1"><title> {<template><title>vorlagenname</title></template> </title></tplarg>} + +{<tplarg><title><tplarg><title> </title></tplarg></title></tplarg>} + +<template lineStart="1"><title><tplarg><title><tplarg><title> </title></tplarg></title></tplarg></title></template> +<tplarg lineStart="1"><title><tplarg><title><template><title> </title></template> </title></tplarg></title></tplarg> +<template lineStart="1"><title><tplarg><title><tplarg><title> </title></tplarg> </title></tplarg></title></template> +{{<tplarg><title><tplarg><title> </title></tplarg>} </title></tplarg>} +<tplarg lineStart="1"><title><template><title><tplarg><title> </title></tplarg></title></template> </title></tplarg> +<template lineStart="1"><title><tplarg><title><tplarg><title> </title></tplarg></title></tplarg> </title></template> +{<tplarg><title><template><title><template><title> </title></template> </title></template> </title></tplarg>} +{<template><title><tplarg><title><template><title> </title></template> </title></tplarg>} </title></template> +{<template><title><template><title><tplarg><title> </title></tplarg>} </title></template> </title></template> +<template lineStart="1"><title><tplarg><title><tplarg><title> </title></tplarg> </title></tplarg> </title></template> +<tplarg lineStart="1"><title><template><title><tplarg><title> </title></tplarg> </title></template> </title></tplarg> +<tplarg lineStart="1"><title><tplarg><title><template><title> </title></template> </title></tplarg> </title></tplarg> +<template lineStart="1"><title><template><title><template><title><template><title> </title></template> </title></template> </title></template> </title></template> + +<template lineStart="1"><title>vorlage</title></template> + +<tplarg lineStart="1"><title>argument</title></tplarg> + +Nach [[:meta:Help:Expansion#XML parse tree]] +{<tplarg><title>vorlagenname</title></tplarg>} +<template lineStart="1"><title> <template><title>vorlagenname</title></template></title></template> +<template lineStart="1"><title><template><title>vorlagenname</title></template> </title></template> +<template lineStart="1"><title><template><title>vorlagenname</title></template>erweiterung</title></template> + +<template lineStart="1"><title><tplarg><title>vorlagenname</title></tplarg></title></template> +<tplarg lineStart="1"><title> <template><title>vorlagenname</title></template></title></tplarg> +<template lineStart="1"><title> <tplarg><title>vorlagenname</title></tplarg></title></template> +<tplarg lineStart="1"><title><template><title>vorlagenname</title></template> </title></tplarg> +<template lineStart="1"><title><tplarg><title>vorlagenname</title></tplarg> </title></template> + +nur etwas erweitert +<tplarg lineStart="1"><title><tplarg><title>vorlagenname</title></tplarg></title></tplarg> +<tplarg lineStart="1"><title> <tplarg><title>vorlagenname</title></tplarg></title></tplarg> +<tplarg lineStart="1"><title><tplarg><title>vorlagenname</title></tplarg> </title></tplarg> +<template lineStart="1"><title> {<tplarg><title>vorlagenname</title></tplarg></title></template>} +{<tplarg><title> <template><title>vorlagenname</title></template></title></tplarg>} +<template lineStart="1"><title> <template><title> <template><title>vorlagenname</title></template></title></template></title></template> +{<tplarg><title> <template><title>vorlagenname</title></template>} </title></tplarg> +{<template><title><tplarg><title>vorlagenname</title></tplarg>} </title></template> +{<tplarg><title><template><title>vorlagenname</title></template> </title></tplarg>} +<template lineStart="1"><title> <template><title><template><title>vorlagenname</title></template> </title></template></title></template> +<tplarg lineStart="1"><title> {<template><title>vorlagenname</title></template> </title></tplarg>} + +{<tplarg><title><tplarg><title> </title></tplarg></title></tplarg>} + +<template lineStart="1"><title><tplarg><title><tplarg><title> </title></tplarg></title></tplarg></title></template> +<tplarg lineStart="1"><title><tplarg><title><template><title> </title></template> </title></tplarg></title></tplarg> +<template lineStart="1"><title><tplarg><title><tplarg><title> </title></tplarg> </title></tplarg></title></template> +{{<tplarg><title><tplarg><title> </title></tplarg>} </title></tplarg>} +<tplarg lineStart="1"><title><template><title><tplarg><title> </title></tplarg></title></template> </title></tplarg> +<template lineStart="1"><title><tplarg><title><tplarg><title> </title></tplarg></title></tplarg> </title></template> +{<tplarg><title><template><title><template><title> </title></template> </title></template> </title></tplarg>} +{<template><title><tplarg><title><template><title> </title></template> </title></tplarg>} </title></template> +{<template><title><template><title><tplarg><title> </title></tplarg>} </title></template> </title></template> +<template lineStart="1"><title><tplarg><title><tplarg><title> </title></tplarg> </title></tplarg> </title></template> +<tplarg lineStart="1"><title><template><title><tplarg><title> </title></tplarg> </title></template> </title></tplarg> +<tplarg lineStart="1"><title><tplarg><title><template><title> </title></template> </title></tplarg> </title></tplarg> +<template lineStart="1"><title><template><title><template><title><template><title> </title></template> </title></template> </title></template> </title></template> +</root>
\ No newline at end of file diff --git a/tests/parser/preprocess/NestedTemplates.txt b/tests/parser/preprocess/NestedTemplates.txt new file mode 100644 index 00000000..aa9a472d --- /dev/null +++ b/tests/parser/preprocess/NestedTemplates.txt @@ -0,0 +1,89 @@ +{{vorlage}} + +{{{argument}}} + +Nach [[:meta:Help:Expansion#XML parse tree]] +{{{{vorlagenname}}}} +{{ {{vorlagenname}}}} +{{{{vorlagenname}} }} +{{{{vorlagenname}}erweiterung}} + +{{{{{vorlagenname}}}}} +{{{ {{vorlagenname}}}}} +{{ {{{vorlagenname}}}}} +{{{{{vorlagenname}} }}} +{{{{{vorlagenname}}} }} + +nur etwas erweitert +{{{{{{vorlagenname}}}}}} +{{{ {{{vorlagenname}}}}}} +{{{{{{vorlagenname}}} }}} +{{ {{{{vorlagenname}}}}}} +{{{{ {{vorlagenname}}}}}} +{{ {{ {{vorlagenname}}}}}} +{{{{ {{vorlagenname}}} }}} +{{{{{{vorlagenname}}}} }} +{{{{{{vorlagenname}} }}}} +{{ {{{{vorlagenname}} }}}} +{{{ {{{vorlagenname}} }}}} + +{{{{{{{ }}}}}}} + +{{{{{{{{ }}}}}}}} +{{{{{{{{ }} }}}}}} +{{{{{{{{ }}} }}}}} +{{{{{{{{ }}}} }}}} +{{{{{{{{ }}}}} }}} +{{{{{{{{ }}}}}} }} +{{{{{{{{ }} }} }}}} +{{{{{{{{ }} }}}} }} +{{{{{{{{ }}}} }} }} +{{{{{{{{ }}} }}} }} +{{{{{{{{ }}} }} }}} +{{{{{{{{ }} }}} }}} +{{{{{{{{ }} }} }} }} + +{{vorlage}} + +{{{argument}}} + +Nach [[:meta:Help:Expansion#XML parse tree]] +{{{{vorlagenname}}}} +{{ {{vorlagenname}}}} +{{{{vorlagenname}} }} +{{{{vorlagenname}}erweiterung}} + +{{{{{vorlagenname}}}}} +{{{ {{vorlagenname}}}}} +{{ {{{vorlagenname}}}}} +{{{{{vorlagenname}} }}} +{{{{{vorlagenname}}} }} + +nur etwas erweitert +{{{{{{vorlagenname}}}}}} +{{{ {{{vorlagenname}}}}}} +{{{{{{vorlagenname}}} }}} +{{ {{{{vorlagenname}}}}}} +{{{{ {{vorlagenname}}}}}} +{{ {{ {{vorlagenname}}}}}} +{{{{ {{vorlagenname}}} }}} +{{{{{{vorlagenname}}}} }} +{{{{{{vorlagenname}} }}}} +{{ {{{{vorlagenname}} }}}} +{{{ {{{vorlagenname}} }}}} + +{{{{{{{ }}}}}}} + +{{{{{{{{ }}}}}}}} +{{{{{{{{ }} }}}}}} +{{{{{{{{ }}} }}}}} +{{{{{{{{ }}}} }}}} +{{{{{{{{ }}}}} }}} +{{{{{{{{ }}}}}} }} +{{{{{{{{ }} }} }}}} +{{{{{{{{ }} }}}} }} +{{{{{{{{ }}}} }} }} +{{{{{{{{ }}} }}} }} +{{{{{{{{ }}} }} }}} +{{{{{{{{ }} }}} }}} +{{{{{{{{ }} }} }} }} diff --git a/tests/parserTests.php b/tests/parserTests.php index 4df9a618..debb3575 100644 --- a/tests/parserTests.php +++ b/tests/parserTests.php @@ -24,10 +24,11 @@ * @ingroup Testing */ -$otions = array( 'quick', 'color', 'quiet', 'help', 'show-output', 'record', 'run-disabled' ); +$otions = array( 'quick', 'color', 'quiet', 'help', 'show-output', 'record', 'run-disabled', 'run-parsoid' ); $optionsWithArgs = array( 'regex', 'filter', 'seed', 'setversion' ); -require_once( __DIR__ . '/../maintenance/commandLine.inc' ); +require_once __DIR__ . '/../maintenance/commandLine.inc'; +require_once __DIR__ . '/TestsAutoLoader.php'; if ( isset( $options['help'] ) ) { echo <<<ENDS @@ -52,6 +53,7 @@ Options: --seed <n> Start the fuzz test from the specified seed --help Show this help message --run-disabled run disabled tests + --run-parsoid run parsoid tests (normally disabled) ENDS; exit( 0 ); @@ -71,7 +73,7 @@ if ( $wgDBtype == 'sqlite' ) { # refer to $wgTitle directly, but instead use the title # passed to it. $wgTitle = Title::newFromText( 'Parser test script do not use' ); -$tester = new ParserTest($options); +$tester = new ParserTest( $options ); if ( isset( $options['file'] ) ) { $files = array( $options['file'] ); @@ -82,11 +84,11 @@ if ( isset( $options['file'] ) ) { # Print out software version to assist with locating regressions $version = SpecialVersion::getVersion(); -echo( "This is MediaWiki version {$version}.\n\n" ); +echo "This is MediaWiki version {$version}.\n\n"; if ( isset( $options['fuzz'] ) ) { $tester->fuzzTest( $files ); } else { $ok = $tester->runTestsFromFiles( $files ); - exit ( $ok ? 0 : 1 ); + exit( $ok ? 0 : 1 ); } diff --git a/tests/phpunit/Makefile b/tests/phpunit/Makefile index 8a55dae0..c3e2a303 100644 --- a/tests/phpunit/Makefile +++ b/tests/phpunit/Makefile @@ -2,7 +2,7 @@ .DEFAULT: warning SHELL = /bin/sh -CONFIG_FILE = $(shell pwd)/suite.xml +CONFIG_FILE = ${PWD}/suite.xml PHP = php PU = ${PHP} phpunit.php --configuration ${CONFIG_FILE} ${FLAGS} diff --git a/tests/phpunit/MediaWikiLangTestCase.php b/tests/phpunit/MediaWikiLangTestCase.php index 6dd8ea35..1131385f 100644 --- a/tests/phpunit/MediaWikiLangTestCase.php +++ b/tests/phpunit/MediaWikiLangTestCase.php @@ -4,39 +4,30 @@ * Base class that store and restore the Language objects */ abstract class MediaWikiLangTestCase extends MediaWikiTestCase { - private static $oldLang; - private static $oldContLang; - - public function setUp() { - global $wgLanguageCode, $wgLang, $wgContLang; + protected function setUp() { + global $wgLanguageCode, $wgContLang; parent::setUp(); - self::$oldLang = $wgLang; - self::$oldContLang = $wgContLang; - - if( $wgLanguageCode != $wgContLang->getCode() ) { - throw new MWException("Error in MediaWikiLangTestCase::setUp(): " . + if ( $wgLanguageCode != $wgContLang->getCode() ) { + throw new MWException( "Error in MediaWikiLangTestCase::setUp(): " . "\$wgLanguageCode ('$wgLanguageCode') is different from " . "\$wgContLang->getCode() (" . $wgContLang->getCode() . ")" ); } - $wgLanguageCode = 'en'; # For mainpage to be 'Main Page' - - $wgContLang = $wgLang = Language::factory( $wgLanguageCode ); - MessageCache::singleton()->disable(); - - } + // HACK: Call getLanguage() so the real $wgContLang is cached as the user language + // rather than our fake one. This is to avoid breaking other, unrelated tests. + RequestContext::getMain()->getLanguage(); - public function tearDown() { - global $wgContLang, $wgLang, $wgLanguageCode; - $wgLang = self::$oldLang; + $langCode = 'en'; # For mainpage to be 'Main Page' + $langObj = Language::factory( $langCode ); - $wgContLang = self::$oldContLang; - $wgLanguageCode = $wgContLang->getCode(); - self::$oldContLang = self::$oldLang = null; + $this->setMwGlobals( array( + 'wgLanguageCode' => $langCode, + 'wgLang' => $langObj, + 'wgContLang' => $langObj, + ) ); - parent::tearDown(); + MessageCache::singleton()->disable(); } - } diff --git a/tests/phpunit/MediaWikiPHPUnitCommand.php b/tests/phpunit/MediaWikiPHPUnitCommand.php index fca32515..042956a9 100644 --- a/tests/phpunit/MediaWikiPHPUnitCommand.php +++ b/tests/phpunit/MediaWikiPHPUnitCommand.php @@ -2,26 +2,45 @@ class MediaWikiPHPUnitCommand extends PHPUnit_TextUI_Command { - static $additionalOptions = array( + public static $additionalOptions = array( 'regex=' => false, 'file=' => false, 'use-filebackend=' => false, + 'use-bagostuff=' => false, + 'use-jobqueue=' => false, 'keep-uploads' => false, 'use-normal-tables' => false, 'reuse-db' => false, + 'wiki=' => false, + 'debug-tests' => false, ); public function __construct() { - foreach( self::$additionalOptions as $option => $default ) { + foreach ( self::$additionalOptions as $option => $default ) { $this->longOptions[$option] = $option . 'Handler'; } + } + + protected function handleArguments( array $argv ) { + parent::handleArguments( $argv ); + + if ( !isset( $this->arguments['listeners'] ) ) { + $this->arguments['listeners'] = array(); + } + foreach ( $this->options[0] as $option ) { + switch ( $option[0] ) { + case '--debug-tests': + $this->arguments['listeners'][] = new MediaWikiPHPUnitTestListener( 'PHPUnitCommand' ); + break; + } + } } public static function main( $exit = true ) { $command = new self; - if( wfIsWindows() ) { + if ( wfIsWindows() ) { # Windows does not come anymore with ANSI.SYS loaded by default # PHPUnit uses the suite.xml parameters to enable/disable colors # which can be then forced to be enabled with --colors. @@ -38,18 +57,40 @@ class MediaWikiPHPUnitCommand extends PHPUnit_TextUI_Command { # See bug 32022 set_include_path( __DIR__ - .PATH_SEPARATOR - . get_include_path() + . PATH_SEPARATOR + . get_include_path() ); - $command->run($_SERVER['argv'], $exit); + $command->run( $_SERVER['argv'], $exit ); } public function __call( $func, $args ) { - if( substr( $func, -7 ) == 'Handler' ) { - if( is_null( $args[0] ) ) $args[0] = true; //Booleans - self::$additionalOptions[substr( $func, 0, -7 ) ] = $args[0]; + if ( substr( $func, -7 ) == 'Handler' ) { + if ( is_null( $args[0] ) ) { + $args[0] = true; + } //Booleans + self::$additionalOptions[substr( $func, 0, -7 )] = $args[0]; + } + } + + public function run( array $argv, $exit = true ) { + wfProfileIn( __METHOD__ ); + + $ret = parent::run( $argv, false ); + + wfProfileOut( __METHOD__ ); + + // Return to real wiki db, so profiling data is preserved + MediaWikiTestCase::teardownTestDB(); + + // Log profiling data, e.g. in the database or UDP + wfLogProfilingData(); + + if ( $exit ) { + exit( $ret ); + } else { + return $ret; } } @@ -61,7 +102,7 @@ class MediaWikiPHPUnitCommand extends PHPUnit_TextUI_Command { ParserTest-specific options: --regex="<regex>" Only run parser tests that match the given regex - --file="<filename>" Prints the version and exits. + --file="<filename>" File describing parser tests --keep-uploads Re-use the same upload directory for each test, don't delete it @@ -70,7 +111,9 @@ Database options: --reuse-db Init DB only if tables are missing and keep after finish. +Debugging options: + --debug-tests Log testing activity to the PHPUnitCommand log channel. + EOT; } - } diff --git a/tests/phpunit/MediaWikiPHPUnitTestListener.php b/tests/phpunit/MediaWikiPHPUnitTestListener.php new file mode 100644 index 00000000..7237ef32 --- /dev/null +++ b/tests/phpunit/MediaWikiPHPUnitTestListener.php @@ -0,0 +1,114 @@ +<?php +class MediaWikiPHPUnitTestListener implements PHPUnit_Framework_TestListener { + + /** + * @var string + */ + protected $logChannel; + + public function __construct( $logChannel ) { + $this->logChannel = $logChannel; + } + + protected function getTestName( PHPUnit_Framework_Test $test ) { + $name = get_class( $test ); + + if ( $test instanceof PHPUnit_Framework_TestCase ) { + $name .= '::' . $test->getName( true ); + } + + return $name; + } + + protected function getErrorName( Exception $exception ) { + $name = get_class( $exception ); + $name = "[$name] " . $exception->getMessage(); + + return $name; + } + + /** + * An error occurred. + * + * @param PHPUnit_Framework_Test $test + * @param Exception $e + * @param float $time + */ + public function addError( PHPUnit_Framework_Test $test, Exception $e, $time ) { + wfDebugLog( $this->logChannel, 'ERROR in ' . $this->getTestName( $test ) . ': ' . $this->getErrorName( $e ) ); + } + + /** + * A failure occurred. + * + * @param PHPUnit_Framework_Test $test + * @param PHPUnit_Framework_AssertionFailedError $e + * @param float $time + */ + public function addFailure( PHPUnit_Framework_Test $test, PHPUnit_Framework_AssertionFailedError $e, $time ) { + wfDebugLog( $this->logChannel, 'FAILURE in ' . $this->getTestName( $test ) . ': ' . $this->getErrorName( $e ) ); + } + + /** + * Incomplete test. + * + * @param PHPUnit_Framework_Test $test + * @param Exception $e + * @param float $time + */ + public function addIncompleteTest( PHPUnit_Framework_Test $test, Exception $e, $time ) { + wfDebugLog( $this->logChannel, 'Incomplete test ' . $this->getTestName( $test ) . ': ' . $this->getErrorName( $e ) ); + } + + /** + * Skipped test. + * + * @param PHPUnit_Framework_Test $test + * @param Exception $e + * @param float $time + * + * @since Method available since Release 3.0.0 + */ + public function addSkippedTest( PHPUnit_Framework_Test $test, Exception $e, $time ) { + wfDebugLog( $this->logChannel, 'Skipped test ' . $this->getTestName( $test ) . ': ' . $this->getErrorName( $e ) ); + } + + /** + * A test suite started. + * + * @param PHPUnit_Framework_TestSuite $suite + * @since Method available since Release 2.2.0 + */ + public function startTestSuite( PHPUnit_Framework_TestSuite $suite ) { + wfDebugLog( $this->logChannel, 'START suite ' . $suite->getName() ); + } + + /** + * A test suite ended. + * + * @param PHPUnit_Framework_TestSuite $suite + * @since Method available since Release 2.2.0 + */ + public function endTestSuite( PHPUnit_Framework_TestSuite $suite ) { + wfDebugLog( $this->logChannel, 'END suite ' . $suite->getName() ); + } + + /** + * A test started. + * + * @param PHPUnit_Framework_Test $test + */ + public function startTest( PHPUnit_Framework_Test $test ) { + wfDebugLog( $this->logChannel, 'Start test ' . $this->getTestName( $test ) ); + } + + /** + * A test ended. + * + * @param PHPUnit_Framework_Test $test + * @param float $time + */ + public function endTest( PHPUnit_Framework_Test $test, $time ) { + wfDebugLog( $this->logChannel, 'End test ' . $this->getTestName( $test ) ); + } +} diff --git a/tests/phpunit/MediaWikiTestCase.php b/tests/phpunit/MediaWikiTestCase.php index 1cc45e08..6ce78b56 100644 --- a/tests/phpunit/MediaWikiTestCase.php +++ b/tests/phpunit/MediaWikiTestCase.php @@ -6,6 +6,20 @@ abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase { public $runDisabled = false; /** + * $called tracks whether the setUp and tearDown method has been called. + * class extending MediaWikiTestCase usually override setUp and tearDown + * but forget to call the parent. + * + * The array format takes a method name as key and anything as a value. + * By asserting the key exist, we know the child class has called the + * parent. + * + * This property must be private, we do not want child to override it, + * they should call the appropriate parent method instead. + */ + private $called = array(); + + /** * @var Array of TestUser */ public static $users; @@ -14,12 +28,12 @@ abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase { * @var DatabaseBase */ protected $db; - protected $oldTablePrefix; - protected $useTemporaryTables = true; - protected $reuseDB = false; protected $tablesUsed = array(); // tables with data + private static $useTemporaryTables = true; + private static $reuseDB = false; private static $dbSetup = false; + private static $oldTablePrefix = false; /** * Holds the paths of temporary files/directories created through getNewTempFile, @@ -29,6 +43,13 @@ abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase { */ private $tmpfiles = array(); + /** + * Holds original values of MediaWiki configuration settings + * to be restored in tearDown(). + * See also setMwGlobal(). + * @var array + */ + private $mwGlobals = array(); /** * Table name prefixes. Oracle likes it shorter. @@ -43,58 +64,80 @@ abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase { 'oracle' ); - function __construct( $name = null, array $data = array(), $dataName = '' ) { + function __construct( $name = null, array $data = array(), $dataName = '' ) { parent::__construct( $name, $data, $dataName ); $this->backupGlobals = false; $this->backupStaticAttributes = false; } - function run( PHPUnit_Framework_TestResult $result = NULL ) { + function run( PHPUnit_Framework_TestResult $result = null ) { /* Some functions require some kind of caching, and will end up using the db, * which we can't allow, as that would open a new connection for mysql. * Replace with a HashBag. They would not be going to persist anyway. */ ObjectCache::$instances[CACHE_DB] = new HashBagOStuff; - if( $this->needsDB() ) { - global $wgDBprefix; - - $this->useTemporaryTables = !$this->getCliArg( 'use-normal-tables' ); - $this->reuseDB = $this->getCliArg('reuse-db'); + $needsResetDB = false; + $logName = get_class( $this ) . '::' . $this->getName( false ); + + if ( $this->needsDB() ) { + // set up a DB connection for this test to use + + self::$useTemporaryTables = !$this->getCliArg( 'use-normal-tables' ); + self::$reuseDB = $this->getCliArg( 'reuse-db' ); $this->db = wfGetDB( DB_MASTER ); $this->checkDbIsSupported(); - $this->oldTablePrefix = $wgDBprefix; + if ( !self::$dbSetup ) { + wfProfileIn( $logName . ' (clone-db)' ); + + // switch to a temporary clone of the database + self::setupTestDB( $this->db, $this->dbPrefix() ); - if( !self::$dbSetup ) { - $this->initDB(); - self::$dbSetup = true; + if ( ( $this->db->getType() == 'oracle' || !self::$useTemporaryTables ) && self::$reuseDB ) { + $this->resetDB(); + } + + wfProfileOut( $logName . ' (clone-db)' ); } + wfProfileIn( $logName . ' (prepare-db)' ); $this->addCoreDBData(); $this->addDBData(); + wfProfileOut( $logName . ' (prepare-db)' ); + + $needsResetDB = true; + } - parent::run( $result ); + wfProfileIn( $logName ); + parent::run( $result ); + wfProfileOut( $logName ); + if ( $needsResetDB ) { + wfProfileIn( $logName . ' (reset-db)' ); $this->resetDB(); - } else { - parent::run( $result ); + wfProfileOut( $logName . ' (reset-db)' ); } } + function usesTemporaryTables() { + return self::$useTemporaryTables; + } + /** * obtains a new temporary file name * * The obtained filename is enlisted to be removed upon tearDown * - * @returns string: absolute name of the temporary file + * @return string: absolute name of the temporary file */ protected function getNewTempFile() { $fname = tempnam( wfTempDir(), 'MW_PHPUnit_' . get_class( $this ) . '_' ); $this->tmpfiles[] = $fname; + return $fname; } @@ -104,7 +147,7 @@ abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase { * The obtained directory is enlisted to be removed (recursively with all its contained * files) upon tearDown. * - * @returns string: absolute name of the temporary directory + * @return string: absolute name of the temporary directory */ protected function getNewTempDirectory() { // Starting of with a temporary /file/. @@ -116,10 +159,55 @@ abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase { // where temporary directory creation is bundled and can be improved unlink( $fname ); $this->assertTrue( wfMkdirParents( $fname ) ); + return $fname; } + /** + * setUp and tearDown should (where significant) + * happen in reverse order. + */ + protected function setUp() { + wfProfileIn( __METHOD__ ); + parent::setUp(); + $this->called['setUp'] = 1; + + /* + // @todo global variables to restore for *every* test + array( + 'wgLang', + 'wgContLang', + 'wgLanguageCode', + 'wgUser', + 'wgTitle', + ); + */ + + // Cleaning up temporary files + foreach ( $this->tmpfiles as $fname ) { + if ( is_file( $fname ) || ( is_link( $fname ) ) ) { + unlink( $fname ); + } elseif ( is_dir( $fname ) ) { + wfRecursiveRemoveDir( $fname ); + } + } + + if ( $this->needsDB() && $this->db ) { + // Clean up open transactions + while ( $this->db->trxLevel() > 0 ) { + $this->db->rollback(); + } + + // don't ignore DB errors + $this->db->ignoreErrors( false ); + } + + wfProfileOut( __METHOD__ ); + } + protected function tearDown() { + wfProfileIn( __METHOD__ ); + // Cleaning up temporary files foreach ( $this->tmpfiles as $fname ) { if ( is_file( $fname ) || ( is_link( $fname ) ) ) { @@ -129,14 +217,114 @@ abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase { } } - // clean up open transactions - if( $this->needsDB() && $this->db ) { - while( $this->db->trxLevel() > 0 ) { + if ( $this->needsDB() && $this->db ) { + // Clean up open transactions + while ( $this->db->trxLevel() > 0 ) { $this->db->rollback(); } + + // don't ignore DB errors + $this->db->ignoreErrors( false ); + } + + // Restore mw globals + foreach ( $this->mwGlobals as $key => $value ) { + $GLOBALS[$key] = $value; } + $this->mwGlobals = array(); parent::tearDown(); + wfProfileOut( __METHOD__ ); + } + + /** + * Make sure MediaWikiTestCase extending classes have called their + * parent setUp method + */ + final public function testMediaWikiTestCaseParentSetupCalled() { + $this->assertArrayHasKey( 'setUp', $this->called, + get_called_class() . "::setUp() must call parent::setUp()" + ); + } + + /** + * Individual test functions may override globals (either directly or through this + * setMwGlobals() function), however one must call this method at least once for + * each key within the setUp(). + * That way the key is added to the array of globals that will be reset afterwards + * in the tearDown(). And, equally important, that way all other tests are executed + * with the same settings (instead of using the unreliable local settings for most + * tests and fix it only for some tests). + * + * @example + * <code> + * protected function setUp() { + * $this->setMwGlobals( 'wgRestrictStuff', true ); + * } + * + * function testFoo() {} + * + * function testBar() {} + * $this->assertTrue( self::getX()->doStuff() ); + * + * $this->setMwGlobals( 'wgRestrictStuff', false ); + * $this->assertTrue( self::getX()->doStuff() ); + * } + * + * function testQuux() {} + * </code> + * + * @param array|string $pairs Key to the global variable, or an array + * of key/value pairs. + * @param mixed $value Value to set the global to (ignored + * if an array is given as first argument). + */ + protected function setMwGlobals( $pairs, $value = null ) { + + // Normalize (string, value) to an array + if ( is_string( $pairs ) ) { + $pairs = array( $pairs => $value ); + } + + foreach ( $pairs as $key => $value ) { + // NOTE: make sure we only save the global once or a second call to + // setMwGlobals() on the same global would override the original + // value. + if ( !array_key_exists( $key, $this->mwGlobals ) ) { + $this->mwGlobals[$key] = $GLOBALS[$key]; + } + + // Override the global + $GLOBALS[$key] = $value; + } + } + + /** + * Merges the given values into a MW global array variable. + * Useful for setting some entries in a configuration array, instead of + * setting the entire array. + * + * @param String $name The name of the global, as in wgFooBar + * @param Array $values The array containing the entries to set in that global + * + * @throws MWException if the designated global is not an array. + */ + protected function mergeMwGlobalArrayValue( $name, $values ) { + if ( !isset( $GLOBALS[$name] ) ) { + $merged = $values; + } else { + if ( !is_array( $GLOBALS[$name] ) ) { + throw new MWException( "MW global $name is not an array." ); + } + + // NOTE: do not use array_merge, it screws up for numeric keys. + $merged = $GLOBALS[$name]; + foreach ( $values as $k => $v ) { + $merged[$k] = $v; + } + } + + $this->setMwGlobals( $name, $merged ); } function dbPrefix() { @@ -162,7 +350,8 @@ abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase { * Stub. If a test needs to add additional data to the database, it should * implement this method and do so */ - function addDBData() {} + function addDBData() { + } private function addCoreDBData() { # disabled for performance @@ -174,8 +363,8 @@ abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase { # Insert 0 user to prevent FK violations # Anonymous user $this->db->insert( 'user', array( - 'user_id' => 0, - 'user_name' => 'Anonymous' ), __METHOD__, array( 'IGNORE' ) ); + 'user_id' => 0, + 'user_name' => 'Anonymous' ), __METHOD__, array( 'IGNORE' ) ); # Insert 0 page to prevent FK violations # Blank page @@ -183,7 +372,7 @@ abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase { 'page_id' => 0, 'page_namespace' => 0, 'page_title' => ' ', - 'page_restrictions' => NULL, + 'page_restrictions' => null, 'page_counter' => 0, 'page_is_redirect' => 0, 'page_is_new' => 0, @@ -191,7 +380,6 @@ abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase { 'page_touched' => $this->db->timestamp(), 'page_latest' => 0, 'page_len' => 0 ), __METHOD__, array( 'IGNORE' ) ); - } User::resetIdByNameCache(); @@ -208,38 +396,80 @@ abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase { $user->saveSettings(); } - //Make 1 page with 1 revision $page = WikiPage::factory( Title::newFromText( 'UTPage' ) ); if ( !$page->getId() == 0 ) { - $page->doEdit( 'UTContent', - 'UTPageSummary', - EDIT_NEW, - false, - User::newFromName( 'UTSysop' ) ); + $page->doEditContent( + new WikitextContent( 'UTContent' ), + 'UTPageSummary', + EDIT_NEW, + false, + User::newFromName( 'UTSysop' ) ); } } - private function initDB() { + /** + * Restores MediaWiki to using the table set (table prefix) it was using before + * setupTestDB() was called. Useful if we need to perform database operations + * after the test run has finished (such as saving logs or profiling info). + */ + public static function teardownTestDB() { + if ( !self::$dbSetup ) { + return; + } + + CloneDatabase::changePrefix( self::$oldTablePrefix ); + + self::$oldTablePrefix = false; + self::$dbSetup = false; + } + + /** + * Creates an empty skeleton of the wiki database by cloning its structure + * to equivalent tables using the given $prefix. Then sets MediaWiki to + * use the new set of tables (aka schema) instead of the original set. + * + * This is used to generate a dummy table set, typically consisting of temporary + * tables, that will be used by tests instead of the original wiki database tables. + * + * @note: the original table prefix is stored in self::$oldTablePrefix. This is used + * by teardownTestDB() to return the wiki to using the original table set. + * + * @note: this method only works when first called. Subsequent calls have no effect, + * even if using different parameters. + * + * @param DatabaseBase $db The database connection + * @param String $prefix The prefix to use for the new table set (aka schema). + * + * @throws MWException if the database table prefix is already $prefix + */ + public static function setupTestDB( DatabaseBase $db, $prefix ) { global $wgDBprefix; - if ( $wgDBprefix === $this->dbPrefix() ) { - throw new MWException( 'Cannot run unit tests, the database prefix is already "unittest_"' ); + if ( $wgDBprefix === $prefix ) { + throw new MWException( 'Cannot run unit tests, the database prefix is already "' . $prefix . '"' ); + } + + if ( self::$dbSetup ) { + return; } - $tablesCloned = $this->listTables(); - $dbClone = new CloneDatabase( $this->db, $tablesCloned, $this->dbPrefix() ); - $dbClone->useTemporaryTables( $this->useTemporaryTables ); + $tablesCloned = self::listTables( $db ); + $dbClone = new CloneDatabase( $db, $tablesCloned, $prefix ); + $dbClone->useTemporaryTables( self::$useTemporaryTables ); + + self::$dbSetup = true; + self::$oldTablePrefix = $wgDBprefix; + + if ( ( $db->getType() == 'oracle' || !self::$useTemporaryTables ) && self::$reuseDB ) { + CloneDatabase::changePrefix( $prefix ); - if ( ( $this->db->getType() == 'oracle' || !$this->useTemporaryTables ) && $this->reuseDB ) { - CloneDatabase::changePrefix( $this->dbPrefix() ); - $this->resetDB(); return; } else { $dbClone->cloneTableStructure(); } - if ( $this->db->getType() == 'oracle' ) { - $this->db->query( 'BEGIN FILL_WIKI_INFO; END;' ); + if ( $db->getType() == 'oracle' ) { + $db->query( 'BEGIN FILL_WIKI_INFO; END;' ); } } @@ -247,20 +477,24 @@ abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase { * Empty all tables so they can be repopulated for tests */ private function resetDB() { - if( $this->db ) { - if ( $this->db->getType() == 'oracle' ) { - if ( $this->useTemporaryTables ) { + if ( $this->db ) { + if ( $this->db->getType() == 'oracle' ) { + if ( self::$useTemporaryTables ) { wfGetLB()->closeAll(); $this->db = wfGetDB( DB_MASTER ); } else { - foreach( $this->tablesUsed as $tbl ) { - if( $tbl == 'interwiki') continue; - $this->db->query( 'TRUNCATE TABLE '.$this->db->tableName($tbl), __METHOD__ ); + foreach ( $this->tablesUsed as $tbl ) { + if ( $tbl == 'interwiki' ) { + continue; + } + $this->db->query( 'TRUNCATE TABLE ' . $this->db->tableName( $tbl ), __METHOD__ ); } } } else { - foreach( $this->tablesUsed as $tbl ) { - if( $tbl == 'interwiki' || $tbl == 'user' ) continue; + foreach ( $this->tablesUsed as $tbl ) { + if ( $tbl == 'interwiki' || $tbl == 'user' ) { + continue; + } $this->db->delete( $tbl, '*', __METHOD__ ); } } @@ -276,9 +510,9 @@ abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase { ); if ( method_exists( $this->suite, $func ) ) { - return call_user_func_array( array( $this->suite, $func ), $args); + return call_user_func_array( array( $this->suite, $func ), $args ); } elseif ( isset( $compatibility[$func] ) ) { - return call_user_func_array( array( $this, $compatibility[$func] ), $args); + return call_user_func_array( array( $this, $compatibility[$func] ), $args ); } else { throw new MWException( "Called non-existant $func method on " . get_class( $this ) ); @@ -289,25 +523,32 @@ abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase { return $this->assertTrue( $value == '', $msg ); } - static private function unprefixTable( $tableName ) { + private static function unprefixTable( $tableName ) { global $wgDBprefix; + return substr( $tableName, strlen( $wgDBprefix ) ); } - static private function isNotUnittest( $table ) { + private static function isNotUnittest( $table ) { return strpos( $table, 'unittest_' ) !== 0; } - protected function listTables() { + public static function listTables( $db ) { global $wgDBprefix; - $tables = $this->db->listTables( $wgDBprefix, __METHOD__ ); + $tables = $db->listTables( $wgDBprefix, __METHOD__ ); + + if ( $db->getType() === 'mysql' ) { + # bug 43571: cannot clone VIEWs under MySQL + $views = $db->listViews( $wgDBprefix, __METHOD__ ); + $tables = array_diff( $tables, $views ); + } $tables = array_map( array( __CLASS__, 'unprefixTable' ), $tables ); // Don't duplicate test tables from the previous fataled run $tables = array_filter( $tables, array( __CLASS__, 'isNotUnittest' ) ); - if ( $this->db->getType() == 'sqlite' ) { + if ( $db->getType() == 'sqlite' ) { $tables = array_flip( $tables ); // these are subtables of searchindex and don't need to be duped/dropped separately unset( $tables['searchindex_content'] ); @@ -315,27 +556,26 @@ abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase { unset( $tables['searchindex_segments'] ); $tables = array_flip( $tables ); } + return $tables; } protected function checkDbIsSupported() { - if( !in_array( $this->db->getType(), $this->supportedDBs ) ) { + if ( !in_array( $this->db->getType(), $this->supportedDBs ) ) { throw new MWException( $this->db->getType() . " is not currently supported for unit testing." ); } } public function getCliArg( $offset ) { - if( isset( MediaWikiPHPUnitCommand::$additionalOptions[$offset] ) ) { + if ( isset( MediaWikiPHPUnitCommand::$additionalOptions[$offset] ) ) { return MediaWikiPHPUnitCommand::$additionalOptions[$offset]; } - } public function setCliArg( $offset, $value ) { MediaWikiPHPUnitCommand::$additionalOptions[$offset] = $value; - } /** @@ -368,10 +608,10 @@ abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase { * or list the tables under testing in $this->tablesUsed, or override the * needsDB() method. */ - protected function assertSelect( $table, $fields, $condition, Array $expectedRows ) { + protected function assertSelect( $table, $fields, $condition, array $expectedRows ) { if ( !$this->needsDB() ) { throw new MWException( 'When testing database state, the test cases\'s needDB()' . - ' method should return true. Use @group Database or $this->tablesUsed.'); + ' method should return true. Use @group Database or $this->tablesUsed.' ); } $db = wfGetDB( DB_SLAVE ); @@ -410,7 +650,7 @@ abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase { */ protected function arrayWrap( array $elements ) { return array_map( - function( $element ) { + function ( $element ) { return array( $element ); }, $elements @@ -458,9 +698,9 @@ abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase { * @param String $actual HTML on oneline * @param String $msg Optional message */ - protected function assertHTMLEquals( $expected, $actual, $msg='' ) { + protected function assertHTMLEquals( $expected, $actual, $msg = '' ) { $expected = str_replace( '>', ">\n", $expected ); - $actual = str_replace( '>', ">\n", $actual ); + $actual = str_replace( '>', ">\n", $actual ); $this->assertEquals( $expected, $actual, $msg ); } @@ -475,7 +715,7 @@ abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase { protected function objectAssociativeSort( array &$array ) { uasort( $array, - function( $a, $b ) { + function ( $a, $b ) { return serialize( $a ) > serialize( $b ) ? 1 : -1; } ); @@ -518,8 +758,7 @@ abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase { protected function assertTypeOrValue( $type, $actual, $value = false, $message = '' ) { if ( $actual === $value ) { $this->assertTrue( true, $message ); - } - else { + } else { $this->assertType( $type, $actual, $message ); } } @@ -536,12 +775,174 @@ abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase { * @param string $message */ protected function assertType( $type, $actual, $message = '' ) { - if ( is_object( $actual ) ) { + if ( class_exists( $type ) || interface_exists( $type ) ) { $this->assertInstanceOf( $type, $actual, $message ); - } - else { + } else { $this->assertInternalType( $type, $actual, $message ); } } + /** + * Returns true if the given namespace defaults to Wikitext + * according to $wgNamespaceContentModels + * + * @param int $ns The namespace ID to check + * + * @return bool + * @since 1.21 + */ + protected function isWikitextNS( $ns ) { + global $wgNamespaceContentModels; + + if ( isset( $wgNamespaceContentModels[$ns] ) ) { + return $wgNamespaceContentModels[$ns] === CONTENT_MODEL_WIKITEXT; + } + + return true; + } + + /** + * Returns the ID of a namespace that defaults to Wikitext. + * Throws an MWException if there is none. + * + * @return int the ID of the wikitext Namespace + * @since 1.21 + */ + protected function getDefaultWikitextNS() { + global $wgNamespaceContentModels; + + static $wikitextNS = null; // this is not going to change + if ( $wikitextNS !== null ) { + return $wikitextNS; + } + + // quickly short out on most common case: + if ( !isset( $wgNamespaceContentModels[NS_MAIN] ) ) { + return NS_MAIN; + } + + // NOTE: prefer content namespaces + $namespaces = array_unique( array_merge( + MWNamespace::getContentNamespaces(), + array( NS_MAIN, NS_HELP, NS_PROJECT ), // prefer these + MWNamespace::getValidNamespaces() + ) ); + + $namespaces = array_diff( $namespaces, array( + NS_FILE, NS_CATEGORY, NS_MEDIAWIKI, NS_USER // don't mess with magic namespaces + ) ); + + $talk = array_filter( $namespaces, function ( $ns ) { + return MWNamespace::isTalk( $ns ); + } ); + + // prefer non-talk pages + $namespaces = array_diff( $namespaces, $talk ); + $namespaces = array_merge( $namespaces, $talk ); + + // check default content model of each namespace + foreach ( $namespaces as $ns ) { + if ( !isset( $wgNamespaceContentModels[$ns] ) || + $wgNamespaceContentModels[$ns] === CONTENT_MODEL_WIKITEXT + ) { + + $wikitextNS = $ns; + + return $wikitextNS; + } + } + + // give up + // @todo Inside a test, we could skip the test as incomplete. + // But frequently, this is used in fixture setup. + throw new MWException( "No namespace defaults to wikitext!" ); + } + + /** + * Check, if $wgDiff3 is set and ready to merge + * Will mark the calling test as skipped, if not ready + * + * @since 1.21 + */ + protected function checkHasDiff3() { + global $wgDiff3; + + # This check may also protect against code injection in + # case of broken installations. + wfSuppressWarnings(); + $haveDiff3 = $wgDiff3 && file_exists( $wgDiff3 ); + wfRestoreWarnings(); + + if ( !$haveDiff3 ) { + $this->markTestSkipped( "Skip test, since diff3 is not configured" ); + } + } + + /** + * Check whether we have the 'gzip' commandline utility, will skip + * the test whenever "gzip -V" fails. + * + * Result is cached at the process level. + * + * @return bool + * + * @since 1.21 + */ + protected function checkHasGzip() { + static $haveGzip; + + if ( $haveGzip === null ) { + $retval = null; + wfShellExec( 'gzip -V', $retval ); + $haveGzip = ( $retval === 0 ); + } + + if ( !$haveGzip ) { + $this->markTestSkipped( "Skip test, requires the gzip utility in PATH" ); + } + + return $haveGzip; + } + + /** + * Check if $extName is a loaded PHP extension, will skip the + * test whenever it is not loaded. + * + * @since 1.21 + */ + protected function checkPHPExtension( $extName ) { + $loaded = extension_loaded( $extName ); + if ( !$loaded ) { + $this->markTestSkipped( "PHP extension '$extName' is not loaded, skipping." ); + } + + return $loaded; + } + + /** + * Asserts that an exception of the specified type occurs when running + * the provided code. + * + * @since 1.21 + * @deprecated since 1.22 Use setExpectedException + * + * @param callable $code + * @param string $expected + * @param string $message + */ + protected function assertException( $code, $expected = 'Exception', $message = '' ) { + $pokemons = null; + + try { + call_user_func( $code ); + } catch ( Exception $pokemons ) { + // Gotta Catch 'Em All! + } + + if ( $message === '' ) { + $message = 'An exception of type "' . $expected . '" should have been thrown'; + } + + $this->assertInstanceOf( $expected, $pokemons, $message ); + } } diff --git a/tests/phpunit/bootstrap.php b/tests/phpunit/bootstrap.php index 933767e7..d929b79d 100644 --- a/tests/phpunit/bootstrap.php +++ b/tests/phpunit/bootstrap.php @@ -11,22 +11,5 @@ if ( !defined( 'MW_PHPUNIT_TEST' ) ) { You are running these tests directly from phpunit. You may not have all globals correctly set. Running phpunit.php instead is recommended. EOF; - require_once ( __DIR__ . "/phpunit.php" ); + require_once __DIR__ . "/phpunit.php"; } - -// Output a notice when running with older versions of PHPUnit -if ( version_compare( PHPUnit_Runner_Version::id(), "3.6.7", "<" ) ) { - echo <<<EOF -******************************************************************************** - -These tests run best with version PHPUnit 3.6.7 or better. Earlier versions may -show failures because earlier versions of PHPUnit do not properly implement -dependencies. - -******************************************************************************** - -EOF; -} - -/** @todo Check if this is really needed */ -MessageCache::destroyInstance(); diff --git a/tests/phpunit/data/db/sqlite/tables-1.16.sql b/tests/phpunit/data/db/sqlite/tables-1.16.sql index 6e56add2..7e8f30ec 100644 --- a/tests/phpunit/data/db/sqlite/tables-1.16.sql +++ b/tests/phpunit/data/db/sqlite/tables-1.16.sql @@ -146,11 +146,6 @@ CREATE TABLE /*_*/externallinks ( CREATE INDEX /*i*/el_from ON /*_*/externallinks (el_from, el_to(40)); CREATE INDEX /*i*/el_to ON /*_*/externallinks (el_to(60), el_from); CREATE INDEX /*i*/el_index ON /*_*/externallinks (el_index(60)); -CREATE TABLE /*_*/external_user ( - eu_local_id int unsigned NOT NULL PRIMARY KEY, - eu_external_id varchar(255) binary NOT NULL -) /*$wgDBTableOptions*/; -CREATE UNIQUE INDEX /*i*/eu_external_id ON /*_*/external_user (eu_external_id); CREATE TABLE /*_*/langlinks ( ll_from int unsigned NOT NULL default 0, ll_lang varbinary(20) NOT NULL default '', diff --git a/tests/phpunit/data/db/sqlite/tables-1.17.sql b/tests/phpunit/data/db/sqlite/tables-1.17.sql index 69ae3764..e02e3e14 100644 --- a/tests/phpunit/data/db/sqlite/tables-1.17.sql +++ b/tests/phpunit/data/db/sqlite/tables-1.17.sql @@ -151,11 +151,6 @@ CREATE TABLE /*_*/externallinks ( CREATE INDEX /*i*/el_from ON /*_*/externallinks (el_from, el_to(40)); CREATE INDEX /*i*/el_to ON /*_*/externallinks (el_to(60), el_from); CREATE INDEX /*i*/el_index ON /*_*/externallinks (el_index(60)); -CREATE TABLE /*_*/external_user ( - eu_local_id int unsigned NOT NULL PRIMARY KEY, - eu_external_id varchar(255) binary NOT NULL -) /*$wgDBTableOptions*/; -CREATE UNIQUE INDEX /*i*/eu_external_id ON /*_*/external_user (eu_external_id); CREATE TABLE /*_*/langlinks ( ll_from int unsigned NOT NULL default 0, ll_lang varbinary(20) NOT NULL default '', diff --git a/tests/phpunit/data/db/sqlite/tables-1.18.sql b/tests/phpunit/data/db/sqlite/tables-1.18.sql index bedf6c33..8bfc28e2 100644 --- a/tests/phpunit/data/db/sqlite/tables-1.18.sql +++ b/tests/phpunit/data/db/sqlite/tables-1.18.sql @@ -157,11 +157,6 @@ CREATE TABLE /*_*/externallinks ( CREATE INDEX /*i*/el_from ON /*_*/externallinks (el_from, el_to(40)); CREATE INDEX /*i*/el_to ON /*_*/externallinks (el_to(60), el_from); CREATE INDEX /*i*/el_index ON /*_*/externallinks (el_index(60)); -CREATE TABLE /*_*/external_user ( - eu_local_id int unsigned NOT NULL PRIMARY KEY, - eu_external_id varchar(255) binary NOT NULL -) /*$wgDBTableOptions*/; -CREATE UNIQUE INDEX /*i*/eu_external_id ON /*_*/external_user (eu_external_id); CREATE TABLE /*_*/langlinks ( ll_from int unsigned NOT NULL default 0, ll_lang varbinary(20) NOT NULL default '', @@ -296,7 +291,7 @@ CREATE TABLE /*_*/uploadstash ( us_size int unsigned NOT NULL, us_sha1 varchar(31) NOT NULL, us_mime varchar(255), - us_media_type ENUM("UNKNOWN", "BITMAP", "DRAWING", "AUDIO", "VIDEO", "MULTIMEDIA", "OFFICE", "TEXT", "EXECUTABLE", "ARCHIVE") default NULL, + us_media_type ENUM("UNKNOWN", "BITMAP", "DRAWING", "AUDIO", "VIDEO", "MULTIMEDIA", "OFFICE", "TEXT", "EXECUTABLE", "ARCHIVE") default NULL, us_image_width int unsigned, us_image_height int unsigned, us_image_bits smallint unsigned diff --git a/tests/phpunit/data/less/common/test.common.mixins.less b/tests/phpunit/data/less/common/test.common.mixins.less new file mode 100644 index 00000000..2fbe9b79 --- /dev/null +++ b/tests/phpunit/data/less/common/test.common.mixins.less @@ -0,0 +1,5 @@ +.test-mixin (@value) { + color: @value; + border: @foo solid @Foo; + line-height: test-sum(@bar, 10, 20); +} diff --git a/tests/phpunit/data/less/module/dependency.less b/tests/phpunit/data/less/module/dependency.less new file mode 100644 index 00000000..c7725a25 --- /dev/null +++ b/tests/phpunit/data/less/module/dependency.less @@ -0,0 +1,3 @@ +@import "test.common.mixins"; + +@unitTestColor: green; diff --git a/tests/phpunit/data/less/module/styles.css b/tests/phpunit/data/less/module/styles.css new file mode 100644 index 00000000..b78780a9 --- /dev/null +++ b/tests/phpunit/data/less/module/styles.css @@ -0,0 +1,6 @@ +/* @noflip */ +.unit-tests { + color: green; + border: 2px solid #eeeeee; + line-height: 35; +} diff --git a/tests/phpunit/data/less/module/styles.less b/tests/phpunit/data/less/module/styles.less new file mode 100644 index 00000000..ecac8392 --- /dev/null +++ b/tests/phpunit/data/less/module/styles.less @@ -0,0 +1,6 @@ +@import "dependency"; + +/* @noflip */ +.unit-tests { + .test-mixin(@unitTestColor); +} diff --git a/tests/phpunit/data/xmp/7.result.php b/tests/phpunit/data/xmp/7.result.php index 0efcfa36..115cdc92 100644 --- a/tests/phpunit/data/xmp/7.result.php +++ b/tests/phpunit/data/xmp/7.result.php @@ -1,52 +1,52 @@ <?php -$result = array ( - 'xmp-exif' => - array ( - 'CameraOwnerName' => 'Me!', - ), - 'xmp-general' => - array ( - 'LicenseUrl' => 'http://creativecommons.com/cc-by-2.9', - 'ImageDescription' => - array ( - 'x-default' => 'Test image for the cc: xmp: xmpRights: namespaces in xmp', - '_type' => 'lang', - ), - 'ObjectName' => - array ( - 'x-default' => 'xmp core/xmp rights/cc ns test', - '_type' => 'lang', - ), - 'DateTimeDigitized' => '2005:04:03', - 'Software' => 'The one true editor: Vi (ok i used gimp)', - 'Identifier' => - array ( - 0 => 'http://example.com/identifierurl', - 1 => 'urn:sha1:342524abcdef', - '_type' => 'ul', - ), - 'Label' => 'Test image', - 'DateTimeMetadata' => '2011:05:12', - 'DateTime' => '2007:03:04 06:34:10', - 'Nickname' => 'My little xmp test image', - 'Rating' => '5', - 'RightsCertificate' => 'http://example.com/rights-certificate/', - 'Copyrighted' => 'True', - 'CopyrightOwner' => - array ( - 0 => 'Bawolff is copyright owner', - '_type' => 'ul', - ), - 'UsageTerms' => - array ( - 'x-default' => 'do whatever you want', - 'en-gb' => 'Do whatever you want in british english', - '_type' => 'lang', - ), - 'WebStatement' => 'http://example.com/web_statement', - ), - 'xmp-deprecated' => - array ( - 'Identifier' => 'http://example.com/identifierurl/wrong', - ), +$result = array( + 'xmp-exif' => + array( + 'CameraOwnerName' => 'Me!', + ), + 'xmp-general' => + array( + 'LicenseUrl' => 'http://creativecommons.com/cc-by-2.9', + 'ImageDescription' => + array( + 'x-default' => 'Test image for the cc: xmp: xmpRights: namespaces in xmp', + '_type' => 'lang', + ), + 'ObjectName' => + array( + 'x-default' => 'xmp core/xmp rights/cc ns test', + '_type' => 'lang', + ), + 'DateTimeDigitized' => '2005:04:03', + 'Software' => 'The one true editor: Vi (ok i used gimp)', + 'Identifier' => + array( + 0 => 'http://example.com/identifierurl', + 1 => 'urn:sha1:342524abcdef', + '_type' => 'ul', + ), + 'Label' => 'Test image', + 'DateTimeMetadata' => '2011:05:12', + 'DateTime' => '2007:03:04 06:34:10', + 'Nickname' => 'My little xmp test image', + 'Rating' => '5', + 'RightsCertificate' => 'http://example.com/rights-certificate/', + 'Copyrighted' => 'True', + 'CopyrightOwner' => + array( + 0 => 'Bawolff is copyright owner', + '_type' => 'ul', + ), + 'UsageTerms' => + array( + 'x-default' => 'do whatever you want', + 'en-gb' => 'Do whatever you want in british english', + '_type' => 'lang', + ), + 'WebStatement' => 'http://example.com/web_statement', + ), + 'xmp-deprecated' => + array( + 'Identifier' => 'http://example.com/identifierurl/wrong', + ), ); diff --git a/tests/phpunit/data/xmp/gps.result.php b/tests/phpunit/data/xmp/gps.result.php index 2d1243d5..8ea9c68c 100644 --- a/tests/phpunit/data/xmp/gps.result.php +++ b/tests/phpunit/data/xmp/gps.result.php @@ -9,4 +9,3 @@ $result = array( 'xmp-exif' => 'GPSVersionID' => '2.2.0.0' ) ); - diff --git a/tests/phpunit/docs/ExportDemoTest.php b/tests/phpunit/docs/ExportDemoTest.php index ce65d494..b09487a6 100644 --- a/tests/phpunit/docs/ExportDemoTest.php +++ b/tests/phpunit/docs/ExportDemoTest.php @@ -26,10 +26,13 @@ class ExportDemoTest extends DumpTestCase { $dom = new DomDocument(); $dom->load( $fname ); + // Ensure, the demo is for the current version + $this->assertEquals( $dom->documentElement->getAttribute( 'version' ), $version, 'export-demo.xml should have the current version' ); + try { $this->assertTrue( $dom->schemaValidate( "../../docs/export-" . $version . ".xsd" ), "schemaValidate has found an error" ); - } catch( Exception $e ) { + } catch ( Exception $e ) { $this->fail( "xml not valid against xsd: " . $e->getMessage() ); } } diff --git a/tests/phpunit/includes/ArticleTablesTest.php b/tests/phpunit/includes/ArticleTablesTest.php index 17cee6e8..469d1d19 100644 --- a/tests/phpunit/includes/ArticleTablesTest.php +++ b/tests/phpunit/includes/ArticleTablesTest.php @@ -5,7 +5,7 @@ */ class ArticleTablesTest extends MediaWikiLangTestCase { - function testbug14404() { + public function testbug14404() { global $wgContLang, $wgLanguageCode, $wgLang; $title = Title::newFromText( 'Bug 14404' ); @@ -16,18 +16,17 @@ class ArticleTablesTest extends MediaWikiLangTestCase { $wgContLang = Language::factory( 'es' ); $wgLang = Language::factory( 'fr' ); - $status = $page->doEdit( '{{:{{int:history}}}}', 'Test code for bug 14404', 0, false, $user ); + $page->doEditContent( new WikitextContent( '{{:{{int:history}}}}' ), 'Test code for bug 14404', 0, false, $user ); $templates1 = $title->getTemplateLinksFrom(); $wgLang = Language::factory( 'de' ); - $page->mPreparedEdit = false; // In order to force the rerendering of the same wikitext + $page = WikiPage::factory( $title ); // In order to force the rerendering of the same wikitext // We need an edit, a purge is not enough to regenerate the tables - $status = $page->doEdit( '{{:{{int:history}}}}', 'Test code for bug 14404', EDIT_UPDATE, false, $user ); + $page->doEditContent( new WikitextContent( '{{:{{int:history}}}}' ), 'Test code for bug 14404', EDIT_UPDATE, false, $user ); $templates2 = $title->getTemplateLinksFrom(); $this->assertEquals( $templates1, $templates2 ); $this->assertEquals( $templates1[0]->getFullText(), 'Historial' ); } - } diff --git a/tests/phpunit/includes/ArticleTest.php b/tests/phpunit/includes/ArticleTest.php index 846d2b86..b4d6dca6 100644 --- a/tests/phpunit/includes/ArticleTest.php +++ b/tests/phpunit/includes/ArticleTest.php @@ -2,31 +2,37 @@ class ArticleTest extends MediaWikiTestCase { - private $title; // holds a Title object - private $article; // holds an article + /** + * @var Title + */ + private $title; + /** + * @var Article + */ + private $article; /** creates a title object and its article object */ - function setUp() { - $this->title = Title::makeTitle( NS_MAIN, 'SomePage' ); + protected function setUp() { + parent::setUp(); + $this->title = Title::makeTitle( NS_MAIN, 'SomePage' ); $this->article = new Article( $this->title ); - } /** cleanup title object and its article object */ - function tearDown() { - $this->title = null; + protected function tearDown() { + parent::tearDown(); + $this->title = null; $this->article = null; - } - function testImplementsGetMagic() { + public function testImplementsGetMagic() { $this->assertEquals( false, $this->article->mLatest, "Article __get magic" ); } /** * @depends testImplementsGetMagic */ - function testImplementsSetMagic() { + public function testImplementsSetMagic() { $this->article->mLatest = 2; $this->assertEquals( 2, $this->article->mLatest, "Article __set magic" ); } @@ -34,17 +40,17 @@ class ArticleTest extends MediaWikiTestCase { /** * @depends testImplementsSetMagic */ - function testImplementsCallMagic() { + public function testImplementsCallMagic() { $this->article->mLatest = 33; $this->article->mDataLoaded = true; $this->assertEquals( 33, $this->article->getLatest(), "Article __call magic" ); } - function testGetOrSetOnNewProperty() { + public function testGetOrSetOnNewProperty() { $this->article->ext_someNewProperty = 12; $this->assertEquals( 12, $this->article->ext_someNewProperty, "Article get/set magic on new field" ); - + $this->article->ext_someNewProperty = -8; $this->assertEquals( -8, $this->article->ext_someNewProperty, "Article get/set magic on update to new field" ); @@ -53,7 +59,11 @@ class ArticleTest extends MediaWikiTestCase { /** * Checks for the existence of the backwards compatibility static functions (forwarders to WikiPage class) */ - function testStaticFunctions() { + public function testStaticFunctions() { + $this->hideDeprecated( 'Article::getAutosummary' ); + $this->hideDeprecated( 'WikiPage::getAutosummary' ); + $this->hideDeprecated( 'CategoryPage::getAutosummary' ); // Inherited from Article + $this->assertEquals( WikiPage::selectFields(), Article::selectFields(), "Article static functions" ); $this->assertEquals( true, is_callable( "Article::onArticleCreate" ), @@ -66,15 +76,15 @@ class ArticleTest extends MediaWikiTestCase { "Article static functions" ); } - function testWikiPageFactory() { + public function testWikiPageFactory() { $title = Title::makeTitle( NS_FILE, 'Someimage.png' ); $page = WikiPage::factory( $title ); $this->assertEquals( 'WikiFilePage', get_class( $page ) ); - + $title = Title::makeTitle( NS_CATEGORY, 'SomeCategory' ); $page = WikiPage::factory( $title ); $this->assertEquals( 'WikiCategoryPage', get_class( $page ) ); - + $title = Title::makeTitle( NS_MAIN, 'SomePage' ); $page = WikiPage::factory( $title ); $this->assertEquals( 'WikiPage', get_class( $page ) ); diff --git a/tests/phpunit/includes/BlockTest.php b/tests/phpunit/includes/BlockTest.php index 0c95b8d1..21de0985 100644 --- a/tests/phpunit/includes/BlockTest.php +++ b/tests/phpunit/includes/BlockTest.php @@ -11,17 +11,18 @@ class BlockTest extends MediaWikiLangTestCase { /* variable used to save up the blockID we insert in this test suite */ private $blockId; - function setUp() { - global $wgContLang; + protected function setUp() { parent::setUp(); - $wgContLang = Language::factory( 'en' ); + $this->setMwGlobals( array( + 'wgLanguageCode' => 'en', + 'wgContLang' => Language::factory( 'en' ) + ) ); } function addDBData() { - //$this->dumpBlocks(); $user = User::newFromName( 'UTBlockee' ); - if( $user->getID() == 0 ) { + if ( $user->getID() == 0 ) { $user->addToDatabase(); $user->setPassword( 'UTBlockeePassword' ); @@ -45,41 +46,41 @@ class BlockTest extends MediaWikiLangTestCase { // its value might change depending on the order the tests are run. // ApiBlockTest insert its own blocks! $newBlockId = $this->block->getId(); - if ($newBlockId) { + if ( $newBlockId ) { $this->blockId = $newBlockId; } else { throw new MWException( "Failed to insert block for BlockTest; old leftover block remaining?" ); } + + $this->addXffBlocks(); } /** * debug function : dump the ipblocks table */ function dumpBlocks() { - $v = $this->db->query( 'SELECT * FROM unittest_ipblocks' ); + $v = $this->db->select( 'ipblocks', '*' ); print "Got " . $v->numRows() . " rows. Full dump follow:\n"; - foreach( $v as $row ) { + foreach ( $v as $row ) { print_r( $row ); } } - function testInitializerFunctionsReturnCorrectBlock() { + public function testInitializerFunctionsReturnCorrectBlock() { // $this->dumpBlocks(); - $this->assertTrue( $this->block->equals( Block::newFromTarget('UTBlockee') ), "newFromTarget() returns the same block as the one that was made"); - - $this->assertTrue( $this->block->equals( Block::newFromID( $this->blockId ) ), "newFromID() returns the same block as the one that was made"); + $this->assertTrue( $this->block->equals( Block::newFromTarget( 'UTBlockee' ) ), "newFromTarget() returns the same block as the one that was made" ); + $this->assertTrue( $this->block->equals( Block::newFromID( $this->blockId ) ), "newFromID() returns the same block as the one that was made" ); } /** * per bug 26425 */ - function testBug26425BlockTimestampDefaultsToTime() { + public function testBug26425BlockTimestampDefaultsToTime() { // delta to stop one-off errors when things happen to go over a second mark. $delta = abs( $this->madeAt - $this->block->mTimestamp ); - $this->assertLessThan( 2, $delta, "If no timestamp is specified, the block is recorded as time()"); - + $this->assertLessThan( 2, $delta, "If no timestamp is specified, the block is recorded as time()" ); } /** @@ -88,13 +89,13 @@ class BlockTest extends MediaWikiLangTestCase { * * This stopped working with r84475 and friends: regression being fixed for bug 29116. * - * @dataProvider dataBug29116 + * @dataProvider provideBug29116Data */ - function testBug29116LoadWithEmptyIp( $vagueTarget ) { + public function testBug29116LoadWithEmptyIp( $vagueTarget ) { $this->hideDeprecated( 'Block::load' ); $uid = User::idFromName( 'UTBlockee' ); - $this->assertTrue( ($uid > 0), 'Must be able to look up the target user during tests' ); + $this->assertTrue( ( $uid > 0 ), 'Must be able to look up the target user during tests' ); $block = new Block(); $ok = $block->load( $vagueTarget, $uid ); @@ -108,14 +109,14 @@ class BlockTest extends MediaWikiLangTestCase { * because the new function didn't accept empty strings like Block::load() * had. Regression bug 29116. * - * @dataProvider dataBug29116 + * @dataProvider provideBug29116Data */ - function testBug29116NewFromTargetWithEmptyIp( $vagueTarget ) { - $block = Block::newFromTarget('UTBlockee', $vagueTarget); + public function testBug29116NewFromTargetWithEmptyIp( $vagueTarget ) { + $block = Block::newFromTarget( 'UTBlockee', $vagueTarget ); $this->assertTrue( $this->block->equals( $block ), "newFromTarget() returns the same block as the one that was made when given empty vagueTarget param " . var_export( $vagueTarget, true ) ); } - function dataBug29116() { + public static function provideBug29116Data() { return array( array( null ), array( '' ), @@ -123,14 +124,13 @@ class BlockTest extends MediaWikiLangTestCase { ); } - function testBlockedUserCanNotCreateAccount() { + public function testBlockedUserCanNotCreateAccount() { $username = 'BlockedUserToCreateAccountWith'; $u = User::newFromName( $username ); $u->setPassword( 'NotRandomPass' ); $u->addToDatabase(); unset( $u ); - // Sanity check $this->assertNull( Block::newFromTarget( $username ), @@ -166,7 +166,7 @@ class BlockTest extends MediaWikiLangTestCase { // Reload block from DB $userBlock = Block::newFromTarget( $username ); $this->assertTrue( - (bool) $block->prevents( 'createaccount' ), + (bool)$block->prevents( 'createaccount' ), "Block object in DB should prevents 'createaccount'" ); @@ -179,12 +179,12 @@ class BlockTest extends MediaWikiLangTestCase { // Reload user $u = User::newFromName( $username ); $this->assertTrue( - (bool) $u->isBlockedFromCreateAccount(), + (bool)$u->isBlockedFromCreateAccount(), "Our sandbox user '$username' should NOT be able to create account" ); } - function testCrappyCrossWikiBlocks() { + public function testCrappyCrossWikiBlocks() { // Delete the last round's block if it's still there $oldBlock = Block::newFromTarget( 'UserOnForeignWiki' ); if ( $oldBlock ) { @@ -222,9 +222,133 @@ class BlockTest extends MediaWikiLangTestCase { $block = Block::newFromID( $res['id'] ); $this->assertEquals( 'UserOnForeignWiki', $block->getTarget()->getName(), 'Correct blockee name' ); - $this->assertEquals( '14146', $block->getTarget()->getId(), 'Correct blockee id' ); + $this->assertEquals( '14146', $block->getTarget()->getId(), 'Correct blockee id' ); $this->assertEquals( 'MetaWikiUser', $block->getBlocker(), 'Correct blocker name' ); $this->assertEquals( 'MetaWikiUser', $block->getByName(), 'Correct blocker name' ); $this->assertEquals( 0, $block->getBy(), 'Correct blocker id' ); } + + protected function addXffBlocks() { + static $inited = false; + + if ( $inited ) { + return; + } + + $inited = true; + + $blockList = array( + array( 'target' => '70.2.0.0/16', + 'type' => Block::TYPE_RANGE, + 'desc' => 'Range Hardblock', + 'ACDisable' => false, + 'isHardblock' => true, + 'isAutoBlocking' => false, + ), + array( 'target' => '2001:4860:4001::/48', + 'type' => Block::TYPE_RANGE, + 'desc' => 'Range6 Hardblock', + 'ACDisable' => false, + 'isHardblock' => true, + 'isAutoBlocking' => false, + ), + array( 'target' => '60.2.0.0/16', + 'type' => Block::TYPE_RANGE, + 'desc' => 'Range Softblock with AC Disabled', + 'ACDisable' => true, + 'isHardblock' => false, + 'isAutoBlocking' => false, + ), + array( 'target' => '50.2.0.0/16', + 'type' => Block::TYPE_RANGE, + 'desc' => 'Range Softblock', + 'ACDisable' => false, + 'isHardblock' => false, + 'isAutoBlocking' => false, + ), + array( 'target' => '50.1.1.1', + 'type' => Block::TYPE_IP, + 'desc' => 'Exact Softblock', + 'ACDisable' => false, + 'isHardblock' => false, + 'isAutoBlocking' => false, + ), + ); + + foreach ( $blockList as $insBlock ) { + $target = $insBlock['target']; + + if ( $insBlock['type'] === Block::TYPE_IP ) { + $target = User::newFromName( IP::sanitizeIP( $target ), false )->getName(); + } elseif ( $insBlock['type'] === Block::TYPE_RANGE ) { + $target = IP::sanitizeRange( $target ); + } + + $block = new Block(); + $block->setTarget( $target ); + $block->setBlocker( 'testblocker@global' ); + $block->mReason = $insBlock['desc']; + $block->mExpiry = 'infinity'; + $block->prevents( 'createaccount', $insBlock['ACDisable'] ); + $block->isHardblock( $insBlock['isHardblock'] ); + $block->isAutoblocking( $insBlock['isAutoBlocking'] ); + $block->insert(); + } + } + + public static function providerXff() { + return array( + array( 'xff' => '1.2.3.4, 70.2.1.1, 60.2.1.1, 2.3.4.5', + 'count' => 2, + 'result' => 'Range Hardblock' + ), + array( 'xff' => '1.2.3.4, 50.2.1.1, 60.2.1.1, 2.3.4.5', + 'count' => 2, + 'result' => 'Range Softblock with AC Disabled' + ), + array( 'xff' => '1.2.3.4, 70.2.1.1, 50.1.1.1, 2.3.4.5', + 'count' => 2, + 'result' => 'Exact Softblock' + ), + array( 'xff' => '1.2.3.4, 70.2.1.1, 50.2.1.1, 50.1.1.1, 2.3.4.5', + 'count' => 3, + 'result' => 'Exact Softblock' + ), + array( 'xff' => '1.2.3.4, 70.2.1.1, 50.2.1.1, 2.3.4.5', + 'count' => 2, + 'result' => 'Range Hardblock' + ), + array( 'xff' => '1.2.3.4, 70.2.1.1, 60.2.1.1, 2.3.4.5', + 'count' => 2, + 'result' => 'Range Hardblock' + ), + array( 'xff' => '50.2.1.1, 60.2.1.1, 2.3.4.5', + 'count' => 2, + 'result' => 'Range Softblock with AC Disabled' + ), + array( 'xff' => '1.2.3.4, 50.1.1.1, 60.2.1.1, 2.3.4.5', + 'count' => 2, + 'result' => 'Exact Softblock' + ), + array( 'xff' => '1.2.3.4, <$A_BUNCH-OF{INVALID}TEXT\>, 60.2.1.1, 2.3.4.5', + 'count' => 1, + 'result' => 'Range Softblock with AC Disabled' + ), + array( 'xff' => '1.2.3.4, 50.2.1.1, 2001:4860:4001:802::1003, 2.3.4.5', + 'count' => 2, + 'result' => 'Range6 Hardblock' + ), + ); + } + + /** + * @dataProvider providerXff + */ + public function testBlocksOnXff( $xff, $exCount, $exResult ) { + $list = array_map( 'trim', explode( ',', $xff ) ); + $xffblocks = Block::getBlocksForIPList( $list, true ); + $this->assertEquals( $exCount, count( $xffblocks ), 'Number of blocks for ' . $xff ); + $block = Block::chooseBlock( $xffblocks, $list ); + $this->assertEquals( $exResult, $block->mReason, 'Correct block type for XFF header ' . $xff ); + } } diff --git a/tests/phpunit/includes/CdbTest.php b/tests/phpunit/includes/CdbTest.php index b5418dd7..e3d9da7c 100644 --- a/tests/phpunit/includes/CdbTest.php +++ b/tests/phpunit/includes/CdbTest.php @@ -3,23 +3,29 @@ /** * Test the CDB reader/writer */ - class CdbTest extends MediaWikiTestCase { - public function setUp() { + protected function setUp() { + parent::setUp(); if ( !CdbReader::haveExtension() ) { $this->markTestSkipped( 'Native CDB support is not available' ); } } + /** + * @group medium + */ public function testCdb() { $dir = wfTempDir(); if ( !is_writable( $dir ) ) { $this->markTestSkipped( "Temp dir isn't writable" ); } - $w1 = new CdbWriter_PHP( "$dir/php.cdb" ); - $w2 = new CdbWriter_DBA( "$dir/dba.cdb" ); + $phpcdbfile = $this->getNewTempFile(); + $dbacdbfile = $this->getNewTempFile(); + + $w1 = new CdbWriter_PHP( $phpcdbfile ); + $w2 = new CdbWriter_DBA( $dbacdbfile ); $data = array(); for ( $i = 0; $i < 1000; $i++ ) { @@ -37,13 +43,13 @@ class CdbTest extends MediaWikiTestCase { $w2->close(); $this->assertEquals( - md5_file( "$dir/dba.cdb" ), - md5_file( "$dir/php.cdb" ), + md5_file( $phpcdbfile ), + md5_file( $dbacdbfile ), 'same hash' ); - $r1 = new CdbReader_PHP( "$dir/php.cdb" ); - $r2 = new CdbReader_DBA( "$dir/dba.cdb" ); + $r1 = new CdbReader_PHP( $phpcdbfile ); + $r2 = new CdbReader_DBA( $dbacdbfile ); foreach ( $data as $key => $value ) { if ( $key === '' ) { @@ -60,9 +66,6 @@ class CdbTest extends MediaWikiTestCase { $this->cdbAssert( "PHP error", $key, $v1, $value ); $this->cdbAssert( "DBA error", $key, $v2, $value ); } - - unlink( "$dir/dba.cdb" ); - unlink( "$dir/php.cdb" ); } private function randomString() { @@ -71,6 +74,7 @@ class CdbTest extends MediaWikiTestCase { for ( $j = 0; $j < $len; $j++ ) { $s .= chr( mt_rand( 0, 255 ) ); } + return $s; } diff --git a/tests/phpunit/includes/CollationTest.php b/tests/phpunit/includes/CollationTest.php new file mode 100644 index 00000000..43bb3941 --- /dev/null +++ b/tests/phpunit/includes/CollationTest.php @@ -0,0 +1,111 @@ +<?php +class CollationTest extends MediaWikiLangTestCase { + protected function setUp() { + parent::setUp(); + if ( !extension_loaded( 'intl' ) ) { + $this->markTestSkipped( 'These tests require intl extension' ); + } + } + + /** + * Test to make sure, that if you + * have "X" and "XY", the binary + * sortkey also has "X" being a + * prefix of "XY". Our collation + * code makes this assumption. + * + * @param $lang String Language code for collator + * @param $base String Base string + * @param $extended String String containing base as a prefix. + * + * @dataProvider prefixDataProvider + */ + public function testIsPrefix( $lang, $base, $extended ) { + $cp = Collator::create( $lang ); + $cp->setStrength( Collator::PRIMARY ); + $baseBin = $cp->getSortKey( $base ); + // Remove sortkey terminator + $baseBin = rtrim( $baseBin, "\0" ); + $extendedBin = $cp->getSortKey( $extended ); + $this->assertStringStartsWith( $baseBin, $extendedBin, "$base is not a prefix of $extended" ); + } + + function prefixDataProvider() { + return array( + array( 'en', 'A', 'AA' ), + array( 'en', 'A', 'AAA' ), + array( 'en', 'Д', 'ДЂ' ), + array( 'en', 'Д', 'ДA' ), + // 'Ʒ' should expand to 'Z ' (note space). + array( 'fi', 'Z', 'Ʒ' ), + // 'Þ' should expand to 'th' + array( 'sv', 't', 'Þ' ), + // Javanese is a limited use alphabet, so should have 3 bytes + // per character, so do some tests with it. + array( 'en', 'ꦲ', 'ꦲꦤ' ), + array( 'en', 'ꦲ', 'ꦲД' ), + array( 'en', 'A', 'Aꦲ' ), + ); + } + + /** + * Opposite of testIsPrefix + * + * @dataProvider notPrefixDataProvider + */ + public function testNotIsPrefix( $lang, $base, $extended ) { + $cp = Collator::create( $lang ); + $cp->setStrength( Collator::PRIMARY ); + $baseBin = $cp->getSortKey( $base ); + // Remove sortkey terminator + $baseBin = rtrim( $baseBin, "\0" ); + $extendedBin = $cp->getSortKey( $extended ); + $this->assertStringStartsNotWith( $baseBin, $extendedBin, "$base is a prefix of $extended" ); + } + + function notPrefixDataProvider() { + return array( + array( 'en', 'A', 'B' ), + array( 'en', 'AC', 'ABC' ), + array( 'en', 'Z', 'Ʒ' ), + array( 'en', 'A', 'ꦲ' ), + ); + } + + /** + * Test correct first letter is fetched. + * + * @param $collation String Collation name (aka uca-en) + * @param $string String String to get first letter of + * @param $firstLetter String Expected first letter. + * + * @dataProvider firstLetterProvider + */ + public function testGetFirstLetter( $collation, $string, $firstLetter ) { + $col = Collation::factory( $collation ); + $this->assertEquals( $firstLetter, $col->getFirstLetter( $string ) ); + } + + function firstLetterProvider() { + return array( + array( 'uppercase', 'Abc', 'A' ), + array( 'uppercase', 'abc', 'A' ), + array( 'identity', 'abc', 'a' ), + array( 'uca-en', 'abc', 'A' ), + array( 'uca-en', ' ', ' ' ), + array( 'uca-en', 'Êveryone', 'E' ), + array( 'uca-vi', 'Êveryone', 'Ê' ), + // Make sure thorn is not a first letter. + array( 'uca-sv', 'The', 'T' ), + array( 'uca-sv', 'Å', 'Å' ), + array( 'uca-hu', 'dzsdo', 'Dzs' ), + array( 'uca-hu', 'dzdso', 'Dz' ), + array( 'uca-hu', 'CSD', 'Cs' ), + array( 'uca-root', 'CSD', 'C' ), + array( 'uca-fi', 'Ǥ', 'G' ), + array( 'uca-fi', 'Ŧ', 'T' ), + array( 'uca-fi', 'Ʒ', 'Z' ), + array( 'uca-fi', 'Ŋ', 'N' ), + ); + } +} diff --git a/tests/phpunit/includes/DiffHistoryBlobTest.php b/tests/phpunit/includes/DiffHistoryBlobTest.php index cdb6ed2f..a4d5b91a 100644 --- a/tests/phpunit/includes/DiffHistoryBlobTest.php +++ b/tests/phpunit/includes/DiffHistoryBlobTest.php @@ -1,34 +1,38 @@ <?php class DiffHistoryBlobTest extends MediaWikiTestCase { - function setUp() { + protected function setUp() { if ( !extension_loaded( 'xdiff' ) ) { $this->markTestSkipped( 'The xdiff extension is not available' ); + return; } if ( !function_exists( 'xdiff_string_rabdiff' ) ) { $this->markTestSkipped( 'The version of xdiff extension is lower than 1.5.0' ); + return; } - if ( !extension_loaded( 'hash' ) && !extension_loaded( 'mhash' ) ) { - $this->markTestSkipped( 'Neither the hash nor mhash extension is available' ); + if ( !extension_loaded( 'hash' ) ) { + $this->markTestSkipped( 'The hash extension is not available' ); + return; } + parent::setUp(); } /** * Test for DiffHistoryBlob::xdiffAdler32() * @dataProvider provideXdiffAdler32 */ - function testXdiffAdler32( $input ) { - $xdiffHash = substr( xdiff_string_rabdiff( $input, '' ), 0, 4 ); + public function testXdiffAdler32( $input ) { + $xdiffHash = substr( xdiff_string_rabdiff( $input, '' ), 0, 4 ); $dhb = new DiffHistoryBlob; $myHash = $dhb->xdiffAdler32( $input ); $this->assertSame( bin2hex( $xdiffHash ), bin2hex( $myHash ), "Hash of " . addcslashes( $input, "\0..\37!@\@\177..\377" ) ); } - function provideXdiffAdler32() { + public static function provideXdiffAdler32() { return array( array( '', 'Empty string' ), array( "\0", 'Null' ), diff --git a/tests/phpunit/includes/EditPageTest.php b/tests/phpunit/includes/EditPageTest.php index 8ecfd7e5..87272a4c 100644 --- a/tests/phpunit/includes/EditPageTest.php +++ b/tests/phpunit/includes/EditPageTest.php @@ -1,19 +1,25 @@ <?php /** - * @group Editing + * @group Editing + * + * @group Database + * ^--- tell jenkins this test needs the database + * + * @group medium + * ^--- tell phpunit that these test cases may take longer than 2 seconds. */ -class EditPageTest extends MediaWikiTestCase { +class EditPageTest extends MediaWikiLangTestCase { /** - * @dataProvider dataExtractSectionTitle + * @dataProvider provideExtractSectionTitle */ - function testExtractSectionTitle( $section, $title ) { + public function testExtractSectionTitle( $section, $title ) { $extracted = EditPage::extractSectionTitle( $section ); $this->assertEquals( $title, $extracted ); } - function dataExtractSectionTitle() { + public static function provideExtractSectionTitle() { return array( array( "== Test ==\n\nJust a test section.", @@ -37,4 +43,449 @@ class EditPageTest extends MediaWikiTestCase { ), ); } + + protected function forceRevisionDate( WikiPage $page, $timestamp ) { + $dbw = wfGetDB( DB_MASTER ); + + $dbw->update( 'revision', + array( 'rev_timestamp' => $dbw->timestamp( $timestamp ) ), + array( 'rev_id' => $page->getLatest() ) ); + + $page->clear(); + } + + /** + * User input text is passed to rtrim() by edit page. This is a simple + * wrapper around assertEquals() which calls rrtrim() to normalize the + * expected and actual texts. + */ + function assertEditedTextEquals( $expected, $actual, $msg = '' ) { + return $this->assertEquals( rtrim( $expected ), rtrim( $actual ), $msg ); + } + + /** + * Performs an edit and checks the result. + * + * @param String|Title $title The title of the page to edit + * @param String|null $baseText Some text to create the page with before attempting the edit. + * @param User|String|null $user The user to perform the edit as. + * @param array $edit An array of request parameters used to define the edit to perform. + * Some well known fields are: + * * wpTextbox1: the text to submit + * * wpSummary: the edit summary + * * wpEditToken: the edit token (will be inserted if not provided) + * * wpEdittime: timestamp of the edit's base revision (will be inserted if not provided) + * * wpStarttime: timestamp when the edit started (will be inserted if not provided) + * * wpSectionTitle: the section to edit + * * wpMinorEdit: mark as minor edit + * * wpWatchthis: whether to watch the page + * @param int|null $expectedCode The expected result code (EditPage::AS_XXX constants). + * Set to null to skip the check. Defaults to EditPage::AS_OK. + * @param String|null $expectedText The text expected to be on the page after the edit. + * Set to null to skip the check. + * @param String|null $message An optional message to show along with any error message. + * + * @return WikiPage The page that was just edited, useful for getting the edit's rev_id, etc. + */ + protected function assertEdit( $title, $baseText, $user = null, array $edit, + $expectedCode = EditPage::AS_OK, $expectedText = null, $message = null + ) { + if ( is_string( $title ) ) { + $ns = $this->getDefaultWikitextNS(); + $title = Title::newFromText( $title, $ns ); + } + + if ( is_string( $user ) ) { + $user = User::newFromName( $user ); + + if ( $user->getId() === 0 ) { + $user->addToDatabase(); + } + } + + $page = WikiPage::factory( $title ); + + if ( $baseText !== null ) { + $content = ContentHandler::makeContent( $baseText, $title ); + $page->doEditContent( $content, "base text for test" ); + $this->forceRevisionDate( $page, '20120101000000' ); + + //sanity check + $page->clear(); + $currentText = ContentHandler::getContentText( $page->getContent() ); + + # EditPage rtrim() the user input, so we alter our expected text + # to reflect that. + $this->assertEditedTextEquals( $baseText, $currentText ); + } + + if ( $user == null ) { + $user = $GLOBALS['wgUser']; + } else { + $this->setMwGlobals( 'wgUser', $user ); + } + + if ( !isset( $edit['wpEditToken'] ) ) { + $edit['wpEditToken'] = $user->getEditToken(); + } + + if ( !isset( $edit['wpEdittime'] ) ) { + $edit['wpEdittime'] = $page->exists() ? $page->getTimestamp() : ''; + } + + if ( !isset( $edit['wpStarttime'] ) ) { + $edit['wpStarttime'] = wfTimestampNow(); + } + + $req = new FauxRequest( $edit, true ); // session ?? + + $ep = new EditPage( new Article( $title ) ); + $ep->setContextTitle( $title ); + $ep->importFormData( $req ); + + $bot = isset( $edit['bot'] ) ? (bool)$edit['bot'] : false; + + // this is where the edit happens! + // Note: don't want to use EditPage::AttemptSave, because it messes with $wgOut + // and throws exceptions like PermissionsError + $status = $ep->internalAttemptSave( $result, $bot ); + + if ( $expectedCode !== null ) { + // check edit code + $this->assertEquals( $expectedCode, $status->value, + "Expected result code mismatch. $message" ); + } + + $page = WikiPage::factory( $title ); + + if ( $expectedText !== null ) { + // check resulting page text + $content = $page->getContent(); + $text = ContentHandler::getContentText( $content ); + + # EditPage rtrim() the user input, so we alter our expected text + # to reflect that. + $this->assertEditedTextEquals( $expectedText, $text, + "Expected article text mismatch. $message" ); + } + + return $page; + } + + public function testCreatePage() { + $this->assertEdit( + 'EditPageTest_testCreatePage', + null, + null, + array( + 'wpTextbox1' => "Hello World!", + ), + EditPage::AS_SUCCESS_NEW_ARTICLE, + "Hello World!", + "expected article being created" + )->doDeleteArticleReal( 'EditPageTest_testCreatePage' ); + + $this->assertEdit( + 'EditPageTest_testCreatePage', + null, + null, + array( + 'wpTextbox1' => "", + ), + EditPage::AS_BLANK_ARTICLE, + null, + "expected article not being created if empty" + ); + + + $this->assertEdit( + 'MediaWiki:January', + null, + 'UTSysop', + array( + 'wpTextbox1' => "Not January", + ), + EditPage::AS_SUCCESS_NEW_ARTICLE, + "Not January", + "expected MediaWiki: page being created" + )->doDeleteArticleReal( 'EditPageTest_testCreatePage' ); + + $this->assertEdit( + 'MediaWiki:EditPageTest_testCreatePage', + null, + 'UTSysop', + array( + 'wpTextbox1' => "", + ), + EditPage::AS_BLANK_ARTICLE, + null, + "expected not-registered MediaWiki: page not being created if empty" + ); + + $this->assertEdit( + 'MediaWiki:January', + null, + 'UTSysop', + array( + 'wpTextbox1' => "", + ), + EditPage::AS_SUCCESS_NEW_ARTICLE, + "", + "expected registered MediaWiki: page being created even if empty" + )->doDeleteArticleReal( 'EditPageTest_testCreatePage' ); + + $this->assertEdit( + 'MediaWiki:Ipb-default-expiry', + null, + 'UTSysop', + array( + 'wpTextbox1' => "", + ), + EditPage::AS_BLANK_ARTICLE, + "", + "expected registered MediaWiki: page whose default content is empty not being created if empty" + ); + + $this->assertEdit( + 'MediaWiki:January', + null, + 'UTSysop', + array( + 'wpTextbox1' => "January", + ), + EditPage::AS_BLANK_ARTICLE, + null, + "expected MediaWiki: page not being created if text equals default message" + ); + } + + public function testUpdatePage() { + $text = "one"; + $edit = array( + 'wpTextbox1' => $text, + 'wpSummary' => 'first update', + ); + + $page = $this->assertEdit( 'EditPageTest_testUpdatePage', "zero", null, $edit, + EditPage::AS_SUCCESS_UPDATE, $text, + "expected successfull update with given text" ); + + $this->forceRevisionDate( $page, '20120101000000' ); + + $text = "two"; + $edit = array( + 'wpTextbox1' => $text, + 'wpSummary' => 'second update', + ); + + $this->assertEdit( 'EditPageTest_testUpdatePage', null, null, $edit, + EditPage::AS_SUCCESS_UPDATE, $text, + "expected successfull update with given text" ); + } + + public static function provideSectionEdit() { + $text = 'Intro + +== one == +first section. + +== two == +second section. +'; + + $sectionOne = '== one == +hello +'; + + $newSection = '== new section == + +hello +'; + + $textWithNewSectionOne = preg_replace( + '/== one ==.*== two ==/ms', + "$sectionOne\n== two ==", $text + ); + + $textWithNewSectionAdded = "$text\n$newSection"; + + return array( + array( #0 + $text, + '', + 'hello', + 'replace all', + 'hello' + ), + + array( #1 + $text, + '1', + $sectionOne, + 'replace first section', + $textWithNewSectionOne, + ), + + array( #2 + $text, + 'new', + 'hello', + 'new section', + $textWithNewSectionAdded, + ), + ); + } + + /** + * @dataProvider provideSectionEdit + */ + public function testSectionEdit( $base, $section, $text, $summary, $expected ) { + $edit = array( + 'wpTextbox1' => $text, + 'wpSummary' => $summary, + 'wpSection' => $section, + ); + + $this->assertEdit( 'EditPageTest_testSectionEdit', $base, null, $edit, + EditPage::AS_SUCCESS_UPDATE, $expected, + "expected successfull update of section" ); + } + + public static function provideAutoMerge() { + $tests = array(); + + $tests[] = array( #0: plain conflict + "Elmo", # base edit user + "one\n\ntwo\n\nthree\n", + array( #adam's edit + 'wpStarttime' => 1, + 'wpTextbox1' => "ONE\n\ntwo\n\nthree\n", + ), + array( #berta's edit + 'wpStarttime' => 2, + 'wpTextbox1' => "(one)\n\ntwo\n\nthree\n", + ), + EditPage::AS_CONFLICT_DETECTED, # expected code + "ONE\n\ntwo\n\nthree\n", # expected text + 'expected edit conflict', # message + ); + + $tests[] = array( #1: successful merge + "Elmo", # base edit user + "one\n\ntwo\n\nthree\n", + array( #adam's edit + 'wpStarttime' => 1, + 'wpTextbox1' => "ONE\n\ntwo\n\nthree\n", + ), + array( #berta's edit + 'wpStarttime' => 2, + 'wpTextbox1' => "one\n\ntwo\n\nTHREE\n", + ), + EditPage::AS_SUCCESS_UPDATE, # expected code + "ONE\n\ntwo\n\nTHREE\n", # expected text + 'expected automatic merge', # message + ); + + $text = "Intro\n\n"; + $text .= "== first section ==\n\n"; + $text .= "one\n\ntwo\n\nthree\n\n"; + $text .= "== second section ==\n\n"; + $text .= "four\n\nfive\n\nsix\n\n"; + + // extract the first section. + $section = preg_replace( '/.*(== first section ==.*)== second section ==.*/sm', '$1', $text ); + + // generate expected text after merge + $expected = str_replace( 'one', 'ONE', str_replace( 'three', 'THREE', $text ) ); + + $tests[] = array( #2: merge in section + "Elmo", # base edit user + $text, + array( #adam's edit + 'wpStarttime' => 1, + 'wpTextbox1' => str_replace( 'one', 'ONE', $section ), + 'wpSection' => '1' + ), + array( #berta's edit + 'wpStarttime' => 2, + 'wpTextbox1' => str_replace( 'three', 'THREE', $section ), + 'wpSection' => '1' + ), + EditPage::AS_SUCCESS_UPDATE, # expected code + $expected, # expected text + 'expected automatic section merge', # message + ); + + // see whether it makes a difference who did the base edit + $testsWithAdam = array_map( function ( $test ) { + $test[0] = 'Adam'; // change base edit user + return $test; + }, $tests ); + + $testsWithBerta = array_map( function ( $test ) { + $test[0] = 'Berta'; // change base edit user + return $test; + }, $tests ); + + return array_merge( $tests, $testsWithAdam, $testsWithBerta ); + } + + /** + * @dataProvider provideAutoMerge + */ + public function testAutoMerge( $baseUser, $text, $adamsEdit, $bertasEdit, + $expectedCode, $expectedText, $message = null + ) { + $this->checkHasDiff3(); + + //create page + $ns = $this->getDefaultWikitextNS(); + $title = Title::newFromText( 'EditPageTest_testAutoMerge', $ns ); + $page = WikiPage::factory( $title ); + + if ( $page->exists() ) { + $page->doDeleteArticle( "clean slate for testing" ); + } + + $baseEdit = array( + 'wpTextbox1' => $text, + ); + + $page = $this->assertEdit( 'EditPageTest_testAutoMerge', null, + $baseUser, $baseEdit, null, null, __METHOD__ ); + + $this->forceRevisionDate( $page, '20120101000000' ); + + $edittime = $page->getTimestamp(); + + // start timestamps for conflict detection + if ( !isset( $adamsEdit['wpStarttime'] ) ) { + $adamsEdit['wpStarttime'] = 1; + } + + if ( !isset( $bertasEdit['wpStarttime'] ) ) { + $bertasEdit['wpStarttime'] = 2; + } + + $starttime = wfTimestampNow(); + $adamsTime = wfTimestamp( TS_MW, (int)wfTimestamp( TS_UNIX, $starttime ) + (int)$adamsEdit['wpStarttime'] ); + $bertasTime = wfTimestamp( TS_MW, (int)wfTimestamp( TS_UNIX, $starttime ) + (int)$bertasEdit['wpStarttime'] ); + + $adamsEdit['wpStarttime'] = $adamsTime; + $bertasEdit['wpStarttime'] = $bertasTime; + + $adamsEdit['wpSummary'] = 'Adam\'s edit'; + $bertasEdit['wpSummary'] = 'Bertas\'s edit'; + + $adamsEdit['wpEdittime'] = $edittime; + $bertasEdit['wpEdittime'] = $edittime; + + // first edit + $this->assertEdit( 'EditPageTest_testAutoMerge', null, 'Adam', $adamsEdit, + EditPage::AS_SUCCESS_UPDATE, null, "expected successfull update" ); + + // second edit + $this->assertEdit( 'EditPageTest_testAutoMerge', null, 'Berta', $bertasEdit, + $expectedCode, $expectedText, $message ); + } } diff --git a/tests/phpunit/includes/ExternalStoreTest.php b/tests/phpunit/includes/ExternalStoreTest.php index 92ec7344..fcffcbc2 100644 --- a/tests/phpunit/includes/ExternalStoreTest.php +++ b/tests/phpunit/includes/ExternalStoreTest.php @@ -4,29 +4,78 @@ */ class ExternalStoreTest extends MediaWikiTestCase { - private $saved_wgExternalStores; - function setUp() { - global $wgExternalStores; - $this->saved_wgExternalStores = $wgExternalStores ; - } + public function testExternalFetchFromURL() { + $this->setMwGlobals( 'wgExternalStores', false ); - function tearDown() { - global $wgExternalStores; - $wgExternalStores = $this->saved_wgExternalStores ; - } + $this->assertFalse( + ExternalStore::fetchFromURL( 'FOO://cluster1/200' ), + 'Deny if wgExternalStores is not set to a non-empty array' + ); - function testExternalStoreDoesNotFetchIncorrectURL() { - global $wgExternalStores; - $wgExternalStores = true; + $this->setMwGlobals( 'wgExternalStores', array( 'FOO' ) ); + $this->assertEquals( + ExternalStore::fetchFromURL( 'FOO://cluster1/200' ), + 'Hello', + 'Allow FOO://cluster1/200' + ); + $this->assertEquals( + ExternalStore::fetchFromURL( 'FOO://cluster1/300/0' ), + 'Hello', + 'Allow FOO://cluster1/300/0' + ); # Assertions for r68900 $this->assertFalse( - ExternalStore::fetchFromURL( 'http://' ) ); + ExternalStore::fetchFromURL( 'ftp.example.org' ), + 'Deny domain ftp.example.org' + ); $this->assertFalse( - ExternalStore::fetchFromURL( 'ftp.wikimedia.org' ) ); + ExternalStore::fetchFromURL( '/example.txt' ), + 'Deny path /example.txt' + ); $this->assertFalse( - ExternalStore::fetchFromURL( '/super.txt' ) ); + ExternalStore::fetchFromURL( 'http://' ), + 'Deny protocol http://' + ); } } +class ExternalStoreFOO { + + protected $data = array( + 'cluster1' => array( + '200' => 'Hello', + '300' => array( + 'Hello', 'World', + ), + ), + ); + + /** + * Fetch data from given URL + * @param $url String: an url of the form FOO://cluster/id or FOO://cluster/id/itemid. + * @return mixed + */ + function fetchFromURL( $url ) { + // Based on ExternalStoreDB + $path = explode( '/', $url ); + $cluster = $path[2]; + $id = $path[3]; + if ( isset( $path[4] ) ) { + $itemID = $path[4]; + } else { + $itemID = false; + } + + if ( !isset( $this->data[$cluster][$id] ) ) { + return null; + } + + if ( $itemID !== false && is_array( $this->data[$cluster][$id] ) && isset( $this->data[$cluster][$id][$itemID] ) ) { + return $this->data[$cluster][$id][$itemID]; + } + + return $this->data[$cluster][$id]; + } +} diff --git a/tests/phpunit/includes/ExtraParserTest.php b/tests/phpunit/includes/ExtraParserTest.php index 903a6d25..6c67beb1 100644 --- a/tests/phpunit/includes/ExtraParserTest.php +++ b/tests/phpunit/includes/ExtraParserTest.php @@ -5,20 +5,21 @@ */ class ExtraParserTest extends MediaWikiTestCase { - function setUp() { - global $wgMemc; - global $wgContLang; - global $wgShowDBErrorBacktrace; - global $wgLanguageCode; - global $wgAlwaysUseTidy; - - $wgShowDBErrorBacktrace = true; - $wgLanguageCode = 'en'; - $wgContLang = new Language( 'en' ); - $wgMemc = new EmptyBagOStuff; - $wgAlwaysUseTidy = false; - - $this->options = new ParserOptions; + protected function setUp() { + parent::setUp(); + + $contLang = Language::factory( 'en' ); + $this->setMwGlobals( array( + 'wgShowDBErrorBacktrace' => true, + 'wgLanguageCode' => 'en', + 'wgContLang' => $contLang, + 'wgLang' => Language::factory( 'en' ), + 'wgMemc' => new EmptyBagOStuff, + 'wgAlwaysUseTidy' => false, + 'wgCleanSignatures' => true, + ) ); + + $this->options = ParserOptions::newFromUserAndLang( new User, $contLang ); $this->options->setTemplateCallback( array( __CLASS__, 'statelessFetchTemplate' ) ); $this->parser = new Parser; @@ -26,117 +27,104 @@ class ExtraParserTest extends MediaWikiTestCase { } // Bug 8689 - Long numeric lines kill the parser - function testBug8689() { - global $wgLang; + public function testBug8689() { global $wgUser; $longLine = '1.' . str_repeat( '1234567890', 100000 ) . "\n"; - if ( $wgLang === null ) $wgLang = new Language; - $t = Title::newFromText( 'Unit test' ); $options = ParserOptions::newFromUser( $wgUser ); $this->assertEquals( "<p>$longLine</p>", $this->parser->parse( $longLine, $t, $options )->getText() ); } - + /* Test the parser entry points */ - function testParse() { + public function testParse() { $title = Title::newFromText( __FUNCTION__ ); - $parserOutput = $this->parser->parse( "Test\n{{Foo}}\n{{Bar}}" , $title, $this->options ); + $parserOutput = $this->parser->parse( "Test\n{{Foo}}\n{{Bar}}", $title, $this->options ); $this->assertEquals( "<p>Test\nContent of <i>Template:Foo</i>\nContent of <i>Template:Bar</i>\n</p>", $parserOutput->getText() ); } - - function testPreSaveTransform() { + + public function testPreSaveTransform() { global $wgUser; $title = Title::newFromText( __FUNCTION__ ); $outputText = $this->parser->preSaveTransform( "Test\r\n{{subst:Foo}}\n{{Bar}}", $title, $wgUser, $this->options ); $this->assertEquals( "Test\nContent of ''Template:Foo''\n{{Bar}}", $outputText ); } - - function testPreprocess() { + + public function testPreprocess() { $title = Title::newFromText( __FUNCTION__ ); - $outputText = $this->parser->preprocess( "Test\n{{Foo}}\n{{Bar}}" , $title, $this->options ); - + $outputText = $this->parser->preprocess( "Test\n{{Foo}}\n{{Bar}}", $title, $this->options ); + $this->assertEquals( "Test\nContent of ''Template:Foo''\nContent of ''Template:Bar''", $outputText ); } - + /** * cleanSig() makes all templates substs and removes tildes */ - function testCleanSig() { - global $wgCleanSignatures; - $oldCleanSignature = $wgCleanSignatures; - $wgCleanSignatures = true; - + public function testCleanSig() { $title = Title::newFromText( __FUNCTION__ ); $outputText = $this->parser->cleanSig( "{{Foo}} ~~~~" ); - $wgCleanSignatures = $oldCleanSignature; - $this->assertEquals( "{{SUBST:Foo}} ", $outputText ); } /** * cleanSig() should do nothing if disabled */ - function testCleanSigDisabled() { - global $wgCleanSignatures; - $oldCleanSignature = $wgCleanSignatures; - $wgCleanSignatures = false; + public function testCleanSigDisabled() { + $this->setMwGlobals( 'wgCleanSignatures', false ); $title = Title::newFromText( __FUNCTION__ ); $outputText = $this->parser->cleanSig( "{{Foo}} ~~~~" ); - $wgCleanSignatures = $oldCleanSignature; - $this->assertEquals( "{{Foo}} ~~~~", $outputText ); } - + /** * cleanSigInSig() just removes tildes * @dataProvider provideStringsForCleanSigInSig */ - function testCleanSigInSig( $in, $out ) { - $this->assertEquals( Parser::cleanSigInSig( $in), $out ); + public function testCleanSigInSig( $in, $out ) { + $this->assertEquals( Parser::cleanSigInSig( $in ), $out ); } - - function provideStringsForCleanSigInSig() { + + public static function provideStringsForCleanSigInSig() { return array( array( "{{Foo}} ~~~~", "{{Foo}} " ), array( "~~~", "" ), array( "~~~~~", "" ), ); } - - function testGetSection() { + + public function testGetSection() { $outputText2 = $this->parser->getSection( "Section 0\n== Heading 1 ==\nSection 1\n=== Heading 2 ===\nSection 2\n== Heading 3 ==\nSection 3\n", 2 ); $outputText1 = $this->parser->getSection( "Section 0\n== Heading 1 ==\nSection 1\n=== Heading 2 ===\nSection 2\n== Heading 3 ==\nSection 3\n", 1 ); - + $this->assertEquals( "=== Heading 2 ===\nSection 2", $outputText2 ); $this->assertEquals( "== Heading 1 ==\nSection 1\n=== Heading 2 ===\nSection 2", $outputText1 ); } - - function testReplaceSection() { + + public function testReplaceSection() { $outputText = $this->parser->replaceSection( "Section 0\n== Heading 1 ==\nSection 1\n=== Heading 2 ===\nSection 2\n== Heading 3 ==\nSection 3\n", 1, "New section 1" ); - + $this->assertEquals( "Section 0\nNew section 1\n\n== Heading 3 ==\nSection 3", $outputText ); } - + /** * Templates and comments are not affected, but noinclude/onlyinclude is. */ - function testGetPreloadText() { + public function testGetPreloadText() { $title = Title::newFromText( __FUNCTION__ ); $outputText = $this->parser->getPreloadText( "{{Foo}}<noinclude> censored</noinclude> information <!-- is very secret -->", $title, $this->options ); - + $this->assertEquals( "{{Foo}} information <!-- is very secret -->", $outputText ); } - - static function statelessFetchTemplate( $title, $parser=false ) { + + static function statelessFetchTemplate( $title, $parser = false ) { $text = "Content of ''" . $title->getFullText() . "''"; $deps = array(); - + return array( 'text' => $text, 'finalTitle' => $title, @@ -146,12 +134,12 @@ class ExtraParserTest extends MediaWikiTestCase { /** * @group Database */ - function testTrackingCategory() { + public function testTrackingCategory() { $title = Title::newFromText( __FUNCTION__ ); - $catName = wfMessage( 'broken-file-category' )->inContentLanguage()->text(); + $catName = wfMessage( 'broken-file-category' )->inContentLanguage()->text(); $cat = Title::makeTitleSafe( NS_CATEGORY, $catName ); $expected = array( $cat->getDBkey() ); - $parserOutput = $this->parser->parse( "[[file:nonexistent]]" , $title, $this->options ); + $parserOutput = $this->parser->parse( "[[file:nonexistent]]", $title, $this->options ); $result = $parserOutput->getCategoryLinks(); $this->assertEquals( $expected, $result ); } @@ -159,11 +147,11 @@ class ExtraParserTest extends MediaWikiTestCase { /** * @group Database */ - function testTrackingCategorySpecial() { + public function testTrackingCategorySpecial() { // Special pages shouldn't have tracking cats. $title = SpecialPage::getTitleFor( 'Contributions' ); - $parserOutput = $this->parser->parse( "[[file:nonexistent]]" , $title, $this->options ); + $parserOutput = $this->parser->parse( "[[file:nonexistent]]", $title, $this->options ); $result = $parserOutput->getCategoryLinks(); $this->assertEmpty( $result ); } - } +} diff --git a/tests/phpunit/includes/FallbackTest.php b/tests/phpunit/includes/FallbackTest.php new file mode 100644 index 00000000..f408f471 --- /dev/null +++ b/tests/phpunit/includes/FallbackTest.php @@ -0,0 +1,73 @@ +<?php + +/** + * @covers Fallback + */ +class FallbackTest extends MediaWikiTestCase { + + public function testFallbackMbstringFunctions() { + + if ( !extension_loaded( 'mbstring' ) ) { + $this->markTestSkipped( "The mb_string functions must be installed to test the fallback functions" ); + } + + $sampleUTF = "Östergötland_coat_of_arms.png"; + + //mb_substr + $substr_params = array( + array( 0, 0 ), + array( 5, -4 ), + array( 33 ), + array( 100, -5 ), + array( -8, 10 ), + array( 1, 1 ), + array( 2, -1 ) + ); + + foreach ( $substr_params as $param_set ) { + $old_param_set = $param_set; + array_unshift( $param_set, $sampleUTF ); + + $this->assertEquals( + call_user_func_array( 'mb_substr', $param_set ), + call_user_func_array( 'Fallback::mb_substr', $param_set ), + 'Fallback mb_substr with params ' . implode( ', ', $old_param_set ) + ); + } + + //mb_strlen + $this->assertEquals( + mb_strlen( $sampleUTF ), + Fallback::mb_strlen( $sampleUTF ), + 'Fallback mb_strlen' + ); + + //mb_str(r?)pos + $strpos_params = array( + //array( 'ter' ), + //array( 'Ö' ), + //array( 'Ö', 3 ), + //array( 'oat_', 100 ), + //array( 'c', -10 ), + //Broken for now + ); + + foreach ( $strpos_params as $param_set ) { + $old_param_set = $param_set; + array_unshift( $param_set, $sampleUTF ); + + $this->assertEquals( + call_user_func_array( 'mb_strpos', $param_set ), + call_user_func_array( 'Fallback::mb_strpos', $param_set ), + 'Fallback mb_strpos with params ' . implode( ', ', $old_param_set ) + ); + + $this->assertEquals( + call_user_func_array( 'mb_strrpos', $param_set ), + call_user_func_array( 'Fallback::mb_strrpos', $param_set ), + 'Fallback mb_strrpos with params ' . implode( ', ', $old_param_set ) + ); + } + } + +}
\ No newline at end of file diff --git a/tests/phpunit/includes/FauxRequestTest.php b/tests/phpunit/includes/FauxRequestTest.php new file mode 100644 index 00000000..9f3aa11d --- /dev/null +++ b/tests/phpunit/includes/FauxRequestTest.php @@ -0,0 +1,15 @@ +<?php + +class FauxRequestTest extends MediaWikiTestCase { + + public function testGetSetHeader() { + $value = 'test/test'; + + $request = new FauxRequest(); + $request->setHeader( 'Content-Type', $value ); + + $this->assertEquals( $request->getHeader( 'Content-Type' ), $value ); + $this->assertEquals( $request->getHeader( 'CONTENT-TYPE' ), $value ); + $this->assertEquals( $request->getHeader( 'content-type' ), $value ); + } +} diff --git a/tests/phpunit/includes/FauxResponseTest.php b/tests/phpunit/includes/FauxResponseTest.php index c0420049..f9ba1b3b 100644 --- a/tests/phpunit/includes/FauxResponseTest.php +++ b/tests/phpunit/includes/FauxResponseTest.php @@ -25,17 +25,18 @@ class FauxResponseTest extends MediaWikiTestCase { var $response; - function setUp() { + protected function setUp() { + parent::setUp(); $this->response = new FauxResponse; } - function testCookie() { + public function testCookie() { $this->assertEquals( null, $this->response->getcookie( 'key' ), 'Non-existing cookie' ); $this->response->setcookie( 'key', 'val' ); $this->assertEquals( 'val', $this->response->getcookie( 'key' ), 'Existing cookie' ); } - function testHeader() { + public function testHeader() { $this->assertEquals( null, $this->response->getheader( 'Location' ), 'Non-existing header' ); $this->response->header( 'Location: http://localhost/' ); @@ -46,9 +47,12 @@ class FauxResponseTest extends MediaWikiTestCase { $this->response->header( 'Location: http://127.0.0.2/', false ); $this->assertEquals( 'http://127.0.0.1/', $this->response->getheader( 'Location' ), 'Same header with override disabled' ); + + $this->response->header( 'Location: http://localhost/' ); + $this->assertEquals( 'http://localhost/', $this->response->getheader( 'LOCATION' ), 'Get header case insensitive' ); } - function testResponseCode() { + public function testResponseCode() { $this->response->header( 'HTTP/1.1 200' ); $this->assertEquals( 200, $this->response->getStatusCode(), 'Header with no message' ); diff --git a/tests/phpunit/includes/FormOptionsInitializationTest.php b/tests/phpunit/includes/FormOptionsInitializationTest.php index d86c95d7..fb2304dc 100644 --- a/tests/phpunit/includes/FormOptionsInitializationTest.php +++ b/tests/phpunit/includes/FormOptionsInitializationTest.php @@ -41,45 +41,44 @@ class FormOptionsInitializationTest extends MediaWikiTestCase { * with. */ protected function setUp() { + parent::setUp(); $this->object = new FormOptionsExposed(); - } public function testAddStringOption() { - $this->object->add( 'foo', 'string value' ); + $this->object->add( 'foo', 'string value' ); $this->assertEquals( array( 'foo' => array( - 'default' => 'string value', + 'default' => 'string value', 'consumed' => false, - 'type' => FormOptions::STRING, - 'value' => null, - ) + 'type' => FormOptions::STRING, + 'value' => null, + ) ), $this->object->getOptions() ); } public function testAddIntegers() { - $this->object->add( 'one', 1 ); - $this->object->add( 'negone', -1 ); + $this->object->add( 'one', 1 ); + $this->object->add( 'negone', -1 ); $this->assertEquals( array( 'negone' => array( - 'default' => -1, - 'value' => null, + 'default' => -1, + 'value' => null, 'consumed' => false, - 'type' => FormOptions::INT, - ), + 'type' => FormOptions::INT, + ), 'one' => array( - 'default' => 1, - 'value' => null, + 'default' => 1, + 'value' => null, 'consumed' => false, - 'type' => FormOptions::INT, - ) + 'type' => FormOptions::INT, + ) ), $this->object->getOptions() ); } - } diff --git a/tests/phpunit/includes/FormOptionsTest.php b/tests/phpunit/includes/FormOptionsTest.php index 749343ec..0a13cfec 100644 --- a/tests/phpunit/includes/FormOptionsTest.php +++ b/tests/phpunit/includes/FormOptionsTest.php @@ -23,17 +23,18 @@ class FormOptionsTest extends MediaWikiTestCase { protected $object; /** - * Instanciates a FormOptions object to play with. + * Instanciates a FormOptions object to play with. * FormOptions::add() is tested by the class FormOptionsInitializationTest * so we assume the function is well tested already an use it to create * the fixture. */ protected function setUp() { + parent::setUp(); $this->object = new FormOptions; $this->object->add( 'string1', 'string one' ); $this->object->add( 'string2', 'string two' ); - $this->object->add( 'integer', 0 ); - $this->object->add( 'intnull', 0, FormOptions::INTNULL ); + $this->object->add( 'integer', 0 ); + $this->object->add( 'intnull', 0, FormOptions::INTNULL ); } /** Helpers for testGuessType() */ @@ -61,30 +62,30 @@ class FormOptionsTest extends MediaWikiTestCase { * Reuse helpers above assertGuessBoolean assertGuessInt assertGuessString */ public function testGuessTypeDetection() { - $this->assertGuessBoolean( true ); + $this->assertGuessBoolean( true ); $this->assertGuessBoolean( false ); - $this->assertGuessInt( 0 ); - $this->assertGuessInt( -5 ); - $this->assertGuessInt( 5 ); + $this->assertGuessInt( 0 ); + $this->assertGuessInt( -5 ); + $this->assertGuessInt( 5 ); $this->assertGuessInt( 0x0F ); - $this->assertGuessString( 'true' ); - $this->assertGuessString( 'false' ); - $this->assertGuessString( '5' ); - $this->assertGuessString( '0' ); + $this->assertGuessString( 'true' ); + $this->assertGuessString( 'false' ); + $this->assertGuessString( '5' ); + $this->assertGuessString( '0' ); } /** - * @expectedException MWException + * @expectedException MWException */ public function testGuessTypeOnArrayThrowException() { - $this->object->guessType( array( 'foo' ) ); + $this->object->guessType( array( 'foo' ) ); } /** - * @expectedException MWException + * @expectedException MWException */ public function testGuessTypeOnNullThrowException() { - $this->object->guessType( null ); + $this->object->guessType( null ); } } diff --git a/tests/phpunit/includes/GlobalFunctions/GlobalTest.php b/tests/phpunit/includes/GlobalFunctions/GlobalTest.php index 9097d301..6154df1d 100644 --- a/tests/phpunit/includes/GlobalFunctions/GlobalTest.php +++ b/tests/phpunit/includes/GlobalFunctions/GlobalTest.php @@ -1,67 +1,92 @@ <?php class GlobalTest extends MediaWikiTestCase { - function setUp() { - global $wgReadOnlyFile, $wgUrlProtocols; - $this->originals['wgReadOnlyFile'] = $wgReadOnlyFile; - $this->originals['wgUrlProtocols'] = $wgUrlProtocols; - $wgReadOnlyFile = tempnam( wfTempDir(), "mwtest_readonly" ); - $wgUrlProtocols[] = 'file://'; - unlink( $wgReadOnlyFile ); + protected function setUp() { + parent::setUp(); + + $readOnlyFile = tempnam( wfTempDir(), "mwtest_readonly" ); + unlink( $readOnlyFile ); + + $this->setMwGlobals( array( + 'wgReadOnlyFile' => $readOnlyFile, + 'wgUrlProtocols' => array( + 'http://', + 'https://', + 'mailto:', + '//', + 'file://', # Non-default + ), + ) ); } - function tearDown() { - global $wgReadOnlyFile, $wgUrlProtocols; + protected function tearDown() { + global $wgReadOnlyFile; + if ( file_exists( $wgReadOnlyFile ) ) { unlink( $wgReadOnlyFile ); } - $wgReadOnlyFile = $this->originals['wgReadOnlyFile']; - $wgUrlProtocols = $this->originals['wgUrlProtocols']; + + parent::tearDown(); } - /** @dataProvider provideForWfArrayDiff2 */ + /** + * @dataProvider provideForWfArrayDiff2 + * @covers ::wfArrayDiff2 + */ public function testWfArrayDiff2( $a, $b, $expected ) { $this->assertEquals( - wfArrayDiff2( $a, $b), $expected + wfArrayDiff2( $a, $b ), $expected ); } // @todo Provide more tests - public function provideForWfArrayDiff2() { + public static function provideForWfArrayDiff2() { // $a $b $expected return array( array( - array( 'a', 'b'), - array( 'a', 'b'), + array( 'a', 'b' ), + array( 'a', 'b' ), array(), ), array( - array( array( 'a'), array( 'a', 'b', 'c' )), - array( array( 'a'), array( 'a', 'b' )), + array( array( 'a' ), array( 'a', 'b', 'c' ) ), + array( array( 'a' ), array( 'a', 'b' ) ), array( 1 => array( 'a', 'b', 'c' ) ), ), ); } - function testRandom() { + /** + * @covers ::wfRandom + */ + public function testRandom() { # This could hypothetically fail, but it shouldn't ;) $this->assertFalse( wfRandom() == wfRandom() ); } - function testUrlencode() { + /** + * @covers ::wfUrlencode + */ + public function testUrlencode() { $this->assertEquals( "%E7%89%B9%E5%88%A5:Contributions/Foobar", wfUrlencode( "\xE7\x89\xB9\xE5\x88\xA5:Contributions/Foobar" ) ); } - function testExpandIRI() { + /** + * @covers ::wfExpandIRI + */ + public function testExpandIRI() { $this->assertEquals( "https://te.wikibooks.org/wiki/ఉబుంటు_వాడుకరి_మార్గదర్శని", wfExpandIRI( "https://te.wikibooks.org/wiki/%E0%B0%89%E0%B0%AC%E0%B1%81%E0%B0%82%E0%B0%9F%E0%B1%81_%E0%B0%B5%E0%B0%BE%E0%B0%A1%E0%B1%81%E0%B0%95%E0%B0%B0%E0%B0%BF_%E0%B0%AE%E0%B0%BE%E0%B0%B0%E0%B1%8D%E0%B0%97%E0%B0%A6%E0%B0%B0%E0%B1%8D%E0%B0%B6%E0%B0%A8%E0%B0%BF" ) ); } - function testReadOnlyEmpty() { + /** + * @covers ::wfReadOnly + */ + public function testReadOnlyEmpty() { global $wgReadOnly; $wgReadOnly = null; @@ -69,7 +94,10 @@ class GlobalTest extends MediaWikiTestCase { $this->assertFalse( wfReadOnly() ); } - function testReadOnlySet() { + /** + * @covers ::wfReadOnly + */ + public function testReadOnlySet() { global $wgReadOnly, $wgReadOnlyFile; $f = fopen( $wgReadOnlyFile, "wt" ); @@ -87,20 +115,7 @@ class GlobalTest extends MediaWikiTestCase { $this->assertFalse( wfReadOnly() ); } - function testQuotedPrintable() { - $this->assertEquals( - "=?UTF-8?Q?=C4=88u=20legebla=3F?=", - UserMailer::quotedPrintable( "\xc4\x88u legebla?", "UTF-8" ) ); - } - - function testTime() { - $start = wfTime(); - $this->assertInternalType( 'float', $start ); - $end = wfTime(); - $this->assertTrue( $end > $start, "Time is running backwards!" ); - } - - function dataArrayToCGI() { + public static function provideArrayToCGI() { return array( array( array(), '' ), // empty array( array( 'foo' => 'bar' ), 'foo=bar' ), // string test @@ -119,22 +134,26 @@ class GlobalTest extends MediaWikiTestCase { } /** - * @dataProvider dataArrayToCGI + * @dataProvider provideArrayToCGI + * @covers ::wfArrayToCgi */ - function testArrayToCGI( $array, $result ) { - $this->assertEquals( $result, wfArrayToCGI( $array ) ); + public function testArrayToCGI( $array, $result ) { + $this->assertEquals( $result, wfArrayToCgi( $array ) ); } - function testArrayToCGI2() { + /** + * @covers ::testWfArrayDiff2 + */ + public function testArrayToCGI2() { $this->assertEquals( "baz=bar&foo=bar", - wfArrayToCGI( + wfArrayToCgi( array( 'baz' => 'bar' ), array( 'foo' => 'bar', 'baz' => 'overridden value' ) ) ); } - function dataCgiToArray() { + public static function provideCgiToArray() { return array( array( '', array() ), // empty array( 'foo=bar', array( 'foo' => 'bar' ) ), // string @@ -150,13 +169,14 @@ class GlobalTest extends MediaWikiTestCase { } /** - * @dataProvider dataCgiToArray + * @dataProvider provideCgiToArray + * @covers ::wfCgiToArray */ - function testCgiToArray( $cgi, $result ) { + public function testCgiToArray( $cgi, $result ) { $this->assertEquals( $result, wfCgiToArray( $cgi ) ); } - function dataCgiRoundTrip() { + public static function provideCgiRoundTrip() { return array( array( '' ), array( 'foo=bar' ), @@ -170,166 +190,104 @@ class GlobalTest extends MediaWikiTestCase { } /** - * @dataProvider dataCgiRoundTrip + * @dataProvider provideCgiRoundTrip + * @covers ::wfArrayToCgi */ - function testCgiRoundTrip( $cgi ) { - $this->assertEquals( $cgi, wfArrayToCGI( wfCgiToArray( $cgi ) ) ); + public function testCgiRoundTrip( $cgi ) { + $this->assertEquals( $cgi, wfArrayToCgi( wfCgiToArray( $cgi ) ) ); } - function testMimeTypeMatch() { + /** + * @covers ::mimeTypeMatch + */ + public function testMimeTypeMatch() { $this->assertEquals( 'text/html', mimeTypeMatch( 'text/html', array( 'application/xhtml+xml' => 1.0, - 'text/html' => 0.7, - 'text/plain' => 0.3 ) ) ); + 'text/html' => 0.7, + 'text/plain' => 0.3 ) ) ); $this->assertEquals( 'text/*', mimeTypeMatch( 'text/html', array( 'image/*' => 1.0, - 'text/*' => 0.5 ) ) ); + 'text/*' => 0.5 ) ) ); $this->assertEquals( '*/*', mimeTypeMatch( 'text/html', array( '*/*' => 1.0 ) ) ); $this->assertNull( mimeTypeMatch( 'text/html', - array( 'image/png' => 1.0, - 'image/svg+xml' => 0.5 ) ) ); + array( 'image/png' => 1.0, + 'image/svg+xml' => 0.5 ) ) ); } - function testNegotiateType() { + /** + * @covers ::wfNegotiateType + */ + public function testNegotiateType() { $this->assertEquals( 'text/html', wfNegotiateType( array( 'application/xhtml+xml' => 1.0, - 'text/html' => 0.7, - 'text/plain' => 0.5, - 'text/*' => 0.2 ), - array( 'text/html' => 1.0 ) ) ); + 'text/html' => 0.7, + 'text/plain' => 0.5, + 'text/*' => 0.2 ), + array( 'text/html' => 1.0 ) ) ); $this->assertEquals( 'application/xhtml+xml', wfNegotiateType( array( 'application/xhtml+xml' => 1.0, - 'text/html' => 0.7, - 'text/plain' => 0.5, - 'text/*' => 0.2 ), + 'text/html' => 0.7, + 'text/plain' => 0.5, + 'text/*' => 0.2 ), array( 'application/xhtml+xml' => 1.0, - 'text/html' => 0.5 ) ) ); + 'text/html' => 0.5 ) ) ); $this->assertEquals( 'text/html', wfNegotiateType( - array( 'text/html' => 1.0, - 'text/plain' => 0.5, - 'text/*' => 0.5, - 'application/xhtml+xml' => 0.2 ), + array( 'text/html' => 1.0, + 'text/plain' => 0.5, + 'text/*' => 0.5, + 'application/xhtml+xml' => 0.2 ), array( 'application/xhtml+xml' => 1.0, - 'text/html' => 0.5 ) ) ); + 'text/html' => 0.5 ) ) ); $this->assertEquals( 'text/html', wfNegotiateType( - array( 'text/*' => 1.0, - 'image/*' => 0.7, - '*/*' => 0.3 ), + array( 'text/*' => 1.0, + 'image/*' => 0.7, + '*/*' => 0.3 ), array( 'application/xhtml+xml' => 1.0, - 'text/html' => 0.5 ) ) ); + 'text/html' => 0.5 ) ) ); $this->assertNull( wfNegotiateType( - array( 'text/*' => 1.0 ), + array( 'text/*' => 1.0 ), array( 'application/xhtml+xml' => 1.0 ) ) ); } - - function testFallbackMbstringFunctions() { - - if( !extension_loaded( 'mbstring' ) ) { - $this->markTestSkipped( "The mb_string functions must be installed to test the fallback functions" ); - } - - $sampleUTF = "Östergötland_coat_of_arms.png"; - - - //mb_substr - $substr_params = array( - array( 0, 0 ), - array( 5, -4 ), - array( 33 ), - array( 100, -5 ), - array( -8, 10 ), - array( 1, 1 ), - array( 2, -1 ) - ); - - foreach( $substr_params as $param_set ) { - $old_param_set = $param_set; - array_unshift( $param_set, $sampleUTF ); - - $this->assertEquals( - MWFunction::callArray( 'mb_substr', $param_set ), - MWFunction::callArray( 'Fallback::mb_substr', $param_set ), - 'Fallback mb_substr with params ' . implode( ', ', $old_param_set ) - ); - } - - - //mb_strlen - $this->assertEquals( - mb_strlen( $sampleUTF ), - Fallback::mb_strlen( $sampleUTF ), - 'Fallback mb_strlen' - ); - - - //mb_str(r?)pos - $strpos_params = array( - //array( 'ter' ), - //array( 'Ö' ), - //array( 'Ö', 3 ), - //array( 'oat_', 100 ), - //array( 'c', -10 ), - //Broken for now - ); - - foreach( $strpos_params as $param_set ) { - $old_param_set = $param_set; - array_unshift( $param_set, $sampleUTF ); - - $this->assertEquals( - MWFunction::callArray( 'mb_strpos', $param_set ), - MWFunction::callArray( 'Fallback::mb_strpos', $param_set ), - 'Fallback mb_strpos with params ' . implode( ', ', $old_param_set ) - ); - - $this->assertEquals( - MWFunction::callArray( 'mb_strrpos', $param_set ), - MWFunction::callArray( 'Fallback::mb_strrpos', $param_set ), - 'Fallback mb_strrpos with params ' . implode( ', ', $old_param_set ) - ); - } - - } - - - function testDebugFunctionTest() { - + + /** + * @covers ::wfDebug + * @covers ::wfDebugMem + */ + public function testDebugFunctionTest() { + global $wgDebugLogFile, $wgDebugTimestamps; - + $old_log_file = $wgDebugLogFile; $wgDebugLogFile = tempnam( wfTempDir(), 'mw-' ); - # @todo FIXME: This setting should be tested + # @todo FIXME: $wgDebugTimestamps should be tested + $old_wgDebugTimestamps = $wgDebugTimestamps; $wgDebugTimestamps = false; - - - + wfDebug( "This is a normal string" ); $this->assertEquals( "This is a normal string", file_get_contents( $wgDebugLogFile ) ); unlink( $wgDebugLogFile ); - - + wfDebug( "This is nöt an ASCII string" ); $this->assertEquals( "This is nöt an ASCII string", file_get_contents( $wgDebugLogFile ) ); unlink( $wgDebugLogFile ); - - + wfDebug( "\00305This has böth UTF and control chars\003" ); $this->assertEquals( " 05This has böth UTF and control chars ", file_get_contents( $wgDebugLogFile ) ); unlink( $wgDebugLogFile ); @@ -337,19 +295,20 @@ class GlobalTest extends MediaWikiTestCase { wfDebugMem(); $this->assertGreaterThan( 5000, preg_replace( '/\D/', '', file_get_contents( $wgDebugLogFile ) ) ); unlink( $wgDebugLogFile ); - - wfDebugMem(true); + + wfDebugMem( true ); $this->assertGreaterThan( 5000000, preg_replace( '/\D/', '', file_get_contents( $wgDebugLogFile ) ) ); unlink( $wgDebugLogFile ); - - - + $wgDebugLogFile = $old_log_file; - + $wgDebugTimestamps = $old_wgDebugTimestamps; } - - function testClientAcceptsGzipTest() { - + + /** + * @covers ::wfClientAcceptsGzip + */ + public function testClientAcceptsGzipTest() { + $settings = array( 'gzip' => true, 'bzip' => false, @@ -362,25 +321,27 @@ class GlobalTest extends MediaWikiTestCase { 'gzip;q=12345678.9' => true, ' gzip' => true, ); - - if( isset( $_SERVER['HTTP_ACCEPT_ENCODING'] ) ) $old_server_setting = $_SERVER['HTTP_ACCEPT_ENCODING']; - + + if ( isset( $_SERVER['HTTP_ACCEPT_ENCODING'] ) ) { + $old_server_setting = $_SERVER['HTTP_ACCEPT_ENCODING']; + } + foreach ( $settings as $encoding => $expect ) { $_SERVER['HTTP_ACCEPT_ENCODING'] = $encoding; - + $this->assertEquals( $expect, wfClientAcceptsGzip( true ), "'$encoding' => " . wfBoolToStr( $expect ) ); } - - if( isset( $old_server_setting ) ) $_SERVER['HTTP_ACCEPT_ENCODING'] = $old_server_setting; + if ( isset( $old_server_setting ) ) { + $_SERVER['HTTP_ACCEPT_ENCODING'] = $old_server_setting; + } } - - - - function testSwapVarsTest() { - + /** + * @covers ::swap + */ + public function testSwapVarsTest() { $var1 = 1; $var2 = 2; @@ -391,89 +352,85 @@ class GlobalTest extends MediaWikiTestCase { $this->assertEquals( $var1, 2, 'var1 is swapped' ); $this->assertEquals( $var2, 1, 'var2 is swapped' ); - } - - function testWfPercentTest() { + /** + * @covers ::wfPercent + */ + public function testWfPercentTest() { $pcts = array( - array( 6/7, '0.86%', 2, false ), - array( 3/3, '1%' ), - array( 22/7, '3.14286%', 5 ), - array( 3/6, '0.5%' ), - array( 1/3, '0%', 0 ), - array( 10/3, '0%', -1 ), - array( 3/4/5, '0.1%', 1 ), - array( 6/7*8, '6.8571428571%', 10 ), + array( 6 / 7, '0.86%', 2, false ), + array( 3 / 3, '1%' ), + array( 22 / 7, '3.14286%', 5 ), + array( 3 / 6, '0.5%' ), + array( 1 / 3, '0%', 0 ), + array( 10 / 3, '0%', -1 ), + array( 3 / 4 / 5, '0.1%', 1 ), + array( 6 / 7 * 8, '6.8571428571%', 10 ), ); - - foreach( $pcts as $pct ) { - if( !isset( $pct[2] ) ) $pct[2] = 2; - if( !isset( $pct[3] ) ) $pct[3] = true; - - $this->assertEquals( wfPercent( $pct[0], $pct[2], $pct[3] ), $pct[1], $pct[1] ); - } - } + foreach ( $pcts as $pct ) { + if ( !isset( $pct[2] ) ) { + $pct[2] = 2; + } + if ( !isset( $pct[3] ) ) { + $pct[3] = true; + } - - function testInStringTest() { - - $this->assertTrue( in_string( 'foo', 'foobar' ), 'foo is in foobar' ); - $this->assertFalse( in_string( 'Bar', 'foobar' ), 'Case-sensitive by default' ); - $this->assertTrue( in_string( 'Foo', 'foobar', true ), 'Case-insensitive when asked' ); - + $this->assertEquals( wfPercent( $pct[0], $pct[2], $pct[3] ), $pct[1], $pct[1] ); + } } /** * test @see wfShorthandToInteger() * @dataProvider provideShorthand + * @covers ::wfShorthandToInteger */ public function testWfShorthandToInteger( $shorthand, $expected ) { $this->assertEquals( $expected, wfShorthandToInteger( $shorthand ) - ); + ); } /** array( shorthand, expected integer ) */ - public function provideShorthand() { + public static function provideShorthand() { return array( - # Null, empty ... - array( '', -1), - array( ' ', -1), - array( null, -1), + # Null, empty ... + array( '', -1 ), + array( ' ', -1 ), + array( null, -1 ), # Failures returns 0 :( array( 'ABCDEFG', 0 ), - array( 'Ak', 0 ), + array( 'Ak', 0 ), # Int, strings with spaces - array( 1, 1 ), - array( ' 1 ', 1 ), - array( 1023, 1023 ), + array( 1, 1 ), + array( ' 1 ', 1 ), + array( 1023, 1023 ), array( ' 1023 ', 1023 ), # kilo, Mega, Giga - array( '1k', 1024 ), - array( '1K', 1024 ), - array( '1m', 1024 * 1024 ), - array( '1M', 1024 * 1024 ), - array( '1g', 1024 * 1024 * 1024 ), - array( '1G', 1024 * 1024 * 1024 ), + array( '1k', 1024 ), + array( '1K', 1024 ), + array( '1m', 1024 * 1024 ), + array( '1M', 1024 * 1024 ), + array( '1g', 1024 * 1024 * 1024 ), + array( '1G', 1024 * 1024 * 1024 ), # Negatives - array( -1, -1 ), - array( -500, -500 ), - array( '-500', -500 ), - array( '-1k', -1024 ), + array( -1, -1 ), + array( -500, -500 ), + array( '-500', -500 ), + array( '-1k', -1024 ), # Zeroes - array( '0', 0 ), - array( '0k', 0 ), - array( '0M', 0 ), - array( '0G', 0 ), - array( '-0', 0 ), + array( '0', 0 ), + array( '0k', 0 ), + array( '0M', 0 ), + array( '0G', 0 ), + array( '-0', 0 ), array( '-0k', 0 ), array( '-0M', 0 ), array( '-0G', 0 ), @@ -481,14 +438,98 @@ class GlobalTest extends MediaWikiTestCase { } /** + * @param String $old: Text as it was in the database + * @param String $mine: Text submitted while user was editing + * @param String $yours: Text submitted by the user + * @param Boolean $expectedMergeResult Whether the merge should be a success + * @param String $expectedText: Text after merge has been completed + * + * @dataProvider provideMerge() + * @group medium + * @covers ::wfMerge + */ + public function testMerge( $old, $mine, $yours, $expectedMergeResult, $expectedText ) { + $this->checkHasDiff3(); + + $mergedText = null; + $isMerged = wfMerge( $old, $mine, $yours, $mergedText ); + + $msg = 'Merge should be a '; + $msg .= $expectedMergeResult ? 'success' : 'failure'; + $this->assertEquals( $expectedMergeResult, $isMerged, $msg ); + + if ( $isMerged ) { + // Verify the merged text + $this->assertEquals( $expectedText, $mergedText, + 'is merged text as expected?' ); + } + } + + public static function provideMerge() { + $EXPECT_MERGE_SUCCESS = true; + $EXPECT_MERGE_FAILURE = false; + + return array( + // #0: clean merge + array( + // old: + "one one one\n" . // trimmed + "\n" . + "two two two", + + // mine: + "one one one ONE ONE\n" . + "\n" . + "two two two\n", // with tailing whitespace + + // yours: + "one one one\n" . + "\n" . + "two two TWO TWO", // trimmed + + // ok: + $EXPECT_MERGE_SUCCESS, + + // result: + "one one one ONE ONE\n" . + "\n" . + "two two TWO TWO\n", // note: will always end in a newline + ), + + // #1: conflict, fail + array( + // old: + "one one one", // trimmed + + // mine: + "one one one ONE ONE\n" . + "\n" . + "bla bla\n" . + "\n", // with tailing whitespace + + // yours: + "one one one\n" . + "\n" . + "two two", // trimmed + + $EXPECT_MERGE_FAILURE, + + // result: + null, + ), + ); + } + + /** * @dataProvider provideMakeUrlIndexes() + * @covers ::wfMakeUrlIndexes */ - function testMakeUrlIndexes( $url, $expected ) { + public function testMakeUrlIndexes( $url, $expected ) { $index = wfMakeUrlIndexes( $url ); $this->assertEquals( $expected, $index, "wfMakeUrlIndexes(\"$url\")" ); } - function provideMakeUrlIndexes() { + public static function provideMakeUrlIndexes() { return array( array( // just a regular :) @@ -536,16 +577,17 @@ class GlobalTest extends MediaWikiTestCase { ), ); } - + /** * @dataProvider provideWfMatchesDomainList + * @covers ::wfMatchesDomainList */ - function testWfMatchesDomainList( $url, $domains, $expected, $description ) { + public function testWfMatchesDomainList( $url, $domains, $expected, $description ) { $actual = wfMatchesDomainList( $url, $domains ); $this->assertEquals( $expected, $actual, $description ); } - - function provideWfMatchesDomainList() { + + public static function provideWfMatchesDomainList() { $a = array(); $protocols = array( 'HTTP' => 'http:', 'HTTPS' => 'https:', 'protocol-relative' => '' ); foreach ( $protocols as $pDesc => $p ) { @@ -556,19 +598,31 @@ class GlobalTest extends MediaWikiTestCase { array( "$p//www.example2.com", array( 'www.example.com', 'www.example2.com', 'www.example3.com' ), true, "Exact match with other domains in array, $pDesc URL" ), array( "$p//www.example2.com", array( 'example.com', 'example2.com', 'example3,com' ), true, "Match without subdomain with other domains in array, $pDesc URL" ), array( "$p//www.example4.com", array( 'example.com', 'example2.com', 'example3,com' ), false, "Domain not in array, $pDesc URL" ), - - // FIXME: This is a bug in wfMatchesDomainList(). If and when this is fixed, update this test case - array( "$p//nds-nl.wikipedia.org", array( 'nl.wikipedia.org' ), true, "Substrings of domains match while they shouldn't, $pDesc URL" ), + array( "$p//nds-nl.wikipedia.org", array( 'nl.wikipedia.org' ), false, "Non-matching substring of domain, $pDesc URL" ), ) ); } + return $a; } /** + * @covers ::wfMkdirParents + */ + public function testWfMkdirParents() { + // Should not return true if file exists instead of directory + $fname = $this->getNewTempFile(); + wfSuppressWarnings(); + $ok = wfMkdirParents( $fname ); + wfRestoreWarnings(); + $this->assertFalse( $ok ); + } + + /** * @dataProvider provideWfShellMaintenanceCmdList + * @covers ::wfShellMaintenanceCmd */ - function testWfShellMaintenanceCmd( $script, $parameters, $options, $expected, $description ) { - if( wfIsWindows() ) { + public function testWfShellMaintenanceCmd( $script, $parameters, $options, $expected, $description ) { + if ( wfIsWindows() ) { // Approximation that's good enough for our purposes just now $expected = str_replace( "'", '"', $expected ); } @@ -576,23 +630,23 @@ class GlobalTest extends MediaWikiTestCase { $this->assertEquals( $expected, $actual, $description ); } - function provideWfShellMaintenanceCmdList() { + public static function provideWfShellMaintenanceCmdList() { global $wgPhpCli; + return array( array( 'eval.php', array( '--help', '--test' ), array(), "'$wgPhpCli' 'eval.php' '--help' '--test'", "Called eval.php --help --test" ), - array( 'eval.php', array( '--help', '--test space' ), array('php' => 'php5'), + array( 'eval.php', array( '--help', '--test space' ), array( 'php' => 'php5' ), "'php5' 'eval.php' '--help' '--test space'", "Called eval.php --help --test with php option" ), - array( 'eval.php', array( '--help', '--test', 'X' ), array('wrapper' => 'MWScript.php'), + array( 'eval.php', array( '--help', '--test', 'X' ), array( 'wrapper' => 'MWScript.php' ), "'$wgPhpCli' 'MWScript.php' 'eval.php' '--help' '--test' 'X'", "Called eval.php --help --test with wrapper option" ), - array( 'eval.php', array( '--help', '--test', 'y' ), array('php' => 'php5', 'wrapper' => 'MWScript.php'), + array( 'eval.php', array( '--help', '--test', 'y' ), array( 'php' => 'php5', 'wrapper' => 'MWScript.php' ), "'php5' 'MWScript.php' 'eval.php' '--help' '--test' 'y'", "Called eval.php --help --test with wrapper and php option" ), ); } - /* TODO: many more! */ + /* @TODO many more! */ } - diff --git a/tests/phpunit/includes/GlobalFunctions/GlobalWithDBTest.php b/tests/phpunit/includes/GlobalFunctions/GlobalWithDBTest.php index 4879a38d..cf891e7b 100644 --- a/tests/phpunit/includes/GlobalFunctions/GlobalWithDBTest.php +++ b/tests/phpunit/includes/GlobalFunctions/GlobalWithDBTest.php @@ -6,13 +6,15 @@ class GlobalWithDBTest extends MediaWikiTestCase { /** * @dataProvider provideWfIsBadImageList + * @covers ::wfIsBadImage */ - function testWfIsBadImage( $name, $title, $blacklist, $expected, $desc ) { + public function testWfIsBadImage( $name, $title, $blacklist, $expected, $desc ) { $this->assertEquals( $expected, wfIsBadImage( $name, $title, $blacklist ), $desc ); } - function provideWfIsBadImageList() { + public static function provideWfIsBadImageList() { $blacklist = '* [[File:Bad.jpg]] except [[Nasty page]]'; + return array( array( 'Bad.jpg', false, $blacklist, true, 'Called on a bad image' ), diff --git a/tests/phpunit/includes/GlobalFunctions/wfAssembleUrlTest.php b/tests/phpunit/includes/GlobalFunctions/wfAssembleUrlTest.php index be6c99e7..9bb74873 100644 --- a/tests/phpunit/includes/GlobalFunctions/wfAssembleUrlTest.php +++ b/tests/phpunit/includes/GlobalFunctions/wfAssembleUrlTest.php @@ -1,10 +1,11 @@ <?php /** - * Unit tests for wfAssembleUrl() + * @covers ::wfAssembleUrl */ - -class wfAssembleUrl extends MediaWikiTestCase { - /** @dataProvider provideURLParts */ +class WfAssembleUrlTest extends MediaWikiTestCase { + /** + * @dataProvider provideURLParts + */ public function testWfAssembleUrl( $parts, $output ) { $partsDump = print_r( $parts, true ); $this->assertEquals( @@ -19,7 +20,7 @@ class wfAssembleUrl extends MediaWikiTestCase { * * @return array */ - public function provideURLParts() { + public static function provideURLParts() { $schemes = array( '' => array(), '//' => array( @@ -83,12 +84,11 @@ class wfAssembleUrl extends MediaWikiTestCase { $parts['query'] = $query; $url .= '?' . $query; } - if( $fragment ) { + if ( $fragment ) { $parts['fragment'] = $fragment; $url .= '#' . $fragment; } - $cases[] = array( $parts, $url, diff --git a/tests/phpunit/includes/GlobalFunctions/wfBCP47Test.php b/tests/phpunit/includes/GlobalFunctions/wfBCP47Test.php index f4ec7a5f..a01c0d49 100644 --- a/tests/phpunit/includes/GlobalFunctions/wfBCP47Test.php +++ b/tests/phpunit/includes/GlobalFunctions/wfBCP47Test.php @@ -1,26 +1,26 @@ <?php /** - * Unit tests for wfBCP47() + * @covers ::wfBCP47 */ -class wfBCP47 extends MediaWikiTestCase { +class WfBCP47Test extends MediaWikiTestCase { /** * test @see wfBCP47(). * Please note the BCP explicitly state that language codes are case * insensitive, there are some exceptions to the rule :) - * This test is used to verify our formatting against all lower and + * This test is used to verify our formatting against all lower and * all upper cases language code. * * @see http://tools.ietf.org/html/bcp47 * @dataProvider provideLanguageCodes() */ - function testBCP47( $code, $expected ) { + public function testBCP47( $code, $expected ) { $code = strtolower( $code ); - $this->assertEquals( $expected, wfBCP47($code), + $this->assertEquals( $expected, wfBCP47( $code ), "Applying BCP47 standard to lower case '$code'" ); $code = strtoupper( $code ); - $this->assertEquals( $expected, wfBCP47($code), + $this->assertEquals( $expected, wfBCP47( $code ), "Applying BCP47 standard to upper case '$code'" ); } @@ -28,19 +28,19 @@ class wfBCP47 extends MediaWikiTestCase { /** * Array format is ($code, $expected) */ - function provideLanguageCodes() { + public static function provideLanguageCodes() { return array( // Extracted from BCP47 (list not exhaustive) # 2.1.1 - array( 'en-ca-x-ca' , 'en-CA-x-ca' ), - array( 'sgn-be-fr' , 'sgn-BE-FR' ), + array( 'en-ca-x-ca', 'en-CA-x-ca' ), + array( 'sgn-be-fr', 'sgn-BE-FR' ), array( 'az-latn-x-latn', 'az-Latn-x-latn' ), # 2.2 array( 'sr-Latn-RS', 'sr-Latn-RS' ), array( 'az-arab-ir', 'az-Arab-IR' ), # 2.2.5 - array( 'sl-nedis' , 'sl-nedis' ), + array( 'sl-nedis', 'sl-nedis' ), array( 'de-ch-1996', 'de-CH-1996' ), # 2.2.6 @@ -56,40 +56,40 @@ class wfBCP47 extends MediaWikiTestCase { array( 'ja', 'ja' ), # Language subtag plus script subtag: - array( 'zh-hans', 'zh-Hans'), - array( 'sr-cyrl', 'sr-Cyrl'), - array( 'sr-latn', 'sr-Latn'), + array( 'zh-hans', 'zh-Hans' ), + array( 'sr-cyrl', 'sr-Cyrl' ), + array( 'sr-latn', 'sr-Latn' ), # Extended language subtags and their primary language subtag # counterparts: array( 'zh-cmn-hans-cn', 'zh-cmn-Hans-CN' ), - array( 'cmn-hans-cn' , 'cmn-Hans-CN' ), - array( 'zh-yue-hk' , 'zh-yue-HK' ), - array( 'yue-hk' , 'yue-HK' ), + array( 'cmn-hans-cn', 'cmn-Hans-CN' ), + array( 'zh-yue-hk', 'zh-yue-HK' ), + array( 'yue-hk', 'yue-HK' ), # Language-Script-Region: array( 'zh-hans-cn', 'zh-Hans-CN' ), array( 'sr-latn-RS', 'sr-Latn-RS' ), # Language-Variant: - array( 'sl-rozaj' , 'sl-rozaj' ), + array( 'sl-rozaj', 'sl-rozaj' ), array( 'sl-rozaj-biske', 'sl-rozaj-biske' ), - array( 'sl-nedis' , 'sl-nedis' ), + array( 'sl-nedis', 'sl-nedis' ), # Language-Region-Variant: - array( 'de-ch-1901' , 'de-CH-1901' ), - array( 'sl-it-nedis' , 'sl-IT-nedis' ), + array( 'de-ch-1901', 'de-CH-1901' ), + array( 'sl-it-nedis', 'sl-IT-nedis' ), # Language-Script-Region-Variant: array( 'hy-latn-it-arevela', 'hy-Latn-IT-arevela' ), # Language-Region: - array( 'de-de' , 'de-DE' ), - array( 'en-us' , 'en-US' ), - array( 'es-419', 'es-419'), + array( 'de-de', 'de-DE' ), + array( 'en-us', 'en-US' ), + array( 'es-419', 'es-419' ), # Private use subtags: - array( 'de-ch-x-phonebk' , 'de-CH-x-phonebk' ), + array( 'de-ch-x-phonebk', 'de-CH-x-phonebk' ), array( 'az-arab-x-aze-derbend', 'az-Arab-x-aze-derbend' ), /** * Previous test does not reflect the BCP which states: @@ -102,7 +102,7 @@ class wfBCP47 extends MediaWikiTestCase { # Private use registry values: array( 'x-whatever', 'x-whatever' ), array( 'qaa-qaaa-qm-x-southern', 'qaa-Qaaa-QM-x-southern' ), - array( 'de-qaaa' , 'de-Qaaa' ), + array( 'de-qaaa', 'de-Qaaa' ), array( 'sr-latn-qm', 'sr-Latn-QM' ), array( 'sr-qaaa-rs', 'sr-Qaaa-RS' ), @@ -115,19 +115,6 @@ class wfBCP47 extends MediaWikiTestCase { // de-419-DE // a-DE // ar-a-aaa-b-bbb-a-ccc - - /* - // ISO 15924 : - array( 'sr-Cyrl', 'sr-Cyrl' ), - # @todo FIXME: Fix our function? - array( 'SR-lATN', 'sr-Latn' ), - array( 'fr-latn', 'fr-Latn' ), - // Use lowercase for single segment - // ISO 3166-1-alpha-2 code - array( 'US', 'us' ), # USA - array( 'uS', 'us' ), # USA - array( 'Fr', 'fr' ), # France - array( 'va', 'va' ), # Holy See (Vatican City State) - */); + ); } } diff --git a/tests/phpunit/includes/GlobalFunctions/wfBaseConvertTest.php b/tests/phpunit/includes/GlobalFunctions/wfBaseConvertTest.php new file mode 100644 index 00000000..7da804e6 --- /dev/null +++ b/tests/phpunit/includes/GlobalFunctions/wfBaseConvertTest.php @@ -0,0 +1,182 @@ +<?php +/** + * @covers ::wfBaseConvert + */ +class WfBaseConvertTest extends MediaWikiTestCase { + public static function provideSingleDigitConversions() { + return array( + // 2 3 5 8 10 16 36 + array( '0', '0', '0', '0', '0', '0', '0' ), + array( '1', '1', '1', '1', '1', '1', '1' ), + array( '10', '2', '2', '2', '2', '2', '2' ), + array( '11', '10', '3', '3', '3', '3', '3' ), + array( '100', '11', '4', '4', '4', '4', '4' ), + array( '101', '12', '10', '5', '5', '5', '5' ), + array( '110', '20', '11', '6', '6', '6', '6' ), + array( '111', '21', '12', '7', '7', '7', '7' ), + array( '1000', '22', '13', '10', '8', '8', '8' ), + array( '1001', '100', '14', '11', '9', '9', '9' ), + array( '1010', '101', '20', '12', '10', 'a', 'a' ), + array( '1011', '102', '21', '13', '11', 'b', 'b' ), + array( '1100', '110', '22', '14', '12', 'c', 'c' ), + array( '1101', '111', '23', '15', '13', 'd', 'd' ), + array( '1110', '112', '24', '16', '14', 'e', 'e' ), + array( '1111', '120', '30', '17', '15', 'f', 'f' ), + array( '10000', '121', '31', '20', '16', '10', 'g' ), + array( '10001', '122', '32', '21', '17', '11', 'h' ), + array( '10010', '200', '33', '22', '18', '12', 'i' ), + array( '10011', '201', '34', '23', '19', '13', 'j' ), + array( '10100', '202', '40', '24', '20', '14', 'k' ), + array( '10101', '210', '41', '25', '21', '15', 'l' ), + array( '10110', '211', '42', '26', '22', '16', 'm' ), + array( '10111', '212', '43', '27', '23', '17', 'n' ), + array( '11000', '220', '44', '30', '24', '18', 'o' ), + array( '11001', '221', '100', '31', '25', '19', 'p' ), + array( '11010', '222', '101', '32', '26', '1a', 'q' ), + array( '11011', '1000', '102', '33', '27', '1b', 'r' ), + array( '11100', '1001', '103', '34', '28', '1c', 's' ), + array( '11101', '1002', '104', '35', '29', '1d', 't' ), + array( '11110', '1010', '110', '36', '30', '1e', 'u' ), + array( '11111', '1011', '111', '37', '31', '1f', 'v' ), + array( '100000', '1012', '112', '40', '32', '20', 'w' ), + array( '100001', '1020', '113', '41', '33', '21', 'x' ), + array( '100010', '1021', '114', '42', '34', '22', 'y' ), + array( '100011', '1022', '120', '43', '35', '23', 'z' ) + ); + } + + /** + * @dataProvider provideSingleDigitConversions + */ + public function testDigitToBase2( $base2, $base3, $base5, $base8, $base10, $base16, $base36 ) { + $this->assertSame( $base2, wfBaseConvert( $base3, '3', '2' ) ); + $this->assertSame( $base2, wfBaseConvert( $base5, '5', '2' ) ); + $this->assertSame( $base2, wfBaseConvert( $base8, '8', '2' ) ); + $this->assertSame( $base2, wfBaseConvert( $base10, '10', '2' ) ); + $this->assertSame( $base2, wfBaseConvert( $base16, '16', '2' ) ); + $this->assertSame( $base2, wfBaseConvert( $base36, '36', '2' ) ); + } + + /** + * @dataProvider provideSingleDigitConversions + */ + public function testDigitToBase3( $base2, $base3, $base5, $base8, $base10, $base16, $base36 ) { + $this->assertSame( $base3, wfBaseConvert( $base2, '2', '3' ) ); + $this->assertSame( $base3, wfBaseConvert( $base5, '5', '3' ) ); + $this->assertSame( $base3, wfBaseConvert( $base8, '8', '3' ) ); + $this->assertSame( $base3, wfBaseConvert( $base10, '10', '3' ) ); + $this->assertSame( $base3, wfBaseConvert( $base16, '16', '3' ) ); + $this->assertSame( $base3, wfBaseConvert( $base36, '36', '3' ) ); + } + + /** + * @dataProvider provideSingleDigitConversions + */ + public function testDigitToBase5( $base2, $base3, $base5, $base8, $base10, $base16, $base36 ) { + $this->assertSame( $base5, wfBaseConvert( $base2, '2', '5' ) ); + $this->assertSame( $base5, wfBaseConvert( $base3, '3', '5' ) ); + $this->assertSame( $base5, wfBaseConvert( $base8, '8', '5' ) ); + $this->assertSame( $base5, wfBaseConvert( $base10, '10', '5' ) ); + $this->assertSame( $base5, wfBaseConvert( $base16, '16', '5' ) ); + $this->assertSame( $base5, wfBaseConvert( $base36, '36', '5' ) ); + } + + /** + * @dataProvider provideSingleDigitConversions + */ + public function testDigitToBase8( $base2, $base3, $base5, $base8, $base10, $base16, $base36 ) { + $this->assertSame( $base8, wfBaseConvert( $base2, '2', '8' ) ); + $this->assertSame( $base8, wfBaseConvert( $base3, '3', '8' ) ); + $this->assertSame( $base8, wfBaseConvert( $base5, '5', '8' ) ); + $this->assertSame( $base8, wfBaseConvert( $base10, '10', '8' ) ); + $this->assertSame( $base8, wfBaseConvert( $base16, '16', '8' ) ); + $this->assertSame( $base8, wfBaseConvert( $base36, '36', '8' ) ); + } + + /** + * @dataProvider provideSingleDigitConversions + */ + public function testDigitToBase10( $base2, $base3, $base5, $base8, $base10, $base16, $base36 ) { + $this->assertSame( $base10, wfBaseConvert( $base2, '2', '10' ) ); + $this->assertSame( $base10, wfBaseConvert( $base3, '3', '10' ) ); + $this->assertSame( $base10, wfBaseConvert( $base5, '5', '10' ) ); + $this->assertSame( $base10, wfBaseConvert( $base8, '8', '10' ) ); + $this->assertSame( $base10, wfBaseConvert( $base16, '16', '10' ) ); + $this->assertSame( $base10, wfBaseConvert( $base36, '36', '10' ) ); + } + + /** + * @dataProvider provideSingleDigitConversions + */ + public function testDigitToBase16( $base2, $base3, $base5, $base8, $base10, $base16, $base36 ) { + $this->assertSame( $base16, wfBaseConvert( $base2, '2', '16' ) ); + $this->assertSame( $base16, wfBaseConvert( $base3, '3', '16' ) ); + $this->assertSame( $base16, wfBaseConvert( $base5, '5', '16' ) ); + $this->assertSame( $base16, wfBaseConvert( $base8, '8', '16' ) ); + $this->assertSame( $base16, wfBaseConvert( $base10, '10', '16' ) ); + $this->assertSame( $base16, wfBaseConvert( $base36, '36', '16' ) ); + } + + /** + * @dataProvider provideSingleDigitConversions + */ + public function testDigitToBase36( $base2, $base3, $base5, $base8, $base10, $base16, $base36 ) { + $this->assertSame( $base36, wfBaseConvert( $base2, '2', '36' ) ); + $this->assertSame( $base36, wfBaseConvert( $base3, '3', '36' ) ); + $this->assertSame( $base36, wfBaseConvert( $base5, '5', '36' ) ); + $this->assertSame( $base36, wfBaseConvert( $base8, '8', '36' ) ); + $this->assertSame( $base36, wfBaseConvert( $base10, '10', '36' ) ); + $this->assertSame( $base36, wfBaseConvert( $base16, '16', '36' ) ); + } + + public function testLargeNumber() { + $this->assertSame( '1100110001111010000000101110100', wfBaseConvert( 'sd89ys', 36, 2 ) ); + $this->assertSame( '11102112120221201101', wfBaseConvert( 'sd89ys', 36, 3 ) ); + $this->assertSame( '12003102232400', wfBaseConvert( 'sd89ys', 36, 5 ) ); + $this->assertSame( '14617200564', wfBaseConvert( 'sd89ys', 36, 8 ) ); + $this->assertSame( '1715274100', wfBaseConvert( 'sd89ys', 36, 10 ) ); + $this->assertSame( '663d0174', wfBaseConvert( 'sd89ys', 36, 16 ) ); + } + + public static function provideNumbers() { + $x = array(); + $chars = '0123456789abcdefghijklmnopqrstuvwxyz'; + for ( $i = 0; $i < 50; $i++ ) { + $base = mt_rand( 2, 36 ); + $len = mt_rand( 10, 100 ); + + $str = ''; + for ( $j = 0; $j < $len; $j++ ) { + $str .= $chars[mt_rand( 0, $base - 1 )]; + } + + $x[] = array( $base, $str ); + } + + return $x; + } + + /** + * @dataProvider provideNumbers + */ + public function testIdentity( $base, $number ) { + $this->assertSame( $number, wfBaseConvert( $number, $base, $base, strlen( $number ) ) ); + } + + public function testInvalid() { + $this->assertFalse( wfBaseConvert( '101', 1, 15 ) ); + $this->assertFalse( wfBaseConvert( '101', 15, 1 ) ); + $this->assertFalse( wfBaseConvert( '101', 37, 15 ) ); + $this->assertFalse( wfBaseConvert( '101', 15, 37 ) ); + $this->assertFalse( wfBaseConvert( 'abcde', 10, 11 ) ); + $this->assertFalse( wfBaseConvert( '12930', 2, 10 ) ); + $this->assertFalse( wfBaseConvert( '101', 'abc', 15 ) ); + $this->assertFalse( wfBaseConvert( '101', 15, 'abc' ) ); + } + + public function testPadding() { + $number = "10101010101"; + $this->assertSame( strlen( $number ) + 5, strlen( wfBaseConvert( $number, 2, 2, strlen( $number ) + 5 ) ) ); + $this->assertSame( strlen( $number ), strlen( wfBaseConvert( $number, 2, 2, strlen( $number ) - 5 ) ) ); + } +} diff --git a/tests/phpunit/includes/GlobalFunctions/wfBaseNameTest.php b/tests/phpunit/includes/GlobalFunctions/wfBaseNameTest.php index 59954b23..8c548040 100644 --- a/tests/phpunit/includes/GlobalFunctions/wfBaseNameTest.php +++ b/tests/phpunit/includes/GlobalFunctions/wfBaseNameTest.php @@ -1,17 +1,17 @@ <?php /** - * Tests for wfBaseName() + * @covers ::wfBaseName */ -class wfBaseName extends MediaWikiTestCase { +class WfBaseNameTest extends MediaWikiTestCase { /** * @dataProvider providePaths */ - function testBaseName( $fullpath, $basename ) { + public function testBaseName( $fullpath, $basename ) { $this->assertEquals( $basename, wfBaseName( $fullpath ), - "wfBaseName('$fullpath') => '$basename'" ); + "wfBaseName('$fullpath') => '$basename'" ); } - - function providePaths() { + + public static function providePaths() { return array( array( '', '' ), array( '/', '' ), diff --git a/tests/phpunit/includes/GlobalFunctions/wfExpandUrlTest.php b/tests/phpunit/includes/GlobalFunctions/wfExpandUrlTest.php index 192689f8..41230a1e 100644 --- a/tests/phpunit/includes/GlobalFunctions/wfExpandUrlTest.php +++ b/tests/phpunit/includes/GlobalFunctions/wfExpandUrlTest.php @@ -1,17 +1,17 @@ <?php /** - * Unit tests for wfExpandUrl() + * @covers ::wfExpandUrl */ - -class wfExpandUrl extends MediaWikiTestCase { - /** @dataProvider provideExpandableUrls */ +class WfExpandUrlTest extends MediaWikiTestCase { + /** + * @dataProvider provideExpandableUrls + */ public function testWfExpandUrl( $fullUrl, $shortUrl, $defaultProto, $server, $canServer, $httpsMode, $message ) { // Fake $wgServer and $wgCanonicalServer - global $wgServer, $wgCanonicalServer; - $oldServer = $wgServer; - $oldCanServer = $wgCanonicalServer; - $wgServer = $server; - $wgCanonicalServer = $canServer; + $this->setMwGlobals( array( + 'wgServer' => $server, + 'wgCanonicalServer' => $canServer, + ) ); // Fake $_SERVER['HTTPS'] if needed if ( $httpsMode ) { @@ -21,10 +21,6 @@ class wfExpandUrl extends MediaWikiTestCase { } $this->assertEquals( $fullUrl, wfExpandUrl( $shortUrl, $defaultProto ), $message ); - - // Restore $wgServer and $wgCanonicalServer - $wgServer = $oldServer; - $wgCanonicalServer = $oldCanServer; } /** @@ -32,25 +28,49 @@ class wfExpandUrl extends MediaWikiTestCase { * * @return array */ - public function provideExpandableUrls() { + public static function provideExpandableUrls() { $modes = array( 'http', 'https' ); - $servers = array( 'http' => 'http://example.com', 'https' => 'https://example.com', 'protocol-relative' => '//example.com' ); - $defaultProtos = array( 'http' => PROTO_HTTP, 'https' => PROTO_HTTPS, 'protocol-relative' => PROTO_RELATIVE, 'current' => PROTO_CURRENT, 'canonical' => PROTO_CANONICAL ); + $servers = array( + 'http' => 'http://example.com', + 'https' => 'https://example.com', + 'protocol-relative' => '//example.com' + ); + $defaultProtos = array( + 'http' => PROTO_HTTP, + 'https' => PROTO_HTTPS, + 'protocol-relative' => PROTO_RELATIVE, + 'current' => PROTO_CURRENT, + 'canonical' => PROTO_CANONICAL + ); $retval = array(); foreach ( $modes as $mode ) { $httpsMode = $mode == 'https'; foreach ( $servers as $serverDesc => $server ) { - foreach ( $modes as $canServerMode ) { + foreach ( $modes as $canServerMode ) { $canServer = "$canServerMode://example2.com"; foreach ( $defaultProtos as $protoDesc => $defaultProto ) { - $retval[] = array( 'http://example.com', 'http://example.com', $defaultProto, $server, $canServer, $httpsMode, "Testing fully qualified http URLs (no need to expand) (defaultProto: $protoDesc , wgServer: $server, wgCanonicalServer: $canServer, current request protocol: $mode )" ); - $retval[] = array( 'https://example.com', 'https://example.com', $defaultProto, $server, $canServer, $httpsMode, "Testing fully qualified https URLs (no need to expand) (defaultProto: $protoDesc , wgServer: $server, wgCanonicalServer: $canServer, current request protocol: $mode )" ); + $retval[] = array( + 'http://example.com', 'http://example.com', + $defaultProto, $server, $canServer, $httpsMode, + "Testing fully qualified http URLs (no need to expand) ' . + '(defaultProto: $protoDesc , wgServer: $server, wgCanonicalServer: $canServer, current request protocol: $mode )" + ); + $retval[] = array( + 'https://example.com', 'https://example.com', + $defaultProto, $server, $canServer, $httpsMode, + "Testing fully qualified https URLs (no need to expand) ' . + '(defaultProto: $protoDesc , wgServer: $server, wgCanonicalServer: $canServer, current request protocol: $mode )" + ); # Would be nice to support this, see fixme on wfExpandUrl() - $retval[] = array( "wiki/FooBar", 'wiki/FooBar', $defaultProto, $server, $canServer, $httpsMode, "Test non-expandable relative URLs (defaultProto: $protoDesc , wgServer: $server, wgCanonicalServer: $canServer, current request protocol: $mode )" ); + $retval[] = array( + "wiki/FooBar", 'wiki/FooBar', + $defaultProto, $server, $canServer, $httpsMode, + "Test non-expandable relative URLs ' . + '(defaultProto: $protoDesc , wgServer: $server, wgCanonicalServer: $canServer, current request protocol: $mode )" + ); // Determine expected protocol - $p = $protoDesc . ':'; // default case if ( $protoDesc == 'protocol-relative' ) { $p = ''; } elseif ( $protoDesc == 'current' ) { @@ -69,12 +89,23 @@ class wfExpandUrl extends MediaWikiTestCase { $srv = $server; } - $retval[] = array( "$p//wikipedia.org", '//wikipedia.org', $defaultProto, $server, $canServer, $httpsMode, "Test protocol-relative URL (defaultProto: $protoDesc, wgServer: $server, wgCanonicalServer: $canServer, current request protocol: $mode )" ); - $retval[] = array( "$srv/wiki/FooBar", '/wiki/FooBar', $defaultProto, $server, $canServer, $httpsMode, "Testing expanding URL beginning with / (defaultProto: $protoDesc , wgServer: $server, wgCanonicalServer: $canServer, current request protocol: $mode )" ); + $retval[] = array( + "$p//wikipedia.org", '//wikipedia.org', + $defaultProto, $server, $canServer, $httpsMode, + "Test protocol-relative URL ' . + '(defaultProto: $protoDesc, wgServer: $server, wgCanonicalServer: $canServer, current request protocol: $mode )" + ); + $retval[] = array( + "$srv/wiki/FooBar", '/wiki/FooBar', + $defaultProto, $server, $canServer, $httpsMode, + "Testing expanding URL beginning with / ' . + '(defaultProto: $protoDesc , wgServer: $server, wgCanonicalServer: $canServer, current request protocol: $mode )" + ); } } } } + return $retval; } } diff --git a/tests/phpunit/includes/GlobalFunctions/wfGetCallerTest.php b/tests/phpunit/includes/GlobalFunctions/wfGetCallerTest.php index 4c4c4c04..62296245 100644 --- a/tests/phpunit/includes/GlobalFunctions/wfGetCallerTest.php +++ b/tests/phpunit/includes/GlobalFunctions/wfGetCallerTest.php @@ -1,8 +1,11 @@ <?php -class wfGetCaller extends MediaWikiTestCase { +/** + * @covers ::wfGetCaller + */ +class WfGetCallerTest extends MediaWikiTestCase { - function testZero() { + public function testZero() { $this->assertEquals( __METHOD__, wfGetCaller( 1 ) ); } @@ -10,26 +13,28 @@ class wfGetCaller extends MediaWikiTestCase { return wfGetCaller(); } - function testOne() { - $this->assertEquals( "wfGetCaller::testOne", self::callerOne() ); + public function testOne() { + $this->assertEquals( 'WfGetCallerTest::testOne', self::callerOne() ); } function intermediateFunction( $level = 2, $n = 0 ) { - if ( $n > 0 ) + if ( $n > 0 ) { return self::intermediateFunction( $level, $n - 1 ); + } + return wfGetCaller( $level ); } - function testTwo() { - $this->assertEquals( "wfGetCaller::testTwo", self::intermediateFunction() ); + public function testTwo() { + $this->assertEquals( 'WfGetCallerTest::testTwo', self::intermediateFunction() ); } - function testN() { - $this->assertEquals( "wfGetCaller::testN", self::intermediateFunction( 2, 0 ) ); - $this->assertEquals( "wfGetCaller::intermediateFunction", self::intermediateFunction( 1, 0 ) ); + public function testN() { + $this->assertEquals( 'WfGetCallerTest::testN', self::intermediateFunction( 2, 0 ) ); + $this->assertEquals( 'WfGetCallerTest::intermediateFunction', self::intermediateFunction( 1, 0 ) ); - for ($i=0; $i < 10; $i++) - $this->assertEquals( "wfGetCaller::intermediateFunction", self::intermediateFunction( $i + 1, $i ) ); + for ( $i = 0; $i < 10; $i++ ) { + $this->assertEquals( 'WfGetCallerTest::intermediateFunction', self::intermediateFunction( $i + 1, $i ) ); + } } } - diff --git a/tests/phpunit/includes/GlobalFunctions/wfParseUrlTest.php b/tests/phpunit/includes/GlobalFunctions/wfParseUrlTest.php new file mode 100644 index 00000000..5032dc11 --- /dev/null +++ b/tests/phpunit/includes/GlobalFunctions/wfParseUrlTest.php @@ -0,0 +1,146 @@ +<?php +/** + * Copyright © 2013 Alexandre Emsenhuber + * + * 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 + * + * @file + */ + +/** + * @covers ::wfParseUrl + */ +class WfParseUrlTest extends MediaWikiTestCase { + protected function setUp() { + parent::setUp(); + + $this->setMwGlobals( 'wgUrlProtocols', array( + '//', 'http://', 'file://', 'mailto:', + ) ); + } + + /** + * @dataProvider provideURLs + */ + public function testWfParseUrl( $url, $parts ) { + $partsDump = var_export( $parts, true ); + $this->assertEquals( + $parts, + wfParseUrl( $url ), + "Testing $url parses to $partsDump" + ); + } + + /** + * Provider of URLs for testing wfParseUrl() + * + * @return array + */ + public static function provideURLs() { + return array( + array( + '//example.org', + array( + 'scheme' => '', + 'delimiter' => '//', + 'host' => 'example.org', + ) + ), + array( + 'http://example.org', + array( + 'scheme' => 'http', + 'delimiter' => '://', + 'host' => 'example.org', + ) + ), + array( + 'http://id:key@example.org:123/path?foo=bar#baz', + array( + 'scheme' => 'http', + 'delimiter' => '://', + 'user' => 'id', + 'pass' => 'key', + 'host' => 'example.org', + 'port' => 123, + 'path' => '/path', + 'query' => 'foo=bar', + 'fragment' => 'baz', + ) + ), + array( + 'file://example.org/etc/php.ini', + array( + 'scheme' => 'file', + 'delimiter' => '://', + 'host' => 'example.org', + 'path' => '/etc/php.ini', + ) + ), + array( + 'file:///etc/php.ini', + array( + 'scheme' => 'file', + 'delimiter' => '://', + 'host' => '', + 'path' => '/etc/php.ini', + ) + ), + array( + 'file:///c:/', + array( + 'scheme' => 'file', + 'delimiter' => '://', + 'host' => '', + 'path' => '/c:/', + ) + ), + array( + 'mailto:id@example.org', + array( + 'scheme' => 'mailto', + 'delimiter' => ':', + 'host' => 'id@example.org', + 'path' => '', + ) + ), + array( + 'mailto:id@example.org?subject=Foo', + array( + 'scheme' => 'mailto', + 'delimiter' => ':', + 'host' => 'id@example.org', + 'path' => '', + 'query' => 'subject=Foo', + ) + ), + array( + 'mailto:?subject=Foo', + array( + 'scheme' => 'mailto', + 'delimiter' => ':', + 'host' => '', + 'path' => '', + 'query' => 'subject=Foo', + ) + ), + array( + 'invalid://test/', + false + ), + ); + } +} diff --git a/tests/phpunit/includes/GlobalFunctions/wfRemoveDotSegmentsTest.php b/tests/phpunit/includes/GlobalFunctions/wfRemoveDotSegmentsTest.php index 1cf0e0fb..238a2c9c 100644 --- a/tests/phpunit/includes/GlobalFunctions/wfRemoveDotSegmentsTest.php +++ b/tests/phpunit/includes/GlobalFunctions/wfRemoveDotSegmentsTest.php @@ -1,10 +1,11 @@ <?php /** - * Unit tests for wfRemoveDotSegments() + *@covers ::wfRemoveDotSegments */ - -class wfRemoveDotSegments extends MediaWikiTestCase { - /** @dataProvider providePaths */ +class WfRemoveDotSegmentsTest extends MediaWikiTestCase { + /** + * @dataProvider providePaths + */ public function testWfRemoveDotSegments( $inputPath, $outputPath ) { $this->assertEquals( $outputPath, @@ -18,7 +19,7 @@ class wfRemoveDotSegments extends MediaWikiTestCase { * * @return array */ - public function providePaths() { + public static function providePaths() { return array( array( '/a/b/c/./../../g', '/a/g' ), array( 'mid/content=5/../6', 'mid/6' ), diff --git a/tests/phpunit/includes/GlobalFunctions/wfShorthandToIntegerTest.php b/tests/phpunit/includes/GlobalFunctions/wfShorthandToIntegerTest.php index 1df26d2c..aadec87f 100644 --- a/tests/phpunit/includes/GlobalFunctions/wfShorthandToIntegerTest.php +++ b/tests/phpunit/includes/GlobalFunctions/wfShorthandToIntegerTest.php @@ -1,10 +1,13 @@ <?php -class wfShorthandToIntegerTest extends MediaWikiTestCase { +/** + * @covers ::wfShorthandToInteger + */ +class WfShorthandToIntegerTest extends MediaWikiTestCase { /** * @dataProvider provideABunchOfShorthands */ - function testWfShorthandToInteger( $input, $output, $description ) { + public function testWfShorthandToInteger( $input, $output, $description ) { $this->assertEquals( wfShorthandToInteger( $input ), $output, @@ -12,7 +15,7 @@ class wfShorthandToIntegerTest extends MediaWikiTestCase { ); } - function provideABunchOfShorthands() { + public static function provideABunchOfShorthands() { return array( array( '', -1, 'Empty string' ), array( ' ', -1, 'String of spaces' ), @@ -24,5 +27,4 @@ class wfShorthandToIntegerTest extends MediaWikiTestCase { array( '1k', 1024, 'One kb lowercased' ), ); } - } diff --git a/tests/phpunit/includes/GlobalFunctions/wfTimestampTest.php b/tests/phpunit/includes/GlobalFunctions/wfTimestampTest.php index 505c28c7..5998f186 100644 --- a/tests/phpunit/includes/GlobalFunctions/wfTimestampTest.php +++ b/tests/phpunit/includes/GlobalFunctions/wfTimestampTest.php @@ -1,19 +1,19 @@ <?php - /* - * Tests for wfTimestamp() + * @covers ::wfTimestamp */ -class wfTimestamp extends MediaWikiTestCase { +class WfTimestampTest extends MediaWikiTestCase { /** * @dataProvider provideNormalTimestamps */ - function testNormalTimestamps( $input, $format, $output, $desc ) { + public function testNormalTimestamps( $input, $format, $output, $desc ) { $this->assertEquals( $output, wfTimestamp( $format, $input ), $desc ); } - function provideNormalTimestamps() { + public static function provideNormalTimestamps() { $t = gmmktime( 12, 34, 56, 1, 15, 2001 ); - return array ( + + return array( // TS_UNIX array( $t, TS_MW, '20010115123456', 'TS_UNIX to TS_MW' ), array( -30281104, TS_MW, '19690115123456', 'Negative TS_UNIX to TS_MW' ), @@ -21,13 +21,13 @@ class wfTimestamp extends MediaWikiTestCase { array( $t, TS_DB, '2001-01-15 12:34:56', 'TS_UNIX to TS_DB' ), array( $t, TS_ISO_8601_BASIC, '20010115T123456Z', 'TS_ISO_8601_BASIC to TS_DB' ), - + // TS_MW array( '20010115123456', TS_MW, '20010115123456', 'TS_MW to TS_MW' ), array( '20010115123456', TS_UNIX, 979562096, 'TS_MW to TS_UNIX' ), array( '20010115123456', TS_DB, '2001-01-15 12:34:56', 'TS_MW to TS_DB' ), array( '20010115123456', TS_ISO_8601_BASIC, '20010115T123456Z', 'TS_MW to TS_ISO_8601_BASIC' ), - + // TS_DB array( '2001-01-15 12:34:56', TS_MW, '20010115123456', 'TS_DB to TS_MW' ), array( '2001-01-15 12:34:56', TS_UNIX, 979562096, 'TS_DB to TS_UNIX' ), @@ -57,12 +57,12 @@ class wfTimestamp extends MediaWikiTestCase { * See r74778 and bug 25451 * @dataProvider provideOldTimestamps */ - function testOldTimestamps( $input, $format, $output, $desc ) { + public function testOldTimestamps( $input, $format, $output, $desc ) { $this->assertEquals( $output, wfTimestamp( $format, $input ), $desc ); } - function provideOldTimestamps() { - return array ( + public static function provideOldTimestamps() { + return array( array( '19011213204554', TS_RFC2822, 'Fri, 13 Dec 1901 20:45:54 GMT', 'Earliest time according to php documentation' ), array( '20380119031407', TS_RFC2822, 'Tue, 19 Jan 2038 03:14:07 GMT', 'Latest 32 bit time' ), array( '19011213204552', TS_UNIX, '-2147483648', 'Earliest 32 bit unix time' ), @@ -96,11 +96,11 @@ class wfTimestamp extends MediaWikiTestCase { * @see http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1 * @dataProvider provideHttpDates */ - function testHttpDate( $input, $output, $desc ) { + public function testHttpDate( $input, $output, $desc ) { $this->assertEquals( $output, wfTimestamp( TS_MW, $input ), $desc ); } - function provideHttpDates() { + public static function provideHttpDates() { return array( array( 'Sun, 06 Nov 1994 08:49:37 GMT', '19941106084937', 'RFC 822 date' ), array( 'Sunday, 06-Nov-94 08:49:37 GMT', '19941106084937', 'RFC 850 date' ), @@ -114,9 +114,9 @@ class wfTimestamp extends MediaWikiTestCase { * There are a number of assumptions in our codebase where wfTimestamp() * should give the current date but it is not given a 0 there. See r71751 CR */ - function testTimestampParameter() { + public function testTimestampParameter() { $now = wfTimestamp( TS_UNIX ); - // We check that wfTimestamp doesn't return false (error) and use a LessThan assert + // We check that wfTimestamp doesn't return false (error) and use a LessThan assert // for the cases where the test is run in a second boundary. $zero = wfTimestamp( TS_UNIX, 0 ); diff --git a/tests/phpunit/includes/GlobalFunctions/wfUrlencodeTest.php b/tests/phpunit/includes/GlobalFunctions/wfUrlencodeTest.php index cd1a8dbd..ce6c82c5 100644 --- a/tests/phpunit/includes/GlobalFunctions/wfUrlencodeTest.php +++ b/tests/phpunit/includes/GlobalFunctions/wfUrlencodeTest.php @@ -1,26 +1,27 @@ <?php /** - * Tests for includes/GlobalFunctions.php -> wfUrlencode() - * * The function only need a string parameter and might react to IIS7.0 + * @covers ::wfUrlencode */ - -class wfUrlencodeTest extends MediaWikiTestCase { - +class WfUrlencodeTest extends MediaWikiTestCase { #### TESTS ############################################################## - - /** @dataProvider provideURLS */ + + /** + * @dataProvider provideURLS + */ public function testEncodingUrlWith( $input, $expected ) { $this->verifyEncodingFor( 'Apache', $input, $expected ); } - /** @dataProvider provideURLS */ + /** + * @dataProvider provideURLS + */ public function testEncodingUrlWithMicrosoftIis7( $input, $expected ) { $this->verifyEncodingFor( 'Microsoft-IIS/7', $input, $expected ); } #### HELPERS ############################################################# - + /** * Internal helper that actually run the test. * Called by the public methods testEncodingUrlWith...() @@ -30,10 +31,9 @@ class wfUrlencodeTest extends MediaWikiTestCase { $expected = $this->extractExpect( $server, $expectations ); // save up global - $old = isset($_SERVER['SERVER_SOFTWARE']) + $old = isset( $_SERVER['SERVER_SOFTWARE'] ) ? $_SERVER['SERVER_SOFTWARE'] - : null - ; + : null; $_SERVER['SERVER_SOFTWARE'] = $server; wfUrlencode( null ); @@ -45,7 +45,7 @@ class wfUrlencodeTest extends MediaWikiTestCase { ); // restore global - if( $old === null ) { + if ( $old === null ) { unset( $_SERVER['SERVER_SOFTWARE'] ); } else { $_SERVER['SERVER_SOFTWARE'] = $old; @@ -58,19 +58,18 @@ class wfUrlencodeTest extends MediaWikiTestCase { * the HTTP server name. */ private function extractExpect( $server, $expectations ) { - if( is_string( $expectations ) ) { + if ( is_string( $expectations ) ) { return $expectations; - } elseif( is_array( $expectations ) ) { - if( !array_key_exists( $server, $expectations ) ) { + } elseif ( is_array( $expectations ) ) { + if ( !array_key_exists( $server, $expectations ) ) { throw new MWException( __METHOD__ . " expectation does not have any value for server name $server. Check the provider array.\n" ); } else { return $expectations[$server]; } - } else { + } else { throw new MWException( __METHOD__ . " given invalid expectation for '$server'. Should be a string or an array( <http server name> => <string> ).\n" ); - } - } - + } + } #### PROVIDERS ########################################################### @@ -83,11 +82,11 @@ class wfUrlencodeTest extends MediaWikiTestCase { * array( 'Microsoft-IIS/7', 'expected' ), * ), * If you want to add other HTTP server name, you will have to add a new - * testing method much like the testEncodingUrlWith() method above. + * testing method much like the testEncodingUrlWith() method above. */ - public function provideURLS() { + public static function provideURLS() { return array( - ### RFC 1738 chars + ### RFC 1738 chars // + is not safe array( '+', '%2B' ), // & and = not safe in queries @@ -95,7 +94,7 @@ class wfUrlencodeTest extends MediaWikiTestCase { array( '=', '%3D' ), array( ':', array( - 'Apache' => ':', + 'Apache' => ':', 'Microsoft-IIS/7' => '%3A', ) ), @@ -105,10 +104,10 @@ class wfUrlencodeTest extends MediaWikiTestCase { ';@$-_.!*', ), - ### Other tests + ### Other tests // slash remain unchanged. %2F seems to break things array( '/', '/' ), - + // Other 'funnies' chars array( '[]', '%5B%5D' ), array( '<>', '%3C%3E' ), diff --git a/tests/phpunit/includes/HTMLCheckMatrixTest.php b/tests/phpunit/includes/HTMLCheckMatrixTest.php new file mode 100644 index 00000000..5bbafd37 --- /dev/null +++ b/tests/phpunit/includes/HTMLCheckMatrixTest.php @@ -0,0 +1,102 @@ +<?php + +/** + * Unit tests for the HTMLCheckMatrix form field + */ +class HtmlCheckMatrixTest extends MediaWikiTestCase { + static private $defaultOptions = array( + 'rows' => array( 'r1', 'r2' ), + 'columns' => array( 'c1', 'c2' ), + 'fieldname' => 'test', + ); + + public function testPlainInstantiation() { + try { + $form = new HTMLCheckMatrix( array() ); + } catch ( MWException $e ) { + $this->assertInstanceOf( 'HTMLFormFieldRequiredOptionsException', $e ); + return; + } + + $this->fail( 'Expected MWException indicating missing parameters but none was thrown.' ); + } + + public function testInstantiationWithMinimumRequiredParameters() { + $form = new HTMLCheckMatrix( self::$defaultOptions ); + $this->assertTrue( true ); // form instantiation must throw exception on failure + } + + public function testValidateCallsUserDefinedValidationCallback() { + $called = false; + $field = new HTMLCheckMatrix( self::$defaultOptions + array( + 'validation-callback' => function() use ( &$called ) { + $called = true; + return false; + }, + ) ); + $this->assertEquals( false, $this->validate( $field, array() ) ); + $this->assertTrue( $called ); + } + + public function testValidateRequiresArrayInput() { + $field = new HTMLCheckMatrix( self::$defaultOptions ); + $this->assertEquals( false, $this->validate( $field, null ) ); + $this->assertEquals( false, $this->validate( $field, true ) ); + $this->assertEquals( false, $this->validate( $field, 'abc' ) ); + $this->assertEquals( false, $this->validate( $field, new stdClass ) ); + $this->assertEquals( true, $this->validate( $field, array() ) ); + } + + public function testValidateAllowsOnlyKnownTags() { + $field = new HTMLCheckMatrix( self::$defaultOptions ); + $this->assertInternalType( 'string', $this->validate( $field, array( 'foo' ) ) ); + } + + public function testValidateAcceptsPartialTagList() { + $field = new HTMLCheckMatrix( self::$defaultOptions ); + $this->assertTrue( $this->validate( $field, array() ) ); + $this->assertTrue( $this->validate( $field, array( 'c1-r1' ) ) ); + $this->assertTrue( $this->validate( $field, array( 'c1-r1', 'c1-r2', 'c2-r1', 'c2-r2' ) ) ); + } + + /** + * This form object actually has no visibility into what happens later on, but essentially + * if the data submitted by the user passes validate the following is run: + * foreach ( $field->filterDataForSubmit( $data ) as $k => $v ) { + * $user->setOption( $k, $v ); + * } + */ + public function testValuesForcedOnRemainOn() { + $field = new HTMLCheckMatrix( self::$defaultOptions + array( + 'force-options-on' => array( 'c2-r1' ), + ) ); + $expected = array( + 'c1-r1' => false, + 'c1-r2' => false, + 'c2-r1' => true, + 'c2-r2' => false, + ); + $this->assertEquals( $expected, $field->filterDataForSubmit( array() ) ); + } + + public function testValuesForcedOffRemainOff() { + $field = new HTMLCheckMatrix( self::$defaultOptions + array( + 'force-options-off' => array( 'c1-r2', 'c2-r2' ), + ) ); + $expected = array( + 'c1-r1' => true, + 'c1-r2' => false, + 'c2-r1' => true, + 'c2-r2' => false, + ); + // array_keys on the result simulates submitting all fields checked + $this->assertEquals( $expected, $field->filterDataForSubmit( array_keys( $expected ) ) ); + } + + protected function validate( HTMLFormField $field, $submitted ) { + return $field->validate( + $submitted, + array( self::$defaultOptions['fieldname'] => $submitted ) + ); + } +} diff --git a/tests/phpunit/includes/HashRingTest.php b/tests/phpunit/includes/HashRingTest.php new file mode 100644 index 00000000..65f13696 --- /dev/null +++ b/tests/phpunit/includes/HashRingTest.php @@ -0,0 +1,53 @@ +<?php + +/** + * @group HashRing + */ +class HashRingTest extends MediaWikiTestCase { + public function testHashRing() { + $ring = new HashRing( array( 's1' => 1, 's2' => 1, 's3' => 2, 's4' => 2, 's5' => 2, 's6' => 3 ) ); + + $locations = array(); + for ( $i = 0; $i < 20; $i++ ) { + $locations[ "hello$i"] = $ring->getLocation( "hello$i" ); + } + $expectedLocations = array( + "hello0" => "s5", + "hello1" => "s6", + "hello2" => "s2", + "hello3" => "s5", + "hello4" => "s6", + "hello5" => "s4", + "hello6" => "s5", + "hello7" => "s4", + "hello8" => "s5", + "hello9" => "s5", + "hello10" => "s3", + "hello11" => "s6", + "hello12" => "s1", + "hello13" => "s3", + "hello14" => "s3", + "hello15" => "s5", + "hello16" => "s4", + "hello17" => "s6", + "hello18" => "s6", + "hello19" => "s3" + ); + + $this->assertEquals( $expectedLocations, $locations, 'Items placed at proper locations' ); + + $locations = array(); + for ( $i = 0; $i < 5; $i++ ) { + $locations[ "hello$i"] = $ring->getLocations( "hello$i", 2 ); + } + + $expectedLocations = array( + "hello0" => array( "s5", "s6" ), + "hello1" => array( "s6", "s4" ), + "hello2" => array( "s2", "s1" ), + "hello3" => array( "s5", "s6" ), + "hello4" => array( "s6", "s4" ), + ); + $this->assertEquals( $expectedLocations, $locations, 'Items placed at proper locations' ); + } +} diff --git a/tests/phpunit/includes/HooksTest.php b/tests/phpunit/includes/HooksTest.php index 2f9d9f8d..81dd4870 100644 --- a/tests/phpunit/includes/HooksTest.php +++ b/tests/phpunit/includes/HooksTest.php @@ -2,101 +2,157 @@ class HooksTest extends MediaWikiTestCase { - public function testOldStyleHooks() { - $foo = 'Foo'; - $bar = 'Bar'; - - $i = new NothingClass(); - + function setUp() { global $wgHooks; + parent::setUp(); + Hooks::clear( 'MediaWikiHooksTest001' ); + unset( $wgHooks['MediaWikiHooksTest001'] ); + } - $wgHooks['MediaWikiHooksTest001'][] = array( $i, 'someNonStatic' ); - - wfRunHooks( 'MediaWikiHooksTest001', array( &$foo, &$bar ) ); - - $this->assertEquals( 'fOO', $foo, 'Standard method' ); - $foo = 'Foo'; - - $wgHooks['MediaWikiHooksTest001'][] = $i; - - wfRunHooks( 'MediaWikiHooksTest001', array( &$foo, &$bar ) ); + public static function provideHooks() { + $i = new NothingClass(); - $this->assertEquals( 'foo', $foo, 'onEventName style' ); - $foo = 'Foo'; + return array( + array( 'Object and method', array( $i, 'someNonStatic' ), 'changed-nonstatic', 'changed-nonstatic' ), + array( 'Object and no method', array( $i ), 'changed-onevent', 'original' ), + array( 'Object and method with data', array( $i, 'someNonStaticWithData', 'data' ), 'data', 'original' ), + array( 'Object and static method', array( $i, 'someStatic' ), 'changed-static', 'original' ), + array( 'Class::method static call', array( 'NothingClass::someStatic' ), 'changed-static', 'original' ), + array( 'Global function', array( 'NothingFunction' ), 'changed-func', 'original' ), + array( 'Global function with data', array( 'NothingFunctionData', 'data' ), 'data', 'original' ), + array( 'Closure', array( function ( &$foo, $bar ) { + $foo = 'changed-closure'; + + return true; + } ), 'changed-closure', 'original' ), + array( 'Closure with data', array( function ( $data, &$foo, $bar ) { + $foo = $data; + + return true; + }, 'data' ), 'data', 'original' ) + ); + } - $wgHooks['MediaWikiHooksTest001'][] = array( $i, 'someNonStaticWithData', 'baz' ); + /** + * @dataProvider provideHooks + */ + public function testOldStyleHooks( $msg, array $hook, $expectedFoo, $expectedBar ) { + global $wgHooks; + $foo = $bar = 'original'; + $wgHooks['MediaWikiHooksTest001'][] = $hook; wfRunHooks( 'MediaWikiHooksTest001', array( &$foo, &$bar ) ); - $this->assertEquals( 'baz', $foo, 'Data included' ); - $foo = 'Foo'; - - $wgHooks['MediaWikiHooksTest001'][] = array( $i, 'someStatic' ); - - wfRunHooks( 'MediaWikiHooksTest001', array( &$foo, &$bar ) ); + $this->assertSame( $expectedFoo, $foo, $msg ); + $this->assertSame( $expectedBar, $bar, $msg ); + } - $this->assertEquals( 'bah', $foo, 'Standard static method' ); - //$foo = 'Foo'; + /** + * @dataProvider provideHooks + */ + public function testNewStyleHooks( $msg, $hook, $expectedFoo, $expectedBar ) { + $foo = $bar = 'original'; - unset( $wgHooks['MediaWikiHooksTest001'] ); + Hooks::register( 'MediaWikiHooksTest001', $hook ); + Hooks::run( 'MediaWikiHooksTest001', array( &$foo, &$bar ) ); + $this->assertSame( $expectedFoo, $foo, $msg ); + $this->assertSame( $expectedBar, $bar, $msg ); } - public function testNewStyleHooks() { - $foo = 'Foo'; - $bar = 'Bar'; - - $i = new NothingClass(); + public function testNewStyleHookInteraction() { + global $wgHooks; - Hooks::register( 'MediaWikiHooksTest001', array( $i, 'someNonStatic' ) ); + $a = new NothingClass(); + $b = new NothingClass(); - Hooks::run( 'MediaWikiHooksTest001', array( &$foo, &$bar ) ); + $wgHooks['MediaWikiHooksTest001'][] = $a; + $this->assertTrue( Hooks::isRegistered( 'MediaWikiHooksTest001' ), 'Hook registered via $wgHooks should be noticed by Hooks::isRegistered' ); - $this->assertEquals( 'fOO', $foo, 'Standard method' ); - $foo = 'Foo'; + Hooks::register( 'MediaWikiHooksTest001', $b ); + $this->assertEquals( 2, count( Hooks::getHandlers( 'MediaWikiHooksTest001' ) ), 'Hooks::getHandlers() should return hooks registered via wgHooks as well as Hooks::register' ); - Hooks::register( 'MediaWikiHooksTest001', $i ); + $foo = 'quux'; + $bar = 'qaax'; Hooks::run( 'MediaWikiHooksTest001', array( &$foo, &$bar ) ); + $this->assertEquals( 1, $a->calls, 'Hooks::run() should run hooks registered via wgHooks as well as Hooks::register' ); + $this->assertEquals( 1, $b->calls, 'Hooks::run() should run hooks registered via wgHooks as well as Hooks::register' ); + } - $this->assertEquals( 'foo', $foo, 'onEventName style' ); - $foo = 'Foo'; + /** + * @expectedException MWException + */ + public function testUncallableFunction() { + Hooks::register( 'MediaWikiHooksTest001', 'ThisFunctionDoesntExist' ); + Hooks::run( 'MediaWikiHooksTest001', array() ); + } - Hooks::register( 'MediaWikiHooksTest001', array( $i, 'someNonStaticWithData', 'baz' ) ); + public function testFalseReturn() { + Hooks::register( 'MediaWikiHooksTest001', function ( &$foo ) { + return false; + } ); + Hooks::register( 'MediaWikiHooksTest001', function ( &$foo ) { + $foo = 'test'; + + return true; + } ); + $foo = 'original'; + Hooks::run( 'MediaWikiHooksTest001', array( &$foo ) ); + $this->assertSame( 'original', $foo, 'Hooks continued processing after a false return.' ); + } - Hooks::run( 'MediaWikiHooksTest001', array( &$foo, &$bar ) ); + /** + * @expectedException FatalError + */ + public function testFatalError() { + Hooks::register( 'MediaWikiHooksTest001', function () { + return 'test'; + } ); + Hooks::run( 'MediaWikiHooksTest001', array() ); + } +} - $this->assertEquals( 'baz', $foo, 'Data included' ); - $foo = 'Foo'; +function NothingFunction( &$foo, &$bar ) { + $foo = 'changed-func'; - Hooks::register( 'MediaWikiHooksTest001', array( $i, 'someStatic' ) ); + return true; +} - Hooks::run( 'MediaWikiHooksTest001', array( &$foo, &$bar ) ); +function NothingFunctionData( $data, &$foo, &$bar ) { + $foo = $data; - $this->assertEquals( 'bah', $foo, 'Standard static method' ); - $foo = 'Foo'; - } + return true; } class NothingClass { - static public function someStatic( &$foo, &$bar ) { - $foo = 'bah'; + public $calls = 0; + + public static function someStatic( &$foo, &$bar ) { + $foo = 'changed-static'; + return true; } public function someNonStatic( &$foo, &$bar ) { - $foo = 'fOO'; - $bar = 'bAR'; + $this->calls++; + $foo = 'changed-nonstatic'; + $bar = 'changed-nonstatic'; + return true; } public function onMediaWikiHooksTest001( &$foo, &$bar ) { - $foo = 'foo'; + $this->calls++; + $foo = 'changed-onevent'; + return true; } - public function someNonStaticWithData( $foo, &$bar ) { - $bar = $foo; + public function someNonStaticWithData( $data, &$foo, &$bar ) { + $this->calls++; + $foo = $data; + return true; } } diff --git a/tests/phpunit/includes/HtmlFormatterTest.php b/tests/phpunit/includes/HtmlFormatterTest.php new file mode 100644 index 00000000..a37df74f --- /dev/null +++ b/tests/phpunit/includes/HtmlFormatterTest.php @@ -0,0 +1,81 @@ +<?php + +/** + * @group HtmlFormatter + */ +class HtmlFormatterTest extends MediaWikiTestCase { + /** + * @dataProvider getHtmlData + */ + public function testTransform( $input, $expected, $callback = false ) { + $input = self::normalize( $input ); + $formatter = new HtmlFormatter( HtmlFormatter::wrapHTML( $input ) ); + if ( $callback ) { + $callback( $formatter ); + } + $formatter->filterContent(); + $html = $formatter->getText(); + $this->assertEquals( self::normalize( $expected ), self::normalize( $html ) ); + } + + private static function normalize( $s ) { + return str_replace( "\n", '', + str_replace( "\r", '', $s ) // "yay" to Windows! + ); + } + + public function getHtmlData() { + $removeImages = function( HtmlFormatter $f ) { + $f->setRemoveMedia(); + }; + $removeTags = function( HtmlFormatter $f ) { + $f->remove( array( 'table', '.foo', '#bar', 'div.baz' ) ); + }; + $flattenSomeStuff = function( HtmlFormatter $f ) { + $f->flatten( array( 's', 'div' ) ); + }; + $flattenEverything = function( HtmlFormatter $f ) { + $f->flattenAllTags(); + }; + return array( + // remove images if asked + array( + '<img src="/foo/bar.jpg" alt="Blah"/>', + '', + $removeImages, + ), + // basic tag removal + array( + '<table><tr><td>foo</td></tr></table><div class="foo">foo</div><div class="foo quux">foo</div><span id="bar">bar</span> +<strong class="foo" id="bar">foobar</strong><div class="notfoo">test</div><div class="baz"/> +<span class="baz">baz</span>', + + '<div class="notfoo">test</div> +<span class="baz">baz</span>', + $removeTags, + ), + // don't flatten tags that start like chosen ones + array( + '<div><s>foo</s> <span>bar</span></div>', + 'foo <span>bar</span>', + $flattenSomeStuff, + ), + // total flattening + array( + '<div style="foo">bar<sup>2</sup></div>', + 'bar2', + $flattenEverything, + ), + // UTF-8 preservation and security + array( + '<span title="" \' &"><Тест!></span> &<&&&&', + '<span title="" \' &"><Тест!></span> &<&&&&', + ), + // https://bugzilla.wikimedia.org/show_bug.cgi?id=53086 + array( + 'Foo<sup id="cite_ref-1" class="reference"><a href="#cite_note-1">[1]</a></sup> <a href="/wiki/Bar" title="Bar" class="mw-redirect">Bar</a>', + 'Foo<sup id="cite_ref-1" class="reference"><a href="#cite_note-1">[1]</a></sup> <a href="/wiki/Bar" title="Bar" class="mw-redirect">Bar</a>', + ), + ); + } +} diff --git a/tests/phpunit/includes/HtmlTest.php b/tests/phpunit/includes/HtmlTest.php index 135ebc5a..1c62d032 100644 --- a/tests/phpunit/includes/HtmlTest.php +++ b/tests/phpunit/includes/HtmlTest.php @@ -2,119 +2,144 @@ /** tests for includes/Html.php */ class HtmlTest extends MediaWikiTestCase { - private static $oldLang; - private static $oldContLang; - private static $oldLanguageCode; - private static $oldNamespaces; - private static $oldHTML5; - public function setUp() { - global $wgLang, $wgContLang, $wgLanguageCode, $wgHTML5; + protected function setUp() { + parent::setUp(); - // Save globals - self::$oldLang = $wgLang; - self::$oldContLang = $wgContLang; - self::$oldNamespaces = $wgContLang->getNamespaces(); - self::$oldLanguageCode = $wgLanguageCode; - self::$oldHTML5 = $wgHTML5; - - $wgLanguageCode = 'en'; - $wgContLang = $wgLang = Language::factory( $wgLanguageCode ); + $langCode = 'en'; + $langObj = Language::factory( $langCode ); // Hardcode namespaces during test runs, // so that html output based on existing namespaces // can be properly evaluated. - $wgContLang->setNamespaces( array( + $langObj->setNamespaces( array( -2 => 'Media', -1 => 'Special', - 0 => '', - 1 => 'Talk', - 2 => 'User', - 3 => 'User_talk', - 4 => 'MyWiki', - 5 => 'MyWiki_Talk', - 6 => 'File', - 7 => 'File_talk', - 8 => 'MediaWiki', - 9 => 'MediaWiki_talk', - 10 => 'Template', - 11 => 'Template_talk', - 14 => 'Category', - 15 => 'Category_talk', - 100 => 'Custom', - 101 => 'Custom_talk', + 0 => '', + 1 => 'Talk', + 2 => 'User', + 3 => 'User_talk', + 4 => 'MyWiki', + 5 => 'MyWiki_Talk', + 6 => 'File', + 7 => 'File_talk', + 8 => 'MediaWiki', + 9 => 'MediaWiki_talk', + 10 => 'Template', + 11 => 'Template_talk', + 14 => 'Category', + 15 => 'Category_talk', + 100 => 'Custom', + 101 => 'Custom_talk', + ) ); + + $this->setMwGlobals( array( + 'wgLanguageCode' => $langCode, + 'wgContLang' => $langObj, + 'wgLang' => $langObj, + 'wgWellFormedXml' => false, ) ); } - public function tearDown() { - global $wgLang, $wgContLang, $wgLanguageCode, $wgHTML5; + public function testElementBasics() { + $this->assertEquals( + '<img>', + Html::element( 'img', null, '' ), + 'No close tag for short-tag elements' + ); - // Restore globals - $wgContLang->setNamespaces( self::$oldNamespaces ); - $wgLang = self::$oldLang; - $wgContLang = self::$oldContLang; - $wgLanguageCode = self::$oldLanguageCode; - $wgHTML5 = self::$oldHTML5; + $this->assertEquals( + '<element></element>', + Html::element( 'element', null, null ), + 'Close tag for empty element (null, null)' + ); + + $this->assertEquals( + '<element></element>', + Html::element( 'element', array(), '' ), + 'Close tag for empty element (array, string)' + ); + + $this->setMwGlobals( 'wgWellFormedXml', true ); + + $this->assertEquals( + '<img />', + Html::element( 'img', null, '' ), + 'Self-closing tag for short-tag elements (wgWellFormedXml = true)' + ); } - /** - * Wrapper to easily set $wgHTML5 = true. - * Original value will be restored after test completion. - * @todo Move to MediaWikiTestCase - */ - public function enableHTML5() { - global $wgHTML5; - $wgHTML5 = true; + public function dataXmlMimeType() { + return array( + // ( $mimetype, $isXmlMimeType ) + # HTML is not an XML MimeType + array( 'text/html', false ), + # XML is an XML MimeType + array( 'text/xml', true ), + array( 'application/xml', true ), + # XHTML is an XML MimeType + array( 'application/xhtml+xml', true ), + # Make sure other +xml MimeTypes are supported + # SVG is another random MimeType even though we don't use it + array( 'image/svg+xml', true ), + # Complete random other MimeTypes are not XML + array( 'text/plain', false ), + ); } + /** - * Wrapper to easily set $wgHTML5 = false - * Original value will be restored after test completion. - * @todo Move to MediaWikiTestCase + * @dataProvider dataXmlMimeType */ - public function disableHTML5() { - global $wgHTML5; - $wgHTML5 = false; + public function testXmlMimeType( $mimetype, $isXmlMimeType ) { + $this->assertEquals( $isXmlMimeType, Html::isXmlMimeType( $mimetype ) ); } public function testExpandAttributesSkipsNullAndFalse() { - + ### EMPTY ######## - $this->AssertEmpty( + $this->assertEmpty( Html::expandAttributes( array( 'foo' => null ) ), 'skip keys with null value' ); - $this->AssertEmpty( + $this->assertEmpty( Html::expandAttributes( array( 'foo' => false ) ), 'skip keys with false value' ); - $this->AssertNotEmpty( + $this->assertNotEmpty( Html::expandAttributes( array( 'foo' => '' ) ), 'keep keys with an empty string' ); } public function testExpandAttributesForBooleans() { - global $wgHtml5; - $this->AssertEquals( + $this->assertEquals( '', Html::expandAttributes( array( 'selected' => false ) ), 'Boolean attributes do not generates output when value is false' ); - $this->AssertEquals( + $this->assertEquals( '', Html::expandAttributes( array( 'selected' => null ) ), 'Boolean attributes do not generates output when value is null' ); - $this->AssertEquals( - $wgHtml5 ? ' selected=""' : ' selected="selected"', + $this->assertEquals( + ' selected', Html::expandAttributes( array( 'selected' => true ) ), - 'Boolean attributes skip value output' + 'Boolean attributes have no value when value is true' ); - $this->AssertEquals( - $wgHtml5 ? ' selected=""' : ' selected="selected"', + $this->assertEquals( + ' selected', Html::expandAttributes( array( 'selected' ) ), - 'Boolean attributes (ex: selected) do not need a value' + 'Boolean attributes have no value when value is true (passed as numerical array)' + ); + + $this->setMwGlobals( 'wgWellFormedXml', true ); + + $this->assertEquals( + ' selected=""', + Html::expandAttributes( array( 'selected' => true ) ), + 'Boolean attributes have empty string value when value is true (wgWellFormedXml)' ); } @@ -124,25 +149,48 @@ class HtmlTest extends MediaWikiTestCase { */ public function testExpandAttributesVariousExpansions() { ### NOT EMPTY #### - $this->AssertEquals( + $this->assertEquals( ' empty_string=""', Html::expandAttributes( array( 'empty_string' => '' ) ), - 'Value with an empty string' + 'Empty string is always quoted' ); - $this->AssertEquals( + $this->assertEquals( + ' key=value', + Html::expandAttributes( array( 'key' => 'value' ) ), + 'Simple string value needs no quotes' + ); + $this->assertEquals( + ' one=1', + Html::expandAttributes( array( 'one' => 1 ) ), + 'Number 1 value needs no quotes' + ); + $this->assertEquals( + ' zero=0', + Html::expandAttributes( array( 'zero' => 0 ) ), + 'Number 0 value needs no quotes' + ); + + $this->setMwGlobals( 'wgWellFormedXml', true ); + + $this->assertEquals( + ' empty_string=""', + Html::expandAttributes( array( 'empty_string' => '' ) ), + 'Attribute values are always quoted (wgWellFormedXml): Empty string' + ); + $this->assertEquals( ' key="value"', Html::expandAttributes( array( 'key' => 'value' ) ), - 'Value is a string' + 'Attribute values are always quoted (wgWellFormedXml): Simple string' ); - $this->AssertEquals( + $this->assertEquals( ' one="1"', Html::expandAttributes( array( 'one' => 1 ) ), - 'Value is a numeric one' + 'Attribute values are always quoted (wgWellFormedXml): Number 1' ); - $this->AssertEquals( + $this->assertEquals( ' zero="0"', Html::expandAttributes( array( 'zero' => 0 ) ), - 'Value is a numeric zero' + 'Attribute values are always quoted (wgWellFormedXml): Number 0' ); } @@ -153,29 +201,29 @@ class HtmlTest extends MediaWikiTestCase { */ public function testExpandAttributesListValueAttributes() { ### STRING VALUES - $this->AssertEquals( + $this->assertEquals( ' class="redundant spaces here"', Html::expandAttributes( array( 'class' => ' redundant spaces here ' ) ), 'Normalization should strip redundant spaces' ); - $this->AssertEquals( + $this->assertEquals( ' class="foo bar"', Html::expandAttributes( array( 'class' => 'foo bar foo bar bar' ) ), 'Normalization should remove duplicates in string-lists' ); ### "EMPTY" ARRAY VALUES - $this->AssertEquals( + $this->assertEquals( ' class=""', Html::expandAttributes( array( 'class' => array() ) ), 'Value with an empty array' ); - $this->AssertEquals( + $this->assertEquals( ' class=""', Html::expandAttributes( array( 'class' => array( null, '', ' ', ' ' ) ) ), 'Array with null, empty string and spaces' ); ### NON-EMPTY ARRAY VALUES - $this->AssertEquals( + $this->assertEquals( ' class="foo bar"', Html::expandAttributes( array( 'class' => array( 'foo', @@ -186,7 +234,7 @@ class HtmlTest extends MediaWikiTestCase { ) ) ), 'Normalization should remove duplicates in the array' ); - $this->AssertEquals( + $this->assertEquals( ' class="foo bar"', Html::expandAttributes( array( 'class' => array( 'foo bar', @@ -202,7 +250,7 @@ class HtmlTest extends MediaWikiTestCase { * Test feature added by r96188, let pass attributes values as * a PHP array. Restricted to class,rel, accesskey. */ - function testExpandAttributesSpaceSeparatedAttributesWithBoolean() { + public function testExpandAttributesSpaceSeparatedAttributesWithBoolean() { $this->assertEquals( ' class="booltrue one"', Html::expandAttributes( array( 'class' => array( @@ -210,12 +258,12 @@ class HtmlTest extends MediaWikiTestCase { 'one' => 1, # Method use isset() internally, make sure we do discard - # attributes values which have been assigned well known values + # attributes values which have been assigned well known values 'emptystring' => '', 'boolfalse' => false, 'zero' => 0, 'null' => null, - ))) + ) ) ) ); } @@ -226,62 +274,62 @@ class HtmlTest extends MediaWikiTestCase { * * Feature added by r96188 */ - function testValueIsAuthoritativeInSpaceSeparatedAttributesArrays() { + public function testValueIsAuthoritativeInSpaceSeparatedAttributesArrays() { $this->assertEquals( ' class=""', Html::expandAttributes( array( 'class' => array( 'GREEN', 'GREEN' => false, 'GREEN', - ))) + ) ) ) ); } - function testNamespaceSelector() { - $this->assertEquals( - '<select>' . "\n" . -'<option value="0">(Main)</option>' . "\n" . -'<option value="1">Talk</option>' . "\n" . -'<option value="2">User</option>' . "\n" . -'<option value="3">User talk</option>' . "\n" . -'<option value="4">MyWiki</option>' . "\n" . -'<option value="5">MyWiki Talk</option>' . "\n" . -'<option value="6">File</option>' . "\n" . -'<option value="7">File talk</option>' . "\n" . -'<option value="8">MediaWiki</option>' . "\n" . -'<option value="9">MediaWiki talk</option>' . "\n" . -'<option value="10">Template</option>' . "\n" . -'<option value="11">Template talk</option>' . "\n" . -'<option value="14">Category</option>' . "\n" . -'<option value="15">Category talk</option>' . "\n" . -'<option value="100">Custom</option>' . "\n" . -'<option value="101">Custom talk</option>' . "\n" . -'</select>', + public function testNamespaceSelector() { + $this->assertEquals( + '<select id=namespace name=namespace>' . "\n" . + '<option value=0>(Main)</option>' . "\n" . + '<option value=1>Talk</option>' . "\n" . + '<option value=2>User</option>' . "\n" . + '<option value=3>User talk</option>' . "\n" . + '<option value=4>MyWiki</option>' . "\n" . + '<option value=5>MyWiki Talk</option>' . "\n" . + '<option value=6>File</option>' . "\n" . + '<option value=7>File talk</option>' . "\n" . + '<option value=8>MediaWiki</option>' . "\n" . + '<option value=9>MediaWiki talk</option>' . "\n" . + '<option value=10>Template</option>' . "\n" . + '<option value=11>Template talk</option>' . "\n" . + '<option value=14>Category</option>' . "\n" . + '<option value=15>Category talk</option>' . "\n" . + '<option value=100>Custom</option>' . "\n" . + '<option value=101>Custom talk</option>' . "\n" . + '</select>', Html::namespaceSelector(), 'Basic namespace selector without custom options' ); $this->assertEquals( - '<label for="mw-test-namespace">Select a namespace:</label> ' . -'<select id="mw-test-namespace" name="wpNamespace">' . "\n" . -'<option value="all">all</option>' . "\n" . -'<option value="0">(Main)</option>' . "\n" . -'<option value="1">Talk</option>' . "\n" . -'<option value="2" selected="">User</option>' . "\n" . -'<option value="3">User talk</option>' . "\n" . -'<option value="4">MyWiki</option>' . "\n" . -'<option value="5">MyWiki Talk</option>' . "\n" . -'<option value="6">File</option>' . "\n" . -'<option value="7">File talk</option>' . "\n" . -'<option value="8">MediaWiki</option>' . "\n" . -'<option value="9">MediaWiki talk</option>' . "\n" . -'<option value="10">Template</option>' . "\n" . -'<option value="11">Template talk</option>' . "\n" . -'<option value="14">Category</option>' . "\n" . -'<option value="15">Category talk</option>' . "\n" . -'<option value="100">Custom</option>' . "\n" . -'<option value="101">Custom talk</option>' . "\n" . -'</select>', + '<label for=mw-test-namespace>Select a namespace:</label> ' . + '<select id=mw-test-namespace name=wpNamespace>' . "\n" . + '<option value=all>all</option>' . "\n" . + '<option value=0>(Main)</option>' . "\n" . + '<option value=1>Talk</option>' . "\n" . + '<option value=2 selected>User</option>' . "\n" . + '<option value=3>User talk</option>' . "\n" . + '<option value=4>MyWiki</option>' . "\n" . + '<option value=5>MyWiki Talk</option>' . "\n" . + '<option value=6>File</option>' . "\n" . + '<option value=7>File talk</option>' . "\n" . + '<option value=8>MediaWiki</option>' . "\n" . + '<option value=9>MediaWiki talk</option>' . "\n" . + '<option value=10>Template</option>' . "\n" . + '<option value=11>Template talk</option>' . "\n" . + '<option value=14>Category</option>' . "\n" . + '<option value=15>Category talk</option>' . "\n" . + '<option value=100>Custom</option>' . "\n" . + '<option value=101>Custom talk</option>' . "\n" . + '</select>', Html::namespaceSelector( array( 'selected' => '2', 'all' => 'all', 'label' => 'Select a namespace:' ), array( 'name' => 'wpNamespace', 'id' => 'mw-test-namespace' ) @@ -290,25 +338,25 @@ class HtmlTest extends MediaWikiTestCase { ); $this->assertEquals( - '<label>Select a namespace:</label> ' . -'<select>' . "\n" . -'<option value="0">(Main)</option>' . "\n" . -'<option value="1">Talk</option>' . "\n" . -'<option value="2">User</option>' . "\n" . -'<option value="3">User talk</option>' . "\n" . -'<option value="4">MyWiki</option>' . "\n" . -'<option value="5">MyWiki Talk</option>' . "\n" . -'<option value="6">File</option>' . "\n" . -'<option value="7">File talk</option>' . "\n" . -'<option value="8">MediaWiki</option>' . "\n" . -'<option value="9">MediaWiki talk</option>' . "\n" . -'<option value="10">Template</option>' . "\n" . -'<option value="11">Template talk</option>' . "\n" . -'<option value="14">Category</option>' . "\n" . -'<option value="15">Category talk</option>' . "\n" . -'<option value="100">Custom</option>' . "\n" . -'<option value="101">Custom talk</option>' . "\n" . -'</select>', + '<label for=namespace>Select a namespace:</label> ' . + '<select id=namespace name=namespace>' . "\n" . + '<option value=0>(Main)</option>' . "\n" . + '<option value=1>Talk</option>' . "\n" . + '<option value=2>User</option>' . "\n" . + '<option value=3>User talk</option>' . "\n" . + '<option value=4>MyWiki</option>' . "\n" . + '<option value=5>MyWiki Talk</option>' . "\n" . + '<option value=6>File</option>' . "\n" . + '<option value=7>File talk</option>' . "\n" . + '<option value=8>MediaWiki</option>' . "\n" . + '<option value=9>MediaWiki talk</option>' . "\n" . + '<option value=10>Template</option>' . "\n" . + '<option value=11>Template talk</option>' . "\n" . + '<option value=14>Category</option>' . "\n" . + '<option value=15>Category talk</option>' . "\n" . + '<option value=100>Custom</option>' . "\n" . + '<option value=101>Custom talk</option>' . "\n" . + '</select>', Html::namespaceSelector( array( 'label' => 'Select a namespace:' ) ), @@ -316,21 +364,21 @@ class HtmlTest extends MediaWikiTestCase { ); } - function testCanFilterOutNamespaces() { - $this->assertEquals( -'<select>' . "\n" . -'<option value="2">User</option>' . "\n" . -'<option value="4">MyWiki</option>' . "\n" . -'<option value="5">MyWiki Talk</option>' . "\n" . -'<option value="6">File</option>' . "\n" . -'<option value="7">File talk</option>' . "\n" . -'<option value="8">MediaWiki</option>' . "\n" . -'<option value="9">MediaWiki talk</option>' . "\n" . -'<option value="10">Template</option>' . "\n" . -'<option value="11">Template talk</option>' . "\n" . -'<option value="14">Category</option>' . "\n" . -'<option value="15">Category talk</option>' . "\n" . -'</select>', + public function testCanFilterOutNamespaces() { + $this->assertEquals( + '<select id=namespace name=namespace>' . "\n" . + '<option value=2>User</option>' . "\n" . + '<option value=4>MyWiki</option>' . "\n" . + '<option value=5>MyWiki Talk</option>' . "\n" . + '<option value=6>File</option>' . "\n" . + '<option value=7>File talk</option>' . "\n" . + '<option value=8>MediaWiki</option>' . "\n" . + '<option value=9>MediaWiki talk</option>' . "\n" . + '<option value=10>Template</option>' . "\n" . + '<option value=11>Template talk</option>' . "\n" . + '<option value=14>Category</option>' . "\n" . + '<option value=15>Category talk</option>' . "\n" . + '</select>', Html::namespaceSelector( array( 'exclude' => array( 0, 1, 3, 100, 101 ) ) ), @@ -338,26 +386,26 @@ class HtmlTest extends MediaWikiTestCase { ); } - function testCanDisableANamespaces() { - $this->assertEquals( -'<select>' . "\n" . -'<option disabled="" value="0">(Main)</option>' . "\n" . -'<option disabled="" value="1">Talk</option>' . "\n" . -'<option disabled="" value="2">User</option>' . "\n" . -'<option disabled="" value="3">User talk</option>' . "\n" . -'<option disabled="" value="4">MyWiki</option>' . "\n" . -'<option value="5">MyWiki Talk</option>' . "\n" . -'<option value="6">File</option>' . "\n" . -'<option value="7">File talk</option>' . "\n" . -'<option value="8">MediaWiki</option>' . "\n" . -'<option value="9">MediaWiki talk</option>' . "\n" . -'<option value="10">Template</option>' . "\n" . -'<option value="11">Template talk</option>' . "\n" . -'<option value="14">Category</option>' . "\n" . -'<option value="15">Category talk</option>' . "\n" . -'<option value="100">Custom</option>' . "\n" . -'<option value="101">Custom talk</option>' . "\n" . -'</select>', + public function testCanDisableANamespaces() { + $this->assertEquals( + '<select id=namespace name=namespace>' . "\n" . + '<option disabled value=0>(Main)</option>' . "\n" . + '<option disabled value=1>Talk</option>' . "\n" . + '<option disabled value=2>User</option>' . "\n" . + '<option disabled value=3>User talk</option>' . "\n" . + '<option disabled value=4>MyWiki</option>' . "\n" . + '<option value=5>MyWiki Talk</option>' . "\n" . + '<option value=6>File</option>' . "\n" . + '<option value=7>File talk</option>' . "\n" . + '<option value=8>MediaWiki</option>' . "\n" . + '<option value=9>MediaWiki talk</option>' . "\n" . + '<option value=10>Template</option>' . "\n" . + '<option value=11>Template talk</option>' . "\n" . + '<option value=14>Category</option>' . "\n" . + '<option value=15>Category talk</option>' . "\n" . + '<option value=100>Custom</option>' . "\n" . + '<option value=101>Custom talk</option>' . "\n" . + '</select>', Html::namespaceSelector( array( 'disable' => array( 0, 1, 2, 3, 4 ) ) ), @@ -366,13 +414,12 @@ class HtmlTest extends MediaWikiTestCase { } /** - * @dataProvider providesHtml5InputTypes + * @dataProvider provideHtml5InputTypes */ - function testHtmlElementAcceptsNewHtml5TypesInHtml5Mode( $HTML5InputType ) { - $this->enableHTML5(); + public function testHtmlElementAcceptsNewHtml5TypesInHtml5Mode( $HTML5InputType ) { $this->assertEquals( - '<input type="' . $HTML5InputType . '" />', - HTML::element( 'input', array( 'type' => $HTML5InputType ) ), + '<input type=' . $HTML5InputType . '>', + Html::element( 'input', array( 'type' => $HTML5InputType ) ), 'In HTML5, HTML::element() should accept type="' . $HTML5InputType . '"' ); } @@ -381,7 +428,7 @@ class HtmlTest extends MediaWikiTestCase { * List of input element types values introduced by HTML5 * Full list at http://www.w3.org/TR/html-markup/input.html */ - function providesHtml5InputTypes() { + public static function provideHtml5InputTypes() { $types = array( 'datetime', 'datetime-local', @@ -398,42 +445,39 @@ class HtmlTest extends MediaWikiTestCase { 'color', ); $cases = array(); - foreach( $types as $type ) { + foreach ( $types as $type ) { $cases[] = array( $type ); } + return $cases; } /** - * Test out Html::element drops default value - * @cover Html::dropDefaults + * Test out Html::element drops or enforces default value + * @covers Html::dropDefaults * @dataProvider provideElementsWithAttributesHavingDefaultValues */ - function testDropDefaults( $expected, $element, $message = '' ) { - $this->enableHTML5(); - $this->assertEquals( $expected, $element, $message ); + public function testDropDefaults( $expected, $element, $attribs, $message = '' ) { + $this->assertEquals( $expected, Html::element( $element, $attribs ), $message ); } - function provideElementsWithAttributesHavingDefaultValues() { + public static function provideElementsWithAttributesHavingDefaultValues() { # Use cases in a concise format: # <expected>, <element name>, <array of attributes> [, <message>] # Will be mapped to Html::element() $cases = array(); ### Generic cases, match $attribDefault static array - $cases[] = array( '<area />', + $cases[] = array( '<area>', 'area', array( 'shape' => 'rect' ) ); - $cases[] = array( '<button></button>', + $cases[] = array( '<button type=submit></button>', 'button', array( 'formaction' => 'GET' ) ); - $cases[] = array( '<button></button>', + $cases[] = array( '<button type=submit></button>', 'button', array( 'formenctype' => 'application/x-www-form-urlencoded' ) ); - $cases[] = array( '<button></button>', - 'button', array( 'type' => 'submit' ) - ); $cases[] = array( '<canvas></canvas>', 'canvas', array( 'height' => '150' ) @@ -449,7 +493,7 @@ class HtmlTest extends MediaWikiTestCase { 'canvas', array( 'width' => 300 ) ); - $cases[] = array( '<command />', + $cases[] = array( '<command>', 'command', array( 'type' => 'command' ) ); @@ -463,18 +507,18 @@ class HtmlTest extends MediaWikiTestCase { 'form', array( 'enctype' => 'application/x-www-form-urlencoded' ) ); - $cases[] = array( '<input />', + $cases[] = array( '<input>', 'input', array( 'formaction' => 'GET' ) ); - $cases[] = array( '<input />', + $cases[] = array( '<input>', 'input', array( 'type' => 'text' ) ); - $cases[] = array( '<keygen />', + $cases[] = array( '<keygen>', 'keygen', array( 'keytype' => 'rsa' ) ); - $cases[] = array( '<link />', + $cases[] = array( '<link>', 'link', array( 'media' => 'all' ) ); @@ -499,37 +543,44 @@ class HtmlTest extends MediaWikiTestCase { ### SPECIFIC CASES - # <link type="text/css" /> - $cases[] = array( '<link />', + # <link type="text/css"> + $cases[] = array( '<link>', 'link', array( 'type' => 'text/css' ) ); - # <input /> specific handling - $cases[] = array( '<input type="checkbox" />', + # <input> specific handling + $cases[] = array( '<input type=checkbox>', 'input', array( 'type' => 'checkbox', 'value' => 'on' ), 'Default value "on" is stripped of checkboxes', ); - $cases[] = array( '<input type="radio" />', + $cases[] = array( '<input type=radio>', 'input', array( 'type' => 'radio', 'value' => 'on' ), 'Default value "on" is stripped of radio buttons', ); - $cases[] = array( '<input type="submit" value="Submit" />', + $cases[] = array( '<input type=submit value=Submit>', 'input', array( 'type' => 'submit', 'value' => 'Submit' ), 'Default value "Submit" is kept on submit buttons (for possible l10n issues)', ); - $cases[] = array( '<input type="color" />', + $cases[] = array( '<input type=color>', 'input', array( 'type' => 'color', 'value' => '' ), ); - $cases[] = array( '<input type="range" />', + $cases[] = array( '<input type=range>', 'input', array( 'type' => 'range', 'value' => '' ), ); - # <select /> specifc handling - $cases[] = array( '<select multiple=""></select>', + # <button> specific handling + # see remarks on http://msdn.microsoft.com/en-us/library/ie/ms535211%28v=vs.85%29.aspx + $cases[] = array( '<button type=submit></button>', + 'button', array( 'type' => 'submit' ), + 'According to standard the default type is "submit". Depending on compatibility mode IE might use "button", instead.', + ); + + # <select> specifc handling + $cases[] = array( '<select multiple></select>', 'select', array( 'size' => '4', 'multiple' => true ), ); # .. with numeric value - $cases[] = array( '<select multiple=""></select>', + $cases[] = array( '<select multiple></select>', 'select', array( 'size' => 4, 'multiple' => true ), ); $cases[] = array( '<select></select>', @@ -553,15 +604,16 @@ class HtmlTest extends MediaWikiTestCase { "dropDefaults accepts values given as an array" ); - # Craft the Html elements $ret = array(); - foreach( $cases as $case ) { + foreach ( $cases as $case ) { $ret[] = array( $case[0], - Html::element( $case[1], $case[2] ) + $case[1], $case[2], + isset( $case[3] ) ? $case[3] : '' ); } + return $ret; } @@ -571,10 +623,9 @@ class HtmlTest extends MediaWikiTestCase { 'Blacklist form validation attributes.' ); $this->assertEquals( - ' step="any"', + ' step=any', Html::expandAttributes( array( 'min' => 1, 'max' => 100, 'pattern' => 'abc', 'required' => true, 'step' => 'any' ) ), - "Allow special case 'step=\"any\"'." + 'Allow special case "step=any".' ); } - } diff --git a/tests/phpunit/includes/HttpTest.php b/tests/phpunit/includes/HttpTest.php index 263383f1..11d8ed60 100644 --- a/tests/phpunit/includes/HttpTest.php +++ b/tests/phpunit/includes/HttpTest.php @@ -5,8 +5,9 @@ class HttpTest extends MediaWikiTestCase { /** * @dataProvider cookieDomains + * @covers Cookie::validateCookieDomain */ - function testValidateCookieDomain( $expected, $domain, $origin = null ) { + public function testValidateCookieDomain( $expected, $domain, $origin = null ) { if ( $origin ) { $ok = Cookie::validateCookieDomain( $domain, $origin ); $msg = "$domain against origin $origin"; @@ -17,12 +18,12 @@ class HttpTest extends MediaWikiTestCase { $this->assertEquals( $expected, $ok, $msg ); } - function cookieDomains() { + public static function cookieDomains() { return array( - array( false, "org"), - array( false, ".org"), - array( true, "wikipedia.org"), - array( true, ".wikipedia.org"), + array( false, "org" ), + array( false, ".org" ), + array( true, "wikipedia.org" ), + array( true, ".wikipedia.org" ), array( false, "co.uk" ), array( false, ".co.uk" ), array( false, "gov.uk" ), @@ -50,11 +51,12 @@ class HttpTest extends MediaWikiTestCase { * Test Http::isValidURI() * @bug 27854 : Http::isValidURI is too lax * @dataProvider provideURI + * @covers Http::isValidURI */ - function testIsValidUri( $expect, $URI, $message = '' ) { + public function testIsValidUri( $expect, $URI, $message = '' ) { $this->assertEquals( $expect, - (bool) Http::isValidURI( $URI ), + (bool)Http::isValidURI( $URI ), $message ); } @@ -62,32 +64,32 @@ class HttpTest extends MediaWikiTestCase { /** * Feeds URI to test a long regular expression in Http::isValidURI */ - function provideURI() { + public static function provideURI() { /** Format: 'boolean expectation', 'URI to test', 'Optional message' */ return array( array( false, '¿non sens before!! http://a', 'Allow anything before URI' ), # (http|https) - only two schemes allowed - array( true, 'http://www.example.org/' ), - array( true, 'https://www.example.org/' ), - array( true, 'http://www.example.org', 'URI without directory' ), - array( true, 'http://a', 'Short name' ), - array( true, 'http://étoile', 'Allow UTF-8 in hostname' ), # 'étoile' is french for 'star' + array( true, 'http://www.example.org/' ), + array( true, 'https://www.example.org/' ), + array( true, 'http://www.example.org', 'URI without directory' ), + array( true, 'http://a', 'Short name' ), + array( true, 'http://étoile', 'Allow UTF-8 in hostname' ), # 'étoile' is french for 'star' array( false, '\\host\directory', 'CIFS share' ), array( false, 'gopher://host/dir', 'Reject gopher scheme' ), array( false, 'telnet://host', 'Reject telnet scheme' ), # :\/\/ - double slashes - array( false, 'http//example.org', 'Reject missing colon in protocol' ), - array( false, 'http:/example.org', 'Reject missing slash in protocol' ), - array( false, 'http:example.org', 'Must have two slashes' ), + array( false, 'http//example.org', 'Reject missing colon in protocol' ), + array( false, 'http:/example.org', 'Reject missing slash in protocol' ), + array( false, 'http:example.org', 'Must have two slashes' ), # Following fail since hostname can be made of anything - array( false, 'http:///example.org', 'Must have exactly two slashes, not three' ), + array( false, 'http:///example.org', 'Must have exactly two slashes, not three' ), # (\w+:{0,1}\w*@)? - optional user:pass - array( true, 'http://user@host', 'Username provided' ), - array( true, 'http://user:@host', 'Username provided, no password' ), - array( true, 'http://user:pass@host', 'Username and password provided' ), + array( true, 'http://user@host', 'Username provided' ), + array( true, 'http://user:@host', 'Username provided, no password' ), + array( true, 'http://user:pass@host', 'Username and password provided' ), # (\S+) - host part is made of anything not whitespaces array( false, 'http://!"èèè¿¿¿~~\'', 'hostname is made of any non whitespace' ), @@ -115,7 +117,7 @@ class HttpTest extends MediaWikiTestCase { array( true, 'http://example/&' ), # Fragment - array( true, 'http://exam#ple.org', ), # This one is valid, really! + array( true, 'http://exam#ple.org', ), # This one is valid, really! array( true, 'http://example.org:80#anchor' ), array( true, 'http://example.org/?id#anchor' ), array( true, 'http://example.org/?#anchor' ), @@ -126,18 +128,19 @@ class HttpTest extends MediaWikiTestCase { /** * Warning: - * + * * These tests are for code that makes use of an artifact of how CURL * handles header reporting on redirect pages, and will need to be * rewritten when bug 29232 is taken care of (high-level handling of * HTTP redirects). */ - function testRelativeRedirections() { - $h = new MWHttpRequestTester( 'http://oldsite/file.ext' ); + public function testRelativeRedirections() { + $h = MWHttpRequestTester::factory( 'http://oldsite/file.ext' ); + # Forge a Location header $h->setRespHeaders( 'location', array( - 'http://newsite/file.ext', - '/newfile.ext', + 'http://newsite/file.ext', + '/newfile.ext', ) ); # Verify we correctly fix the Location @@ -148,7 +151,7 @@ class HttpTest extends MediaWikiTestCase { ); $h->setRespHeaders( 'location', array( - 'https://oldsite/file.ext' + 'https://oldsite/file.ext' ) ); $this->assertEquals( @@ -158,23 +161,56 @@ class HttpTest extends MediaWikiTestCase { ); $h->setRespHeaders( 'location', array( - '/anotherfile.ext', - 'http://anotherfile/hoster.ext', - 'https://anotherfile/hoster.ext' + '/anotherfile.ext', + 'http://anotherfile/hoster.ext', + 'https://anotherfile/hoster.ext' ) ); $this->assertEquals( 'https://anotherfile/hoster.ext', - $h->getFinalUrl( "Relative file path Location: should keep the latest host and scheme!") + $h->getFinalUrl( "Relative file path Location: should keep the latest host and scheme!" ) ); } } /** - * Class to let us overwrite MWHttpREquest respHeaders variable + * Class to let us overwrite MWHttpRequest respHeaders variable */ class MWHttpRequestTester extends MWHttpRequest { + + // function derived from the MWHttpRequest factory function but + // returns appropriate tester class here + public static function factory( $url, $options = null ) { + if ( !Http::$httpEngine ) { + Http::$httpEngine = function_exists( 'curl_init' ) ? 'curl' : 'php'; + } elseif ( Http::$httpEngine == 'curl' && !function_exists( 'curl_init' ) ) { + throw new MWException( __METHOD__ . ': curl (http://php.net/curl) is not installed, but' . + 'Http::$httpEngine is set to "curl"' ); + } + + switch ( Http::$httpEngine ) { + case 'curl': + return new CurlHttpRequestTester( $url, $options ); + case 'php': + if ( !wfIniGetBool( 'allow_url_fopen' ) ) { + throw new MWException( __METHOD__ . ': allow_url_fopen needs to be enabled for pure PHP' . + ' http requests to work. If possible, curl should be used instead. See http://php.net/curl.' ); + } + + return new PhpHttpRequestTester( $url, $options ); + default: + } + } +} + +class CurlHttpRequestTester extends CurlHttpRequest { + function setRespHeaders( $name, $value ) { + $this->respHeaders[$name] = $value; + } +} + +class PhpHttpRequestTester extends PhpHttpRequest { function setRespHeaders( $name, $value ) { - $this->respHeaders[$name] = $value ; + $this->respHeaders[$name] = $value; } } diff --git a/tests/phpunit/includes/IPTest.php b/tests/phpunit/includes/IPTest.php index f50b2fe9..c074eea6 100644 --- a/tests/phpunit/includes/IPTest.php +++ b/tests/phpunit/includes/IPTest.php @@ -1,7 +1,12 @@ <?php /** - * Tests for IP validity functions. Ported from /t/inc/IP.t by avar. + * Tests for IP validity functions. + * + * Ported from /t/inc/IP.t by avar. + * * @group IP + * @todo Test methods in this call should be split into a method and a + * dataprovider. */ class IPTest extends MediaWikiTestCase { @@ -11,13 +16,13 @@ class IPTest extends MediaWikiTestCase { */ public function testisIPAddress() { $this->assertFalse( IP::isIPAddress( false ), 'Boolean false is not an IP' ); - $this->assertFalse( IP::isIPAddress( true ), 'Boolean true is not an IP' ); + $this->assertFalse( IP::isIPAddress( true ), 'Boolean true is not an IP' ); $this->assertFalse( IP::isIPAddress( "" ), 'Empty string is not an IP' ); $this->assertFalse( IP::isIPAddress( 'abc' ), 'Garbage IP string' ); $this->assertFalse( IP::isIPAddress( ':' ), 'Single ":" is not an IP' ); - $this->assertFalse( IP::isIPAddress( '2001:0DB8::A:1::1'), 'IPv6 with a double :: occurrence' ); - $this->assertFalse( IP::isIPAddress( '2001:0DB8::A:1::'), 'IPv6 with a double :: occurrence, last at end' ); - $this->assertFalse( IP::isIPAddress( '::2001:0DB8::5:1'), 'IPv6 with a double :: occurrence, firt at beginning' ); + $this->assertFalse( IP::isIPAddress( '2001:0DB8::A:1::1' ), 'IPv6 with a double :: occurrence' ); + $this->assertFalse( IP::isIPAddress( '2001:0DB8::A:1::' ), 'IPv6 with a double :: occurrence, last at end' ); + $this->assertFalse( IP::isIPAddress( '::2001:0DB8::5:1' ), 'IPv6 with a double :: occurrence, firt at beginning' ); $this->assertFalse( IP::isIPAddress( '124.24.52' ), 'IPv4 not enough quads' ); $this->assertFalse( IP::isIPAddress( '24.324.52.13' ), 'IPv4 out of range' ); $this->assertFalse( IP::isIPAddress( '.24.52.13' ), 'IPv4 starts with period' ); @@ -81,9 +86,9 @@ class IPTest extends MediaWikiTestCase { $this->assertTrue( IP::isIPv6( 'fc::100:a:d:1' ), 'IPv6 with "::" and 5 words' ); $this->assertTrue( IP::isIPv6( 'fc::100:a:d:1:e' ), 'IPv6 with "::" and 6 words' ); $this->assertTrue( IP::isIPv6( 'fc::100:a:d:1:e:ac' ), 'IPv6 with "::" and 7 words' ); - $this->assertTrue( IP::isIPv6( '2001::df'), 'IPv6 with "::" and 2 words' ); - $this->assertTrue( IP::isIPv6( '2001:5c0:1400:a::df'), 'IPv6 with "::" and 5 words' ); - $this->assertTrue( IP::isIPv6( '2001:5c0:1400:a::df:2'), 'IPv6 with "::" and 6 words' ); + $this->assertTrue( IP::isIPv6( '2001::df' ), 'IPv6 with "::" and 2 words' ); + $this->assertTrue( IP::isIPv6( '2001:5c0:1400:a::df' ), 'IPv6 with "::" and 5 words' ); + $this->assertTrue( IP::isIPv6( '2001:5c0:1400:a::df:2' ), 'IPv6 with "::" and 6 words' ); $this->assertFalse( IP::isIPv6( 'fc::100:a:d:1:e:ac:0' ), 'IPv6 with "::" and 8 words' ); $this->assertFalse( IP::isIPv6( 'fc::100:a:d:1:e:ac:0:1' ), 'IPv6 with 9 words' ); @@ -96,7 +101,7 @@ class IPTest extends MediaWikiTestCase { */ public function testisIPv4() { $this->assertFalse( IP::isIPv4( false ), 'Boolean false is not an IP' ); - $this->assertFalse( IP::isIPv4( true ), 'Boolean true is not an IP' ); + $this->assertFalse( IP::isIPv4( true ), 'Boolean true is not an IP' ); $this->assertFalse( IP::isIPv4( "" ), 'Empty string is not an IP' ); $this->assertFalse( IP::isIPv4( 'abc' ) ); $this->assertFalse( IP::isIPv4( ':' ) ); @@ -119,7 +124,7 @@ class IPTest extends MediaWikiTestCase { $c = sprintf( "%01d", $i ); foreach ( array_unique( array( $a, $b, $c ) ) as $f ) { $ip = "$f.$f.$f.$f"; - $this->assertTrue( IP::isValid( $ip ) , "$ip is a valid IPv4 address" ); + $this->assertTrue( IP::isValid( $ip ), "$ip is a valid IPv4 address" ); } } foreach ( range( 0x0, 0xFFFF, 0xF ) as $i ) { @@ -128,7 +133,7 @@ class IPTest extends MediaWikiTestCase { $c = sprintf( "%02x", $i ); foreach ( array_unique( array( $a, $b, $c ) ) as $f ) { $ip = "$f:$f:$f:$f:$f:$f:$f:$f"; - $this->assertTrue( IP::isValid( $ip ) , "$ip is a valid IPv6 address" ); + $this->assertTrue( IP::isValid( $ip ), "$ip is a valid IPv6 address" ); } } // test with some abbreviations @@ -143,9 +148,9 @@ class IPTest extends MediaWikiTestCase { $this->assertTrue( IP::isValid( 'fc::100' ), 'IPv6 with "::" and 2 words' ); $this->assertTrue( IP::isValid( 'fc::100:a' ), 'IPv6 with "::" and 3 words' ); - $this->assertTrue( IP::isValid( '2001::df'), 'IPv6 with "::" and 2 words' ); - $this->assertTrue( IP::isValid( '2001:5c0:1400:a::df'), 'IPv6 with "::" and 5 words' ); - $this->assertTrue( IP::isValid( '2001:5c0:1400:a::df:2'), 'IPv6 with "::" and 6 words' ); + $this->assertTrue( IP::isValid( '2001::df' ), 'IPv6 with "::" and 2 words' ); + $this->assertTrue( IP::isValid( '2001:5c0:1400:a::df' ), 'IPv6 with "::" and 5 words' ); + $this->assertTrue( IP::isValid( '2001:5c0:1400:a::df:2' ), 'IPv6 with "::" and 6 words' ); $this->assertTrue( IP::isValid( 'fc::100:a:d:1' ), 'IPv6 with "::" and 5 words' ); $this->assertTrue( IP::isValid( 'fc::100:a:d:1:e:ac' ), 'IPv6 with "::" and 7 words' ); @@ -173,7 +178,7 @@ class IPTest extends MediaWikiTestCase { $c = sprintf( "%02s", $i ); foreach ( array_unique( array( $a, $b, $c ) ) as $f ) { $ip = "$f:$f:$f:$f:$f:$f:$f:$f"; - $this->assertFalse( IP::isValid( $ip ) , "$ip is not a valid IPv6 address" ); + $this->assertFalse( IP::isValid( $ip ), "$ip is not a valid IPv6 address" ); } } // Have CIDR @@ -254,19 +259,68 @@ class IPTest extends MediaWikiTestCase { * @todo Most probably incomplete */ public function testSanitizeIP() { - $this->assertNull( IP::sanitizeIP('') ); - $this->assertNull( IP::sanitizeIP(' ') ); + $this->assertNull( IP::sanitizeIP( '' ) ); + $this->assertNull( IP::sanitizeIP( ' ' ) ); } /** - * test wrapper around ip2long which might return -1 or false depending on PHP version * @covers IP::toUnsigned + * @dataProvider provideToUnsigned */ - public function testip2longWrapper() { - // @todo FIXME: Add more tests ? - $this->assertEquals( pow(2,32) - 1, IP::toUnsigned( '255.255.255.255' )); - $i = 'IN.VA.LI.D'; - $this->assertFalse( IP::toUnSigned( $i ) ); + public function testToUnsigned( $expected, $input ) { + $result = IP::toUnsigned( $input ); + $this->assertTrue( $result === false || is_string( $result ) || is_int( $result ) ); + $this->assertEquals( $expected, $result ); + } + + /** + * Provider for IP::testToUnsigned() + */ + public static function provideToUnsigned() { + return array( + array( 1, '0.0.0.1' ), + array( 16909060, '1.2.3.4' ), + array( 2130706433, '127.0.0.1' ), + array( '2147483648', '128.0.0.0' ), + array( '3735931646', '222.173.202.254' ), + array( pow( 2, 32 ) - 1, '255.255.255.255' ), + array( false, 'IN.VA.LI.D' ), + array( 1, '::1' ), + array( '42540766452641154071740215577757643572', '2001:0db8:85a3:0000:0000:8a2e:0370:7334' ), + array( '42540766452641154071740215577757643572', '2001:db8:85a3::8a2e:0370:7334' ), + array( false, 'IN:VA::LI:D' ), + array( false, ':::1' ) + ); + } + + /** + * @covers IP::toHex + * @dataProvider provideToHex + */ + public function testToHex( $expected, $input ) { + $result = IP::toHex( $input ); + $this->assertTrue( $result === false || is_string( $result ) ); + $this->assertEquals( $expected, $result ); + } + + /** + * Provider for IP::testToHex() + */ + public static function provideToHex() { + return array( + array( '00000001', '0.0.0.1' ), + array( '01020304', '1.2.3.4' ), + array( '7F000001', '127.0.0.1' ), + array( '80000000', '128.0.0.0' ), + array( 'DEADCAFE', '222.173.202.254' ), + array( 'FFFFFFFF', '255.255.255.255' ), + array( false, 'IN.VA.LI.D' ), + array( 'v6-00000000000000000000000000000001', '::1' ), + array( 'v6-20010DB885A3000000008A2E03707334', '2001:0db8:85a3:0000:0000:8a2e:0370:7334' ), + array( 'v6-20010DB885A3000000008A2E03707334', '2001:db8:85a3::8a2e:0370:7334' ), + array( false, 'IN:VA::LI:D' ), + array( false, ':::1' ) + ); } /** @@ -284,7 +338,7 @@ class IPTest extends MediaWikiTestCase { } // Private wrapper used to test CIDR Parsing. - private function assertFalseCIDR( $CIDR, $msg='' ) { + private function assertFalseCIDR( $CIDR, $msg = '' ) { $ff = array( false, false ); $this->assertEquals( $ff, IP::parseCIDR( $CIDR ), $msg ); } @@ -299,15 +353,15 @@ class IPTest extends MediaWikiTestCase { * @covers IP::hexToQuad */ public function testHexToQuad() { - $this->assertEquals( '0.0.0.1' , IP::hexToQuad( '00000001' ) ); - $this->assertEquals( '255.0.0.0' , IP::hexToQuad( 'FF000000' ) ); + $this->assertEquals( '0.0.0.1', IP::hexToQuad( '00000001' ) ); + $this->assertEquals( '255.0.0.0', IP::hexToQuad( 'FF000000' ) ); $this->assertEquals( '255.255.255.255', IP::hexToQuad( 'FFFFFFFF' ) ); - $this->assertEquals( '10.188.222.255' , IP::hexToQuad( '0ABCDEFF' ) ); + $this->assertEquals( '10.188.222.255', IP::hexToQuad( '0ABCDEFF' ) ); // hex not left-padded... - $this->assertEquals( '0.0.0.0' , IP::hexToQuad( '0' ) ); - $this->assertEquals( '0.0.0.1' , IP::hexToQuad( '1' ) ); - $this->assertEquals( '0.0.0.255' , IP::hexToQuad( 'FF' ) ); - $this->assertEquals( '0.0.255.0' , IP::hexToQuad( 'FF00' ) ); + $this->assertEquals( '0.0.0.0', IP::hexToQuad( '0' ) ); + $this->assertEquals( '0.0.0.1', IP::hexToQuad( '1' ) ); + $this->assertEquals( '0.0.0.255', IP::hexToQuad( 'FF' ) ); + $this->assertEquals( '0.0.255.0', IP::hexToQuad( 'FF00' ) ); } /** @@ -325,11 +379,11 @@ class IPTest extends MediaWikiTestCase { $this->assertEquals( 'FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF', IP::hexToOctet( 'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF' ) ); // hex not left-padded... - $this->assertEquals( '0:0:0:0:0:0:0:0' , IP::hexToOctet( '0' ) ); - $this->assertEquals( '0:0:0:0:0:0:0:1' , IP::hexToOctet( '1' ) ); - $this->assertEquals( '0:0:0:0:0:0:0:FF' , IP::hexToOctet( 'FF' ) ); - $this->assertEquals( '0:0:0:0:0:0:0:FFD0' , IP::hexToOctet( 'FFD0' ) ); - $this->assertEquals( '0:0:0:0:0:0:FA00:0' , IP::hexToOctet( 'FA000000' ) ); + $this->assertEquals( '0:0:0:0:0:0:0:0', IP::hexToOctet( '0' ) ); + $this->assertEquals( '0:0:0:0:0:0:0:1', IP::hexToOctet( '1' ) ); + $this->assertEquals( '0:0:0:0:0:0:0:FF', IP::hexToOctet( 'FF' ) ); + $this->assertEquals( '0:0:0:0:0:0:0:FFD0', IP::hexToOctet( 'FFD0' ) ); + $this->assertEquals( '0:0:0:0:0:0:FA00:0', IP::hexToOctet( 'FA000000' ) ); $this->assertEquals( '0:0:0:0:0:0:FCCF:FAFF', IP::hexToOctet( 'FCCFFAFF' ) ); } @@ -338,43 +392,42 @@ class IPTest extends MediaWikiTestCase { * representing the network mask and the bit mask. * @covers IP::parseCIDR */ - function testCIDRParsing() { - $this->assertFalseCIDR( '192.0.2.0' , "missing mask" ); + public function testCIDRParsing() { + $this->assertFalseCIDR( '192.0.2.0', "missing mask" ); $this->assertFalseCIDR( '192.0.2.0/', "missing bitmask" ); // Verify if statement - $this->assertFalseCIDR( '256.0.0.0/32', "invalid net" ); + $this->assertFalseCIDR( '256.0.0.0/32', "invalid net" ); $this->assertFalseCIDR( '192.0.2.0/AA', "mask not numeric" ); - $this->assertFalseCIDR( '192.0.2.0/-1', "mask < 0" ); - $this->assertFalseCIDR( '192.0.2.0/33', "mask > 32" ); + $this->assertFalseCIDR( '192.0.2.0/-1', "mask < 0" ); + $this->assertFalseCIDR( '192.0.2.0/33', "mask > 32" ); // Check internal logic # 0 mask always result in array(0,0) - $this->assertEquals( array( 0, 0 ), IP::parseCIDR('192.0.0.2/0') ); - $this->assertEquals( array( 0, 0 ), IP::parseCIDR('0.0.0.0/0') ); - $this->assertEquals( array( 0, 0 ), IP::parseCIDR('255.255.255.255/0') ); + $this->assertEquals( array( 0, 0 ), IP::parseCIDR( '192.0.0.2/0' ) ); + $this->assertEquals( array( 0, 0 ), IP::parseCIDR( '0.0.0.0/0' ) ); + $this->assertEquals( array( 0, 0 ), IP::parseCIDR( '255.255.255.255/0' ) ); // @todo FIXME: Add more tests. # This part test network shifting - $this->assertNet( '192.0.0.0' , '192.0.0.2/24' ); - $this->assertNet( '192.168.5.0', '192.168.5.13/24'); - $this->assertNet( '10.0.0.160' , '10.0.0.161/28' ); - $this->assertNet( '10.0.0.0' , '10.0.0.3/28' ); - $this->assertNet( '10.0.0.0' , '10.0.0.3/30' ); - $this->assertNet( '10.0.0.4' , '10.0.0.4/30' ); + $this->assertNet( '192.0.0.0', '192.0.0.2/24' ); + $this->assertNet( '192.168.5.0', '192.168.5.13/24' ); + $this->assertNet( '10.0.0.160', '10.0.0.161/28' ); + $this->assertNet( '10.0.0.0', '10.0.0.3/28' ); + $this->assertNet( '10.0.0.0', '10.0.0.3/30' ); + $this->assertNet( '10.0.0.4', '10.0.0.4/30' ); $this->assertNet( '172.17.32.0', '172.17.35.48/21' ); - $this->assertNet( '10.128.0.0' , '10.135.0.0/9' ); - $this->assertNet( '134.0.0.0' , '134.0.5.1/8' ); + $this->assertNet( '10.128.0.0', '10.135.0.0/9' ); + $this->assertNet( '134.0.0.0', '134.0.5.1/8' ); } - /** * @covers IP::canonicalize */ public function testIPCanonicalizeOnValidIp() { $this->assertEquals( '192.0.2.152', IP::canonicalize( '192.0.2.152' ), - 'Canonicalization of a valid IP returns it unchanged' ); + 'Canonicalization of a valid IP returns it unchanged' ); } /** @@ -405,27 +458,27 @@ class IPTest extends MediaWikiTestCase { } /** Provider for testIPIsInRange() */ - function provideIPsAndRanges() { - # Format: (expected boolean, address, range, optional message) + public static function provideIPsAndRanges() { + # Format: (expected boolean, address, range, optional message) return array( # IPv4 - array( true , '192.0.2.0' , '192.0.2.0/24', 'Network address' ), - array( true , '192.0.2.77' , '192.0.2.0/24', 'Simple address' ), - array( true , '192.0.2.255' , '192.0.2.0/24', 'Broadcast address' ), + array( true, '192.0.2.0', '192.0.2.0/24', 'Network address' ), + array( true, '192.0.2.77', '192.0.2.0/24', 'Simple address' ), + array( true, '192.0.2.255', '192.0.2.0/24', 'Broadcast address' ), - array( false, '0.0.0.0' , '192.0.2.0/24' ), - array( false, '255.255.255' , '192.0.2.0/24' ), + array( false, '0.0.0.0', '192.0.2.0/24' ), + array( false, '255.255.255', '192.0.2.0/24' ), # IPv6 - array( false, '::1' , '2001:DB8::/32' ), - array( false, '::' , '2001:DB8::/32' ), + array( false, '::1', '2001:DB8::/32' ), + array( false, '::', '2001:DB8::/32' ), array( false, 'FE80::1', '2001:DB8::/32' ), - array( true , '2001:DB8::' , '2001:DB8::/32' ), - array( true , '2001:0DB8::' , '2001:DB8::/32' ), - array( true , '2001:DB8::1' , '2001:DB8::/32' ), - array( true , '2001:0DB8::1', '2001:DB8::/32' ), - array( true , '2001:0DB8:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF', + array( true, '2001:DB8::', '2001:DB8::/32' ), + array( true, '2001:0DB8::', '2001:DB8::/32' ), + array( true, '2001:DB8::1', '2001:DB8::/32' ), + array( true, '2001:0DB8::1', '2001:DB8::/32' ), + array( true, '2001:0DB8:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF', '2001:DB8::/32' ), array( false, '2001:0DB8:F::', '2001:DB8::/96' ), @@ -436,14 +489,14 @@ class IPTest extends MediaWikiTestCase { * Test for IP::splitHostAndPort(). * @dataProvider provideSplitHostAndPort */ - function testSplitHostAndPort( $expected, $input, $description ) { + public function testSplitHostAndPort( $expected, $input, $description ) { $this->assertEquals( $expected, IP::splitHostAndPort( $input ), $description ); } /** * Provider for IP::splitHostAndPort() */ - function provideSplitHostAndPort() { + public static function provideSplitHostAndPort() { return array( array( false, '[', 'Unclosed square bracket' ), array( false, '[::', 'Unclosed square bracket 2' ), @@ -463,7 +516,7 @@ class IPTest extends MediaWikiTestCase { * Test for IP::combineHostAndPort() * @dataProvider provideCombineHostAndPort */ - function testCombineHostAndPort( $expected, $input, $description ) { + public function testCombineHostAndPort( $expected, $input, $description ) { list( $host, $port, $defaultPort ) = $input; $this->assertEquals( $expected, @@ -474,7 +527,7 @@ class IPTest extends MediaWikiTestCase { /** * Provider for IP::combineHostAndPort() */ - function provideCombineHostAndPort() { + public static function provideCombineHostAndPort() { return array( array( '[::1]', array( '::1', 2, 2 ), 'IPv6 default port' ), array( '[::1]:2', array( '::1', 2, 3 ), 'IPv6 non-default port' ), @@ -487,14 +540,14 @@ class IPTest extends MediaWikiTestCase { * Test for IP::sanitizeRange() * @dataProvider provideIPCIDRs */ - function testSanitizeRange( $input, $expected, $description ) { + public function testSanitizeRange( $input, $expected, $description ) { $this->assertEquals( $expected, IP::sanitizeRange( $input ), $description ); } /** * Provider for IP::testSanitizeRange() */ - function provideIPCIDRs() { + public static function provideIPCIDRs() { return array( array( '35.56.31.252/16', '35.56.0.0/16', 'IPv4 range' ), array( '135.16.21.252/24', '135.16.21.0/24', 'IPv4 range' ), @@ -511,14 +564,14 @@ class IPTest extends MediaWikiTestCase { * Test for IP::prettifyIP() * @dataProvider provideIPsToPrettify */ - function testPrettifyIP( $ip, $prettified ) { + public function testPrettifyIP( $ip, $prettified ) { $this->assertEquals( $prettified, IP::prettifyIP( $ip ), "Prettify of $ip" ); } /** * Provider for IP::testPrettifyIP() */ - function provideIPsToPrettify() { + public static function provideIPsToPrettify() { return array( array( '0:0:0:0:0:0:0:0', '::' ), array( '0:0:0::0:0:0', '::' ), diff --git a/tests/phpunit/includes/JsonTest.php b/tests/phpunit/includes/JsonTest.php deleted file mode 100644 index 75dd18d5..00000000 --- a/tests/phpunit/includes/JsonTest.php +++ /dev/null @@ -1,33 +0,0 @@ -<?php - -class JsonTest extends MediaWikiTestCase { - - function testPhpBug46944Test() { - - $this->assertNotEquals( - '\ud840\udc00', - strtolower( FormatJson::encode( "\xf0\xa0\x80\x80" ) ), - 'Test encoding an broken json_encode character (U+20000)' - ); - - - } - - function testDecodeVarTypes() { - - $this->assertInternalType( - 'object', - FormatJson::decode( '{"Name": "Cheeso", "Rank": 7}' ), - 'Default to object' - ); - - $this->assertInternalType( - 'array', - FormatJson::decode( '{"Name": "Cheeso", "Rank": 7}', true ), - 'Optional array' - ); - - } - -} - diff --git a/tests/phpunit/includes/LanguageConverterTest.php b/tests/phpunit/includes/LanguageConverterTest.php index baf28b07..7c2134b9 100644 --- a/tests/phpunit/includes/LanguageConverterTest.php +++ b/tests/phpunit/includes/LanguageConverterTest.php @@ -4,60 +4,65 @@ class LanguageConverterTest extends MediaWikiLangTestCase { protected $lang = null; protected $lc = null; - function setUp() { + protected function setUp() { parent::setUp(); - global $wgMemc, $wgRequest, $wgUser, $wgContLang; - $wgUser = new User; - $wgRequest = new FauxRequest( array() ); - $wgMemc = new EmptyBagOStuff; - $wgContLang = Language::factory( 'tg' ); + $this->setMwGlobals( array( + 'wgContLang' => Language::factory( 'tg' ), + 'wgLanguageCode' => 'tg', + 'wgDefaultLanguageVariant' => false, + 'wgMemc' => new EmptyBagOStuff, + 'wgRequest' => new FauxRequest( array() ), + 'wgUser' => new User, + ) ); + $this->lang = new LanguageToTest(); - $this->lc = new TestConverter( $this->lang, 'tg', - array( 'tg', 'tg-latn' ) ); + $this->lc = new TestConverter( + $this->lang, 'tg', + array( 'tg', 'tg-latn' ) + ); } - function tearDown() { - global $wgMemc; - unset( $wgMemc ); + protected function tearDown() { unset( $this->lc ); unset( $this->lang ); + parent::tearDown(); } - function testGetPreferredVariantDefaults() { + public function testGetPreferredVariantDefaults() { $this->assertEquals( 'tg', $this->lc->getPreferredVariant() ); } - function testGetPreferredVariantHeaders() { + public function testGetPreferredVariantHeaders() { global $wgRequest; $wgRequest->setHeader( 'Accept-Language', 'tg-latn' ); $this->assertEquals( 'tg-latn', $this->lc->getPreferredVariant() ); } - function testGetPreferredVariantHeaderWeight() { + public function testGetPreferredVariantHeaderWeight() { global $wgRequest; $wgRequest->setHeader( 'Accept-Language', 'tg;q=1' ); $this->assertEquals( 'tg', $this->lc->getPreferredVariant() ); } - function testGetPreferredVariantHeaderWeight2() { + public function testGetPreferredVariantHeaderWeight2() { global $wgRequest; $wgRequest->setHeader( 'Accept-Language', 'tg-latn;q=1' ); $this->assertEquals( 'tg-latn', $this->lc->getPreferredVariant() ); } - function testGetPreferredVariantHeaderMulti() { + public function testGetPreferredVariantHeaderMulti() { global $wgRequest; $wgRequest->setHeader( 'Accept-Language', 'en, tg-latn;q=1' ); $this->assertEquals( 'tg-latn', $this->lc->getPreferredVariant() ); } - function testGetPreferredVariantUserOption() { + public function testGetPreferredVariantUserOption() { global $wgUser; $wgUser = new User; @@ -70,8 +75,22 @@ class LanguageConverterTest extends MediaWikiLangTestCase { $this->assertEquals( 'tg-latn', $this->lc->getPreferredVariant() ); } - function testGetPreferredVariantHeaderUserVsUrl() { - global $wgRequest, $wgUser, $wgContLang; + public function testGetPreferredVariantUserOptionForForeignLanguage() { + global $wgContLang, $wgUser; + + $wgContLang = Language::factory( 'en' ); + $wgUser = new User; + $wgUser->load(); // from 'defaults' + $wgUser->mId = 1; + $wgUser->mDataLoaded = true; + $wgUser->mOptionsLoaded = true; + $wgUser->setOption( 'variant-tg', 'tg-latn' ); + + $this->assertEquals( 'tg-latn', $this->lc->getPreferredVariant() ); + } + + public function testGetPreferredVariantHeaderUserVsUrl() { + global $wgContLang, $wgRequest, $wgUser; $wgContLang = Language::factory( 'tg-latn' ); $wgRequest->setVal( 'variant', 'tg' ); @@ -79,20 +98,20 @@ class LanguageConverterTest extends MediaWikiLangTestCase { $wgUser->setId( 1 ); $wgUser->mFrom = 'defaults'; $wgUser->mOptionsLoaded = true; - $wgUser->setOption( 'variant', 'tg-latn' ); // The user's data is ignored - // because the variant is set in the URL. + // The user's data is ignored because the variant is set in the URL. + $wgUser->setOption( 'variant', 'tg-latn' ); $this->assertEquals( 'tg', $this->lc->getPreferredVariant() ); } - function testGetPreferredVariantDefaultLanguageVariant() { + public function testGetPreferredVariantDefaultLanguageVariant() { global $wgDefaultLanguageVariant; $wgDefaultLanguageVariant = 'tg-latn'; $this->assertEquals( 'tg-latn', $this->lc->getPreferredVariant() ); } - function testGetPreferredVariantDefaultLanguageVsUrlVariant() { + public function testGetPreferredVariantDefaultLanguageVsUrlVariant() { global $wgDefaultLanguageVariant, $wgRequest, $wgContLang; $wgContLang = Language::factory( 'tg-latn' ); @@ -115,10 +134,9 @@ class TestConverter extends LanguageConverter { function loadDefaultTables() { $this->mTables = array( 'tg-latn' => new ReplacementArray( $this->table ), - 'tg' => new ReplacementArray() + 'tg' => new ReplacementArray() ); } - } class LanguageToTest extends Language { diff --git a/tests/phpunit/includes/LicensesTest.php b/tests/phpunit/includes/LicensesTest.php index e467f3cd..478a2ffc 100644 --- a/tests/phpunit/includes/LicensesTest.php +++ b/tests/phpunit/includes/LicensesTest.php @@ -2,7 +2,7 @@ class LicensesTest extends MediaWikiTestCase { - function testLicenses() { + public function testLicenses() { $str = " * Free licenses: ** GFDL|Debian disagrees @@ -14,8 +14,8 @@ class LicensesTest extends MediaWikiTestCase { 'section' => 'description', 'id' => 'wpLicense', 'label' => 'A label text', # Note can't test label-message because $wgOut is not defined - 'name' => 'AnotherName', - 'licenses' => $str, + 'name' => 'AnotherName', + 'licenses' => $str, ) ); $this->assertThat( $lc, $this->isInstanceOf( 'Licenses' ) ); } diff --git a/tests/phpunit/includes/LinkerTest.php b/tests/phpunit/includes/LinkerTest.php new file mode 100644 index 00000000..b605f08f --- /dev/null +++ b/tests/phpunit/includes/LinkerTest.php @@ -0,0 +1,71 @@ +<?php + +class LinkerTest extends MediaWikiLangTestCase { + + /** + * @dataProvider provideCasesForUserLink + * @covers Linker::userLink + */ + public function testUserLink( $expected, $userId, $userName, $altUserName = false, $msg = '' ) { + $this->setMwGlobals( array( + 'wgArticlePath' => '/wiki/$1', + 'wgWellFormedXml' => true, + ) ); + + $this->assertEquals( $expected, + Linker::userLink( $userId, $userName, $altUserName, $msg ) + ); + } + + public static function provideCasesForUserLink() { + # Format: + # - expected + # - userid + # - username + # - optional altUserName + # - optional message + return array( + + ### ANONYMOUS USER ######################################## + array( + '<a href="/wiki/Special:Contributions/JohnDoe" title="Special:Contributions/JohnDoe" class="mw-userlink">JohnDoe</a>', + 0, 'JohnDoe', false, + ), + array( + '<a href="/wiki/Special:Contributions/::1" title="Special:Contributions/::1" class="mw-userlink">::1</a>', + 0, '::1', false, + 'Anonymous with pretty IPv6' + ), + array( + '<a href="/wiki/Special:Contributions/0:0:0:0:0:0:0:1" title="Special:Contributions/0:0:0:0:0:0:0:1" class="mw-userlink">::1</a>', + 0, '0:0:0:0:0:0:0:1', false, + 'Anonymous with almost pretty IPv6' + ), + array( + '<a href="/wiki/Special:Contributions/0000:0000:0000:0000:0000:0000:0000:0001" title="Special:Contributions/0000:0000:0000:0000:0000:0000:0000:0001" class="mw-userlink">::1</a>', + 0, '0000:0000:0000:0000:0000:0000:0000:0001', false, + 'Anonymous with full IPv6' + ), + array( + '<a href="/wiki/Special:Contributions/::1" title="Special:Contributions/::1" class="mw-userlink">AlternativeUsername</a>', + 0, '::1', 'AlternativeUsername', + 'Anonymous with pretty IPv6 and an alternative username' + ), + + # IPV4 + array( + '<a href="/wiki/Special:Contributions/127.0.0.1" title="Special:Contributions/127.0.0.1" class="mw-userlink">127.0.0.1</a>', + 0, '127.0.0.1', false, + 'Anonymous with IPv4' + ), + array( + '<a href="/wiki/Special:Contributions/127.0.0.1" title="Special:Contributions/127.0.0.1" class="mw-userlink">AlternativeUsername</a>', + 0, '127.0.0.1', 'AlternativeUsername', + 'Anonymous with IPv4 and an alternative username' + ), + + ### Regular user ########################################## + # TODO! + ); + } +} diff --git a/tests/phpunit/includes/LinksUpdateTest.php b/tests/phpunit/includes/LinksUpdateTest.php index 49462001..5ade250e 100644 --- a/tests/phpunit/includes/LinksUpdateTest.php +++ b/tests/phpunit/includes/LinksUpdateTest.php @@ -7,33 +7,39 @@ */ class LinksUpdateTest extends MediaWikiTestCase { - function __construct( $name = null, array $data = array(), $dataName = '' ) { + function __construct( $name = null, array $data = array(), $dataName = '' ) { parent::__construct( $name, $data, $dataName ); - $this->tablesUsed = array_merge ( $this->tablesUsed, - array( 'interwiki', - - 'page_props', - 'pagelinks', - 'categorylinks', - 'langlinks', - 'externallinks', - 'imagelinks', - 'templatelinks', - 'iwlinks' ) ); + $this->tablesUsed = array_merge( $this->tablesUsed, + array( + 'interwiki', + 'page_props', + 'pagelinks', + 'categorylinks', + 'langlinks', + 'externallinks', + 'imagelinks', + 'templatelinks', + 'iwlinks' + ) + ); } - function setUp() { + protected function setUp() { + parent::setUp(); $dbw = wfGetDB( DB_MASTER ); - $dbw->replace( 'interwiki', - array('iw_prefix'), - array( 'iw_prefix' => 'linksupdatetest', - 'iw_url' => 'http://testing.com/wiki/$1', - 'iw_api' => 'http://testing.com/w/api.php', - 'iw_local' => 0, - 'iw_trans' => 0, - 'iw_wikiid' => 'linksupdatetest', - ) ); + $dbw->replace( + 'interwiki', + array( 'iw_prefix' ), + array( + 'iw_prefix' => 'linksupdatetest', + 'iw_url' => 'http://testing.com/wiki/$1', + 'iw_api' => 'http://testing.com/w/api.php', + 'iw_local' => 0, + 'iw_trans' => 0, + 'iw_wikiid' => 'linksupdatetest', + ) + ); } protected function makeTitleAndParserOutput( $name, $id ) { @@ -54,18 +60,30 @@ class LinksUpdateTest extends MediaWikiTestCase { $po->addLink( Title::newFromText( "linksupdatetest:Foo" ) ); // interwiki link should be ignored $po->addLink( Title::newFromText( "#Foo" ) ); // hash link should be ignored - $this->assertLinksUpdate( $t, $po, 'pagelinks', 'pl_namespace, pl_title', 'pl_from = 111', array( + $update = $this->assertLinksUpdate( $t, $po, 'pagelinks', 'pl_namespace, pl_title', 'pl_from = 111', array( array( NS_MAIN, 'Foo' ), ) ); + $this->assertArrayEquals( array( + Title::makeTitle( NS_MAIN, 'Foo' ), // newFromText doesn't yield the same internal state.... + ), $update->getAddedLinks() ); $po = new ParserOutput(); $po->setTitleText( $t->getPrefixedText() ); $po->addLink( Title::newFromText( "Bar" ) ); + $po->addLink( Title::newFromText( "Talk:Bar" ) ); - $this->assertLinksUpdate( $t, $po, 'pagelinks', 'pl_namespace, pl_title', 'pl_from = 111', array( + $update = $this->assertLinksUpdate( $t, $po, 'pagelinks', 'pl_namespace, pl_title', 'pl_from = 111', array( array( NS_MAIN, 'Bar' ), + array( NS_TALK, 'Bar' ), ) ); + $this->assertArrayEquals( array( + Title::makeTitle( NS_MAIN, 'Bar' ), + Title::makeTitle( NS_TALK, 'Bar' ), + ), $update->getAddedLinks() ); + $this->assertArrayEquals( array( + Title::makeTitle( NS_MAIN, 'Foo' ), + ), $update->getRemovedLinks() ); } public function testUpdate_externallinks() { @@ -79,6 +97,8 @@ class LinksUpdateTest extends MediaWikiTestCase { } public function testUpdate_categorylinks() { + $this->setMwGlobals( 'wgCategoryCollation', 'uppercase' ); + list( $t, $po ) = $this->makeTitleAndParserOutput( "Testing", 111 ); $po->addCategory( "Foo", "FOO" ); @@ -114,7 +134,6 @@ class LinksUpdateTest extends MediaWikiTestCase { $po->addImage( "Foo.png" ); - $this->assertLinksUpdate( $t, $po, 'imagelinks', 'il_to', 'il_from = 111', array( array( 'Foo.png' ), ) ); @@ -123,8 +142,7 @@ class LinksUpdateTest extends MediaWikiTestCase { public function testUpdate_langlinks() { list( $t, $po ) = $this->makeTitleAndParserOutput( "Testing", 111 ); - $po->addLanguageLink( Title::newFromText( "en:Foo" ) ); - + $po->addLanguageLink( Title::newFromText( "en:Foo" )->getFullText() ); $this->assertLinksUpdate( $t, $po, 'langlinks', 'll_lang, ll_title', 'll_from = 111', array( array( 'En', 'Foo' ), @@ -141,14 +159,17 @@ class LinksUpdateTest extends MediaWikiTestCase { ) ); } - #@todo: test recursive, too! + // @todo test recursive, too! - protected function assertLinksUpdate( Title $title, ParserOutput $parserOutput, $table, $fields, $condition, Array $expectedRows ) { + protected function assertLinksUpdate( Title $title, ParserOutput $parserOutput, $table, $fields, $condition, array $expectedRows ) { $update = new LinksUpdate( $title, $parserOutput ); + //NOTE: make sure LinksUpdate does not generate warnings when called inside a transaction. + $update->beginTransaction(); $update->doUpdate(); + $update->commitTransaction(); $this->assertSelect( $table, $fields, $condition, $expectedRows ); + return $update; } } - diff --git a/tests/phpunit/includes/LocalFileTest.php b/tests/phpunit/includes/LocalFileTest.php index 5b26b89c..2501c783 100644 --- a/tests/phpunit/includes/LocalFileTest.php +++ b/tests/phpunit/includes/LocalFileTest.php @@ -6,19 +6,20 @@ */ class LocalFileTest extends MediaWikiTestCase { - function setUp() { - global $wgCapitalLinks; - $wgCapitalLinks = true; + protected function setUp() { + parent::setUp(); + + $this->setMwGlobals( 'wgCapitalLinks', true ); $info = array( - 'name' => 'test', - 'directory' => '/testdir', - 'url' => '/testurl', - 'hashLevels' => 2, + 'name' => 'test', + 'directory' => '/testdir', + 'url' => '/testurl', + 'hashLevels' => 2, 'transformVia404' => false, - 'backend' => new FSFileBackend( array( - 'name' => 'local-backend', + 'backend' => new FSFileBackend( array( + 'name' => 'local-backend', 'lockManager' => 'fsLockManager', 'containerPaths' => array( 'cont1' => "/testdir/local-backend/tempimages/cont1", @@ -34,75 +35,73 @@ class LocalFileTest extends MediaWikiTestCase { $this->file_lc = $this->repo_lc->newFile( 'test!' ); } - function testGetHashPath() { + public function testGetHashPath() { $this->assertEquals( '', $this->file_hl0->getHashPath() ); $this->assertEquals( 'a/a2/', $this->file_hl2->getHashPath() ); $this->assertEquals( 'c/c4/', $this->file_lc->getHashPath() ); } - function testGetRel() { + public function testGetRel() { $this->assertEquals( 'Test!', $this->file_hl0->getRel() ); $this->assertEquals( 'a/a2/Test!', $this->file_hl2->getRel() ); $this->assertEquals( 'c/c4/test!', $this->file_lc->getRel() ); } - function testGetUrlRel() { + public function testGetUrlRel() { $this->assertEquals( 'Test%21', $this->file_hl0->getUrlRel() ); $this->assertEquals( 'a/a2/Test%21', $this->file_hl2->getUrlRel() ); $this->assertEquals( 'c/c4/test%21', $this->file_lc->getUrlRel() ); } - function testGetArchivePath() { + public function testGetArchivePath() { $this->assertEquals( 'mwstore://local-backend/test-public/archive', $this->file_hl0->getArchivePath() ); $this->assertEquals( 'mwstore://local-backend/test-public/archive/a/a2', $this->file_hl2->getArchivePath() ); $this->assertEquals( 'mwstore://local-backend/test-public/archive/!', $this->file_hl0->getArchivePath( '!' ) ); $this->assertEquals( 'mwstore://local-backend/test-public/archive/a/a2/!', $this->file_hl2->getArchivePath( '!' ) ); } - function testGetThumbPath() { + public function testGetThumbPath() { $this->assertEquals( 'mwstore://local-backend/test-thumb/Test!', $this->file_hl0->getThumbPath() ); $this->assertEquals( 'mwstore://local-backend/test-thumb/a/a2/Test!', $this->file_hl2->getThumbPath() ); $this->assertEquals( 'mwstore://local-backend/test-thumb/Test!/x', $this->file_hl0->getThumbPath( 'x' ) ); $this->assertEquals( 'mwstore://local-backend/test-thumb/a/a2/Test!/x', $this->file_hl2->getThumbPath( 'x' ) ); } - function testGetArchiveUrl() { + public function testGetArchiveUrl() { $this->assertEquals( '/testurl/archive', $this->file_hl0->getArchiveUrl() ); $this->assertEquals( '/testurl/archive/a/a2', $this->file_hl2->getArchiveUrl() ); $this->assertEquals( '/testurl/archive/%21', $this->file_hl0->getArchiveUrl( '!' ) ); $this->assertEquals( '/testurl/archive/a/a2/%21', $this->file_hl2->getArchiveUrl( '!' ) ); } - function testGetThumbUrl() { + public function testGetThumbUrl() { $this->assertEquals( '/testurl/thumb/Test%21', $this->file_hl0->getThumbUrl() ); $this->assertEquals( '/testurl/thumb/a/a2/Test%21', $this->file_hl2->getThumbUrl() ); $this->assertEquals( '/testurl/thumb/Test%21/x', $this->file_hl0->getThumbUrl( 'x' ) ); $this->assertEquals( '/testurl/thumb/a/a2/Test%21/x', $this->file_hl2->getThumbUrl( 'x' ) ); } - function testGetArchiveVirtualUrl() { + public function testGetArchiveVirtualUrl() { $this->assertEquals( 'mwrepo://test/public/archive', $this->file_hl0->getArchiveVirtualUrl() ); $this->assertEquals( 'mwrepo://test/public/archive/a/a2', $this->file_hl2->getArchiveVirtualUrl() ); $this->assertEquals( 'mwrepo://test/public/archive/%21', $this->file_hl0->getArchiveVirtualUrl( '!' ) ); $this->assertEquals( 'mwrepo://test/public/archive/a/a2/%21', $this->file_hl2->getArchiveVirtualUrl( '!' ) ); } - function testGetThumbVirtualUrl() { + public function testGetThumbVirtualUrl() { $this->assertEquals( 'mwrepo://test/thumb/Test%21', $this->file_hl0->getThumbVirtualUrl() ); $this->assertEquals( 'mwrepo://test/thumb/a/a2/Test%21', $this->file_hl2->getThumbVirtualUrl() ); $this->assertEquals( 'mwrepo://test/thumb/Test%21/%21', $this->file_hl0->getThumbVirtualUrl( '!' ) ); $this->assertEquals( 'mwrepo://test/thumb/a/a2/Test%21/%21', $this->file_hl2->getThumbVirtualUrl( '!' ) ); } - function testGetUrl() { + public function testGetUrl() { $this->assertEquals( '/testurl/Test%21', $this->file_hl0->getUrl() ); $this->assertEquals( '/testurl/a/a2/Test%21', $this->file_hl2->getUrl() ); } - function testWfLocalFile() { + public function testWfLocalFile() { $file = wfLocalFile( "File:Some_file_that_probably_doesn't exist.png" ); $this->assertThat( $file, $this->isInstanceOf( 'LocalFile' ), 'wfLocalFile() returns LocalFile for valid Titles' ); } } - - diff --git a/tests/phpunit/includes/LocalisationCacheTest.php b/tests/phpunit/includes/LocalisationCacheTest.php index 356db87c..b34847aa 100644 --- a/tests/phpunit/includes/LocalisationCacheTest.php +++ b/tests/phpunit/includes/LocalisationCacheTest.php @@ -5,15 +5,15 @@ class LocalisationCacheTest extends MediaWikiTestCase { $cache = Language::getLocalisationCache(); $this->assertEquals( - $cache->getItem( 'ru', 'pluralRules' ), - $cache->getItem( 'os', 'pluralRules' ), - 'os plural rules (undefined) fallback to ru (defined)' + $cache->getItem( 'ar', 'pluralRules' ), + $cache->getItem( 'arz', 'pluralRules' ), + 'arz plural rules (undefined) fallback to ar (defined)' ); $this->assertEquals( - $cache->getItem( 'ru', 'compiledPluralRules' ), - $cache->getItem( 'os', 'compiledPluralRules' ), - 'os compiled plural rules (undefined) fallback to ru (defined)' + $cache->getItem( 'ar', 'compiledPluralRules' ), + $cache->getItem( 'arz', 'compiledPluralRules' ), + 'arz compiled plural rules (undefined) fallback to ar (defined)' ); $this->assertNotEquals( diff --git a/tests/phpunit/includes/MWExceptionHandlerTest.php b/tests/phpunit/includes/MWExceptionHandlerTest.php new file mode 100644 index 00000000..987dfa83 --- /dev/null +++ b/tests/phpunit/includes/MWExceptionHandlerTest.php @@ -0,0 +1,73 @@ +<?php +/** + * Tests for includes/Exception.php. + * + * @author Antoine Musso + * @copyright Copyright © 2013, Antoine Musso + * @copyright Copyright © 2013, Wikimedia Foundation Inc. + * @file + */ + +class MWExceptionHandlerTest extends MediaWikiTestCase { + + /** + * @covers MWExceptionHandler::getRedactedTrace + */ + function testGetRedactedTrace() { + try { + $array = array( 'a', 'b' ); + $object = new StdClass(); + self::helperThrowAnException( $array, $object ); + } catch (Exception $e) { + } + + # Make sure our strack trace contains an array and an object passed to + # some function in the stacktrace. Else, we can not assert the trace + # redaction achieved its job. + $trace = $e->getTrace(); + $hasObject = false; + $hasArray = false; + foreach ( $trace as $frame ) { + if ( ! isset( $frame['args'] ) ) { + continue; + } + foreach ( $frame['args'] as $arg ) { + $hasObject = $hasObject || is_object( $arg ); + $hasArray = $hasArray || is_array( $arg ); + } + + if( $hasObject && $hasArray ) { + break; + } + } + $this->assertTrue( $hasObject, + "The stacktrace must have a function having an object has parameter" ); + $this->assertTrue( $hasArray, + "The stacktrace must have a function having an array has parameter" ); + + # Now we redact the trace.. and make sure no function arguments are + # arrays or objects. + $redacted = MWExceptionHandler::getRedactedTrace( $e ); + + foreach ( $redacted as $frame ) { + if ( ! isset( $frame['args'] ) ) { + continue; + } + foreach ( $frame['args'] as $arg ) { + $this->assertNotInternalType( 'array', $arg); + $this->assertNotInternalType( 'object', $arg); + } + } + } + + /** + * Helper function for testExpandArgumentsInCall + * + * Pass it an object and an array :-) + * + * @throws Exception + */ + protected static function helperThrowAnException( $a, $b ) { + throw new Exception(); + } +} diff --git a/tests/phpunit/includes/MWFunctionTest.php b/tests/phpunit/includes/MWFunctionTest.php index ed5e7602..d86f2c9b 100644 --- a/tests/phpunit/includes/MWFunctionTest.php +++ b/tests/phpunit/includes/MWFunctionTest.php @@ -1,85 +1,29 @@ <?php class MWFunctionTest extends MediaWikiTestCase { - - function testCallUserFuncWorkarounds() { - - $this->assertEquals( - call_user_func( array( 'MWFunctionTest', 'someMethod' ) ), - MWFunction::call( 'MWFunctionTest::someMethod' ) - ); - $this->assertEquals( - call_user_func( array( 'MWFunctionTest', 'someMethod' ), 'foo', 'bar', 'baz' ), - MWFunction::call( 'MWFunctionTest::someMethod', 'foo', 'bar', 'baz' ) - ); - - - - $this->assertEquals( - call_user_func_array( array( 'MWFunctionTest', 'someMethod' ), array() ), - MWFunction::callArray( 'MWFunctionTest::someMethod', array() ) - ); - $this->assertEquals( - call_user_func_array( array( 'MWFunctionTest', 'someMethod' ), array( 'foo', 'bar', 'baz' ) ), - MWFunction::callArray( 'MWFunctionTest::someMethod', array( 'foo', 'bar', 'baz' ) ) - ); - - } - - function testNewObjFunction() { - + public function testNewObjFunction() { $arg1 = 'Foo'; $arg2 = 'Bar'; $arg3 = array( 'Baz' ); $arg4 = new ExampleObject; - + $args = array( $arg1, $arg2, $arg3, $arg4 ); - + $newObject = new MWBlankClass( $arg1, $arg2, $arg3, $arg4 ); - - $this->assertEquals( - MWFunction::newObj( 'MWBlankClass', $args )->args, + $this->assertEquals( + MWFunction::newObj( 'MWBlankClass', $args )->args, $newObject->args ); - - $this->assertEquals( - MWFunction::newObj( 'MWBlankClass', $args, true )->args, - $newObject->args, - 'Works even with PHP version < 5.1.3' - ); - - } - - /** - * @expectedException MWException - */ - function testCallingParentFails() { - - MWFunction::call( 'parent::foo' ); - } - - /** - * @expectedException MWException - */ - function testCallingSelfFails() { - - MWFunction::call( 'self::foo' ); } - - public static function someMethod() { - return func_get_args(); - } - } class MWBlankClass { - + public $args = array(); - + function __construct( $arg1, $arg2, $arg3, $arg4 ) { $this->args = array( $arg1, $arg2, $arg3, $arg4 ); } - } class ExampleObject { diff --git a/tests/phpunit/includes/MWNamespaceTest.php b/tests/phpunit/includes/MWNamespaceTest.php index 3b05d675..10e9db61 100644 --- a/tests/phpunit/includes/MWNamespaceTest.php +++ b/tests/phpunit/includes/MWNamespaceTest.php @@ -11,21 +11,22 @@ * */ class MWNamespaceTest extends MediaWikiTestCase { - /** - * Sets up the fixture, for example, opens a network connection. - * This method is called before a test is executed. - */ protected function setUp() { - } + parent::setUp(); - /** - * Tears down the fixture, for example, closes a network connection. - * This method is called after a test is executed. - */ - protected function tearDown() { + $this->setMwGlobals( array( + 'wgContentNamespaces' => array( NS_MAIN ), + 'wgNamespacesWithSubpages' => array( + NS_TALK => true, + NS_USER => true, + NS_USER_TALK => true, + ), + 'wgCapitalLinks' => true, + 'wgCapitalLinkOverrides' => array(), + 'wgNonincludableNamespaces' => array(), + ) ); } - #### START OF TESTS ######################################################### /** @@ -41,18 +42,18 @@ class MWNamespaceTest extends MediaWikiTestCase { */ public function testIsSubject() { // Special namespaces - $this->assertIsSubject( NS_MEDIA ); + $this->assertIsSubject( NS_MEDIA ); $this->assertIsSubject( NS_SPECIAL ); // Subject pages $this->assertIsSubject( NS_MAIN ); $this->assertIsSubject( NS_USER ); - $this->assertIsSubject( 100 ); # user defined + $this->assertIsSubject( 100 ); # user defined // Talk pages - $this->assertIsNotSubject( NS_TALK ); + $this->assertIsNotSubject( NS_TALK ); $this->assertIsNotSubject( NS_USER_TALK ); - $this->assertIsNotSubject( 101 ); # user defined + $this->assertIsNotSubject( 101 ); # user defined } /** @@ -61,18 +62,18 @@ class MWNamespaceTest extends MediaWikiTestCase { */ public function testIsTalk() { // Special namespaces - $this->assertIsNotTalk( NS_MEDIA ); + $this->assertIsNotTalk( NS_MEDIA ); $this->assertIsNotTalk( NS_SPECIAL ); // Subject pages - $this->assertIsNotTalk( NS_MAIN ); - $this->assertIsNotTalk( NS_USER ); - $this->assertIsNotTalk( 100 ); # user defined + $this->assertIsNotTalk( NS_MAIN ); + $this->assertIsNotTalk( NS_USER ); + $this->assertIsNotTalk( 100 ); # user defined // Talk pages - $this->assertIsTalk( NS_TALK ); + $this->assertIsTalk( NS_TALK ); $this->assertIsTalk( NS_USER_TALK ); - $this->assertIsTalk( 101 ); # user defined + $this->assertIsTalk( 101 ); # user defined } /** @@ -124,7 +125,6 @@ class MWNamespaceTest extends MediaWikiTestCase { public function testGetAssociated() { $this->assertEquals( NS_TALK, MWNamespace::getAssociated( NS_MAIN ) ); $this->assertEquals( NS_MAIN, MWNamespace::getAssociated( NS_TALK ) ); - } ### Exceptions with getAssociated() @@ -134,7 +134,7 @@ class MWNamespaceTest extends MediaWikiTestCase { * @expectedException MWException */ public function testGetAssociatedExceptionsForNsMedia() { - $this->assertNull( MWNamespace::getAssociated( NS_MEDIA ) ); + $this->assertNull( MWNamespace::getAssociated( NS_MEDIA ) ); } /** @@ -147,14 +147,14 @@ class MWNamespaceTest extends MediaWikiTestCase { /** * @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.' ); } -*/ + */ /** * Test MWNamespace::equals @@ -188,7 +188,7 @@ class MWNamespaceTest extends MediaWikiTestCase { $this->assertSameSubject( NS_USER, NS_USER_TALK ); $this->assertDifferentSubject( NS_PROJECT, NS_TEMPLATE ); - $this->assertDifferentSubject( NS_SPECIAL, NS_MAIN ); + $this->assertDifferentSubject( NS_SPECIAL, NS_MAIN ); } public function testSpecialAndMediaAreDifferentSubjects() { @@ -200,62 +200,63 @@ class MWNamespaceTest extends MediaWikiTestCase { NS_SPECIAL, NS_MEDIA, "NS_SPECIAL and NS_MEDIA are different subject namespaces" ); - } /** * @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.' - ); - } -*/ + /* + 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->assertCanNotTalk( NS_MEDIA ); + $this->assertCanNotTalk( NS_MEDIA ); $this->assertCanNotTalk( NS_SPECIAL ); - $this->assertCanTalk( NS_MAIN ); - $this->assertCanTalk( NS_TALK ); - $this->assertCanTalk( NS_USER ); + $this->assertCanTalk( NS_MAIN ); + $this->assertCanTalk( NS_TALK ); + $this->assertCanTalk( NS_USER ); $this->assertCanTalk( NS_USER_TALK ); // User defined namespaces @@ -268,82 +269,41 @@ class MWNamespaceTest extends MediaWikiTestCase { public function testIsContent() { // NS_MAIN is a content namespace per DefaultSettings.php // and per function definition. - $this->assertIsContent( NS_MAIN ); - - global $wgContentNamespaces; - - $saved = $wgContentNamespaces; - $wgContentNamespaces[] = NS_MAIN; $this->assertIsContent( NS_MAIN ); // Other namespaces which are not expected to be content - if ( isset( $wgContentNamespaces[NS_MEDIA] ) ) { - unset( $wgContentNamespaces[NS_MEDIA] ); - } - $this->assertIsNotContent( NS_MEDIA ); - if ( isset( $wgContentNamespaces[NS_SPECIAL] ) ) { - unset( $wgContentNamespaces[NS_SPECIAL] ); - } + $this->assertIsNotContent( NS_MEDIA ); $this->assertIsNotContent( NS_SPECIAL ); - - if ( isset( $wgContentNamespaces[NS_TALK] ) ) { - unset( $wgContentNamespaces[NS_TALK] ); - } $this->assertIsNotContent( NS_TALK ); - - if ( isset( $wgContentNamespaces[NS_USER] ) ) { - unset( $wgContentNamespaces[NS_USER] ); - } $this->assertIsNotContent( NS_USER ); - - if ( isset( $wgContentNamespaces[NS_CATEGORY] ) ) { - unset( $wgContentNamespaces[NS_CATEGORY] ); - } $this->assertIsNotContent( NS_CATEGORY ); - - if ( isset( $wgContentNamespaces[100] ) ) { - unset( $wgContentNamespaces[100] ); - } $this->assertIsNotContent( 100 ); - - $wgContentNamespaces = $saved; } /** * 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->assertIsContent( NS_MAIN ); + public function testIsContentAdvanced() { + global $wgContentNamespaces; - // Tests that user defined namespace #252 is not content: + // Test that user defined namespace #252 is not content $this->assertIsNotContent( 252 ); - # @todo FIXME: Is global saving really required for PHPUnit? // Bless namespace # 252 as a content namespace - global $wgContentNamespaces; - $savedGlobal = $wgContentNamespaces; $wgContentNamespaces[] = 252; + $this->assertIsContent( 252 ); // Makes sure NS_MAIN was not impacted $this->assertIsContent( NS_MAIN ); - - // Restore global - $wgContentNamespaces = $savedGlobal; - - // Verify namespaces after global restauration - $this->assertIsContent( NS_MAIN ); - $this->assertIsNotContent( 252 ); } public function testIsWatchable() { // Specials namespaces are not watchable - $this->assertIsNotWatchable( NS_MEDIA ); + $this->assertIsNotWatchable( NS_MEDIA ); $this->assertIsNotWatchable( NS_SPECIAL ); // Core defined namespaces are watchables @@ -356,68 +316,60 @@ class MWNamespaceTest extends MediaWikiTestCase { } public function testHasSubpages() { + global $wgNamespacesWithSubpages; + // Special namespaces: - $this->assertHasNotSubpages( NS_MEDIA ); + $this->assertHasNotSubpages( NS_MEDIA ); $this->assertHasNotSubpages( NS_SPECIAL ); - // namespaces without subpages - # save up global - global $wgNamespacesWithSubpages; - $saved = null; - if( array_key_exists( NS_MAIN, $wgNamespacesWithSubpages ) ) { - $saved = $wgNamespacesWithSubpages[NS_MAIN]; - unset( $wgNamespacesWithSubpages[NS_MAIN] ); - } - + // Namespaces without subpages $this->assertHasNotSubpages( NS_MAIN ); $wgNamespacesWithSubpages[NS_MAIN] = true; $this->assertHasSubpages( NS_MAIN ); + $wgNamespacesWithSubpages[NS_MAIN] = false; $this->assertHasNotSubpages( NS_MAIN ); - # restore global - if( $saved !== null ) { - $wgNamespacesWithSubpages[NS_MAIN] = $saved; - } - // Some namespaces with subpages - $this->assertHasSubpages( NS_TALK ); - $this->assertHasSubpages( NS_USER ); + $this->assertHasSubpages( NS_TALK ); + $this->assertHasSubpages( NS_USER ); $this->assertHasSubpages( NS_USER_TALK ); } /** */ public function testGetContentNamespaces() { + global $wgContentNamespaces; + $this->assertEquals( array( NS_MAIN ), - MWNamespace::getcontentNamespaces(), + MWNamespace::getContentNamespaces(), '$wgContentNamespaces is an array with only NS_MAIN by default' ); - global $wgContentNamespaces; - - $saved = $wgContentNamespaces; # test !is_array( $wgcontentNamespaces ) $wgContentNamespaces = ''; - $this->assertEquals( NS_MAIN, MWNamespace::getcontentNamespaces() ); + $this->assertEquals( array( NS_MAIN ), MWNamespace::getContentNamespaces() ); + $wgContentNamespaces = false; - $this->assertEquals( NS_MAIN, MWNamespace::getcontentNamespaces() ); + $this->assertEquals( array( NS_MAIN ), MWNamespace::getContentNamespaces() ); + $wgContentNamespaces = null; - $this->assertEquals( NS_MAIN, MWNamespace::getcontentNamespaces() ); + $this->assertEquals( array( NS_MAIN ), MWNamespace::getContentNamespaces() ); + $wgContentNamespaces = 5; - $this->assertEquals( NS_MAIN, MWNamespace::getcontentNamespaces() ); + $this->assertEquals( array( NS_MAIN ), MWNamespace::getContentNamespaces() ); # test $wgContentNamespaces === array() $wgContentNamespaces = array(); - $this->assertEquals( NS_MAIN, MWNamespace::getcontentNamespaces() ); + $this->assertEquals( array( NS_MAIN ), MWNamespace::getContentNamespaces() ); # test !in_array( NS_MAIN, $wgContentNamespaces ) $wgContentNamespaces = array( NS_USER, NS_CATEGORY ); $this->assertEquals( array( NS_MAIN, NS_USER, NS_CATEGORY ), - MWNamespace::getcontentNamespaces(), + MWNamespace::getContentNamespaces(), 'NS_MAIN is forced in $wgContentNamespaces even if unwanted' ); @@ -425,23 +377,21 @@ class MWNamespaceTest extends MediaWikiTestCase { $wgContentNamespaces = array( NS_MAIN ); $this->assertEquals( array( NS_MAIN ), - MWNamespace::getcontentNamespaces() + MWNamespace::getContentNamespaces() ); $wgContentNamespaces = array( NS_MAIN, NS_USER, NS_CATEGORY ); $this->assertEquals( array( NS_MAIN, NS_USER, NS_CATEGORY ), - MWNamespace::getcontentNamespaces() + MWNamespace::getContentNamespaces() ); - - $wgContentNamespaces = $saved; } /** */ public function testGetSubjectNamespaces() { $subjectsNS = MWNamespace::getSubjectNamespaces(); - $this->assertContains( NS_MAIN, $subjectsNS, + $this->assertContains( NS_MAIN, $subjectsNS, "Talk namespaces should have NS_MAIN" ); $this->assertNotContains( NS_TALK, $subjectsNS, "Talk namespaces should have NS_TALK" ); @@ -456,7 +406,7 @@ class MWNamespaceTest extends MediaWikiTestCase { */ public function testGetTalkNamespaces() { $talkNS = MWNamespace::getTalkNamespaces(); - $this->assertContains( NS_TALK, $talkNS, + $this->assertContains( NS_TALK, $talkNS, "Subject namespaces should have NS_TALK" ); $this->assertNotContains( NS_MAIN, $talkNS, "Subject namespaces should not have NS_MAIN" ); @@ -475,18 +425,18 @@ class MWNamespaceTest extends MediaWikiTestCase { // NS_MEDIA and NS_FILE are treated the same $this->assertEquals( MWNamespace::isCapitalized( NS_MEDIA ), - MWNamespace::isCapitalized( NS_FILE ), + MWNamespace::isCapitalized( NS_FILE ), 'NS_MEDIA and NS_FILE have same capitalization rendering' ); // Boths are capitalized by default $this->assertIsCapitalized( NS_MEDIA ); - $this->assertIsCapitalized( NS_FILE ); + $this->assertIsCapitalized( NS_FILE ); // Always capitalized namespaces // @see MWNamespace::$alwaysCapitalizedNamespaces - $this->assertIsCapitalized( NS_SPECIAL ); - $this->assertIsCapitalized( NS_USER ); + $this->assertIsCapitalized( NS_SPECIAL ); + $this->assertIsCapitalized( NS_USER ); $this->assertIsCapitalized( NS_MEDIAWIKI ); } @@ -498,30 +448,26 @@ class MWNamespaceTest extends MediaWikiTestCase { * $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->assertIsCapitalized( NS_PROJECT ); + $this->assertIsCapitalized( NS_PROJECT ); $this->assertIsCapitalized( NS_PROJECT_TALK ); $wgCapitalLinks = false; + // hardcoded namespaces (see above function) are still capitalized: - $this->assertIsCapitalized( NS_SPECIAL ); - $this->assertIsCapitalized( NS_USER ); + $this->assertIsCapitalized( NS_SPECIAL ); + $this->assertIsCapitalized( NS_USER ); $this->assertIsCapitalized( NS_MEDIAWIKI ); + // setting is correctly applied - $this->assertIsNotCapitalized( NS_PROJECT ); + $this->assertIsNotCapitalized( NS_PROJECT ); $this->assertIsNotCapitalized( NS_PROJECT_TALK ); - - // reset global state: - $wgCapitalLinks = $savedGlobal; } /** @@ -532,81 +478,78 @@ class MWNamespaceTest extends MediaWikiTestCase { */ public function testIsCapitalizedWithWgCapitalLinkOverrides() { global $wgCapitalLinkOverrides; - // Save the global to easily reset to MediaWiki default settings - $savedGlobal = $wgCapitalLinkOverrides; // Test default settings - $this->assertIsCapitalized( NS_PROJECT ); + $this->assertIsCapitalized( NS_PROJECT ); $this->assertIsCapitalized( NS_PROJECT_TALK ); + // hardcoded namespaces (see above function) are capitalized: - $this->assertIsCapitalized( NS_SPECIAL ); - $this->assertIsCapitalized( NS_USER ); + $this->assertIsCapitalized( NS_SPECIAL ); + $this->assertIsCapitalized( NS_USER ); $this->assertIsCapitalized( NS_MEDIAWIKI ); // Hardcoded namespaces remains capitalized - $wgCapitalLinkOverrides[NS_SPECIAL] = false; - $wgCapitalLinkOverrides[NS_USER] = false; + $wgCapitalLinkOverrides[NS_SPECIAL] = false; + $wgCapitalLinkOverrides[NS_USER] = false; $wgCapitalLinkOverrides[NS_MEDIAWIKI] = false; - $this->assertIsCapitalized( NS_SPECIAL ); - $this->assertIsCapitalized( NS_USER ); + + $this->assertIsCapitalized( NS_SPECIAL ); + $this->assertIsCapitalized( NS_USER ); $this->assertIsCapitalized( NS_MEDIAWIKI ); - $wgCapitalLinkOverrides = $savedGlobal; $wgCapitalLinkOverrides[NS_PROJECT] = false; $this->assertIsNotCapitalized( NS_PROJECT ); - $wgCapitalLinkOverrides[NS_PROJECT] = true ; - $this->assertIsCapitalized( NS_PROJECT ); - unset( $wgCapitalLinkOverrides[NS_PROJECT] ); + + $wgCapitalLinkOverrides[NS_PROJECT] = true; $this->assertIsCapitalized( NS_PROJECT ); - // reset global state: - $wgCapitalLinkOverrides = $savedGlobal; + unset( $wgCapitalLinkOverrides[NS_PROJECT] ); + $this->assertIsCapitalized( NS_PROJECT ); } public function testHasGenderDistinction() { // Namespaces with gender distinctions - $this->assertTrue( MWNamespace::hasGenderDistinction( NS_USER ) ); + $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_MEDIA ) ); $this->assertFalse( MWNamespace::hasGenderDistinction( NS_SPECIAL ) ); - $this->assertFalse( MWNamespace::hasGenderDistinction( NS_MAIN ) ); - $this->assertFalse( MWNamespace::hasGenderDistinction( NS_TALK ) ); - + $this->assertFalse( MWNamespace::hasGenderDistinction( NS_MAIN ) ); + $this->assertFalse( MWNamespace::hasGenderDistinction( NS_TALK ) ); } public function testIsNonincludable() { global $wgNonincludableNamespaces; + $wgNonincludableNamespaces = array( NS_USER ); $this->assertTrue( MWNamespace::isNonincludable( NS_USER ) ); - $this->assertFalse( MWNamespace::isNonincludable( NS_TEMPLATE ) ); } ####### HELPERS ########################################################### function __call( $method, $args ) { // Call the real method if it exists - if( method_exists($this, $method ) ) { + if ( method_exists( $this, $method ) ) { return $this->$method( $args ); } - if( preg_match( '/^assert(Has|Is|Can)(Not|)(Subject|Talk|Watchable|Content|Subpages|Capitalized)$/', $method, $m ) ) { + if ( preg_match( '/^assert(Has|Is|Can)(Not|)(Subject|Talk|Watchable|Content|Subpages|Capitalized)$/', $method, $m ) ) { # Interprets arguments: - $ns = $args[0]; - $msg = isset($args[1]) ? $args[1] : " dummy message"; + $ns = $args[0]; + $msg = isset( $args[1] ) ? $args[1] : " dummy message"; # Forge the namespace constant name: - if( $ns === 0 ) { + if ( $ns === 0 ) { $ns_name = "NS_MAIN"; } else { - $ns_name = "NS_" . strtoupper( MWNamespace::getCanonicalName( $ns ) ); + $ns_name = "NS_" . strtoupper( MWNamespace::getCanonicalName( $ns ) ); } # ... and the MWNamespace method name $nsMethod = strtolower( $m[1] ) . $m[3]; - $expect = ($m[2] === ''); + $expect = ( $m[2] === '' ); $expect_name = $expect ? 'TRUE' : 'FALSE'; return $this->assertEquals( $expect, @@ -621,8 +564,8 @@ class MWNamespaceTest extends MediaWikiTestCase { function assertSameSubject( $ns1, $ns2, $msg = '' ) { $this->assertTrue( MWNamespace::subjectEquals( $ns1, $ns2, $msg ) ); } + function assertDifferentSubject( $ns1, $ns2, $msg = '' ) { $this->assertFalse( MWNamespace::subjectEquals( $ns1, $ns2, $msg ) ); } } - diff --git a/tests/phpunit/includes/MessageTest.php b/tests/phpunit/includes/MessageTest.php index 20181fd4..1e18f975 100644 --- a/tests/phpunit/includes/MessageTest.php +++ b/tests/phpunit/includes/MessageTest.php @@ -1,8 +1,16 @@ <?php class MessageTest extends MediaWikiLangTestCase { + protected function setUp() { + parent::setUp(); - function testExists() { + $this->setMwGlobals( array( + 'wgLang' => Language::factory( 'en' ), + 'wgForceUIMsgAsContentMsg' => array(), + ) ); + } + + public function testExists() { $this->assertTrue( wfMessage( 'mainpage' )->exists() ); $this->assertTrue( wfMessage( 'mainpage' )->params( array() )->exists() ); $this->assertTrue( wfMessage( 'mainpage' )->rawParams( 'foo', 123 )->exists() ); @@ -11,7 +19,7 @@ class MessageTest extends MediaWikiLangTestCase { $this->assertFalse( wfMessage( 'i-dont-exist-evar' )->rawParams( 'foo', 123 )->exists() ); } - function testKey() { + public function testKey() { $this->assertInstanceOf( 'Message', wfMessage( 'mainpage' ) ); $this->assertInstanceOf( 'Message', wfMessage( 'i-dont-exist-evar' ) ); $this->assertEquals( 'Main Page', wfMessage( 'mainpage' )->text() ); @@ -20,45 +28,103 @@ class MessageTest extends MediaWikiLangTestCase { $this->assertEquals( '<i-dont-exist-evar>', wfMessage( 'i-dont-exist-evar' )->escaped() ); } - function testInLanguage() { + public function testInLanguage() { $this->assertEquals( 'Main Page', wfMessage( 'mainpage' )->inLanguage( 'en' )->text() ); $this->assertEquals( 'Заглавная страница', wfMessage( 'mainpage' )->inLanguage( 'ru' )->text() ); $this->assertEquals( 'Main Page', wfMessage( 'mainpage' )->inLanguage( Language::factory( 'en' ) )->text() ); $this->assertEquals( 'Заглавная страница', wfMessage( 'mainpage' )->inLanguage( Language::factory( 'ru' ) )->text() ); } - function testMessageParams() { + public function testMessageParams() { $this->assertEquals( 'Return to $1.', wfMessage( 'returnto' )->text() ); $this->assertEquals( 'Return to $1.', wfMessage( 'returnto', array() )->text() ); $this->assertEquals( 'You have foo (bar).', wfMessage( 'youhavenewmessages', 'foo', 'bar' )->text() ); $this->assertEquals( 'You have foo (bar).', wfMessage( 'youhavenewmessages', array( 'foo', 'bar' ) )->text() ); } - function testMessageParamSubstitution() { + public function testMessageParamSubstitution() { $this->assertEquals( '(Заглавная страница)', wfMessage( 'parentheses', 'Заглавная страница' )->plain() ); $this->assertEquals( '(Заглавная страница $1)', wfMessage( 'parentheses', 'Заглавная страница $1' )->plain() ); $this->assertEquals( '(Заглавная страница)', wfMessage( 'parentheses' )->rawParams( 'Заглавная страница' )->plain() ); $this->assertEquals( '(Заглавная страница $1)', wfMessage( 'parentheses' )->rawParams( 'Заглавная страница $1' )->plain() ); } - function testInContentLanguage() { - global $wgLang, $wgForceUIMsgAsContentMsg; - $oldLang = $wgLang; - $wgLang = Language::factory( 'fr' ); + public function testDeliciouslyManyParams() { + $msg = new RawMessage( '$1$2$3$4$5$6$7$8$9$10$11$12' ); + // One less than above has placeholders + $params = array( 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k' ); + $this->assertEquals( 'abcdefghijka2', $msg->params( $params )->plain(), 'Params > 9 are replaced correctly' ); + } + + /** + * FIXME: This should not need database, but Language#formatExpiry does (bug 55912) + * @group Database + */ + public function testMessageParamTypes() { + $lang = Language::factory( 'en' ); + + $msg = new RawMessage( '$1' ); + $this->assertEquals( + $lang->formatNum( 123456.789 ), + $msg->inLanguage( $lang )->numParams( 123456.789 )->plain(), + 'numParams is handled correctly' + ); + + $msg = new RawMessage( '$1' ); + $this->assertEquals( + $lang->formatDuration( 1234 ), + $msg->inLanguage( $lang )->durationParams( 1234 )->plain(), + 'durationParams is handled correctly' + ); + + $msg = new RawMessage( '$1' ); + $this->assertEquals( + $lang->formatExpiry( wfTimestampNow() ), + $msg->inLanguage( $lang )->expiryParams( wfTimestampNow() )->plain(), + 'expiryParams is handled correctly' + ); + + $msg = new RawMessage( '$1' ); + $this->assertEquals( + $lang->formatTimePeriod( 1234 ), + $msg->inLanguage( $lang )->timeperiodParams( 1234 )->plain(), + 'timeperiodParams is handled correctly' + ); + + $msg = new RawMessage( '$1' ); + $this->assertEquals( + $lang->formatSize( 123456 ), + $msg->inLanguage( $lang )->sizeParams( 123456 )->plain(), + 'sizeParams is handled correctly' + ); + + $msg = new RawMessage( '$1' ); + $this->assertEquals( + $lang->formatBitrate( 123456 ), + $msg->inLanguage( $lang )->bitrateParams( 123456 )->plain(), + 'bitrateParams is handled correctly' + ); + } + + public function testInContentLanguageDisabled() { + $this->setMwGlobals( 'wgLang', Language::factory( 'fr' ) ); $this->assertEquals( 'Main Page', wfMessage( 'mainpage' )->inContentLanguage()->plain(), 'ForceUIMsg disabled' ); - $wgForceUIMsgAsContentMsg['testInContentLanguage'] = 'mainpage'; - $this->assertEquals( 'Accueil', wfMessage( 'mainpage' )->inContentLanguage()->plain(), 'ForceUIMsg enabled' ); + } - /* Restore globals */ - $wgLang = $oldLang; - unset( $wgForceUIMsgAsContentMsg['testInContentLanguage'] ); + public function testInContentLanguageEnabled() { + $this->setMwGlobals( array( + 'wgLang' => Language::factory( 'fr' ), + 'wgForceUIMsgAsContentMsg' => array( 'mainpage' ), + ) ); + + $this->assertEquals( 'Accueil', wfMessage( 'mainpage' )->inContentLanguage()->plain(), 'ForceUIMsg enabled' ); } /** * @expectedException MWException */ - function testInLanguageThrows() { + public function testInLanguageThrows() { wfMessage( 'foo' )->inLanguage( 123 ); } } diff --git a/tests/phpunit/includes/OutputPageTest.php b/tests/phpunit/includes/OutputPageTest.php new file mode 100644 index 00000000..56bb0fce --- /dev/null +++ b/tests/phpunit/includes/OutputPageTest.php @@ -0,0 +1,133 @@ +<?php + +/** + * + * @author Matthew Flaschen + * + * @group Output + * + */ +class OutputPageTest extends MediaWikiTestCase { + const SCREEN_MEDIA_QUERY = 'screen and (min-width: 982px)'; + const SCREEN_ONLY_MEDIA_QUERY = 'only screen and (min-width: 982px)'; + + /** + * Tests a particular case of transformCssMedia, using the given input, globals, + * expected return, and message + * + * Asserts that $expectedReturn is returned. + * + * options['printableQuery'] - value of query string for printable, or omitted for none + * options['handheldQuery'] - value of query string for handheld, or omitted for none + * options['media'] - passed into the method under the same name + * options['expectedReturn'] - expected return value + * options['message'] - PHPUnit message for assertion + * + * @param array $args key-value array of arguments as shown above + */ + protected function assertTransformCssMediaCase( $args ) { + $queryData = array(); + if ( isset( $args['printableQuery'] ) ) { + $queryData['printable'] = $args['printableQuery']; + } + + if ( isset( $args['handheldQuery'] ) ) { + $queryData['handheld'] = $args['handheldQuery']; + } + + $fauxRequest = new FauxRequest( $queryData, false ); + $this->setMWGlobals( array( + 'wgRequest' => $fauxRequest, + ) ); + + $actualReturn = OutputPage::transformCssMedia( $args['media'] ); + $this->assertSame( $args['expectedReturn'], $actualReturn, $args['message'] ); + } + + /** + * Tests print requests + */ + public function testPrintRequests() { + $this->assertTransformCssMediaCase( array( + 'printableQuery' => '1', + 'media' => 'screen', + 'expectedReturn' => null, + 'message' => 'On printable request, screen returns null' + ) ); + + $this->assertTransformCssMediaCase( array( + 'printableQuery' => '1', + 'media' => self::SCREEN_MEDIA_QUERY, + 'expectedReturn' => null, + 'message' => 'On printable request, screen media query returns null' + ) ); + + $this->assertTransformCssMediaCase( array( + 'printableQuery' => '1', + 'media' => self::SCREEN_ONLY_MEDIA_QUERY, + 'expectedReturn' => null, + 'message' => 'On printable request, screen media query with only returns null' + ) ); + + $this->assertTransformCssMediaCase( array( + 'printableQuery' => '1', + 'media' => 'print', + 'expectedReturn' => '', + 'message' => 'On printable request, media print returns empty string' + ) ); + } + + /** + * Tests screen requests, without either query parameter set + */ + public function testScreenRequests() { + $this->assertTransformCssMediaCase( array( + 'media' => 'screen', + 'expectedReturn' => 'screen', + 'message' => 'On screen request, screen media type is preserved' + ) ); + + $this->assertTransformCssMediaCase( array( + 'media' => 'handheld', + 'expectedReturn' => 'handheld', + 'message' => 'On screen request, handheld media type is preserved' + ) ); + + $this->assertTransformCssMediaCase( array( + 'media' => self::SCREEN_MEDIA_QUERY, + 'expectedReturn' => self::SCREEN_MEDIA_QUERY, + 'message' => 'On screen request, screen media query is preserved.' + ) ); + + $this->assertTransformCssMediaCase( array( + 'media' => self::SCREEN_ONLY_MEDIA_QUERY, + 'expectedReturn' => self::SCREEN_ONLY_MEDIA_QUERY, + 'message' => 'On screen request, screen media query with only is preserved.' + ) ); + + $this->assertTransformCssMediaCase( array( + 'media' => 'print', + 'expectedReturn' => 'print', + 'message' => 'On screen request, print media type is preserved' + ) ); + } + + /** + * Tests handheld behavior + */ + public function testHandheld() { + $this->assertTransformCssMediaCase( array( + 'handheldQuery' => '1', + 'media' => 'handheld', + 'expectedReturn' => '', + 'message' => 'On request with handheld querystring and media is handheld, returns empty string' + ) ); + + $this->assertTransformCssMediaCase( array( + 'handheldQuery' => '1', + 'media' => 'screen', + 'expectedReturn' => null, + 'message' => 'On request with handheld querystring and media is screen, returns null' + ) ); + } +} diff --git a/tests/phpunit/includes/ParserOptionsTest.php b/tests/phpunit/includes/ParserOptionsTest.php deleted file mode 100644 index 59c955fe..00000000 --- a/tests/phpunit/includes/ParserOptionsTest.php +++ /dev/null @@ -1,35 +0,0 @@ -<?php - -class ParserOptionsTest extends MediaWikiTestCase { - - private $popts; - private $pcache; - - function setUp() { - global $wgContLang, $wgUser, $wgLanguageCode; - $wgContLang = Language::factory( $wgLanguageCode ); - $this->popts = ParserOptions::newFromUserAndLang( $wgUser, $wgContLang ); - $this->pcache = ParserCache::singleton(); - } - - function tearDown() { - parent::tearDown(); - } - - /** - * ParserOptions::optionsHash was not giving consistent results when $wgUseDynamicDates was set - * @group Database - */ - function testGetParserCacheKeyWithDynamicDates() { - global $wgUseDynamicDates; - $wgUseDynamicDates = true; - - $title = Title::newFromText( "Some test article" ); - $page = WikiPage::factory( $title ); - - $pcacheKeyBefore = $this->pcache->getKey( $page, $this->popts ); - $this->assertNotNull( $this->popts->getDateFormat() ); - $pcacheKeyAfter = $this->pcache->getKey( $page, $this->popts ); - $this->assertEquals( $pcacheKeyBefore, $pcacheKeyAfter ); - } -} diff --git a/tests/phpunit/includes/PathRouterTest.php b/tests/phpunit/includes/PathRouterTest.php index f6274584..adfb215a 100644 --- a/tests/phpunit/includes/PathRouterTest.php +++ b/tests/phpunit/includes/PathRouterTest.php @@ -1,13 +1,21 @@ <?php /** - * Tests for the PathRouter parsing + * Tests for the PathRouter parsing. + * + * @todo Add covers tags. */ class PathRouterTest extends MediaWikiTestCase { - public function setUp() { + /** + * @var PathRouter + */ + protected $basicRouter; + + protected function setUp() { + parent::setUp(); $router = new PathRouter; - $router->add("/wiki/$1"); + $router->add( "/wiki/$1" ); $this->basicRouter = $router; } @@ -24,17 +32,17 @@ class PathRouterTest extends MediaWikiTestCase { */ public function testLoose() { $router = new PathRouter; - $router->add("/"); # Should be the same as "/$1" + $router->add( "/" ); # Should be the same as "/$1" $matches = $router->parse( "/Foo" ); $this->assertEquals( $matches, array( 'title' => "Foo" ) ); $router = new PathRouter; - $router->add("/wiki"); # Should be the same as /wiki/$1 + $router->add( "/wiki" ); # Should be the same as /wiki/$1 $matches = $router->parse( "/wiki/Foo" ); $this->assertEquals( $matches, array( 'title' => "Foo" ) ); $router = new PathRouter; - $router->add("/wiki/"); # Should be the same as /wiki/$1 + $router->add( "/wiki/" ); # Should be the same as /wiki/$1 $matches = $router->parse( "/wiki/Foo" ); $this->assertEquals( $matches, array( 'title' => "Foo" ) ); } @@ -44,16 +52,16 @@ class PathRouterTest extends MediaWikiTestCase { */ public function testOrder() { $router = new PathRouter; - $router->add("/$1"); - $router->add("/a/$1"); - $router->add("/b/$1"); + $router->add( "/$1" ); + $router->add( "/a/$1" ); + $router->add( "/b/$1" ); $matches = $router->parse( "/a/Foo" ); $this->assertEquals( $matches, array( 'title' => "Foo" ) ); $router = new PathRouter; - $router->add("/b/$1"); - $router->add("/a/$1"); - $router->add("/$1"); + $router->add( "/b/$1" ); + $router->add( "/a/$1" ); + $router->add( "/$1" ); $matches = $router->parse( "/a/Foo" ); $this->assertEquals( $matches, array( 'title' => "Foo" ) ); } @@ -150,18 +158,20 @@ class PathRouterTest extends MediaWikiTestCase { $router->add( array( 'qwerty' => "/qwerty/$1" ), array( 'qwerty' => '$key' ) ); $router->add( "/$2/$1", array( 'restricted-to-y' => '$2' ), array( '$2' => 'y' ) ); - foreach( array( - "/Foo" => array( 'title' => "Foo" ), - "/Bar" => array( 'ping' => 'pong' ), - "/Baz" => array( 'marco' => 'polo' ), - "/asdf-foo" => array( 'title' => "qwerty-foo" ), - "/qwerty-bar" => array( 'title' => "asdf-bar" ), - "/a/Foo" => array( 'title' => "Foo" ), - "/asdf/Foo" => array( 'title' => "Foo" ), - "/qwerty/Foo" => array( 'title' => "Foo", 'qwerty' => 'qwerty' ), - "/baz/Foo" => array( 'title' => "Foo", 'unrestricted' => 'baz' ), - "/y/Foo" => array( 'title' => "Foo", 'restricted-to-y' => 'y' ), - ) as $path => $result ) { + foreach ( + array( + '/Foo' => array( 'title' => 'Foo' ), + '/Bar' => array( 'ping' => 'pong' ), + '/Baz' => array( 'marco' => 'polo' ), + '/asdf-foo' => array( 'title' => 'qwerty-foo' ), + '/qwerty-bar' => array( 'title' => 'asdf-bar' ), + '/a/Foo' => array( 'title' => 'Foo' ), + '/asdf/Foo' => array( 'title' => 'Foo' ), + '/qwerty/Foo' => array( 'title' => 'Foo', 'qwerty' => 'qwerty' ), + '/baz/Foo' => array( 'title' => 'Foo', 'unrestricted' => 'baz' ), + '/y/Foo' => array( 'title' => 'Foo', 'restricted-to-y' => 'y' ), + ) as $path => $result + ) { $this->assertEquals( $router->parse( $path ), $result ); } } @@ -182,7 +192,7 @@ class PathRouterTest extends MediaWikiTestCase { $this->assertEquals( $matches, array( 'title' => "Title_With Space" ) ); } - public function dataRegexpChars() { + public static function provideRegexpChars() { return array( array( "$" ), array( "$1" ), @@ -193,7 +203,7 @@ class PathRouterTest extends MediaWikiTestCase { /** * Make sure the router doesn't break on special characters like $ used in regexp replacements - * @dataProvider dataRegexpChars + * @dataProvider provideRegexpChars */ public function testRegexpChars( $char ) { $matches = $this->basicRouter->parse( "/wiki/$char" ); @@ -250,5 +260,4 @@ class PathRouterTest extends MediaWikiTestCase { $matches = $router->parse( "/wiki/Foo" ); $this->assertEquals( $matches, array( 'title' => 'bar%20$1' ) ); } - } diff --git a/tests/phpunit/includes/PreferencesTest.php b/tests/phpunit/includes/PreferencesTest.php index 0e123177..3dec2da0 100644 --- a/tests/phpunit/includes/PreferencesTest.php +++ b/tests/phpunit/includes/PreferencesTest.php @@ -1,13 +1,20 @@ <?php +/** + * @group Database + */ class PreferencesTest extends MediaWikiTestCase { - /** Array of User objects */ + /** + * @var User[] + */ private $prefUsers; + /** + * @var RequestContext + */ private $context; - function __construct() { + public function __construct() { parent::__construct(); - global $wgEnableEmail; $this->prefUsers['noemail'] = new User; @@ -15,46 +22,54 @@ class PreferencesTest extends MediaWikiTestCase { $this->prefUsers['notauth'] ->setEmail( 'noauth@example.org' ); - $this->prefUsers['auth'] = new User; + $this->prefUsers['auth'] = new User; $this->prefUsers['auth'] ->setEmail( 'noauth@example.org' ); $this->prefUsers['auth'] ->setEmailAuthenticationTimestamp( 1330946623 ); $this->context = new RequestContext; - $this->context->setTitle( Title::newFromText('PreferencesTest') ); + $this->context->setTitle( Title::newFromText( 'PreferencesTest' ) ); + } - //some tests depends on email setting - $wgEnableEmail = true; + protected function setUp() { + parent::setUp(); + + $this->setMwGlobals( array( + 'wgEnableEmail' => true, + 'wgEmailAuthentication' => true, + ) ); } /** * Placeholder to verify bug 34302 * @covers Preferences::profilePreferences */ - function testEmailFieldsWhenUserHasNoEmail() { + public function testEmailFieldsWhenUserHasNoEmail() { $prefs = $this->prefsFor( 'noemail' ); $this->assertArrayHasKey( 'cssclass', $prefs['emailaddress'] ); $this->assertEquals( 'mw-email-none', $prefs['emailaddress']['cssclass'] ); } + /** * Placeholder to verify bug 34302 * @covers Preferences::profilePreferences */ - function testEmailFieldsWhenUserEmailNotAuthenticated() { + public function testEmailFieldsWhenUserEmailNotAuthenticated() { $prefs = $this->prefsFor( 'notauth' ); $this->assertArrayHasKey( 'cssclass', $prefs['emailaddress'] ); $this->assertEquals( 'mw-email-not-authenticated', $prefs['emailaddress']['cssclass'] ); } + /** * Placeholder to verify bug 34302 * @covers Preferences::profilePreferences */ - function testEmailFieldsWhenUserEmailIsAuthenticated() { + public function testEmailFieldsWhenUserEmailIsAuthenticated() { $prefs = $this->prefsFor( 'auth' ); $this->assertArrayHasKey( 'cssclass', $prefs['emailaddress'] @@ -63,13 +78,14 @@ class PreferencesTest extends MediaWikiTestCase { } /** Helper */ - function prefsFor( $user_key ) { + protected function prefsFor( $user_key ) { $preferences = array(); Preferences::profilePreferences( $this->prefUsers[$user_key] , $this->context , $preferences ); + return $preferences; } } diff --git a/tests/phpunit/includes/Providers.php b/tests/phpunit/includes/Providers.php deleted file mode 100644 index f451f8a0..00000000 --- a/tests/phpunit/includes/Providers.php +++ /dev/null @@ -1,44 +0,0 @@ -<?php -/** - * Generic providers for the MediaWiki PHPUnit test suite - * - * @author Antoine Musso - * @copyright Copyright © 2011, Antoine Musso - * @file - */ - -/** */ -class MediaWikiProvide { - - /* provide an array of numbers from 1 up to @param $num */ - private static function createProviderUpTo( $num ) { - $ret = array(); - for( $i=1; $i<=$num;$i++ ) { - $ret[] = array( $i ); - } - return $ret; - } - - /* array of months numbers (as an integer) */ - public static function Months() { - return self::createProviderUpTo( 12 ); - } - - /* array of days numbers (as an integer) */ - public static function Days() { - return self::createProviderUpTo( 31 ); - } - - public static function DaysMonths() { - $ret = array(); - - $months = self::Months(); - $days = self::Days(); - foreach( $months as $month) { - foreach( $days as $day ) { - $ret[] = array( $day[0], $month[0] ); - } - } - return $ret; - } -} diff --git a/tests/phpunit/includes/RecentChangeTest.php b/tests/phpunit/includes/RecentChangeTest.php index fbf271cc..cfa3e777 100644 --- a/tests/phpunit/includes/RecentChangeTest.php +++ b/tests/phpunit/includes/RecentChangeTest.php @@ -9,12 +9,12 @@ class RecentChangeTest extends MediaWikiTestCase { protected $user_comment; protected $context; - function __construct() { + public function __construct() { parent::__construct(); - $this->title = Title::newFromText( 'SomeTitle' ); + $this->title = Title::newFromText( 'SomeTitle' ); $this->target = Title::newFromText( 'TestTarget' ); - $this->user = User::newFromName( 'UserName' ); + $this->user = User::newFromName( 'UserName' ); $this->user_comment = '<User comment about action>'; $this->context = RequestContext::newExtraneousContext( $this->title ); @@ -56,17 +56,19 @@ class RecentChangeTest extends MediaWikiTestCase { /** * @covers LogFormatter::getIRCActionText */ - function testIrcMsgForLogTypeBlock() { + public function testIrcMsgForLogTypeBlock() { + $sep = $this->context->msg( 'colon-separator' )->text(); + # block/block $this->assertIRCComment( - $this->context->msg( 'blocklogentry', 'SomeTitle' )->plain() . ': ' . $this->user_comment, + $this->context->msg( 'blocklogentry', 'SomeTitle' )->plain() . $sep . $this->user_comment, 'block', 'block', array(), $this->user_comment ); # block/unblock $this->assertIRCComment( - $this->context->msg( 'unblocklogentry', 'SomeTitle' )->plain() . ': ' . $this->user_comment, + $this->context->msg( 'unblocklogentry', 'SomeTitle' )->plain() . $sep . $this->user_comment, 'block', 'unblock', array(), $this->user_comment @@ -76,10 +78,12 @@ class RecentChangeTest extends MediaWikiTestCase { /** * @covers LogFormatter::getIRCActionText */ - function testIrcMsgForLogTypeDelete() { + public function testIrcMsgForLogTypeDelete() { + $sep = $this->context->msg( 'colon-separator' )->text(); + # delete/delete $this->assertIRCComment( - $this->context->msg( 'deletedarticle', 'SomeTitle' )->plain() . ': ' . $this->user_comment, + $this->context->msg( 'deletedarticle', 'SomeTitle' )->plain() . $sep . $this->user_comment, 'delete', 'delete', array(), $this->user_comment @@ -87,7 +91,7 @@ class RecentChangeTest extends MediaWikiTestCase { # delete/restore $this->assertIRCComment( - $this->context->msg( 'undeletedarticle', 'SomeTitle' )->plain() . ': ' . $this->user_comment, + $this->context->msg( 'undeletedarticle', 'SomeTitle' )->plain() . $sep . $this->user_comment, 'delete', 'restore', array(), $this->user_comment @@ -97,7 +101,7 @@ class RecentChangeTest extends MediaWikiTestCase { /** * @covers LogFormatter::getIRCActionText */ - function testIrcMsgForLogTypeNewusers() { + public function testIrcMsgForLogTypeNewusers() { $this->assertIRCComment( 'New user account', 'newusers', 'newusers', @@ -123,15 +127,16 @@ class RecentChangeTest extends MediaWikiTestCase { /** * @covers LogFormatter::getIRCActionText */ - function testIrcMsgForLogTypeMove() { + public function testIrcMsgForLogTypeMove() { $move_params = array( - '4::target' => $this->target->getPrefixedText(), + '4::target' => $this->target->getPrefixedText(), '5::noredir' => 0, ); + $sep = $this->context->msg( 'colon-separator' )->text(); # move/move $this->assertIRCComment( - $this->context->msg( '1movedto2', 'SomeTitle', 'TestTarget' )->plain() . ': ' . $this->user_comment, + $this->context->msg( '1movedto2', 'SomeTitle', 'TestTarget' )->plain() . $sep . $this->user_comment, 'move', 'move', $move_params, $this->user_comment @@ -139,7 +144,7 @@ class RecentChangeTest extends MediaWikiTestCase { # move/move_redir $this->assertIRCComment( - $this->context->msg( '1movedto2_redir', 'SomeTitle', 'TestTarget' )->plain() . ': ' . $this->user_comment, + $this->context->msg( '1movedto2_redir', 'SomeTitle', 'TestTarget' )->plain() . $sep . $this->user_comment, 'move', 'move_redir', $move_params, $this->user_comment @@ -149,15 +154,15 @@ class RecentChangeTest extends MediaWikiTestCase { /** * @covers LogFormatter::getIRCActionText */ - function testIrcMsgForLogTypePatrol() { + public function testIrcMsgForLogTypePatrol() { # patrol/patrol $this->assertIRCComment( $this->context->msg( 'patrol-log-line', 'revision 777', '[[SomeTitle]]', '' )->plain(), 'patrol', 'patrol', array( - '4::curid' => '777', + '4::curid' => '777', '5::previd' => '666', - '6::auto' => 0, + '6::auto' => 0, ) ); } @@ -165,14 +170,15 @@ class RecentChangeTest extends MediaWikiTestCase { /** * @covers LogFormatter::getIRCActionText */ - function testIrcMsgForLogTypeProtect() { + public function testIrcMsgForLogTypeProtect() { $protectParams = array( '[edit=sysop] (indefinite) [move=sysop] (indefinite)' ); + $sep = $this->context->msg( 'colon-separator' )->text(); # protect/protect $this->assertIRCComment( - $this->context->msg( 'protectedarticle', 'SomeTitle ' . $protectParams[0] )->plain() . ': ' . $this->user_comment, + $this->context->msg( 'protectedarticle', 'SomeTitle ' . $protectParams[0] )->plain() . $sep . $this->user_comment, 'protect', 'protect', $protectParams, $this->user_comment @@ -180,7 +186,7 @@ class RecentChangeTest extends MediaWikiTestCase { # protect/unprotect $this->assertIRCComment( - $this->context->msg( 'unprotectedarticle', 'SomeTitle' )->plain() . ': ' . $this->user_comment, + $this->context->msg( 'unprotectedarticle', 'SomeTitle' )->plain() . $sep . $this->user_comment, 'protect', 'unprotect', array(), $this->user_comment @@ -188,7 +194,7 @@ class RecentChangeTest extends MediaWikiTestCase { # protect/modify $this->assertIRCComment( - $this->context->msg( 'modifiedarticleprotection', 'SomeTitle ' . $protectParams[0] )->plain() . ': ' . $this->user_comment, + $this->context->msg( 'modifiedarticleprotection', 'SomeTitle ' . $protectParams[0] )->plain() . $sep . $this->user_comment, 'protect', 'modify', $protectParams, $this->user_comment @@ -198,10 +204,12 @@ class RecentChangeTest extends MediaWikiTestCase { /** * @covers LogFormatter::getIRCActionText */ - function testIrcMsgForLogTypeUpload() { + public function testIrcMsgForLogTypeUpload() { + $sep = $this->context->msg( 'colon-separator' )->text(); + # upload/upload $this->assertIRCComment( - $this->context->msg( 'uploadedimage', 'SomeTitle' )->plain() . ': ' . $this->user_comment, + $this->context->msg( 'uploadedimage', 'SomeTitle' )->plain() . $sep . $this->user_comment, 'upload', 'upload', array(), $this->user_comment @@ -209,7 +217,7 @@ class RecentChangeTest extends MediaWikiTestCase { # upload/overwrite $this->assertIRCComment( - $this->context->msg( 'overwroteimage', 'SomeTitle' )->plain() . ': ' . $this->user_comment, + $this->context->msg( 'overwroteimage', 'SomeTitle' )->plain() . $sep . $this->user_comment, 'upload', 'overwrite', array(), $this->user_comment @@ -217,37 +225,37 @@ class RecentChangeTest extends MediaWikiTestCase { } /** - * @todo: Emulate these edits somehow and extract + * @todo Emulate these edits somehow and extract * raw edit summary from RecentChange object * -- - - function testIrcMsgForBlankingAES() { + */ + /* + public function testIrcMsgForBlankingAES() { // $this->context->msg( 'autosumm-blank', .. ); } - function testIrcMsgForReplaceAES() { + public function testIrcMsgForReplaceAES() { // $this->context->msg( 'autosumm-replace', .. ); } - function testIrcMsgForRollbackAES() { + public function testIrcMsgForRollbackAES() { // $this->context->msg( 'revertpage', .. ); } - function testIrcMsgForUndoAES() { + public function testIrcMsgForUndoAES() { // $this->context->msg( 'undo-summary', .. ); } - - * -- - */ + */ /** * @param $expected String Expected IRC text without colors codes * @param $type String Log type (move, delete, suppress, patrol ...) * @param $action String A log type action + * @param $params * @param $comment String (optional) A comment for the log action * @param $msg String (optional) A message for PHPUnit :-) */ - function assertIRCComment( $expected, $type, $action, $params, $comment = null, $msg = '' ) { + protected function assertIRCComment( $expected, $type, $action, $params, $comment = null, $msg = '' ) { $logEntry = new ManualLogEntry( $type, $action ); $logEntry->setPerformer( $this->user ); @@ -260,8 +268,8 @@ class RecentChangeTest extends MediaWikiTestCase { $formatter = LogFormatter::newFromEntry( $logEntry ); $formatter->setContext( $this->context ); - // Apply the same transformation as done in RecentChange::getIRCLine for rc_comment - $ircRcComment = RecentChange::cleanupForIRC( $formatter->getIRCActionComment() ); + // Apply the same transformation as done in IRCColourfulRCFeedFormatter::getLine for rc_comment + $ircRcComment = IRCColourfulRCFeedFormatter::cleanupForIRC( $formatter->getIRCActionComment() ); $this->assertEquals( $expected, @@ -269,5 +277,4 @@ class RecentChangeTest extends MediaWikiTestCase { $msg ); } - } diff --git a/tests/phpunit/includes/RequestContextTest.php b/tests/phpunit/includes/RequestContextTest.php new file mode 100644 index 00000000..1776b5d5 --- /dev/null +++ b/tests/phpunit/includes/RequestContextTest.php @@ -0,0 +1,73 @@ +<?php + +/** + * @group Database + */ +class RequestContextTest extends MediaWikiTestCase { + + /** + * Test the relationship between title and wikipage in RequestContext + * @covers RequestContext::getWikiPage + * @covers RequestContext::getTitle + */ + public function testWikiPageTitle() { + $context = new RequestContext(); + + $curTitle = Title::newFromText( "A" ); + $context->setTitle( $curTitle ); + $this->assertTrue( $curTitle->equals( $context->getWikiPage()->getTitle() ), + "When a title is first set WikiPage should be created on-demand for that title." ); + + $curTitle = Title::newFromText( "B" ); + $context->setWikiPage( WikiPage::factory( $curTitle ) ); + $this->assertTrue( $curTitle->equals( $context->getTitle() ), + "Title must be updated when a new WikiPage is provided." ); + + $curTitle = Title::newFromText( "C" ); + $context->setTitle( $curTitle ); + $this->assertTrue( $curTitle->equals( $context->getWikiPage()->getTitle() ), + "When a title is updated the WikiPage should be purged and recreated on-demand with the new title." ); + } + + /** + * @covers RequestContext::importScopedSession + */ + public function testImportScopedSession() { + $context = RequestContext::getMain(); + + $oInfo = $context->exportSession(); + $this->assertEquals( '127.0.0.1', $oInfo['ip'], "Correct initial IP address." ); + $this->assertEquals( 0, $oInfo['userId'], "Correct initial user ID." ); + + $user = User::newFromName( 'UnitTestContextUser' ); + $user->addToDatabase(); + + $sinfo = array( + 'sessionId' => 'd612ee607c87e749ef14da4983a702cd', + 'userId' => $user->getId(), + 'ip' => '192.0.2.0', + 'headers' => array( 'USER-AGENT' => 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:18.0) Gecko/20100101 Firefox/18.0' ) + ); + $sc = RequestContext::importScopedSession( $sinfo ); // load new context + + $info = $context->exportSession(); + $this->assertEquals( $sinfo['ip'], $info['ip'], "Correct IP address." ); + $this->assertEquals( $sinfo['headers'], $info['headers'], "Correct headers." ); + $this->assertEquals( $sinfo['sessionId'], $info['sessionId'], "Correct session ID." ); + $this->assertEquals( $sinfo['userId'], $info['userId'], "Correct user ID." ); + $this->assertEquals( $sinfo['ip'], $context->getRequest()->getIP(), "Correct context IP address." ); + $this->assertEquals( $sinfo['headers'], $context->getRequest()->getAllHeaders(), "Correct context headers." ); + $this->assertEquals( $sinfo['sessionId'], session_id(), "Correct context session ID." ); + $this->assertEquals( true, $context->getUser()->isLoggedIn(), "Correct context user." ); + $this->assertEquals( $sinfo['userId'], $context->getUser()->getId(), "Correct context user ID." ); + $this->assertEquals( 'UnitTestContextUser', $context->getUser()->getName(), "Correct context user name." ); + + unset( $sc ); // restore previous context + + $info = $context->exportSession(); + $this->assertEquals( $oInfo['ip'], $info['ip'], "Correct initial IP address." ); + $this->assertEquals( $oInfo['headers'], $info['headers'], "Correct initial headers." ); + $this->assertEquals( $oInfo['sessionId'], $info['sessionId'], "Correct initial session ID." ); + $this->assertEquals( $oInfo['userId'], $info['userId'], "Correct initial user ID." ); + } +} diff --git a/tests/phpunit/includes/ResourceLoaderTest.php b/tests/phpunit/includes/ResourceLoaderTest.php index ab704839..ca8b2b6e 100644 --- a/tests/phpunit/includes/ResourceLoaderTest.php +++ b/tests/phpunit/includes/ResourceLoaderTest.php @@ -4,6 +4,32 @@ class ResourceLoaderTest extends MediaWikiTestCase { protected static $resourceLoaderRegisterModulesHook; + protected function setUp() { + parent::setUp(); + + // $wgResourceLoaderLESSFunctions, $wgResourceLoaderLESSImportPaths; $wgResourceLoaderLESSVars; + + $this->setMwGlobals( array( + 'wgResourceLoaderLESSFunctions' => array( + 'test-sum' => function ( $frame, $less ) { + $sum = 0; + foreach ( $frame[2] as $arg ) { + $sum += (int)$arg[1]; + } + return $sum; + }, + ), + 'wgResourceLoaderLESSImportPaths' => array( + dirname( __DIR__ ) . '/data/less/common', + ), + 'wgResourceLoaderLESSVars' => array( + 'foo' => '2px', + 'Foo' => '#eeeeee', + 'bar' => 5, + ), + ) ); + } + /* Hook Methods */ /** @@ -11,16 +37,25 @@ class ResourceLoaderTest extends MediaWikiTestCase { */ public static function resourceLoaderRegisterModules( &$resourceLoader ) { self::$resourceLoaderRegisterModulesHook = true; + return true; } /* Provider Methods */ - public function provideValidModules() { + public static function provideValidModules() { return array( array( 'TEST.validModule1', new ResourceLoaderTestModule() ), ); } + public static function provideResourceLoaderContext() { + $resourceLoader = new ResourceLoader(); + $request = new FauxRequest(); + return array( + array( new ResourceLoaderContext( $resourceLoader, $request ) ), + ); + } + /* Test Methods */ /** @@ -31,6 +66,7 @@ class ResourceLoaderTest extends MediaWikiTestCase { self::$resourceLoaderRegisterModulesHook = false; $resourceLoader = new ResourceLoader(); $this->assertTrue( self::$resourceLoaderRegisterModulesHook ); + return $resourceLoader; } @@ -48,7 +84,22 @@ class ResourceLoaderTest extends MediaWikiTestCase { } /** + * @dataProvider provideResourceLoaderContext + * @covers ResourceLoaderFileModule::compileLessFile + */ + public function testLessFileCompilation( $context ) { + $basePath = __DIR__ . '/../data/less/module'; + $module = new ResourceLoaderFileModule( array( + 'localBasePath' => $basePath, + 'styles' => array( 'styles.less' ), + ) ); + $styles = $module->getStyles( $context ); + $this->assertStringEqualsFile( $basePath . '/styles.css', $styles['all'] ); + } + + /** * @dataProvider providePackedModules + * @covers ResourceLoader::makePackedModulesString */ public function testMakePackedModulesString( $desc, $modules, $packed ) { $this->assertEquals( $packed, ResourceLoader::makePackedModulesString( $modules ), $desc ); @@ -56,12 +107,13 @@ class ResourceLoaderTest extends MediaWikiTestCase { /** * @dataProvider providePackedModules + * @covers ResourceLoaderContext::expandModuleNames */ public function testexpandModuleNames( $desc, $modules, $packed ) { $this->assertEquals( $modules, ResourceLoaderContext::expandModuleNames( $packed ), $desc ); } - public function providePackedModules() { + public static function providePackedModules() { return array( array( 'Example from makePackedModulesString doc comment', @@ -77,14 +129,20 @@ class ResourceLoaderTest extends MediaWikiTestCase { 'Regression fixed in r88706 with dotless names', array( 'foo', 'bar', 'baz' ), 'foo,bar,baz', - ) + ), + array( + 'Prefixless modules after a prefixed module', + array( 'single.module', 'foobar', 'foobaz' ), + 'single.module|foobar,foobaz', + ), ); } } /* Stubs */ -class ResourceLoaderTestModule extends ResourceLoaderModule { } +class ResourceLoaderTestModule extends ResourceLoaderModule { +} /* Hooks */ global $wgHooks; diff --git a/tests/phpunit/includes/RevisionStorageTest.php b/tests/phpunit/includes/RevisionStorageTest.php index 8a7facec..e17c7b0f 100644 --- a/tests/phpunit/includes/RevisionStorageTest.php +++ b/tests/phpunit/includes/RevisionStorageTest.php @@ -3,6 +3,7 @@ /** * Test class for Revision storage. * + * @group ContentHandler * @group Database * ^--- important, causes temporary tables to be used instead of the real database * @@ -11,41 +12,81 @@ */ class RevisionStorageTest extends MediaWikiTestCase { + /** + * @var WikiPage $the_page + */ var $the_page; - function __construct( $name = null, array $data = array(), $dataName = '' ) { + function __construct( $name = null, array $data = array(), $dataName = '' ) { parent::__construct( $name, $data, $dataName ); $this->tablesUsed = array_merge( $this->tablesUsed, - array( 'page', - 'revision', - 'text', - - 'recentchanges', - 'logging', - - 'page_props', - 'pagelinks', - 'categorylinks', - 'langlinks', - 'externallinks', - 'imagelinks', - 'templatelinks', - 'iwlinks' ) ); + array( 'page', + 'revision', + 'text', + + 'recentchanges', + 'logging', + + 'page_props', + 'pagelinks', + 'categorylinks', + 'langlinks', + 'externallinks', + 'imagelinks', + 'templatelinks', + 'iwlinks' ) ); } - public function setUp() { + protected function setUp() { + global $wgExtraNamespaces, $wgNamespaceContentModels, $wgContentHandlers, $wgContLang; + + parent::setUp(); + + $wgExtraNamespaces[12312] = 'Dummy'; + $wgExtraNamespaces[12313] = 'Dummy_talk'; + + $wgNamespaceContentModels[12312] = 'DUMMY'; + $wgContentHandlers['DUMMY'] = 'DummyContentHandlerForTesting'; + + MWNamespace::getCanonicalNamespaces( true ); # reset namespace cache + $wgContLang->resetNamespaces(); # reset namespace cache if ( !$this->the_page ) { - $this->the_page = $this->createPage( 'RevisionStorageTest_the_page', "just a dummy page" ); + $this->the_page = $this->createPage( 'RevisionStorageTest_the_page', "just a dummy page", CONTENT_MODEL_WIKITEXT ); } } + public function tearDown() { + global $wgExtraNamespaces, $wgNamespaceContentModels, $wgContentHandlers, $wgContLang; + + parent::tearDown(); + + unset( $wgExtraNamespaces[12312] ); + unset( $wgExtraNamespaces[12313] ); + + unset( $wgNamespaceContentModels[12312] ); + unset( $wgContentHandlers['DUMMY'] ); + + MWNamespace::getCanonicalNamespaces( true ); # reset namespace cache + $wgContLang->resetNamespaces(); # reset namespace cache + } + protected function makeRevision( $props = null ) { - if ( $props === null ) $props = array(); + if ( $props === null ) { + $props = array(); + } + + if ( !isset( $props['content'] ) && !isset( $props['text'] ) ) { + $props['text'] = 'Lorem Ipsum'; + } - if ( !isset( $props['content'] ) && !isset( $props['text'] ) ) $props['text'] = 'Lorem Ipsum'; - if ( !isset( $props['comment'] ) ) $props['comment'] = 'just a test'; - if ( !isset( $props['page'] ) ) $props['page'] = $this->the_page->getId(); + if ( !isset( $props['comment'] ) ) { + $props['comment'] = 'just a test'; + } + + if ( !isset( $props['page'] ) ) { + $props['page'] = $this->the_page->getId(); + } $rev = new Revision( $props ); @@ -56,14 +97,27 @@ class RevisionStorageTest extends MediaWikiTestCase { } protected function createPage( $page, $text, $model = null ) { - if ( is_string( $page ) ) $page = Title::newFromText( $page ); - if ( $page instanceof Title ) $page = new WikiPage( $page ); + if ( is_string( $page ) ) { + if ( !preg_match( '/:/', $page ) && + ( $model === null || $model === CONTENT_MODEL_WIKITEXT ) + ) { + $ns = $this->getDefaultWikitextNS(); + $page = MWNamespace::getCanonicalName( $ns ) . ':' . $page; + } + + $page = Title::newFromText( $page ); + } + + if ( $page instanceof Title ) { + $page = new WikiPage( $page ); + } if ( $page->exists() ) { $page->doDeleteArticle( "done" ); } - $page->doEdit( $text, "testing", EDIT_NEW ); + $content = ContentHandler::makeContent( $text, $page->getTitle(), $model ); + $page->doEditContent( $content, "testing", EDIT_NEW ); return $page; } @@ -75,14 +129,15 @@ class RevisionStorageTest extends MediaWikiTestCase { $this->assertEquals( $orig->getPage(), $rev->getPage() ); $this->assertEquals( $orig->getTimestamp(), $rev->getTimestamp() ); $this->assertEquals( $orig->getUser(), $rev->getUser() ); + $this->assertEquals( $orig->getContentModel(), $rev->getContentModel() ); + $this->assertEquals( $orig->getContentFormat(), $rev->getContentFormat() ); $this->assertEquals( $orig->getSha1(), $rev->getSha1() ); } /** * @covers Revision::__construct */ - public function testConstructFromRow() - { + public function testConstructFromRow() { $orig = $this->makeRevision(); $dbr = wfgetDB( DB_SLAVE ); @@ -100,8 +155,7 @@ class RevisionStorageTest extends MediaWikiTestCase { /** * @covers Revision::newFromRow */ - public function testNewFromRow() - { + public function testNewFromRow() { $orig = $this->makeRevision(); $dbr = wfgetDB( DB_SLAVE ); @@ -120,9 +174,8 @@ class RevisionStorageTest extends MediaWikiTestCase { /** * @covers Revision::newFromArchiveRow */ - public function testNewFromArchiveRow() - { - $page = $this->createPage( 'RevisionStorageTest_testNewFromArchiveRow', 'Lorem Ipsum' ); + public function testNewFromArchiveRow() { + $page = $this->createPage( 'RevisionStorageTest_testNewFromArchiveRow', 'Lorem Ipsum', CONTENT_MODEL_WIKITEXT ); $orig = $page->getRevision(); $page->doDeleteArticle( 'test Revision::newFromArchiveRow' ); @@ -141,8 +194,7 @@ class RevisionStorageTest extends MediaWikiTestCase { /** * @covers Revision::newFromId */ - public function testNewFromId() - { + public function testNewFromId() { $orig = $this->makeRevision(); $rev = Revision::newFromId( $orig->getId() ); @@ -153,12 +205,11 @@ class RevisionStorageTest extends MediaWikiTestCase { /** * @covers Revision::fetchRevision */ - public function testFetchRevision() - { - $page = $this->createPage( 'RevisionStorageTest_testFetchRevision', 'one' ); + public function testFetchRevision() { + $page = $this->createPage( 'RevisionStorageTest_testFetchRevision', 'one', CONTENT_MODEL_WIKITEXT ); $id1 = $page->getRevision()->getId(); - $page->doEdit( 'two', 'second rev' ); + $page->doEditContent( new WikitextContent( 'two' ), 'second rev' ); $id2 = $page->getRevision()->getId(); $res = Revision::fetchRevision( $page->getTitle() ); @@ -166,32 +217,39 @@ class RevisionStorageTest extends MediaWikiTestCase { #note: order is unspecified $rows = array(); while ( ( $row = $res->fetchObject() ) ) { - $rows[ $row->rev_id ]= $row; + $rows[$row->rev_id] = $row; } $row = $res->fetchObject(); - $this->assertEquals( 1, count($rows), 'expected exactly one revision' ); + $this->assertEquals( 1, count( $rows ), 'expected exactly one revision' ); $this->assertArrayHasKey( $id2, $rows, 'missing revision with id ' . $id2 ); } /** * @covers Revision::selectFields */ - public function testSelectFields() - { + public function testSelectFields() { + global $wgContentHandlerUseDB; + $fields = Revision::selectFields(); - $this->assertTrue( in_array( 'rev_id', $fields ), 'missing rev_id in list of fields'); - $this->assertTrue( in_array( 'rev_page', $fields ), 'missing rev_page in list of fields'); - $this->assertTrue( in_array( 'rev_timestamp', $fields ), 'missing rev_timestamp in list of fields'); - $this->assertTrue( in_array( 'rev_user', $fields ), 'missing rev_user in list of fields'); + $this->assertTrue( in_array( 'rev_id', $fields ), 'missing rev_id in list of fields' ); + $this->assertTrue( in_array( 'rev_page', $fields ), 'missing rev_page in list of fields' ); + $this->assertTrue( in_array( 'rev_timestamp', $fields ), 'missing rev_timestamp in list of fields' ); + $this->assertTrue( in_array( 'rev_user', $fields ), 'missing rev_user in list of fields' ); + + if ( $wgContentHandlerUseDB ) { + $this->assertTrue( in_array( 'rev_content_model', $fields ), + 'missing rev_content_model in list of fields' ); + $this->assertTrue( in_array( 'rev_content_format', $fields ), + 'missing rev_content_format in list of fields' ); + } } /** * @covers Revision::getPage */ - public function testGetPage() - { + public function testGetPage() { $page = $this->the_page; $orig = $this->makeRevision( array( 'page' => $page->getId() ) ); @@ -203,8 +261,9 @@ class RevisionStorageTest extends MediaWikiTestCase { /** * @covers Revision::getText */ - public function testGetText() - { + public function testGetText() { + $this->hideDeprecated( 'Revision::getText' ); + $orig = $this->makeRevision( array( 'text' => 'hello hello.' ) ); $rev = Revision::newFromId( $orig->getId() ); @@ -212,10 +271,37 @@ class RevisionStorageTest extends MediaWikiTestCase { } /** + * @covers Revision::getContent + */ + public function testGetContent_failure() { + $rev = new Revision( array( + 'page' => $this->the_page->getId(), + 'content_model' => $this->the_page->getContentModel(), + 'text_id' => 123456789, // not in the test DB + ) ); + + $this->assertNull( $rev->getContent(), + "getContent() should return null if the revision's text blob could not be loaded." ); + + //NOTE: check this twice, once for lazy initialization, and once with the cached value. + $this->assertNull( $rev->getContent(), + "getContent() should return null if the revision's text blob could not be loaded." ); + } + + /** + * @covers Revision::getContent + */ + public function testGetContent() { + $orig = $this->makeRevision( array( 'text' => 'hello hello.' ) ); + $rev = Revision::newFromId( $orig->getId() ); + + $this->assertEquals( 'hello hello.', $rev->getContent()->getNativeData() ); + } + + /** * @covers Revision::revText */ - public function testRevText() - { + public function testRevText() { $this->hideDeprecated( 'Revision::revText' ); $orig = $this->makeRevision( array( 'text' => 'hello hello rev.' ) ); $rev = Revision::newFromId( $orig->getId() ); @@ -226,31 +312,69 @@ class RevisionStorageTest extends MediaWikiTestCase { /** * @covers Revision::getRawText */ - public function testGetRawText() - { + public function testGetRawText() { + $this->hideDeprecated( 'Revision::getRawText' ); + $orig = $this->makeRevision( array( 'text' => 'hello hello raw.' ) ); $rev = Revision::newFromId( $orig->getId() ); $this->assertEquals( 'hello hello raw.', $rev->getRawText() ); } + + /** + * @covers Revision::getContentModel + */ + public function testGetContentModel() { + global $wgContentHandlerUseDB; + + if ( !$wgContentHandlerUseDB ) { + $this->markTestSkipped( '$wgContentHandlerUseDB is disabled' ); + } + + $orig = $this->makeRevision( array( 'text' => 'hello hello.', + 'content_model' => CONTENT_MODEL_JAVASCRIPT ) ); + $rev = Revision::newFromId( $orig->getId() ); + + $this->assertEquals( CONTENT_MODEL_JAVASCRIPT, $rev->getContentModel() ); + } + + /** + * @covers Revision::getContentFormat + */ + public function testGetContentFormat() { + global $wgContentHandlerUseDB; + + if ( !$wgContentHandlerUseDB ) { + $this->markTestSkipped( '$wgContentHandlerUseDB is disabled' ); + } + + $orig = $this->makeRevision( array( + 'text' => 'hello hello.', + 'content_model' => CONTENT_MODEL_JAVASCRIPT, + 'content_format' => CONTENT_FORMAT_JAVASCRIPT + ) ); + $rev = Revision::newFromId( $orig->getId() ); + + $this->assertEquals( CONTENT_FORMAT_JAVASCRIPT, $rev->getContentFormat() ); + } + /** * @covers Revision::isCurrent */ - public function testIsCurrent() - { - $page = $this->createPage( 'RevisionStorageTest_testIsCurrent', 'Lorem Ipsum' ); + public function testIsCurrent() { + $page = $this->createPage( 'RevisionStorageTest_testIsCurrent', 'Lorem Ipsum', CONTENT_MODEL_WIKITEXT ); $rev1 = $page->getRevision(); - # @todo: find out if this should be true + # @todo find out if this should be true # $this->assertTrue( $rev1->isCurrent() ); $rev1x = Revision::newFromId( $rev1->getId() ); $this->assertTrue( $rev1x->isCurrent() ); - $page->doEdit( 'Bla bla', 'second rev' ); + $page->doEditContent( ContentHandler::makeContent( 'Bla bla', $page->getTitle(), CONTENT_MODEL_WIKITEXT ), 'second rev' ); $rev2 = $page->getRevision(); - # @todo: find out if this should be true + # @todo find out if this should be true # $this->assertTrue( $rev2->isCurrent() ); $rev1x = Revision::newFromId( $rev1->getId() ); @@ -263,14 +387,14 @@ class RevisionStorageTest extends MediaWikiTestCase { /** * @covers Revision::getPrevious */ - public function testGetPrevious() - { - $page = $this->createPage( 'RevisionStorageTest_testGetPrevious', 'Lorem Ipsum testGetPrevious' ); + public function testGetPrevious() { + $page = $this->createPage( 'RevisionStorageTest_testGetPrevious', 'Lorem Ipsum testGetPrevious', CONTENT_MODEL_WIKITEXT ); $rev1 = $page->getRevision(); $this->assertNull( $rev1->getPrevious() ); - $page->doEdit( 'Bla bla', 'second rev testGetPrevious' ); + $page->doEditContent( ContentHandler::makeContent( 'Bla bla', $page->getTitle(), CONTENT_MODEL_WIKITEXT ), + 'second rev testGetPrevious' ); $rev2 = $page->getRevision(); $this->assertNotNull( $rev2->getPrevious() ); @@ -280,14 +404,14 @@ class RevisionStorageTest extends MediaWikiTestCase { /** * @covers Revision::getNext */ - public function testGetNext() - { - $page = $this->createPage( 'RevisionStorageTest_testGetNext', 'Lorem Ipsum testGetNext' ); + public function testGetNext() { + $page = $this->createPage( 'RevisionStorageTest_testGetNext', 'Lorem Ipsum testGetNext', CONTENT_MODEL_WIKITEXT ); $rev1 = $page->getRevision(); $this->assertNull( $rev1->getNext() ); - $page->doEdit( 'Bla bla', 'second rev testGetNext' ); + $page->doEditContent( ContentHandler::makeContent( 'Bla bla', $page->getTitle(), CONTENT_MODEL_WIKITEXT ), + 'second rev testGetNext' ); $rev2 = $page->getRevision(); $this->assertNotNull( $rev1->getNext() ); @@ -297,20 +421,21 @@ class RevisionStorageTest extends MediaWikiTestCase { /** * @covers Revision::newNullRevision */ - public function testNewNullRevision() - { - $page = $this->createPage( 'RevisionStorageTest_testNewNullRevision', 'some testing text' ); + public function testNewNullRevision() { + $page = $this->createPage( 'RevisionStorageTest_testNewNullRevision', 'some testing text', CONTENT_MODEL_WIKITEXT ); $orig = $page->getRevision(); $dbw = wfGetDB( DB_MASTER ); $rev = Revision::newNullRevision( $dbw, $page->getId(), 'a null revision', false ); - $this->assertNotEquals( $orig->getId(), $rev->getId(), 'new null revision shold have a different id from the original revision' ); - $this->assertEquals( $orig->getTextId(), $rev->getTextId(), 'new null revision shold have the same text id as the original revision' ); - $this->assertEquals( 'some testing text', $rev->getText() ); + $this->assertNotEquals( $orig->getId(), $rev->getId(), + 'new null revision shold have a different id from the original revision' ); + $this->assertEquals( $orig->getTextId(), $rev->getTextId(), + 'new null revision shold have the same text id as the original revision' ); + $this->assertEquals( 'some testing text', $rev->getContent()->getNativeData() ); } - public function dataUserWasLastToEdit() { + public static function provideUserWasLastToEdit() { return array( array( #0 3, true, # actually the last edit @@ -328,32 +453,37 @@ class RevisionStorageTest extends MediaWikiTestCase { } /** - * @dataProvider dataUserWasLastToEdit + * @dataProvider provideUserWasLastToEdit */ public function testUserWasLastToEdit( $sinceIdx, $expectedLast ) { - $userA = \User::newFromName( "RevisionStorageTest_userA" ); - $userB = \User::newFromName( "RevisionStorageTest_userB" ); + $userA = User::newFromName( "RevisionStorageTest_userA" ); + $userB = User::newFromName( "RevisionStorageTest_userB" ); if ( $userA->getId() === 0 ) { - $userA = \User::createNew( $userA->getName() ); + $userA = User::createNew( $userA->getName() ); } if ( $userB->getId() === 0 ) { - $userB = \User::createNew( $userB->getName() ); + $userB = User::createNew( $userB->getName() ); } + $ns = $this->getDefaultWikitextNS(); + $dbw = wfGetDB( DB_MASTER ); $revisions = array(); // create revisions ----------------------------- - $page = WikiPage::factory( Title::newFromText( 'RevisionStorageTest_testUserWasLastToEdit' ) ); + $page = WikiPage::factory( Title::newFromText( + 'RevisionStorageTest_testUserWasLastToEdit', $ns ) ); # zero $revisions[0] = new Revision( array( 'page' => $page->getId(), + 'title' => $page->getTitle(), // we need the title to determine the page's default content model 'timestamp' => '20120101000000', 'user' => $userA->getId(), 'text' => 'zero', + 'content_model' => CONTENT_MODEL_WIKITEXT, 'summary' => 'edit zero' ) ); $revisions[0]->insertOn( $dbw ); @@ -361,9 +491,11 @@ class RevisionStorageTest extends MediaWikiTestCase { # one $revisions[1] = new Revision( array( 'page' => $page->getId(), + 'title' => $page->getTitle(), // still need the title, because $page->getId() is 0 (there's no entry in the page table) 'timestamp' => '20120101000100', 'user' => $userA->getId(), 'text' => 'one', + 'content_model' => CONTENT_MODEL_WIKITEXT, 'summary' => 'edit one' ) ); $revisions[1]->insertOn( $dbw ); @@ -371,9 +503,11 @@ class RevisionStorageTest extends MediaWikiTestCase { # two $revisions[2] = new Revision( array( 'page' => $page->getId(), + 'title' => $page->getTitle(), 'timestamp' => '20120101000200', 'user' => $userB->getId(), 'text' => 'two', + 'content_model' => CONTENT_MODEL_WIKITEXT, 'summary' => 'edit two' ) ); $revisions[2]->insertOn( $dbw ); @@ -381,9 +515,11 @@ class RevisionStorageTest extends MediaWikiTestCase { # three $revisions[3] = new Revision( array( 'page' => $page->getId(), + 'title' => $page->getTitle(), 'timestamp' => '20120101000300', 'user' => $userA->getId(), 'text' => 'three', + 'content_model' => CONTENT_MODEL_WIKITEXT, 'summary' => 'edit three' ) ); $revisions[3]->insertOn( $dbw ); @@ -391,15 +527,17 @@ class RevisionStorageTest extends MediaWikiTestCase { # four $revisions[4] = new Revision( array( 'page' => $page->getId(), + 'title' => $page->getTitle(), 'timestamp' => '20120101000200', 'user' => $userA->getId(), 'text' => 'zero', + 'content_model' => CONTENT_MODEL_WIKITEXT, 'summary' => 'edit four' ) ); $revisions[4]->insertOn( $dbw ); // test it --------------------------------- - $since = $revisions[ $sinceIdx ]->getTimestamp(); + $since = $revisions[$sinceIdx]->getTimestamp(); $wasLast = Revision::userWasLastToEdit( $dbw, $page->getId(), $userA->getId(), $since ); diff --git a/tests/phpunit/includes/RevisionStorageTest_ContentHandlerUseDB.php b/tests/phpunit/includes/RevisionStorageTest_ContentHandlerUseDB.php new file mode 100644 index 00000000..4e83e355 --- /dev/null +++ b/tests/phpunit/includes/RevisionStorageTest_ContentHandlerUseDB.php @@ -0,0 +1,81 @@ +<?php + +/** + * @group ContentHandler + * @group Database + * ^--- important, causes temporary tables to be used instead of the real database + */ +class RevisionTest_ContentHandlerUseDB extends RevisionStorageTest { + + protected function setUp() { + $this->setMwGlobals( 'wgContentHandlerUseDB', false ); + + $dbw = wfGetDB( DB_MASTER ); + + $page_table = $dbw->tableName( 'page' ); + $revision_table = $dbw->tableName( 'revision' ); + $archive_table = $dbw->tableName( 'archive' ); + + if ( $dbw->fieldExists( $page_table, 'page_content_model' ) ) { + $dbw->query( "alter table $page_table drop column page_content_model" ); + $dbw->query( "alter table $revision_table drop column rev_content_model" ); + $dbw->query( "alter table $revision_table drop column rev_content_format" ); + $dbw->query( "alter table $archive_table drop column ar_content_model" ); + $dbw->query( "alter table $archive_table drop column ar_content_format" ); + } + + parent::setUp(); + } + + /** + * @covers Revision::selectFields + */ + public function testSelectFields() { + $fields = Revision::selectFields(); + + $this->assertTrue( in_array( 'rev_id', $fields ), 'missing rev_id in list of fields' ); + $this->assertTrue( in_array( 'rev_page', $fields ), 'missing rev_page in list of fields' ); + $this->assertTrue( in_array( 'rev_timestamp', $fields ), 'missing rev_timestamp in list of fields' ); + $this->assertTrue( in_array( 'rev_user', $fields ), 'missing rev_user in list of fields' ); + + $this->assertFalse( in_array( 'rev_content_model', $fields ), 'missing rev_content_model in list of fields' ); + $this->assertFalse( in_array( 'rev_content_format', $fields ), 'missing rev_content_format in list of fields' ); + } + + /** + * @covers Revision::getContentModel + */ + public function testGetContentModel() { + try { + $this->makeRevision( array( 'text' => 'hello hello.', + 'content_model' => CONTENT_MODEL_JAVASCRIPT ) ); + + $this->fail( "Creating JavaScript content on a wikitext page should fail with " + . "\$wgContentHandlerUseDB disabled" ); + } catch ( MWException $ex ) { + $this->assertTrue( true ); // ok + } + } + + + /** + * @covers Revision::getContentFormat + */ + public function testGetContentFormat() { + try { + // @todo change this to test failure on using a non-standard (but supported) format + // for a content model supported in the given location. As of 1.21, there are + // no alternative formats for any of the standard content models that could be + // used for this though. + + $this->makeRevision( array( 'text' => 'hello hello.', + 'content_model' => CONTENT_MODEL_JAVASCRIPT, + 'content_format' => 'text/javascript' ) ); + + $this->fail( "Creating JavaScript content on a wikitext page should fail with " + . "\$wgContentHandlerUseDB disabled" ); + } catch ( MWException $ex ) { + $this->assertTrue( true ); // ok + } + } +} diff --git a/tests/phpunit/includes/RevisionTest.php b/tests/phpunit/includes/RevisionTest.php index d7654db9..b5819ff6 100644 --- a/tests/phpunit/includes/RevisionTest.php +++ b/tests/phpunit/includes/RevisionTest.php @@ -1,28 +1,63 @@ <?php +/** + * @group ContentHandler + */ class RevisionTest extends MediaWikiTestCase { - var $saveGlobals = array(); - - function setUp() { + protected function setUp() { global $wgContLang; - $wgContLang = Language::factory( 'en' ); - $globalSet = array( + + parent::setUp(); + + $this->setMwGlobals( array( + 'wgContLang' => Language::factory( 'en' ), + 'wgLanguageCode' => 'en', 'wgLegacyEncoding' => false, 'wgCompressRevisions' => false, + + 'wgContentHandlerTextFallback' => 'ignore', + ) ); + + $this->mergeMwGlobalArrayValue( + 'wgExtraNamespaces', + array( + 12312 => 'Dummy', + 12313 => 'Dummy_talk', + ) ); - foreach ( $globalSet as $var => $data ) { - $this->saveGlobals[$var] = $GLOBALS[$var]; - $GLOBALS[$var] = $data; - } + + $this->mergeMwGlobalArrayValue( + 'wgNamespaceContentModels', + array( + 12312 => 'testing', + ) + ); + + $this->mergeMwGlobalArrayValue( + 'wgContentHandlers', + array( + 'testing' => 'DummyContentHandlerForTesting', + 'RevisionTestModifyableContent' => 'RevisionTestModifyableContentHandler', + ) + ); + + MWNamespace::getCanonicalNamespaces( true ); # reset namespace cache + $wgContLang->resetNamespaces(); # reset namespace cache } function tearDown() { - foreach ( $this->saveGlobals as $var => $data ) { - $GLOBALS[$var] = $data; - } + global $wgContLang; + + MWNamespace::getCanonicalNamespaces( true ); # reset namespace cache + $wgContLang->resetNamespaces(); # reset namespace cache + + parent::tearDown(); } - function testGetRevisionText() { + /** + * @covers Revision::getRevisionText + */ + public function testGetRevisionText() { $row = new stdClass; $row->old_flags = ''; $row->old_text = 'This is a bunch of revision text.'; @@ -31,20 +66,24 @@ class RevisionTest extends MediaWikiTestCase { Revision::getRevisionText( $row ) ); } - function testGetRevisionTextGzip() { - if ( !function_exists( 'gzdeflate' ) ) { - $this->markTestSkipped( 'Gzip compression is not enabled (requires zlib).' ); - } else { - $row = new stdClass; - $row->old_flags = 'gzip'; - $row->old_text = gzdeflate( 'This is a bunch of revision text.' ); - $this->assertEquals( - 'This is a bunch of revision text.', - Revision::getRevisionText( $row ) ); - } + /** + * @covers Revision::getRevisionText + */ + public function testGetRevisionTextGzip() { + $this->checkPHPExtension( 'zlib' ); + + $row = new stdClass; + $row->old_flags = 'gzip'; + $row->old_text = gzdeflate( 'This is a bunch of revision text.' ); + $this->assertEquals( + 'This is a bunch of revision text.', + Revision::getRevisionText( $row ) ); } - function testGetRevisionTextUtf8Native() { + /** + * @covers Revision::getRevisionText + */ + public function testGetRevisionTextUtf8Native() { $row = new stdClass; $row->old_flags = 'utf-8'; $row->old_text = "Wiki est l'\xc3\xa9cole superieur !"; @@ -54,7 +93,10 @@ class RevisionTest extends MediaWikiTestCase { Revision::getRevisionText( $row ) ); } - function testGetRevisionTextUtf8Legacy() { + /** + * @covers Revision::getRevisionText + */ + public function testGetRevisionTextUtf8Legacy() { $row = new stdClass; $row->old_flags = ''; $row->old_text = "Wiki est l'\xe9cole superieur !"; @@ -64,35 +106,40 @@ class RevisionTest extends MediaWikiTestCase { Revision::getRevisionText( $row ) ); } - function testGetRevisionTextUtf8NativeGzip() { - if ( !function_exists( 'gzdeflate' ) ) { - $this->markTestSkipped( 'Gzip compression is not enabled (requires zlib).' ); - } else { - $row = new stdClass; - $row->old_flags = 'gzip,utf-8'; - $row->old_text = gzdeflate( "Wiki est l'\xc3\xa9cole superieur !" ); - $GLOBALS['wgLegacyEncoding'] = 'iso-8859-1'; - $this->assertEquals( - "Wiki est l'\xc3\xa9cole superieur !", - Revision::getRevisionText( $row ) ); - } + /** + * @covers Revision::getRevisionText + */ + public function testGetRevisionTextUtf8NativeGzip() { + $this->checkPHPExtension( 'zlib' ); + + $row = new stdClass; + $row->old_flags = 'gzip,utf-8'; + $row->old_text = gzdeflate( "Wiki est l'\xc3\xa9cole superieur !" ); + $GLOBALS['wgLegacyEncoding'] = 'iso-8859-1'; + $this->assertEquals( + "Wiki est l'\xc3\xa9cole superieur !", + Revision::getRevisionText( $row ) ); } - function testGetRevisionTextUtf8LegacyGzip() { - if ( !function_exists( 'gzdeflate' ) ) { - $this->markTestSkipped( 'Gzip compression is not enabled (requires zlib).' ); - } else { - $row = new stdClass; - $row->old_flags = 'gzip'; - $row->old_text = gzdeflate( "Wiki est l'\xe9cole superieur !" ); - $GLOBALS['wgLegacyEncoding'] = 'iso-8859-1'; - $this->assertEquals( - "Wiki est l'\xc3\xa9cole superieur !", - Revision::getRevisionText( $row ) ); - } + /** + * @covers Revision::getRevisionText + */ + public function testGetRevisionTextUtf8LegacyGzip() { + $this->checkPHPExtension( 'zlib' ); + + $row = new stdClass; + $row->old_flags = 'gzip'; + $row->old_text = gzdeflate( "Wiki est l'\xe9cole superieur !" ); + $GLOBALS['wgLegacyEncoding'] = 'iso-8859-1'; + $this->assertEquals( + "Wiki est l'\xc3\xa9cole superieur !", + Revision::getRevisionText( $row ) ); } - function testCompressRevisionTextUtf8() { + /** + * @covers Revision::compressRevisionText + */ + public function testCompressRevisionTextUtf8() { $row = new stdClass; $row->old_text = "Wiki est l'\xc3\xa9cole superieur !"; $row->old_flags = Revision::compressRevisionText( $row->old_text ); @@ -106,8 +153,13 @@ class RevisionTest extends MediaWikiTestCase { Revision::getRevisionText( $row ), "getRevisionText" ); } - function testCompressRevisionTextUtf8Gzip() { - $GLOBALS['wgCompressRevisions'] = true; + /** + * @covers Revision::compressRevisionText + */ + public function testCompressRevisionTextUtf8Gzip() { + $this->checkPHPExtension( 'zlib' ); + $this->setMwGlobals( 'wgCompressRevisions', true ); + $row = new stdClass; $row->old_text = "Wiki est l'\xc3\xa9cole superieur !"; $row->old_flags = Revision::compressRevisionText( $row->old_text ); @@ -120,6 +172,310 @@ class RevisionTest extends MediaWikiTestCase { $this->assertEquals( "Wiki est l'\xc3\xa9cole superieur !", Revision::getRevisionText( $row ), "getRevisionText" ); } + + # ================================================================================================================= + + /** + * @param string $text + * @param string $title + * @param string $model + * @param null $format + * + * @return Revision + */ + function newTestRevision( $text, $title = "Test", $model = CONTENT_MODEL_WIKITEXT, $format = null ) { + if ( is_string( $title ) ) { + $title = Title::newFromText( $title ); + } + + $content = ContentHandler::makeContent( $text, $title, $model, $format ); + + $rev = new Revision( + array( + 'id' => 42, + 'page' => 23, + 'title' => $title, + + 'content' => $content, + 'length' => $content->getSize(), + 'comment' => "testing", + 'minor_edit' => false, + + 'content_format' => $format, + ) + ); + + return $rev; + } + + function dataGetContentModel() { + //NOTE: we expect the help namespace to always contain wikitext + return array( + array( 'hello world', 'Help:Hello', null, null, CONTENT_MODEL_WIKITEXT ), + array( 'hello world', 'User:hello/there.css', null, null, CONTENT_MODEL_CSS ), + array( serialize( 'hello world' ), 'Dummy:Hello', null, null, "testing" ), + ); + } + + /** + * @group Database + * @dataProvider dataGetContentModel + * @covers Revision::getContentModel + */ + public function testGetContentModel( $text, $title, $model, $format, $expectedModel ) { + $rev = $this->newTestRevision( $text, $title, $model, $format ); + + $this->assertEquals( $expectedModel, $rev->getContentModel() ); + } + + function dataGetContentFormat() { + //NOTE: we expect the help namespace to always contain wikitext + return array( + array( 'hello world', 'Help:Hello', null, null, CONTENT_FORMAT_WIKITEXT ), + array( 'hello world', 'Help:Hello', CONTENT_MODEL_CSS, null, CONTENT_FORMAT_CSS ), + array( 'hello world', 'User:hello/there.css', null, null, CONTENT_FORMAT_CSS ), + array( serialize( 'hello world' ), 'Dummy:Hello', null, null, "testing" ), + ); + } + + /** + * @group Database + * @dataProvider dataGetContentFormat + * @covers Revision::getContentFormat + */ + public function testGetContentFormat( $text, $title, $model, $format, $expectedFormat ) { + $rev = $this->newTestRevision( $text, $title, $model, $format ); + + $this->assertEquals( $expectedFormat, $rev->getContentFormat() ); + } + + function dataGetContentHandler() { + //NOTE: we expect the help namespace to always contain wikitext + return array( + array( 'hello world', 'Help:Hello', null, null, 'WikitextContentHandler' ), + array( 'hello world', 'User:hello/there.css', null, null, 'CssContentHandler' ), + array( serialize( 'hello world' ), 'Dummy:Hello', null, null, 'DummyContentHandlerForTesting' ), + ); + } + + /** + * @group Database + * @dataProvider dataGetContentHandler + * @covers Revision::getContentHandler + */ + public function testGetContentHandler( $text, $title, $model, $format, $expectedClass ) { + $rev = $this->newTestRevision( $text, $title, $model, $format ); + + $this->assertEquals( $expectedClass, get_class( $rev->getContentHandler() ) ); + } + + function dataGetContent() { + //NOTE: we expect the help namespace to always contain wikitext + return array( + array( 'hello world', 'Help:Hello', null, null, Revision::FOR_PUBLIC, 'hello world' ), + array( serialize( 'hello world' ), 'Hello', "testing", null, Revision::FOR_PUBLIC, serialize( 'hello world' ) ), + array( serialize( 'hello world' ), 'Dummy:Hello', null, null, Revision::FOR_PUBLIC, serialize( 'hello world' ) ), + ); + } + + /** + * @group Database + * @dataProvider dataGetContent + * @covers Revision::getContent + */ + public function testGetContent( $text, $title, $model, $format, $audience, $expectedSerialization ) { + $rev = $this->newTestRevision( $text, $title, $model, $format ); + $content = $rev->getContent( $audience ); + + $this->assertEquals( $expectedSerialization, is_null( $content ) ? null : $content->serialize( $format ) ); + } + + function dataGetText() { + //NOTE: we expect the help namespace to always contain wikitext + return array( + array( 'hello world', 'Help:Hello', null, null, Revision::FOR_PUBLIC, 'hello world' ), + array( serialize( 'hello world' ), 'Hello', "testing", null, Revision::FOR_PUBLIC, null ), + array( serialize( 'hello world' ), 'Dummy:Hello', null, null, Revision::FOR_PUBLIC, null ), + ); + } + + /** + * @group Database + * @dataProvider dataGetText + * @covers Revision::getText + */ + public function testGetText( $text, $title, $model, $format, $audience, $expectedText ) { + $this->hideDeprecated( 'Revision::getText' ); + + $rev = $this->newTestRevision( $text, $title, $model, $format ); + + $this->assertEquals( $expectedText, $rev->getText( $audience ) ); + } + + /** + * @group Database + * @dataProvider dataGetText + * @covers Revision::getRawText + */ + public function testGetRawText( $text, $title, $model, $format, $audience, $expectedText ) { + $this->hideDeprecated( 'Revision::getRawText' ); + + $rev = $this->newTestRevision( $text, $title, $model, $format ); + + $this->assertEquals( $expectedText, $rev->getRawText( $audience ) ); + } + + + public function dataGetSize() { + return array( + array( "hello world.", CONTENT_MODEL_WIKITEXT, 12 ), + array( serialize( "hello world." ), "testing", 12 ), + ); + } + + /** + * @covers Revision::getSize + * @group Database + * @dataProvider dataGetSize + */ + public function testGetSize( $text, $model, $expected_size ) { + $rev = $this->newTestRevision( $text, 'RevisionTest_testGetSize', $model ); + $this->assertEquals( $expected_size, $rev->getSize() ); + } + + public function dataGetSha1() { + return array( + array( "hello world.", CONTENT_MODEL_WIKITEXT, Revision::base36Sha1( "hello world." ) ), + array( serialize( "hello world." ), "testing", Revision::base36Sha1( serialize( "hello world." ) ) ), + ); + } + + /** + * @covers Revision::getSha1 + * @group Database + * @dataProvider dataGetSha1 + */ + public function testGetSha1( $text, $model, $expected_hash ) { + $rev = $this->newTestRevision( $text, 'RevisionTest_testGetSha1', $model ); + $this->assertEquals( $expected_hash, $rev->getSha1() ); + } + + /** + * @covers Revision::__construct + */ + public function testConstructWithText() { + $this->hideDeprecated( "Revision::getText" ); + + $rev = new Revision( array( + 'text' => 'hello world.', + 'content_model' => CONTENT_MODEL_JAVASCRIPT + ) ); + + $this->assertNotNull( $rev->getText(), 'no content text' ); + $this->assertNotNull( $rev->getContent(), 'no content object available' ); + $this->assertEquals( CONTENT_MODEL_JAVASCRIPT, $rev->getContent()->getModel() ); + $this->assertEquals( CONTENT_MODEL_JAVASCRIPT, $rev->getContentModel() ); + } + + /** + * @covers Revision::__construct + */ + public function testConstructWithContent() { + $this->hideDeprecated( "Revision::getText" ); + + $title = Title::newFromText( 'RevisionTest_testConstructWithContent' ); + + $rev = new Revision( array( + 'content' => ContentHandler::makeContent( 'hello world.', $title, CONTENT_MODEL_JAVASCRIPT ), + ) ); + + $this->assertNotNull( $rev->getText(), 'no content text' ); + $this->assertNotNull( $rev->getContent(), 'no content object available' ); + $this->assertEquals( CONTENT_MODEL_JAVASCRIPT, $rev->getContent()->getModel() ); + $this->assertEquals( CONTENT_MODEL_JAVASCRIPT, $rev->getContentModel() ); + } + + /** + * Tests whether $rev->getContent() returns a clone when needed. + * + * @group Database + * @covers Revision::getContent + */ + public function testGetContentClone() { + $content = new RevisionTestModifyableContent( "foo" ); + + $rev = new Revision( + array( + 'id' => 42, + 'page' => 23, + 'title' => Title::newFromText( "testGetContentClone_dummy" ), + + 'content' => $content, + 'length' => $content->getSize(), + 'comment' => "testing", + 'minor_edit' => false, + ) + ); + + $content = $rev->getContent( Revision::RAW ); + $content->setText( "bar" ); + + $content2 = $rev->getContent( Revision::RAW ); + $this->assertNotSame( $content, $content2, "expected a clone" ); // content is mutable, expect clone + $this->assertEquals( "foo", $content2->getText() ); // clone should contain the original text + + $content2->setText( "bla bla" ); + $this->assertEquals( "bar", $content->getText() ); // clones should be independent + } + + + /** + * Tests whether $rev->getContent() returns the same object repeatedly if appropriate. + * + * @group Database + * @covers Revision::getContent + */ + public function testGetContentUncloned() { + $rev = $this->newTestRevision( "hello", "testGetContentUncloned_dummy", CONTENT_MODEL_WIKITEXT ); + $content = $rev->getContent( Revision::RAW ); + $content2 = $rev->getContent( Revision::RAW ); + + // for immutable content like wikitext, this should be the same object + $this->assertSame( $content, $content2 ); + } } +class RevisionTestModifyableContent extends TextContent { + public function __construct( $text ) { + parent::__construct( $text, "RevisionTestModifyableContent" ); + } + + public function copy() { + return new RevisionTestModifyableContent( $this->mText ); + } + + public function getText() { + return $this->mText; + } + + public function setText( $text ) { + $this->mText = $text; + } +} +class RevisionTestModifyableContentHandler extends TextContentHandler { + + public function __construct() { + parent::__construct( "RevisionTestModifyableContent", array( CONTENT_FORMAT_TEXT ) ); + } + + public function unserializeContent( $text, $format = null ) { + $this->checkFormat( $format ); + + return new RevisionTestModifyableContent( $text ); + } + + public function makeEmptyContent() { + return new RevisionTestModifyableContent( '' ); + } +} diff --git a/tests/phpunit/includes/SampleTest.php b/tests/phpunit/includes/SampleTest.php index 59ba0a04..8516a4ce 100644 --- a/tests/phpunit/includes/SampleTest.php +++ b/tests/phpunit/includes/SampleTest.php @@ -5,43 +5,50 @@ class TestSample extends MediaWikiLangTestCase { /** * Anything that needs to happen before your tests should go here. */ - function setUp() { - global $wgContLang; + protected function setUp() { + // Be sure to do call the parent setup and teardown functions. + // This makes sure that all the various cleanup and restorations + // happen as they should (including the restoration for setMwGlobals). parent::setUp(); - /* For example, we need to set $wgContLang for creating a new Title */ - $wgContLang = Language::factory( 'en' ); + // This sets the globals and will restore them automatically + // after each test. + $this->setMwGlobals( array( + 'wgContLang' => Language::factory( 'en' ), + 'wgLanguageCode' => 'en', + ) ); } /** * Anything cleanup you need to do should go here. */ - function tearDown() { + protected function tearDown() { parent::tearDown(); } /** - * Name tests so that PHPUnit can turn them into sentances when + * Name tests so that PHPUnit can turn them into sentences when * they run. While MediaWiki isn't strictly an Agile Programming * project, you are encouraged to use the naming described under * "Agile Documentation" at * http://www.phpunit.de/manual/3.4/en/other-uses-for-tests.html */ - function testTitleObjectStringConversion() { - $title = Title::newFromText("text"); - $this->assertEquals("Text", $title->__toString(), "Title creation"); - $this->assertEquals("Text", "Text", "Automatic string conversion"); - - $title = Title::newFromText("text", NS_MEDIA); - $this->assertEquals("Media:Text", $title->__toString(), "Title creation with namespace"); + public function testTitleObjectStringConversion() { + $title = Title::newFromText( "text" ); + $this->assertInstanceOf( 'Title', $title, "Title creation" ); + $this->assertEquals( "Text", $title, "Automatic string conversion" ); + $title = Title::newFromText( "text", NS_MEDIA ); + $this->assertEquals( "Media:Text", $title, "Title creation with namespace" ); } /** * If you want to run a the same test with a variety of data. use a data provider. * see: http://www.phpunit.de/manual/3.4/en/writing-tests-for-phpunit.html + * + * Note: Data providers are always called statically and outside setUp/tearDown! */ - public function provideTitles() { + public static function provideTitles() { return array( array( 'Text', NS_MEDIA, 'Media:Text' ), array( 'Text', null, 'Text' ), @@ -55,14 +62,14 @@ class TestSample extends MediaWikiLangTestCase { * @dataProvider provideTitles * See http://www.phpunit.de/manual/3.4/en/appendixes.annotations.html#appendixes.annotations.dataProvider */ - public function testCreateBasicListOfTitles($titleName, $ns, $text) { - $title = Title::newFromText($titleName, $ns); - $this->assertEquals($text, "$title", "see if '$titleName' matches '$text'"); + public function testCreateBasicListOfTitles( $titleName, $ns, $text ) { + $title = Title::newFromText( $titleName, $ns ); + $this->assertEquals( $text, "$title", "see if '$titleName' matches '$text'" ); } public function testSetUpMainPageTitleForNextTest() { $title = Title::newMainPage(); - $this->assertEquals("Main Page", "$title", "Test initial creation of a title"); + $this->assertEquals( "Main Page", "$title", "Test initial creation of a title" ); return $title; } @@ -78,6 +85,7 @@ class TestSample extends MediaWikiLangTestCase { * example) as arguments to the next method (e.g. $title in * testTitleDepends is whatever testInitialCreatiion returned.) */ + /** * @depends testSetUpMainPageTitleForNextTest * See http://www.phpunit.de/manual/3.4/en/appendixes.annotations.html#appendixes.annotations.depends @@ -90,9 +98,8 @@ class TestSample extends MediaWikiLangTestCase { * @expectedException MWException object * See http://www.phpunit.de/manual/3.4/en/appendixes.annotations.html#appendixes.annotations.expectedException */ - function testTitleObjectFromObject() { + public function testTitleObjectFromObject() { $title = Title::newFromText( Title::newFromText( "test" ) ); $this->assertEquals( "Test", $title->isLocal() ); } } - diff --git a/tests/phpunit/includes/SanitizerTest.php b/tests/phpunit/includes/SanitizerTest.php index 66af2581..81246d33 100644 --- a/tests/phpunit/includes/SanitizerTest.php +++ b/tests/phpunit/includes/SanitizerTest.php @@ -1,12 +1,21 @@ <?php +/** + * @todo Tests covering decodeCharReferences can be refactored into a single + * method and dataprovider. + */ class SanitizerTest extends MediaWikiTestCase { - function setUp() { + protected function setUp() { + parent::setUp(); + AutoLoader::loadClass( 'Sanitizer' ); } - function testDecodeNamedEntities() { + /** + * @covers Sanitizer::decodeCharReferences + */ + public function testDecodeNamedEntities() { $this->assertEquals( "\xc3\xa9cole", Sanitizer::decodeCharReferences( 'école' ), @@ -14,7 +23,10 @@ class SanitizerTest extends MediaWikiTestCase { ); } - function testDecodeNumericEntities() { + /** + * @covers Sanitizer::decodeCharReferences + */ + public function testDecodeNumericEntities() { $this->assertEquals( "\xc4\x88io bonas dans l'\xc3\xa9cole!", Sanitizer::decodeCharReferences( "Ĉio bonas dans l'école!" ), @@ -22,7 +34,10 @@ class SanitizerTest extends MediaWikiTestCase { ); } - function testDecodeMixedEntities() { + /** + * @covers Sanitizer::decodeCharReferences + */ + public function testDecodeMixedEntities() { $this->assertEquals( "\xc4\x88io bonas dans l'\xc3\xa9cole!", Sanitizer::decodeCharReferences( "Ĉio bonas dans l'école!" ), @@ -30,7 +45,10 @@ class SanitizerTest extends MediaWikiTestCase { ); } - function testDecodeMixedComplexEntities() { + /** + * @covers Sanitizer::decodeCharReferences + */ + public function testDecodeMixedComplexEntities() { $this->assertEquals( "\xc4\x88io bonas dans l'\xc3\xa9cole! (mais pas Ĉio dans l'école)", Sanitizer::decodeCharReferences( @@ -40,7 +58,10 @@ class SanitizerTest extends MediaWikiTestCase { ); } - function testInvalidAmpersand() { + /** + * @covers Sanitizer::decodeCharReferences + */ + public function testInvalidAmpersand() { $this->assertEquals( 'a & b', Sanitizer::decodeCharReferences( 'a & b' ), @@ -48,7 +69,10 @@ class SanitizerTest extends MediaWikiTestCase { ); } - function testInvalidEntities() { + /** + * @covers Sanitizer::decodeCharReferences + */ + public function testInvalidEntities() { $this->assertEquals( '&foo;', Sanitizer::decodeCharReferences( '&foo;' ), @@ -56,68 +80,150 @@ class SanitizerTest extends MediaWikiTestCase { ); } - function testInvalidNumberedEntities() { + /** + * @covers Sanitizer::decodeCharReferences + */ + public function testInvalidNumberedEntities() { $this->assertEquals( UTF8_REPLACEMENT, Sanitizer::decodeCharReferences( "�" ), 'Invalid numbered entity' ); } - function testSelfClosingTag() { + /** + * @covers Sanitizer::removeHTMLtags + * @dataProvider provideHtml5Tags + * + * @param String $tag Name of an HTML5 element (ie: 'video') + * @param Boolean $escaped Wheter sanitizer let the tag in or escape it (ie: '<video>') + */ + public function testRemovehtmltagsOnHtml5Tags( $tag, $escaped ) { + $this->setMwGlobals( array( + 'wgUseTidy' => false + ) ); + + if ( $escaped ) { + $this->assertEquals( "<$tag>", + Sanitizer::removeHTMLtags( "<$tag>" ) + ); + } else { + $this->assertEquals( "<$tag></$tag>\n", + Sanitizer::removeHTMLtags( "<$tag>" ) + ); + } + } + + /** + * Provide HTML5 tags + */ + public static function provideHtml5Tags() { + $ESCAPED = true; # We want tag to be escaped + $VERBATIM = false; # We want to keep the tag + return array( + array( 'data', $VERBATIM ), + array( 'mark', $VERBATIM ), + array( 'time', $VERBATIM ), + array( 'video', $ESCAPED ), + ); + } + + function dataRemoveHTMLtags() { + return array( + // former testSelfClosingTag + array( + '<div>Hello world</div />', + '<div>Hello world</div>', + 'Self-closing closing div' + ), + // Make sure special nested HTML5 semantics are not broken + // http://www.whatwg.org/html/text-level-semantics.html#the-kbd-element + array( + '<kbd><kbd>Shift</kbd>+<kbd>F3</kbd></kbd>', + '<kbd><kbd>Shift</kbd>+<kbd>F3</kbd></kbd>', + 'Nested <kbd>.' + ), + // http://www.whatwg.org/html/text-level-semantics.html#the-sub-and-sup-elements + array( + '<var>x<sub><var>i</var></sub></var>, <var>y<sub><var>i</var></sub></var>', + '<var>x<sub><var>i</var></sub></var>, <var>y<sub><var>i</var></sub></var>', + 'Nested <var>.' + ), + // http://www.whatwg.org/html/text-level-semantics.html#the-dfn-element + array( + '<dfn><abbr title="Garage Door Opener">GDO</abbr></dfn>', + '<dfn><abbr title="Garage Door Opener">GDO</abbr></dfn>', + '<abbr> inside <dfn>', + ), + ); + } + + /** + * @dataProvider dataRemoveHTMLtags + * @covers Sanitizer::removeHTMLtags + */ + public function testRemoveHTMLtags( $input, $output, $msg = null ) { $GLOBALS['wgUseTidy'] = false; - $this->assertEquals( - '<div>Hello world</div>', - Sanitizer::removeHTMLtags( '<div>Hello world</div />' ), - 'Self-closing closing div' - ); - } - - function testDecodeTagAttributes() { - $this->assertEquals( Sanitizer::decodeTagAttributes( 'foo=bar' ), array( 'foo' => 'bar' ), 'Unquoted attribute' ); - $this->assertEquals( Sanitizer::decodeTagAttributes( ' foo = bar ' ), array( 'foo' => 'bar' ), 'Spaced attribute' ); - $this->assertEquals( Sanitizer::decodeTagAttributes( 'foo="bar"' ), array( 'foo' => 'bar' ), 'Double-quoted attribute' ); - $this->assertEquals( Sanitizer::decodeTagAttributes( 'foo=\'bar\'' ), array( 'foo' => 'bar' ), 'Single-quoted attribute' ); - $this->assertEquals( Sanitizer::decodeTagAttributes( 'foo=\'bar\' baz="foo"' ), array( 'foo' => 'bar', 'baz' => 'foo' ), 'Several attributes' ); - - $this->assertEquals( Sanitizer::decodeTagAttributes( 'foo=\'bar\' baz="foo"' ), array( 'foo' => 'bar', 'baz' => 'foo' ), 'Several attributes' ); - $this->assertEquals( Sanitizer::decodeTagAttributes( 'foo=\'bar\' baz="foo"' ), array( 'foo' => 'bar', 'baz' => 'foo' ), 'Several attributes' ); - - $this->assertEquals( Sanitizer::decodeTagAttributes( ':foo=\'bar\'' ), array( ':foo' => 'bar' ), 'Leading :' ); - $this->assertEquals( Sanitizer::decodeTagAttributes( '_foo=\'bar\'' ), array( '_foo' => 'bar' ), 'Leading _' ); - $this->assertEquals( Sanitizer::decodeTagAttributes( 'Foo=\'bar\'' ), array( 'foo' => 'bar' ), 'Leading capital' ); - $this->assertEquals( Sanitizer::decodeTagAttributes( 'FOO=BAR' ), array( 'foo' => 'BAR' ), 'Attribute keys are normalized to lowercase' ); - - # Invalid beginning - $this->assertEquals( Sanitizer::decodeTagAttributes( '-foo=bar' ), array(), 'Leading - is forbidden' ); - $this->assertEquals( Sanitizer::decodeTagAttributes( '.foo=bar' ), array(), 'Leading . is forbidden' ); - $this->assertEquals( Sanitizer::decodeTagAttributes( 'foo-bar=bar' ), array( 'foo-bar' => 'bar' ), 'A - is allowed inside the attribute' ); - $this->assertEquals( Sanitizer::decodeTagAttributes( 'foo-=bar' ), array( 'foo-' => 'bar' ), 'A - is allowed inside the attribute' ); - - $this->assertEquals( Sanitizer::decodeTagAttributes( 'foo.bar=baz' ), array( 'foo.bar' => 'baz' ), 'A . is allowed inside the attribute' ); - $this->assertEquals( Sanitizer::decodeTagAttributes( 'foo.=baz' ), array( 'foo.' => 'baz' ), 'A . is allowed as last character' ); - - $this->assertEquals( Sanitizer::decodeTagAttributes( 'foo6=baz' ), array( 'foo6' => 'baz' ), 'Numbers are allowed' ); - - # This bit is more relaxed than XML rules, but some extensions use it, like ProofreadPage (see bug 27539) - $this->assertEquals( Sanitizer::decodeTagAttributes( '1foo=baz' ), array( '1foo' => 'baz' ), 'Leading numbers are allowed' ); - - $this->assertEquals( Sanitizer::decodeTagAttributes( 'foo$=baz' ), array(), 'Symbols are not allowed' ); - $this->assertEquals( Sanitizer::decodeTagAttributes( 'foo@=baz' ), array(), 'Symbols are not allowed' ); - $this->assertEquals( Sanitizer::decodeTagAttributes( 'foo~=baz' ), array(), 'Symbols are not allowed' ); - - - $this->assertEquals( Sanitizer::decodeTagAttributes( 'foo=1[#^`*%w/(' ), array( 'foo' => '1[#^`*%w/(' ), 'All kind of characters are allowed as values' ); - $this->assertEquals( Sanitizer::decodeTagAttributes( 'foo="1[#^`*%\'w/("' ), array( 'foo' => '1[#^`*%\'w/(' ), 'Double quotes are allowed if quoted by single quotes' ); - $this->assertEquals( Sanitizer::decodeTagAttributes( 'foo=\'1[#^`*%"w/(\'' ), array( 'foo' => '1[#^`*%"w/(' ), 'Single quotes are allowed if quoted by double quotes' ); - $this->assertEquals( Sanitizer::decodeTagAttributes( 'foo=&"' ), array( 'foo' => '&"' ), 'Special chars can be provided as entities' ); - $this->assertEquals( Sanitizer::decodeTagAttributes( 'foo=&foobar;' ), array( 'foo' => '&foobar;' ), 'Entity-like items are accepted' ); + $this->assertEquals( $output, Sanitizer::removeHTMLtags( $input ), $msg ); + } + + /** + * @dataProvider provideTagAttributesToDecode + * @covers Sanitizer::decodeTagAttributes + */ + public function testDecodeTagAttributes( $expected, $attributes, $message = '' ) { + $this->assertEquals( $expected, + Sanitizer::decodeTagAttributes( $attributes ), + $message + ); + } + + public static function provideTagAttributesToDecode() { + return array( + array( array( 'foo' => 'bar' ), 'foo=bar', 'Unquoted attribute' ), + array( array( 'foo' => 'bar' ), ' foo = bar ', 'Spaced attribute' ), + array( array( 'foo' => 'bar' ), 'foo="bar"', 'Double-quoted attribute' ), + array( array( 'foo' => 'bar' ), 'foo=\'bar\'', 'Single-quoted attribute' ), + array( array( 'foo' => 'bar', 'baz' => 'foo' ), 'foo=\'bar\' baz="foo"', 'Several attributes' ), + array( array( 'foo' => 'bar', 'baz' => 'foo' ), 'foo=\'bar\' baz="foo"', 'Several attributes' ), + array( array( 'foo' => 'bar', 'baz' => 'foo' ), 'foo=\'bar\' baz="foo"', 'Several attributes' ), + array( array( ':foo' => 'bar' ), ':foo=\'bar\'', 'Leading :' ), + array( array( '_foo' => 'bar' ), '_foo=\'bar\'', 'Leading _' ), + array( array( 'foo' => 'bar' ), 'Foo=\'bar\'', 'Leading capital' ), + array( array( 'foo' => 'BAR' ), 'FOO=BAR', 'Attribute keys are normalized to lowercase' ), + + # Invalid beginning + array( array(), '-foo=bar', 'Leading - is forbidden' ), + array( array(), '.foo=bar', 'Leading . is forbidden' ), + array( array( 'foo-bar' => 'bar' ), 'foo-bar=bar', 'A - is allowed inside the attribute' ), + array( array( 'foo-' => 'bar' ), 'foo-=bar', 'A - is allowed inside the attribute' ), + array( array( 'foo.bar' => 'baz' ), 'foo.bar=baz', 'A . is allowed inside the attribute' ), + array( array( 'foo.' => 'baz' ), 'foo.=baz', 'A . is allowed as last character' ), + array( array( 'foo6' => 'baz' ), 'foo6=baz', 'Numbers are allowed' ), + + # This bit is more relaxed than XML rules, but some extensions use + # it, like ProofreadPage (see bug 27539) + array( array( '1foo' => 'baz' ), '1foo=baz', 'Leading numbers are allowed' ), + array( array(), 'foo$=baz', 'Symbols are not allowed' ), + array( array(), 'foo@=baz', 'Symbols are not allowed' ), + array( array(), 'foo~=baz', 'Symbols are not allowed' ), + array( array( 'foo' => '1[#^`*%w/(' ), 'foo=1[#^`*%w/(', 'All kind of characters are allowed as values' ), + array( array( 'foo' => '1[#^`*%\'w/(' ), 'foo="1[#^`*%\'w/("', 'Double quotes are allowed if quoted by single quotes' ), + array( array( 'foo' => '1[#^`*%"w/(' ), 'foo=\'1[#^`*%"w/(\'', 'Single quotes are allowed if quoted by double quotes' ), + array( array( 'foo' => '&"' ), 'foo=&"', 'Special chars can be provided as entities' ), + array( array( 'foo' => '&foobar;' ), 'foo=&foobar;', 'Entity-like items are accepted' ), + ); } /** * @dataProvider provideDeprecatedAttributes + * @covers Sanitizer::fixTagAttributes */ - function testDeprecatedAttributesUnaltered( $inputAttr, $inputEl ) { - $this->assertEquals( " $inputAttr", Sanitizer::fixTagAttributes( $inputAttr, $inputEl ) ); + public function testDeprecatedAttributesUnaltered( $inputAttr, $inputEl, $message = '' ) { + $this->assertEquals( " $inputAttr", + Sanitizer::fixTagAttributes( $inputAttr, $inputEl ), + $message + ); } public static function provideDeprecatedAttributes() { + /** array( <attribute>, <element>, [message] ) */ return array( array( 'clear="left"', 'br' ), array( 'clear="all"', 'br' ), @@ -135,28 +241,62 @@ class SanitizerTest extends MediaWikiTestCase { /** * @dataProvider provideCssCommentsFixtures + * @covers Sanitizer::checkCss */ - function testCssCommentsChecking( $expected, $css, $message = '' ) { - $this->assertEquals( - $expected, + public function testCssCommentsChecking( $expected, $css, $message = '' ) { + $this->assertEquals( $expected, Sanitizer::checkCss( $css ), $message ); } - function provideCssCommentsFixtures() { + public static function provideCssCommentsFixtures() { /** array( <expected>, <css>, [message] ) */ return array( - array( ' ', '/**/' ), + // Valid comments spanning entire input + array( '/**/', '/**/' ), + array( '/* comment */', '/* comment */' ), + // Weird stuff array( ' ', '/****/' ), - array( ' ', '/* comment */' ), - array( ' ', "\\2f\\2a foo \\2a\\2f", + array( ' ', '/* /* */' ), + array( 'display: block;', "display:/* foo */block;" ), + array( 'display: block;', "display:\\2f\\2a foo \\2a\\2f block;", 'Backslash-escaped comments must be stripped (bug 28450)' ), array( '', '/* unfinished comment structure', - 'Remove anything after a comment-start token' ), + 'Remove anything after a comment-start token' ), array( '', "\\2f\\2a unifinished comment'", - 'Remove anything after a backslash-escaped comment-start token' ), + 'Remove anything after a backslash-escaped comment-start token' ), + array( '/* insecure input */', 'filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src=\'asdf.png\',sizingMethod=\'scale\');' ), + array( '/* insecure input */', '-ms-filter: "progid:DXImageTransform.Microsoft.AlphaImageLoader(src=\'asdf.png\',sizingMethod=\'scale\')";' ), + array( '/* insecure input */', 'width: expression(1+1);' ), + array( '/* insecure input */', 'background-image: image(asdf.png);' ), + array( '/* insecure input */', 'background-image: -webkit-image(asdf.png);' ), + array( '/* insecure input */', 'background-image: -moz-image(asdf.png);' ), + array( '/* insecure input */', 'background-image: image-set("asdf.png" 1x, "asdf.png" 2x);' ), + array( '/* insecure input */', 'background-image: -webkit-image-set("asdf.png" 1x, "asdf.png" 2x);' ), + array( '/* insecure input */', 'background-image: -moz-image-set("asdf.png" 1x, "asdf.png" 2x);' ), + ); + } + + /** + * Test for support or lack of support for specific attributes in the attribute whitelist. + */ + public static function provideAttributeSupport() { + /** array( <attributes>, <expected>, <message> ) */ + return array( + array( 'div', ' role="presentation"', ' role="presentation"', 'Support for WAI-ARIA\'s role="presentation".' ), + array( 'div', ' role="main"', '', "Other WAI-ARIA roles are currently not supported." ), ); } -} + /** + * @dataProvider provideAttributeSupport + * @covers Sanitizer::fixTagAttributes + */ + public function testAttributeSupport( $tag, $attributes, $expected, $message ) { + $this->assertEquals( $expected, + Sanitizer::fixTagAttributes( $attributes, $tag ), + $message + ); + } +} diff --git a/tests/phpunit/includes/SanitizerValidateEmailTest.php b/tests/phpunit/includes/SanitizerValidateEmailTest.php index 14d799cf..f13e8382 100644 --- a/tests/phpunit/includes/SanitizerValidateEmailTest.php +++ b/tests/phpunit/includes/SanitizerValidateEmailTest.php @@ -1,62 +1,82 @@ <?php +/** + * @covers Sanitizer::validateEmail + * @TODO all test methods in this class should be refactored and... + * use a single test method and a single data provider... + */ class SanitizerValidateEmailTest extends MediaWikiTestCase { - private function checkEmail( $addr, $expected = true, $msg = '') { - if( $msg == '' ) { $msg = "Testing $addr"; } + private function checkEmail( $addr, $expected = true, $msg = '' ) { + if ( $msg == '' ) { + $msg = "Testing $addr"; + } + $this->assertEquals( $expected, Sanitizer::validateEmail( $addr ), $msg ); } + private function valid( $addr, $msg = '' ) { $this->checkEmail( $addr, true, $msg ); } + private function invalid( $addr, $msg = '' ) { $this->checkEmail( $addr, false, $msg ); } - function testEmailWellKnownUserAtHostDotTldAreValid() { + public function testEmailWellKnownUserAtHostDotTldAreValid() { $this->valid( 'user@example.com' ); $this->valid( 'user@example.museum' ); } - function testEmailWithUpperCaseCharactersAreValid() { + + public function testEmailWithUpperCaseCharactersAreValid() { $this->valid( 'USER@example.com' ); $this->valid( 'user@EXAMPLE.COM' ); $this->valid( 'user@Example.com' ); $this->valid( 'USER@eXAMPLE.com' ); } - function testEmailWithAPlusInUserName() { + + public function testEmailWithAPlusInUserName() { $this->valid( 'user+sub@example.com' ); $this->valid( 'user+@example.com' ); } - function testEmailDoesNotNeedATopLevelDomain() { + + public function testEmailDoesNotNeedATopLevelDomain() { $this->valid( "user@localhost" ); $this->valid( "FooBar@localdomain" ); $this->valid( "nobody@mycompany" ); } - function testEmailWithWhiteSpacesBeforeOrAfterAreInvalids() { + + public function testEmailWithWhiteSpacesBeforeOrAfterAreInvalids() { $this->invalid( " user@host.com" ); $this->invalid( "user@host.com " ); $this->invalid( "\tuser@host.com" ); $this->invalid( "user@host.com\t" ); } - function testEmailWithWhiteSpacesAreInvalids() { + + public function testEmailWithWhiteSpacesAreInvalids() { $this->invalid( "User user@host" ); $this->invalid( "first last@mycompany" ); $this->invalid( "firstlast@my company" ); } - // bug 26948 : comma were matched by an incorrect regexp range - function testEmailWithCommasAreInvalids() { + + /** + * bug 26948 : comma were matched by an incorrect regexp range + */ + public function testEmailWithCommasAreInvalids() { $this->invalid( "user,foo@example.org" ); $this->invalid( "userfoo@ex,ample.org" ); } - function testEmailWithHyphens() { + + public function testEmailWithHyphens() { $this->valid( "user-foo@example.org" ); $this->valid( "userfoo@ex-ample.org" ); } - function testEmailDomainCanNotBeginWithDot() { + + public function testEmailDomainCanNotBeginWithDot() { $this->invalid( "user@." ); $this->invalid( "user@.localdomain" ); $this->invalid( "user@localdomain." ); @@ -64,16 +84,20 @@ class SanitizerValidateEmailTest extends MediaWikiTestCase { $this->valid( ".@localdomain" ); $this->invalid( ".@a............" ); } - function testEmailWithFunnyCharacters() { + + public function testEmailWithFunnyCharacters() { $this->valid( "\$user!ex{this}@123.com" ); } - function testEmailTopLevelDomainCanBeNumerical() { + + public function testEmailTopLevelDomainCanBeNumerical() { $this->valid( "user@example.1234" ); } - function testEmailWithoutAtSignIsInvalid() { + + public function testEmailWithoutAtSignIsInvalid() { $this->invalid( 'useràexample.com' ); } - function testEmailWithOneCharacterDomainIsValid() { + + public function testEmailWithOneCharacterDomainIsValid() { $this->valid( 'user@a' ); } } diff --git a/tests/phpunit/includes/SeleniumConfigurationTest.php b/tests/phpunit/includes/SeleniumConfigurationTest.php deleted file mode 100644 index 8589c188..00000000 --- a/tests/phpunit/includes/SeleniumConfigurationTest.php +++ /dev/null @@ -1,228 +0,0 @@ -<?php - -class SeleniumConfigurationTest extends MediaWikiTestCase { - - /** - * The file where the test temporarity stores the selenium config. - * This should be cleaned up as part of teardown. - */ - private $tempFileName; - - /** - * String containing the a sample selenium settings - */ - private $testConfig0 = -' -[SeleniumSettings] -browsers[firefox] = "*firefox" -browsers[iexplorer] = "*iexploreproxy" -browsers[chrome] = "*chrome" -host = "localhost" -port = "foobarr" -wikiUrl = "http://localhost/deployment" -username = "xxxxxxx" -userPassword = "" -testBrowser = "chrome" -startserver = -stopserver = -jUnitLogFile = -runAgainstGrid = false - -[SeleniumTests] -testSuite[SimpleSeleniumTestSuite] = "tests/selenium/SimpleSeleniumTestSuite.php" -testSuite[TestSuiteName] = "testSuitePath" -'; - /** - * Array of expected browsers from $testConfig0 - */ - private $testBrowsers0 = array( 'firefox' => '*firefox', - 'iexplorer' => '*iexploreproxy', - 'chrome' => '*chrome' - ); - /** - * Array of expected selenium settings from $testConfig0 - */ - private $testSettings0 = array( - 'host' => 'localhost', - 'port' => 'foobarr', - 'wikiUrl' => 'http://localhost/deployment', - 'username' => 'xxxxxxx', - 'userPassword' => '', - 'testBrowser' => 'chrome', - 'startserver' => null, - 'stopserver' => null, - 'seleniumserverexecpath' => null, - 'jUnitLogFile' => null, - 'runAgainstGrid' => null - ); - /** - * Array of expected testSuites from $testConfig0 - */ - private $testSuites0 = array( - 'SimpleSeleniumTestSuite' => 'tests/selenium/SimpleSeleniumTestSuite.php', - 'TestSuiteName' => 'testSuitePath' - ); - - - /** - * Another sample selenium settings file contents - */ - private $testConfig1 = -' -[SeleniumSettings] -host = "localhost" -testBrowser = "firefox" -'; - /** - * Expected browsers from $testConfig1 - */ - private $testBrowsers1 = null; - /** - * Expected selenium settings from $testConfig1 - */ - private $testSettings1 = array( - 'host' => 'localhost', - 'port' => null, - 'wikiUrl' => null, - 'username' => null, - 'userPassword' => null, - 'testBrowser' => 'firefox', - 'startserver' => null, - 'stopserver' => null, - 'seleniumserverexecpath' => null, - 'jUnitLogFile' => null, - 'runAgainstGrid' => null - ); - /** - * Expected test suites from $testConfig1 - */ - private $testSuites1 = null; - - - public function setUp() { - if ( !defined( 'SELENIUMTEST' ) ) { - define( 'SELENIUMTEST', true ); - } - } - - /** - * Clean up the temporary file used to store the selenium settings. - */ - public function tearDown() { - if ( strlen( $this->tempFileName ) > 0 ) { - unlink( $this->tempFileName ); - unset( $this->tempFileName ); - } - parent::tearDown(); - } - - /** - * @expectedException MWException - * @group SeleniumFramework - */ - public function testErrorOnIncorrectConfigFile() { - $seleniumSettings = array(); - $seleniumBrowsers = array(); - $seleniumTestSuites = array(); - - SeleniumConfig::getSeleniumSettings($seleniumSettings, - $seleniumBrowsers, - $seleniumTestSuites, - "Some_fake_settings_file.ini" ); - - } - - /** - * @expectedException MWException - * @group SeleniumFramework - */ - public function testErrorOnMissingConfigFile() { - $seleniumSettings = array(); - $seleniumBrowsers = array(); - $seleniumTestSuites = array(); - global $wgSeleniumConfigFile; - $wgSeleniumConfigFile = ''; - SeleniumConfig::getSeleniumSettings($seleniumSettings, - $seleniumBrowsers, - $seleniumTestSuites); - } - - /** - * @group SeleniumFramework - */ - public function testUsesGlobalVarForConfigFile() { - $seleniumSettings = array(); - $seleniumBrowsers = array(); - $seleniumTestSuites = array(); - global $wgSeleniumConfigFile; - $this->writeToTempFile( $this->testConfig0 ); - $wgSeleniumConfigFile = $this->tempFileName; - SeleniumConfig::getSeleniumSettings($seleniumSettings, - $seleniumBrowsers, - $seleniumTestSuites); - $this->assertEquals($seleniumSettings, $this->testSettings0 , - 'The selenium settings should have been read from the file defined in $wgSeleniumConfigFile' - ); - $this->assertEquals($seleniumBrowsers, $this->testBrowsers0, - 'The available browsers should have been read from the file defined in $wgSeleniumConfigFile' - ); - $this->assertEquals($seleniumTestSuites, $this->testSuites0, - 'The test suites should have been read from the file defined in $wgSeleniumConfigFile' - ); - } - - /** - * @group SeleniumFramework - * @dataProvider sampleConfigs - */ - public function testgetSeleniumSettings($sampleConfig, $expectedSettings, $expectedBrowsers, $expectedSuites ) { - $this->writeToTempFile( $sampleConfig ); - $seleniumSettings = array(); - $seleniumBrowsers = array(); - $seleniumTestSuites = null; - - SeleniumConfig::getSeleniumSettings($seleniumSettings, - $seleniumBrowsers, - $seleniumTestSuites, - $this->tempFileName ); - - $this->assertEquals($seleniumSettings, $expectedSettings, - "The selenium settings for the following test configuration was not retrieved correctly" . $sampleConfig - ); - $this->assertEquals($seleniumBrowsers, $expectedBrowsers, - "The available browsers for the following test configuration was not retrieved correctly" . $sampleConfig - ); - $this->assertEquals($seleniumTestSuites, $expectedSuites, - "The test suites for the following test configuration was not retrieved correctly" . $sampleConfig - ); - - - } - - /** - * create a temp file and write text to it. - * @param $testToWrite the text to write to the temp file - */ - private function writeToTempFile($textToWrite) { - $this->tempFileName = tempnam(sys_get_temp_dir(), 'test_settings.'); - $tempFile = fopen( $this->tempFileName, "w" ); - fwrite($tempFile , $textToWrite); - fclose($tempFile); - } - - /** - * Returns an array containing: - * The contents of the selenium cingiguration ini file - * The expected selenium configuration array that getSeleniumSettings should return - * The expected available browsers array that getSeleniumSettings should return - * The expected test suites arrya that getSeleniumSettings should return - */ - public function sampleConfigs() { - return array( - array($this->testConfig0, $this->testSettings0, $this->testBrowsers0, $this->testSuites0 ), - array($this->testConfig1, $this->testSettings1, $this->testBrowsers1, $this->testSuites1 ) - ); - } - - -} diff --git a/tests/phpunit/includes/SiteConfigurationTest.php b/tests/phpunit/includes/SiteConfigurationTest.php index 57d3532a..053d8a7d 100644 --- a/tests/phpunit/includes/SiteConfigurationTest.php +++ b/tests/phpunit/includes/SiteConfigurationTest.php @@ -10,6 +10,7 @@ function getSiteParams( $conf, $wiki ) { break; } } + return array( 'suffix' => $site, 'lang' => $lang, @@ -23,12 +24,18 @@ function getSiteParams( $conf, $wiki ) { } class SiteConfigurationTest extends MediaWikiTestCase { - var $mConf; - function setUp() { + /** + * @var SiteConfiguration + */ + protected $mConf; + + protected function setUp() { + parent::setUp(); + $this->mConf = new SiteConfiguration; - $this->mConf->suffixes = array( 'wiki' ); + $this->mConf->suffixes = array( 'wikipedia' => 'wiki' ); $this->mConf->wikis = array( 'enwiki', 'dewiki', 'frwiki' ); $this->mConf->settings = array( 'simple' => array( @@ -92,8 +99,10 @@ class SiteConfigurationTest extends MediaWikiTestCase { $GLOBALS['global'] = array( 'global' => 'global' ); } - - function testSiteFromDb() { + /** + * @covers SiteConfiguration::siteFromDB + */ + public function testSiteFromDb() { $this->assertEquals( array( 'wikipedia', 'en' ), $this->mConf->siteFromDB( 'enwiki' ), @@ -118,7 +127,10 @@ class SiteConfigurationTest extends MediaWikiTestCase { ); } - function testGetLocalDatabases() { + /** + * @covers SiteConfiguration::getLocalDatabases + */ + public function testGetLocalDatabases() { $this->assertEquals( array( 'enwiki', 'dewiki', 'frwiki' ), $this->mConf->getLocalDatabases(), @@ -126,7 +138,10 @@ class SiteConfigurationTest extends MediaWikiTestCase { ); } - function testGetConfVariables() { + /** + * @covers SiteConfiguration::get + */ + public function testGetConfVariables() { $this->assertEquals( 'enwiki', $this->mConf->get( 'simple', 'enwiki', 'wiki' ), @@ -238,7 +253,10 @@ class SiteConfigurationTest extends MediaWikiTestCase { ); } - function testSiteFromDbWithCallback() { + /** + * @covers SiteConfiguration::siteFromDB + */ + public function testSiteFromDbWithCallback() { $this->mConf->siteParamsCallback = 'getSiteParams'; $this->assertEquals( @@ -258,7 +276,10 @@ class SiteConfigurationTest extends MediaWikiTestCase { ); } - function testParameterReplacement() { + /** + * @covers SiteConfiguration::get + */ + public function testParameterReplacement() { $this->mConf->siteParamsCallback = 'getSiteParams'; $this->assertEquals( @@ -288,7 +309,10 @@ class SiteConfigurationTest extends MediaWikiTestCase { ); } - function testGetAllGlobals() { + /** + * @covers SiteConfiguration::getAll + */ + public function testGetAllGlobals() { $this->mConf->siteParamsCallback = 'getSiteParams'; $getall = array( @@ -305,7 +329,7 @@ class SiteConfigurationTest extends MediaWikiTestCase { $this->assertEquals( $getall['simple'], $GLOBALS['simple'], 'extractAllGlobals(): simple setting' ); $this->assertEquals( $getall['fallback'], $GLOBALS['fallback'], 'extractAllGlobals(): fallback setting' ); $this->assertEquals( $getall['params'], $GLOBALS['params'], 'extractAllGlobals(): parameter replacement' ); - $this->assertEquals( $getall['global'], $GLOBALS['global'], 'extractAllGlobals(): merging with global' ); - $this->assertEquals( $getall['merge'], $GLOBALS['merge'], 'extractAllGlobals(): merging setting' ); + $this->assertEquals( $getall['global'], $GLOBALS['global'], 'extractAllGlobals(): merging with global' ); + $this->assertEquals( $getall['merge'], $GLOBALS['merge'], 'extractAllGlobals(): merging setting' ); } } diff --git a/tests/phpunit/includes/StringUtilsTest.php b/tests/phpunit/includes/StringUtilsTest.php new file mode 100644 index 00000000..89759e5c --- /dev/null +++ b/tests/phpunit/includes/StringUtilsTest.php @@ -0,0 +1,147 @@ +<?php + +class StringUtilsTest extends MediaWikiTestCase { + + /** + * This tests StringUtils::isUtf8 whenever we have the mbstring extension + * loaded. + * + * @covers StringUtils::isUtf8 + * @dataProvider provideStringsForIsUtf8Check + */ + public function testIsUtf8WithMbstring( $expected, $string ) { + if ( !function_exists( 'mb_check_encoding' ) ) { + $this->markTestSkipped( 'Test requires the mbstring PHP extension' ); + } + $this->assertEquals( $expected, + StringUtils::isUtf8( $string ), + 'Testing string "' . $this->escaped( $string ) . '" with mb_check_encoding' + ); + } + + /** + * This tests StringUtils::isUtf8 making sure we use the pure PHP + * implementation used as a fallback when mb_check_encoding() is + * not available. + * + * @covers StringUtils::isUtf8 + * @dataProvider provideStringsForIsUtf8Check + */ + public function testIsUtf8WithPhpFallbackImplementation( $expected, $string ) { + $this->assertEquals( $expected, + StringUtils::isUtf8( $string, /** disable mbstring: */true ), + 'Testing string "' . $this->escaped( $string ) . '" with pure PHP implementation' + ); + } + + /** + * Print high range characters as an hexadecimal + */ + function escaped( $string ) { + $escaped = ''; + $length = strlen( $string ); + for ( $i = 0; $i < $length; $i++ ) { + $char = $string[$i]; + $val = ord( $char ); + if ( $val > 127 ) { + $escaped .= '\x' . dechex( $val ); + } else { + $escaped .= $char; + } + } + + return $escaped; + } + + /** + * See also "UTF-8 decoder capability and stress test" by + * Markus Kuhn: + * http://www.cl.cam.ac.uk/~mgk25/ucs/examples/UTF-8-test.txt + */ + public static function provideStringsForIsUtf8Check() { + // Expected return values for StringUtils::isUtf8() + $PASS = true; + $FAIL = false; + + return array( + 'some ASCII' => array( $PASS, 'Some ASCII' ), + 'euro sign' => array( $PASS, "Euro sign €" ), + + 'first possible sequence 1 byte' => array( $PASS, "\x00" ), + 'first possible sequence 2 bytes' => array( $PASS, "\xc2\x80" ), + 'first possible sequence 3 bytes' => array( $PASS, "\xe0\xa0\x80" ), + 'first possible sequence 4 bytes' => array( $PASS, "\xf0\x90\x80\x80" ), + 'first possible sequence 5 bytes' => array( $FAIL, "\xf8\x88\x80\x80\x80" ), + 'first possible sequence 6 bytes' => array( $FAIL, "\xfc\x84\x80\x80\x80\x80" ), + + 'last possible sequence 1 byte' => array( $PASS, "\x7f" ), + 'last possible sequence 2 bytes' => array( $PASS, "\xdf\xbf" ), + 'last possible sequence 3 bytes' => array( $PASS, "\xef\xbf\xbf" ), + 'last possible sequence 4 bytes (U+1FFFFF)' => array( $FAIL, "\xf7\xbf\xbf\xbf" ), + 'last possible sequence 5 bytes' => array( $FAIL, "\xfb\xbf\xbf\xbf\xbf" ), + 'last possible sequence 6 bytes' => array( $FAIL, "\xfd\xbf\xbf\xbf\xbf\xbf" ), + + 'boundary 1' => array( $PASS, "\xed\x9f\xbf" ), + 'boundary 2' => array( $PASS, "\xee\x80\x80" ), + 'boundary 3' => array( $PASS, "\xef\xbf\xbd" ), + 'boundary 4' => array( $PASS, "\xf2\x80\x80\x80" ), + 'boundary 5 (U+FFFFF)' => array( $PASS, "\xf3\xbf\xbf\xbf" ), + 'boundary 6 (U+100000)' => array( $PASS, "\xf4\x80\x80\x80" ), + 'boundary 7 (U+10FFFF)' => array( $PASS, "\xf4\x8f\xbf\xbf" ), + 'boundary 8 (U+110000)' => array( $FAIL, "\xf4\x90\x80\x80" ), + + 'malformed 1' => array( $FAIL, "\x80" ), + 'malformed 2' => array( $FAIL, "\xbf" ), + 'malformed 3' => array( $FAIL, "\x80\xbf" ), + 'malformed 4' => array( $FAIL, "\x80\xbf\x80" ), + 'malformed 5' => array( $FAIL, "\x80\xbf\x80\xbf" ), + 'malformed 6' => array( $FAIL, "\x80\xbf\x80\xbf\x80" ), + 'malformed 7' => array( $FAIL, "\x80\xbf\x80\xbf\x80\xbf" ), + 'malformed 8' => array( $FAIL, "\x80\xbf\x80\xbf\x80\xbf\x80" ), + + 'last byte missing 1' => array( $FAIL, "\xc0" ), + 'last byte missing 2' => array( $FAIL, "\xe0\x80" ), + 'last byte missing 3' => array( $FAIL, "\xf0\x80\x80" ), + 'last byte missing 4' => array( $FAIL, "\xf8\x80\x80\x80" ), + 'last byte missing 5' => array( $FAIL, "\xfc\x80\x80\x80\x80" ), + 'last byte missing 6' => array( $FAIL, "\xdf" ), + 'last byte missing 7' => array( $FAIL, "\xef\xbf" ), + 'last byte missing 8' => array( $FAIL, "\xf7\xbf\xbf" ), + 'last byte missing 9' => array( $FAIL, "\xfb\xbf\xbf\xbf" ), + 'last byte missing 10' => array( $FAIL, "\xfd\xbf\xbf\xbf\xbf" ), + + 'extra continuation byte 1' => array( $FAIL, "e\xaf" ), + 'extra continuation byte 2' => array( $FAIL, "\xc3\x89\xaf" ), + 'extra continuation byte 3' => array( $FAIL, "\xef\xbc\xa5\xaf" ), + 'extra continuation byte 4' => array( $FAIL, "\xf0\x9d\x99\xb4\xaf" ), + + 'impossible bytes 1' => array( $FAIL, "\xfe" ), + 'impossible bytes 2' => array( $FAIL, "\xff" ), + 'impossible bytes 3' => array( $FAIL, "\xfe\xfe\xff\xff" ), + + 'overlong sequences 1' => array( $FAIL, "\xc0\xaf" ), + 'overlong sequences 2' => array( $FAIL, "\xc1\xaf" ), + 'overlong sequences 3' => array( $FAIL, "\xe0\x80\xaf" ), + 'overlong sequences 4' => array( $FAIL, "\xf0\x80\x80\xaf" ), + 'overlong sequences 5' => array( $FAIL, "\xf8\x80\x80\x80\xaf" ), + 'overlong sequences 6' => array( $FAIL, "\xfc\x80\x80\x80\x80\xaf" ), + + 'maximum overlong sequences 1' => array( $FAIL, "\xc1\xbf" ), + 'maximum overlong sequences 2' => array( $FAIL, "\xe0\x9f\xbf" ), + 'maximum overlong sequences 3' => array( $FAIL, "\xf0\x8f\xbf\xbf" ), + 'maximum overlong sequences 4' => array( $FAIL, "\xf8\x87\xbf\xbf" ), + 'maximum overlong sequences 5' => array( $FAIL, "\xfc\x83\xbf\xbf\xbf\xbf" ), + + 'surrogates 1 (U+D799)' => array( $PASS, "\xed\x9f\xbf" ), + 'surrogates 2 (U+E000)' => array( $PASS, "\xee\x80\x80" ), + 'surrogates 3 (U+D800)' => array( $FAIL, "\xed\xa0\x80" ), + 'surrogates 4 (U+DBFF)' => array( $FAIL, "\xed\xaf\xbf" ), + 'surrogates 5 (U+DC00)' => array( $FAIL, "\xed\xb0\x80" ), + 'surrogates 6 (U+DFFF)' => array( $FAIL, "\xed\xbf\xbf" ), + 'surrogates 7 (U+D800 U+DC00)' => array( $FAIL, "\xed\xa0\x80\xed\xb0\x80" ), + + 'noncharacters 1' => array( $PASS, "\xef\xbf\xbe" ), + 'noncharacters 2' => array( $PASS, "\xef\xbf\xbf" ), + ); + } +} diff --git a/tests/phpunit/includes/TemplateCategoriesTest.php b/tests/phpunit/includes/TemplateCategoriesTest.php index 39ce6e31..fb63a564 100644 --- a/tests/phpunit/includes/TemplateCategoriesTest.php +++ b/tests/phpunit/includes/TemplateCategoriesTest.php @@ -7,22 +7,40 @@ require __DIR__ . "/../../../maintenance/runJobs.php"; class TemplateCategoriesTest extends MediaWikiLangTestCase { - function testTemplateCategories() { + /** + * @covers Title::getParentCategories + */ + public function testTemplateCategories() { $title = Title::newFromText( "Categorized from template" ); $page = WikiPage::factory( $title ); $user = new User(); $user->mRights = array( 'createpage', 'edit', 'purge' ); - $status = $page->doEdit( '{{Categorising template}}', 'Create a page with a template', 0, false, $user ); + $page->doEditContent( + new WikitextContent( '{{Categorising template}}' ), + 'Create a page with a template', + 0, + false, + $user + ); + $this->assertEquals( array() , $title->getParentCategories() ); $template = WikiPage::factory( Title::newFromText( 'Template:Categorising template' ) ); - $status = $template->doEdit( '[[Category:Solved bugs]]', 'Add a category through a template', 0, false, $user ); + + $template->doEditContent( + new WikitextContent( '[[Category:Solved bugs]]' ), + 'Add a category through a template', + 0, + false, + $user + ); // Run the job queue + JobQueueGroup::destroySingletons(); $jobs = new RunJobs; $jobs->loadParamsAndArgs( null, array( 'quiet' => true ), null ); $jobs->execute(); @@ -32,5 +50,4 @@ class TemplateCategoriesTest extends MediaWikiLangTestCase { , $title->getParentCategories() ); } - } diff --git a/tests/phpunit/includes/TestUser.php b/tests/phpunit/includes/TestUser.php index c4d89455..23e65031 100644 --- a/tests/phpunit/includes/TestUser.php +++ b/tests/phpunit/includes/TestUser.php @@ -1,6 +1,8 @@ <?php -/* Wraps the user object, so we can also retain full access to properties like password if we log in via the API */ +/** + * Wraps the user object, so we can also retain full access to properties like password if we log in via the API + */ class TestUser { public $username; public $password; @@ -8,7 +10,7 @@ class TestUser { public $groups; public $user; - function __construct( $username, $realname = 'Real Name', $email = 'sample@example.com', $groups = array() ) { + public function __construct( $username, $realname = 'Real Name', $email = 'sample@example.com', $groups = array() ) { $this->username = $username; $this->realname = $realname; $this->email = $email; @@ -53,6 +55,5 @@ class TestUser { } } $this->user->saveSettings(); - } } diff --git a/tests/phpunit/includes/TimeAdjustTest.php b/tests/phpunit/includes/TimeAdjustTest.php index cd027c5b..0b368c25 100644 --- a/tests/phpunit/includes/TimeAdjustTest.php +++ b/tests/phpunit/includes/TimeAdjustTest.php @@ -1,51 +1,41 @@ <?php class TimeAdjustTest extends MediaWikiLangTestCase { - static $offset; - - public function setUp() { + protected function setUp() { parent::setUp(); - global $wgLocalTZoffset; - self::$offset = $wgLocalTZoffset; $this->iniSet( 'precision', 15 ); } - public function tearDown() { - global $wgLocalTZoffset; - $wgLocalTZoffset = self::$offset; - parent::tearDown(); - } + /** + * Test offset usage for a given Language::userAdjust + * @dataProvider dataUserAdjust + * @covers Language::userAdjust + */ + public function testUserAdjust( $date, $localTZoffset, $expected ) { + global $wgContLang; - # Test offset usage for a given language::userAdjust - function testUserAdjust() { - global $wgLocalTZoffset, $wgContLang; + $this->setMwGlobals( 'wgLocalTZoffset', $localTZoffset ); - $wgContLang = $en = Language::factory( 'en' ); + $this->assertEquals( + strval( $expected ), + strval( $wgContLang->userAdjust( $date, '' ) ), + "User adjust {$date} by {$localTZoffset} minutes should give {$expected}" + ); + } - # Collection of parameters for Language_t_Offset. - # Format: date to be formatted, localTZoffset value, expected date - $userAdjust_tests = array( - array( 20061231235959, 0, 20061231235959 ), - array( 20061231235959, 5, 20070101000459 ), - array( 20061231235959, 15, 20070101001459 ), - array( 20061231235959, 60, 20070101005959 ), - array( 20061231235959, 90, 20070101012959 ), + public static function dataUserAdjust() { + return array( + array( 20061231235959, 0, 20061231235959 ), + array( 20061231235959, 5, 20070101000459 ), + array( 20061231235959, 15, 20070101001459 ), + array( 20061231235959, 60, 20070101005959 ), + array( 20061231235959, 90, 20070101012959 ), array( 20061231235959, 120, 20070101015959 ), array( 20061231235959, 540, 20070101085959 ), - array( 20061231235959, -5, 20061231235459 ), + array( 20061231235959, -5, 20061231235459 ), array( 20061231235959, -30, 20061231232959 ), array( 20061231235959, -60, 20061231225959 ), ); - - foreach ( $userAdjust_tests as $data ) { - $wgLocalTZoffset = $data[1]; - - $this->assertEquals( - strval( $data[2] ), - strval( $en->userAdjust( $data[0], '' ) ), - "User adjust {$data[0]} by {$data[1]} minutes should give {$data[2]}" - ); - } } } diff --git a/tests/phpunit/includes/TimestampTest.php b/tests/phpunit/includes/TimestampTest.php index 231228f5..53388392 100644 --- a/tests/phpunit/includes/TimestampTest.php +++ b/tests/phpunit/includes/TimestampTest.php @@ -3,12 +3,20 @@ /** * Tests timestamp parsing and output. */ -class TimestampTest extends MediaWikiTestCase { +class TimestampTest extends MediaWikiLangTestCase { + + protected function setUp() { + parent::setUp(); + + RequestContext::getMain()->setLanguage( Language::factory( 'en' ) ); + } + /** * Test parsing of valid timestamps and outputing to MW format. * @dataProvider provideValidTimestamps + * @covers MWTimestamp::getTimestamp */ - function testValidParse( $format, $original, $expected ) { + public function testValidParse( $format, $original, $expected ) { $timestamp = new MWTimestamp( $original ); $this->assertEquals( $expected, $timestamp->getTimestamp( TS_MW ) ); } @@ -16,42 +24,37 @@ class TimestampTest extends MediaWikiTestCase { /** * Test outputting valid timestamps to different formats. * @dataProvider provideValidTimestamps + * @covers MWTimestamp::getTimestamp */ - function testValidOutput( $format, $expected, $original ) { + public function testValidOutput( $format, $expected, $original ) { $timestamp = new MWTimestamp( $original ); - $this->assertEquals( $expected, (string) $timestamp->getTimestamp( $format ) ); + $this->assertEquals( $expected, (string)$timestamp->getTimestamp( $format ) ); } /** * Test an invalid timestamp. * @expectedException TimestampException + * @covers MWTimestamp */ - function testInvalidParse() { - $timestamp = new MWTimestamp( "This is not a timestamp." ); + public function testInvalidParse() { + new MWTimestamp( "This is not a timestamp." ); } /** * Test requesting an invalid output format. * @expectedException TimestampException + * @covers MWTimestamp::getTimestamp */ - function testInvalidOutput() { + public function testInvalidOutput() { $timestamp = new MWTimestamp( '1343761268' ); $timestamp->getTimestamp( 98 ); } /** - * Test human readable timestamp format. - */ - function testHumanOutput() { - $timestamp = new MWTimestamp( time() - 3600 ); - $this->assertEquals( "1 hour ago", $timestamp->getHumanTimestamp()->toString() ); - } - - /** * Returns a list of valid timestamps in the format: * array( type, timestamp_of_type, timestamp_in_MW ) */ - function provideValidTimestamps() { + public static function provideValidTimestamps() { return array( // Various formats array( TS_UNIX, '1343761268', '20120731190108' ), @@ -63,10 +66,239 @@ class TimestampTest extends MediaWikiTestCase { array( TS_RFC2822, 'Tue, 31 Jul 2012 19:01:08 GMT', '20120731190108' ), array( TS_ORACLE, '31-07-2012 19:01:08.000000', '20120731190108' ), array( TS_POSTGRES, '2012-07-31 19:01:08 GMT', '20120731190108' ), - array( TS_DB2, '2012-07-31 19:01:08', '20120731190108' ), // Some extremes and weird values array( TS_ISO_8601, '9999-12-31T23:59:59Z', '99991231235959' ), array( TS_UNIX, '-62135596801', '00001231235959' ) ); } + + /** + * @dataProvider provideHumanTimestampTests + * @covers MWTimestamp::getHumanTimestamp + */ + public function testHumanTimestamp( + $tsTime, // The timestamp to format + $currentTime, // The time to consider "now" + $timeCorrection, // The time offset to use + $dateFormat, // The date preference to use + $expectedOutput, // The expected output + $desc // Description + ) { + $user = $this->getMock( 'User' ); + $user->expects( $this->any() ) + ->method( 'getOption' ) + ->with( 'timecorrection' ) + ->will( $this->returnValue( $timeCorrection ) ); + + $user->expects( $this->any() ) + ->method( 'getDatePreference' ) + ->will( $this->returnValue( $dateFormat ) ); + + $tsTime = new MWTimestamp( $tsTime ); + $currentTime = new MWTimestamp( $currentTime ); + + $this->assertEquals( + $expectedOutput, + $tsTime->getHumanTimestamp( $currentTime, $user ), + $desc + ); + } + + public static function provideHumanTimestampTests() { + return array( + array( + '20111231170000', + '20120101000000', + 'Offset|0', + 'mdy', + 'Yesterday at 17:00', + '"Yesterday" across years', + ), + array( + '20120717190900', + '20120717190929', + 'Offset|0', + 'mdy', + 'just now', + '"Just now"', + ), + array( + '20120717190900', + '20120717191530', + 'Offset|0', + 'mdy', + '6 minutes ago', + 'X minutes ago', + ), + array( + '20121006173100', + '20121006173200', + 'Offset|0', + 'mdy', + '1 minute ago', + '"1 minute ago"', + ), + array( + '20120617190900', + '20120717190900', + 'Offset|0', + 'mdy', + 'June 17', + 'Another month' + ), + array( + '19910130151500', + '20120716193700', + 'Offset|0', + 'mdy', + '15:15, January 30, 1991', + 'Different year', + ), + array( + '20120101050000', + '20120101080000', + 'Offset|-360', + 'mdy', + 'Yesterday at 23:00', + '"Yesterday" across years with time correction', + ), + array( + '20120714184300', + '20120716184300', + 'Offset|-420', + 'mdy', + 'Saturday at 11:43', + 'Recent weekday with time correction', + ), + array( + '20120714184300', + '20120715040000', + 'Offset|-420', + 'mdy', + '11:43', + 'Today at another time with time correction', + ), + array( + '20120617190900', + '20120717190900', + 'Offset|0', + 'dmy', + '17 June', + 'Another month with dmy' + ), + array( + '20120617190900', + '20120717190900', + 'Offset|0', + 'ISO 8601', + '06-17', + 'Another month with ISO-8601' + ), + array( + '19910130151500', + '20120716193700', + 'Offset|0', + 'ISO 8601', + '1991-01-30T15:15:00', + 'Different year with ISO-8601', + ), + ); + } + + /** + * @dataProvider provideRelativeTimestampTests + * @covers MWTimestamp::getRelativeTimestamp + */ + public function testRelativeTimestamp( + $tsTime, // The timestamp to format + $currentTime, // The time to consider "now" + $timeCorrection, // The time offset to use + $dateFormat, // The date preference to use + $expectedOutput, // The expected output + $desc // Description + ) { + $user = $this->getMock( 'User' ); + $user->expects( $this->any() ) + ->method( 'getOption' ) + ->with( 'timecorrection' ) + ->will( $this->returnValue( $timeCorrection ) ); + + $tsTime = new MWTimestamp( $tsTime ); + $currentTime = new MWTimestamp( $currentTime ); + + $this->assertEquals( + $expectedOutput, + $tsTime->getRelativeTimestamp( $currentTime, $user ), + $desc + ); + } + + public static function provideRelativeTimestampTests() { + return array( + array( + '20111231170000', + '20120101000000', + 'Offset|0', + 'mdy', + '7 hours ago', + '"Yesterday" across years', + ), + array( + '20120717190900', + '20120717190929', + 'Offset|0', + 'mdy', + '29 seconds ago', + '"Just now"', + ), + array( + '20120717190900', + '20120717191530', + 'Offset|0', + 'mdy', + '6 minutes and 30 seconds ago', + 'Combination of multiple units', + ), + array( + '20121006173100', + '20121006173200', + 'Offset|0', + 'mdy', + '1 minute ago', + '"1 minute ago"', + ), + array( + '19910130151500', + '20120716193700', + 'Offset|0', + 'mdy', + '2 decades, 1 year, 168 days, 2 hours, 8 minutes and 48 seconds ago', + 'A long time ago', + ), + array( + '20120101050000', + '20120101080000', + 'Offset|-360', + 'mdy', + '3 hours ago', + '"Yesterday" across years with time correction', + ), + array( + '20120714184300', + '20120716184300', + 'Offset|-420', + 'mdy', + '2 days ago', + 'Recent weekday with time correction', + ), + array( + '20120714184300', + '20120715040000', + 'Offset|-420', + 'mdy', + '9 hours and 17 minutes ago', + 'Today at another time with time correction', + ), + ); + } } diff --git a/tests/phpunit/includes/TitleMethodsTest.php b/tests/phpunit/includes/TitleMethodsTest.php index aed658ba..3079d73a 100644 --- a/tests/phpunit/includes/TitleMethodsTest.php +++ b/tests/phpunit/includes/TitleMethodsTest.php @@ -1,8 +1,48 @@ <?php +/** + * @group ContentHandler + * @group Database + * + * @note: We don't make assumptions about the main namespace. + * But we do expect the Help namespace to contain Wikitext. + */ class TitleMethodsTest extends MediaWikiTestCase { - public function dataEquals() { + public function setUp() { + global $wgContLang; + + parent::setUp(); + + $this->mergeMwGlobalArrayValue( + 'wgExtraNamespaces', + array( + 12302 => 'TEST-JS', + 12303 => 'TEST-JS_TALK', + ) + ); + + $this->mergeMwGlobalArrayValue( + 'wgNamespaceContentModels', + array( + 12302 => CONTENT_MODEL_JAVASCRIPT, + ) + ); + + MWNamespace::getCanonicalNamespaces( true ); # reset namespace cache + $wgContLang->resetNamespaces(); # reset namespace cache + } + + public function tearDown() { + global $wgContLang; + + parent::tearDown(); + + MWNamespace::getCanonicalNamespaces( true ); # reset namespace cache + $wgContLang->resetNamespaces(); # reset namespace cache + } + + public static function provideEquals() { return array( array( 'Main Page', 'Main Page', true ), array( 'Main Page', 'Not The Main Page', false ), @@ -15,7 +55,8 @@ class TitleMethodsTest extends MediaWikiTestCase { } /** - * @dataProvider dataEquals + * @dataProvider provideEquals + * @covers Title::equals */ public function testEquals( $titleA, $titleB, $expectedBool ) { $titleA = Title::newFromText( $titleA ); @@ -25,7 +66,7 @@ class TitleMethodsTest extends MediaWikiTestCase { $this->assertEquals( $expectedBool, $titleB->equals( $titleA ) ); } - public function dataInNamespace() { + public static function provideInNamespace() { return array( array( 'Main Page', NS_MAIN, true ), array( 'Main Page', NS_TALK, false ), @@ -39,13 +80,17 @@ class TitleMethodsTest extends MediaWikiTestCase { } /** - * @dataProvider dataInNamespace + * @dataProvider provideInNamespace + * @covers Title::inNamespace */ public function testInNamespace( $title, $ns, $expectedBool ) { $title = Title::newFromText( $title ); $this->assertEquals( $expectedBool, $title->inNamespace( $ns ) ); } + /** + * @covers Title::inNamespaces + */ public function testInNamespaces() { $mainpage = Title::newFromText( 'Main Page' ); $this->assertTrue( $mainpage->inNamespaces( NS_MAIN, NS_USER ) ); @@ -54,7 +99,7 @@ class TitleMethodsTest extends MediaWikiTestCase { $this->assertFalse( $mainpage->inNamespaces( array( NS_PROJECT, NS_TEMPLATE ) ) ); } - public function dataHasSubjectNamespace() { + public static function provideHasSubjectNamespace() { return array( array( 'Main Page', NS_MAIN, true ), array( 'Main Page', NS_TALK, true ), @@ -68,18 +113,62 @@ class TitleMethodsTest extends MediaWikiTestCase { } /** - * @dataProvider dataHasSubjectNamespace + * @dataProvider provideHasSubjectNamespace + * @covers Title::hasSubjectNamespace */ public function testHasSubjectNamespace( $title, $ns, $expectedBool ) { $title = Title::newFromText( $title ); $this->assertEquals( $expectedBool, $title->hasSubjectNamespace( $ns ) ); } - public function dataIsCssOrJsPage() { + public function dataGetContentModel() { + return array( + array( 'Help:Foo', CONTENT_MODEL_WIKITEXT ), + array( 'Help:Foo.js', CONTENT_MODEL_WIKITEXT ), + array( 'Help:Foo/bar.js', CONTENT_MODEL_WIKITEXT ), + array( 'User:Foo', CONTENT_MODEL_WIKITEXT ), + array( 'User:Foo.js', CONTENT_MODEL_WIKITEXT ), + array( 'User:Foo/bar.js', CONTENT_MODEL_JAVASCRIPT ), + array( 'User:Foo/bar.css', CONTENT_MODEL_CSS ), + array( 'User talk:Foo/bar.css', CONTENT_MODEL_WIKITEXT ), + array( 'User:Foo/bar.js.xxx', CONTENT_MODEL_WIKITEXT ), + array( 'User:Foo/bar.xxx', CONTENT_MODEL_WIKITEXT ), + array( 'MediaWiki:Foo.js', CONTENT_MODEL_JAVASCRIPT ), + array( 'MediaWiki:Foo.css', CONTENT_MODEL_CSS ), + array( 'MediaWiki:Foo/bar.css', CONTENT_MODEL_CSS ), + array( 'MediaWiki:Foo.JS', CONTENT_MODEL_WIKITEXT ), + array( 'MediaWiki:Foo.CSS', CONTENT_MODEL_WIKITEXT ), + array( 'MediaWiki:Foo.css.xxx', CONTENT_MODEL_WIKITEXT ), + array( 'TEST-JS:Foo', CONTENT_MODEL_JAVASCRIPT ), + array( 'TEST-JS:Foo.js', CONTENT_MODEL_JAVASCRIPT ), + array( 'TEST-JS:Foo/bar.js', CONTENT_MODEL_JAVASCRIPT ), + array( 'TEST-JS_TALK:Foo.js', CONTENT_MODEL_WIKITEXT ), + ); + } + + /** + * @dataProvider dataGetContentModel + * @covers Title::getContentModel + */ + public function testGetContentModel( $title, $expectedModelId ) { + $title = Title::newFromText( $title ); + $this->assertEquals( $expectedModelId, $title->getContentModel() ); + } + + /** + * @dataProvider dataGetContentModel + * @covers Title::hasContentModel + */ + public function testHasContentModel( $title, $expectedModelId ) { + $title = Title::newFromText( $title ); + $this->assertTrue( $title->hasContentModel( $expectedModelId ) ); + } + + public static function provideIsCssOrJsPage() { return array( - array( 'Foo', false ), - array( 'Foo.js', false ), - array( 'Foo/bar.js', false ), + array( 'Help:Foo', false ), + array( 'Help:Foo.js', false ), + array( 'Help:Foo/bar.js', false ), array( 'User:Foo', false ), array( 'User:Foo.js', false ), array( 'User:Foo/bar.js', false ), @@ -92,23 +181,25 @@ class TitleMethodsTest extends MediaWikiTestCase { array( 'MediaWiki:Foo.JS', false ), array( 'MediaWiki:Foo.CSS', false ), array( 'MediaWiki:Foo.css.xxx', false ), + array( 'TEST-JS:Foo', false ), + array( 'TEST-JS:Foo.js', false ), ); } /** - * @dataProvider dataIsCssOrJsPage + * @dataProvider provideIsCssOrJsPage + * @covers Title::isCssOrJsPage */ public function testIsCssOrJsPage( $title, $expectedBool ) { $title = Title::newFromText( $title ); $this->assertEquals( $expectedBool, $title->isCssOrJsPage() ); } - - public function dataIsCssJsSubpage() { + public static function provideIsCssJsSubpage() { return array( - array( 'Foo', false ), - array( 'Foo.js', false ), - array( 'Foo/bar.js', false ), + array( 'Help:Foo', false ), + array( 'Help:Foo.js', false ), + array( 'Help:Foo/bar.js', false ), array( 'User:Foo', false ), array( 'User:Foo.js', false ), array( 'User:Foo/bar.js', true ), @@ -119,21 +210,24 @@ class TitleMethodsTest extends MediaWikiTestCase { array( 'MediaWiki:Foo.js', false ), array( 'User:Foo/bar.JS', false ), array( 'User:Foo/bar.CSS', false ), + array( 'TEST-JS:Foo', false ), + array( 'TEST-JS:Foo.js', false ), ); } /** - * @dataProvider dataIsCssJsSubpage + * @dataProvider provideIsCssJsSubpage + * @covers Title::isCssJsSubpage */ public function testIsCssJsSubpage( $title, $expectedBool ) { $title = Title::newFromText( $title ); $this->assertEquals( $expectedBool, $title->isCssJsSubpage() ); } - public function dataIsCssSubpage() { + public static function provideIsCssSubpage() { return array( - array( 'Foo', false ), - array( 'Foo.css', false ), + array( 'Help:Foo', false ), + array( 'Help:Foo.css', false ), array( 'User:Foo', false ), array( 'User:Foo.js', false ), array( 'User:Foo.css', false ), @@ -143,17 +237,18 @@ class TitleMethodsTest extends MediaWikiTestCase { } /** - * @dataProvider dataIsCssSubpage + * @dataProvider provideIsCssSubpage + * @covers Title::isCssSubpage */ public function testIsCssSubpage( $title, $expectedBool ) { $title = Title::newFromText( $title ); $this->assertEquals( $expectedBool, $title->isCssSubpage() ); } - public function dataIsJsSubpage() { + public static function provideIsJsSubpage() { return array( - array( 'Foo', false ), - array( 'Foo.css', false ), + array( 'Help:Foo', false ), + array( 'Help:Foo.css', false ), array( 'User:Foo', false ), array( 'User:Foo.js', false ), array( 'User:Foo.css', false ), @@ -163,18 +258,19 @@ class TitleMethodsTest extends MediaWikiTestCase { } /** - * @dataProvider dataIsJsSubpage + * @dataProvider provideIsJsSubpage + * @covers Title::isJsSubpage */ public function testIsJsSubpage( $title, $expectedBool ) { $title = Title::newFromText( $title ); $this->assertEquals( $expectedBool, $title->isJsSubpage() ); } - public function dataIsWikitextPage() { + public static function provideIsWikitextPage() { return array( - array( 'Foo', true ), - array( 'Foo.js', true ), - array( 'Foo/bar.js', true ), + array( 'Help:Foo', true ), + array( 'Help:Foo.js', true ), + array( 'Help:Foo/bar.js', true ), array( 'User:Foo', true ), array( 'User:Foo.js', true ), array( 'User:Foo/bar.js', false ), @@ -187,15 +283,18 @@ class TitleMethodsTest extends MediaWikiTestCase { array( 'MediaWiki:Foo/bar.css', false ), array( 'User:Foo/bar.JS', true ), array( 'User:Foo/bar.CSS', true ), + array( 'TEST-JS:Foo', false ), + array( 'TEST-JS:Foo.js', false ), + array( 'TEST-JS_TALK:Foo.js', true ), ); } /** - * @dataProvider dataIsWikitextPage + * @dataProvider provideIsWikitextPage + * @covers Title::isWikitextPage */ public function testIsWikitextPage( $title, $expectedBool ) { $title = Title::newFromText( $title ); $this->assertEquals( $expectedBool, $title->isWikitextPage() ); } - } diff --git a/tests/phpunit/includes/TitlePermissionTest.php b/tests/phpunit/includes/TitlePermissionTest.php index f62ac5dd..f15c1772 100644 --- a/tests/phpunit/includes/TitlePermissionTest.php +++ b/tests/phpunit/includes/TitlePermissionTest.php @@ -2,33 +2,47 @@ /** * @group Database + * @todo covers tags */ class TitlePermissionTest extends MediaWikiLangTestCase { - protected $title; /** - * @var User + * @var string */ - protected $user, $anonUser, $userUser, $altUser; + protected $userName, $altUserName; /** - * @var string + * @var Title */ - protected $userName, $altUserName; + protected $title; - function setUp() { - global $wgLocaltimezone, $wgLocalTZoffset, $wgMemc, $wgContLang, $wgLang; - parent::setUp(); + /** + * @var User + */ + protected $user, $anonUser, $userUser, $altUser; - if(!$wgMemc) { - $wgMemc = new EmptyBagOStuff; - } - $wgContLang = $wgLang = Language::factory( 'en' ); + protected function setUp() { + parent::setUp(); - $this->userName = "Useruser"; - $this->altUserName = "Altuseruser"; - date_default_timezone_set( $wgLocaltimezone ); - $wgLocalTZoffset = date( "Z" ) / 60; + $langObj = Language::factory( 'en' ); + $localZone = 'UTC'; + $localOffset = date( 'Z' ) / 60; + + $this->setMwGlobals( array( + 'wgMemc' => new EmptyBagOStuff, + 'wgContLang' => $langObj, + 'wgLanguageCode' => 'en', + 'wgLang' => $langObj, + 'wgLocaltimezone' => $localZone, + 'wgLocalTZoffset' => $localOffset, + 'wgNamespaceProtection' => array( + NS_MEDIAWIKI => 'editinterface', + ), + ) ); + + $this->userName = 'Useruser'; + $this->altUserName = 'Altuseruser'; + date_default_timezone_set( $localZone ); $this->title = Title::makeTitle( NS_MAIN, "Main Page" ); if ( !isset( $this->userUser ) || !( $this->userUser instanceOf User ) ) { @@ -55,11 +69,7 @@ class TitlePermissionTest extends MediaWikiLangTestCase { } } - function tearDown() { - parent::tearDown(); - } - - function setUserPerm( $perm ) { + protected function setUserPerm( $perm ) { // Setting member variables is evil!!! if ( is_array( $perm ) ) { @@ -69,11 +79,11 @@ class TitlePermissionTest extends MediaWikiLangTestCase { } } - function setTitle( $ns, $title = "Main_Page" ) { + protected function setTitle( $ns, $title = "Main_Page" ) { $this->title = Title::makeTitle( $ns, $title ); } - function setUser( $userName = null ) { + protected function setUser( $userName = null ) { if ( $userName === 'anon' ) { $this->user = $this->anonUser; } elseif ( $userName === null || $userName === $this->userName ) { @@ -81,12 +91,13 @@ class TitlePermissionTest extends MediaWikiLangTestCase { } else { $this->user = $this->altUser; } - - global $wgUser; - $wgUser = $this->user; } - function testQuickPermissions() { + /** + * @todo This test method should be split up into separate test methods and + * data providers + */ + public function testQuickPermissions() { global $wgContLang; $prefix = $wgContLang->getFormattedNsText( NS_PROJECT ); @@ -109,7 +120,7 @@ class TitlePermissionTest extends MediaWikiLangTestCase { $this->setTitle( NS_MAIN ); $this->setUserPerm( "createpage" ); $res = $this->title->getUserPermissionsErrors( 'create', $this->user ); - $this->assertEquals( array( ), $res ); + $this->assertEquals( array(), $res ); $this->setTitle( NS_MAIN ); $this->setUserPerm( "createtalk" ); @@ -120,7 +131,7 @@ class TitlePermissionTest extends MediaWikiLangTestCase { $this->setTitle( NS_TALK ); $this->setUserPerm( "createtalk" ); $res = $this->title->getUserPermissionsErrors( 'create', $this->user ); - $this->assertEquals( array( ), $res ); + $this->assertEquals( array(), $res ); $this->setTitle( NS_TALK ); $this->setUserPerm( "createpage" ); @@ -135,7 +146,7 @@ class TitlePermissionTest extends MediaWikiLangTestCase { $this->setTitle( NS_MAIN ); $this->setUserPerm( "createpage" ); $res = $this->title->getUserPermissionsErrors( 'create', $this->user ); - $this->assertEquals( array( ), $res ); + $this->assertEquals( array(), $res ); $this->setTitle( NS_MAIN ); $this->setUserPerm( "createtalk" ); @@ -225,36 +236,41 @@ class TitlePermissionTest extends MediaWikiLangTestCase { $this->runGroupPermissions( 'move', array( array( 'movenotallowedfile' ), array( 'movenotallowed' ) ), array( array( 'movenotallowedfile' ), array( 'movenologintext' ) ) ); - $this->setTitle( NS_MAIN ); - $this->setUser( 'anon' ); - $this->setUserPerm( "move" ); - $this->runGroupPermissions( 'move', array( ) ); + if ( $this->isWikitextNS( NS_MAIN ) ) { + //NOTE: some content models don't allow moving + // @todo find a Wikitext namespace for testing - $this->setUserPerm( "" ); - $this->runGroupPermissions( 'move', array( array( 'movenotallowed' ) ), - array( array( 'movenologintext' ) ) ); + $this->setTitle( NS_MAIN ); + $this->setUser( 'anon' ); + $this->setUserPerm( "move" ); + $this->runGroupPermissions( 'move', array() ); - $this->setUser( $this->userName ); - $this->setUserPerm( "" ); - $this->runGroupPermissions( 'move', array( array( 'movenotallowed' ) ) ); + $this->setUserPerm( "" ); + $this->runGroupPermissions( 'move', array( array( 'movenotallowed' ) ), + array( array( 'movenologintext' ) ) ); - $this->setUserPerm( "move" ); - $this->runGroupPermissions( 'move', array( ) ); + $this->setUser( $this->userName ); + $this->setUserPerm( "" ); + $this->runGroupPermissions( 'move', array( array( 'movenotallowed' ) ) ); - $this->setUser( 'anon' ); - $this->setUserPerm( 'move' ); - $res = $this->title->getUserPermissionsErrors( 'move-target', $this->user ); - $this->assertEquals( array( ), $res ); + $this->setUserPerm( "move" ); + $this->runGroupPermissions( 'move', array() ); - $this->setUserPerm( '' ); - $res = $this->title->getUserPermissionsErrors( 'move-target', $this->user ); - $this->assertEquals( array( array( 'movenotallowed' ) ), $res ); + $this->setUser( 'anon' ); + $this->setUserPerm( 'move' ); + $res = $this->title->getUserPermissionsErrors( 'move-target', $this->user ); + $this->assertEquals( array(), $res ); + + $this->setUserPerm( '' ); + $res = $this->title->getUserPermissionsErrors( 'move-target', $this->user ); + $this->assertEquals( array( array( 'movenotallowed' ) ), $res ); + } $this->setTitle( NS_USER ); $this->setUser( $this->userName ); $this->setUserPerm( array( "move", "move-rootuserpages" ) ); $res = $this->title->getUserPermissionsErrors( 'move-target', $this->user ); - $this->assertEquals( array( ), $res ); + $this->assertEquals( array(), $res ); $this->setUserPerm( "move" ); $res = $this->title->getUserPermissionsErrors( 'move-target', $this->user ); @@ -263,27 +279,26 @@ class TitlePermissionTest extends MediaWikiLangTestCase { $this->setUser( 'anon' ); $this->setUserPerm( array( "move", "move-rootuserpages" ) ); $res = $this->title->getUserPermissionsErrors( 'move-target', $this->user ); - $this->assertEquals( array( ), $res ); + $this->assertEquals( array(), $res ); $this->setTitle( NS_USER, "User/subpage" ); $this->setUserPerm( array( "move", "move-rootuserpages" ) ); $res = $this->title->getUserPermissionsErrors( 'move-target', $this->user ); - $this->assertEquals( array( ), $res ); + $this->assertEquals( array(), $res ); $this->setUserPerm( "move" ); $res = $this->title->getUserPermissionsErrors( 'move-target', $this->user ); - $this->assertEquals( array( ), $res ); + $this->assertEquals( array(), $res ); $this->setUser( 'anon' ); $check = array( 'edit' => array( array( array( 'badaccess-groups', "*, [[$prefix:Users|Users]]", 2 ) ), - array( array( 'badaccess-group0' ) ), - array( ), true ), - 'protect' => array( array( array( 'badaccess-groups', "[[$prefix:Administrators|Administrators]]", 1 ), array( 'protect-cantedit' ) ), - array( array( 'badaccess-group0' ), array( 'protect-cantedit' ) ), - array( array( 'protect-cantedit' ) ), false ), - '' => array( array( ), array( ), array( ), true ) ); - global $wgUser; - $wgUser = $this->user; + array( array( 'badaccess-group0' ) ), + array(), true ), + 'protect' => array( array( array( 'badaccess-groups', "[[$prefix:Administrators|Administrators]]", 1 ), array( 'protect-cantedit' ) ), + array( array( 'badaccess-group0' ), array( 'protect-cantedit' ) ), + array( array( 'protect-cantedit' ) ), false ), + '' => array( array(), array(), array(), true ) ); + foreach ( array( "edit", "protect", "" ) as $action ) { $this->setUserPerm( null ); $this->assertEquals( $check[$action][0], @@ -303,18 +318,19 @@ class TitlePermissionTest extends MediaWikiLangTestCase { $this->setUserPerm( $action ); $this->assertEquals( $check[$action][3], - $this->title->userCan( $action, true ) ); + $this->title->userCan( $action, $this->user, true ) ); $this->assertEquals( $check[$action][3], - $this->title->quickUserCan( $action ) ); - + $this->title->quickUserCan( $action, $this->user ) ); # count( User::getGroupsWithPermissions( $action ) ) < 1 } } - function runGroupPermissions( $action, $result, $result2 = null ) { + protected function runGroupPermissions( $action, $result, $result2 = null ) { global $wgGroupPermissions; - if ( $result2 === null ) $result2 = $result; + if ( $result2 === null ) { + $result2 = $result; + } $wgGroupPermissions['autoconfirmed']['move'] = false; $wgGroupPermissions['user']['move'] = false; @@ -337,185 +353,247 @@ class TitlePermissionTest extends MediaWikiLangTestCase { $this->assertEquals( $result2, $res ); } - function testSpecialsAndNSPermissions() { + /** + * @todo This test method should be split up into separate test methods and + * data providers + */ + public function testSpecialsAndNSPermissions() { + global $wgNamespaceProtection; $this->setUser( $this->userName ); - global $wgUser; - $wgUser = $this->user; $this->setTitle( NS_SPECIAL ); $this->assertEquals( array( array( 'badaccess-group0' ), array( 'ns-specialprotected' ) ), - $this->title->getUserPermissionsErrors( 'bogus', $this->user ) ); - $this->assertEquals( array( array( 'badaccess-group0' ) ), - $this->title->getUserPermissionsErrors( 'execute', $this->user ) ); + $this->title->getUserPermissionsErrors( 'bogus', $this->user ) ); $this->setTitle( NS_MAIN ); $this->setUserPerm( 'bogus' ); - $this->assertEquals( array( ), - $this->title->getUserPermissionsErrors( 'bogus', $this->user ) ); + $this->assertEquals( array(), + $this->title->getUserPermissionsErrors( 'bogus', $this->user ) ); $this->setTitle( NS_MAIN ); $this->setUserPerm( '' ); $this->assertEquals( array( array( 'badaccess-group0' ) ), - $this->title->getUserPermissionsErrors( 'bogus', $this->user ) ); + $this->title->getUserPermissionsErrors( 'bogus', $this->user ) ); + + $wgNamespaceProtection[NS_USER] = array( 'bogus' ); - global $wgNamespaceProtection; - $wgNamespaceProtection[NS_USER] = array ( 'bogus' ); $this->setTitle( NS_USER ); $this->setUserPerm( '' ); $this->assertEquals( array( array( 'badaccess-group0' ), array( 'namespaceprotected', 'User' ) ), - $this->title->getUserPermissionsErrors( 'bogus', $this->user ) ); + $this->title->getUserPermissionsErrors( 'bogus', $this->user ) ); $this->setTitle( NS_MEDIAWIKI ); $this->setUserPerm( 'bogus' ); $this->assertEquals( array( array( 'protectedinterface' ) ), - $this->title->getUserPermissionsErrors( 'bogus', $this->user ) ); + $this->title->getUserPermissionsErrors( 'bogus', $this->user ) ); $this->setTitle( NS_MEDIAWIKI ); $this->setUserPerm( 'bogus' ); $this->assertEquals( array( array( 'protectedinterface' ) ), - $this->title->getUserPermissionsErrors( 'bogus', $this->user ) ); + $this->title->getUserPermissionsErrors( 'bogus', $this->user ) ); $wgNamespaceProtection = null; + $this->setUserPerm( 'bogus' ); - $this->assertEquals( array( ), - $this->title->getUserPermissionsErrors( 'bogus', $this->user ) ); + $this->assertEquals( array(), + $this->title->getUserPermissionsErrors( 'bogus', $this->user ) ); $this->assertEquals( true, - $this->title->userCan( 'bogus' ) ); + $this->title->userCan( 'bogus', $this->user ) ); $this->setUserPerm( '' ); $this->assertEquals( array( array( 'badaccess-group0' ) ), - $this->title->getUserPermissionsErrors( 'bogus', $this->user ) ); + $this->title->getUserPermissionsErrors( 'bogus', $this->user ) ); $this->assertEquals( false, - $this->title->userCan( 'bogus' ) ); + $this->title->userCan( 'bogus', $this->user ) ); } - function testCssAndJavascriptPermissions() { + /** + * @todo This test method should be split up into separate test methods and + * data providers + */ + public function testCssAndJavascriptPermissions() { $this->setUser( $this->userName ); - global $wgUser; - $wgUser = $this->user; + + $this->setTitle( NS_USER, $this->userName . '/test.js' ); + $this->runCSSandJSPermissions( + array( array( 'badaccess-group0' ), array( 'mycustomjsprotected' ) ), + array( array( 'badaccess-group0' ), array( 'mycustomjsprotected' ) ), + array( array( 'badaccess-group0' ) ), + array( array( 'badaccess-group0' ), array( 'mycustomjsprotected' ) ), + array( array( 'badaccess-group0' ) ) + ); + + $this->setTitle( NS_USER, $this->userName . '/test.css' ); + $this->runCSSandJSPermissions( + array( array( 'badaccess-group0' ), array( 'mycustomcssprotected' ) ), + array( array( 'badaccess-group0' ) ), + array( array( 'badaccess-group0' ), array( 'mycustomcssprotected' ) ), + array( array( 'badaccess-group0' ) ), + array( array( 'badaccess-group0' ), array( 'mycustomcssprotected' ) ) + ); $this->setTitle( NS_USER, $this->altUserName . '/test.js' ); $this->runCSSandJSPermissions( array( array( 'badaccess-group0' ), array( 'customjsprotected' ) ), array( array( 'badaccess-group0' ), array( 'customjsprotected' ) ), - array( array( 'badaccess-group0' ) ) ); + array( array( 'badaccess-group0' ), array( 'customjsprotected' ) ), + array( array( 'badaccess-group0' ), array( 'customjsprotected' ) ), + array( array( 'badaccess-group0' ) ) + ); $this->setTitle( NS_USER, $this->altUserName . '/test.css' ); $this->runCSSandJSPermissions( array( array( 'badaccess-group0' ), array( 'customcssprotected' ) ), + array( array( 'badaccess-group0' ), array( 'customcssprotected' ) ), + array( array( 'badaccess-group0' ), array( 'customcssprotected' ) ), array( array( 'badaccess-group0' ) ), - array( array( 'badaccess-group0' ), array( 'customcssprotected' ) ) ); + array( array( 'badaccess-group0' ), array( 'customcssprotected' ) ) + ); $this->setTitle( NS_USER, $this->altUserName . '/tempo' ); $this->runCSSandJSPermissions( array( array( 'badaccess-group0' ) ), array( array( 'badaccess-group0' ) ), - array( array( 'badaccess-group0' ) ) ); + array( array( 'badaccess-group0' ) ), + array( array( 'badaccess-group0' ) ), + array( array( 'badaccess-group0' ) ) + ); } - function runCSSandJSPermissions( $result0, $result1, $result2 ) { + protected function runCSSandJSPermissions( $result0, $result1, $result2, $result3, $result4 ) { $this->setUserPerm( '' ); $this->assertEquals( $result0, - $this->title->getUserPermissionsErrors( 'bogus', - $this->user ) ); + $this->title->getUserPermissionsErrors( 'bogus', + $this->user ) ); - $this->setUserPerm( 'editusercss' ); + $this->setUserPerm( 'editmyusercss' ); $this->assertEquals( $result1, - $this->title->getUserPermissionsErrors( 'bogus', - $this->user ) ); + $this->title->getUserPermissionsErrors( 'bogus', + $this->user ) ); - $this->setUserPerm( 'edituserjs' ); + $this->setUserPerm( 'editmyuserjs' ); $this->assertEquals( $result2, - $this->title->getUserPermissionsErrors( 'bogus', - $this->user ) ); + $this->title->getUserPermissionsErrors( 'bogus', + $this->user ) ); + + $this->setUserPerm( 'editusercss' ); + $this->assertEquals( $result3, + $this->title->getUserPermissionsErrors( 'bogus', + $this->user ) ); + + $this->setUserPerm( 'edituserjs' ); + $this->assertEquals( $result4, + $this->title->getUserPermissionsErrors( 'bogus', + $this->user ) ); $this->setUserPerm( 'editusercssjs' ); $this->assertEquals( array( array( 'badaccess-group0' ) ), - $this->title->getUserPermissionsErrors( 'bogus', - $this->user ) ); + $this->title->getUserPermissionsErrors( 'bogus', + $this->user ) ); $this->setUserPerm( array( 'edituserjs', 'editusercss' ) ); $this->assertEquals( array( array( 'badaccess-group0' ) ), - $this->title->getUserPermissionsErrors( 'bogus', - $this->user ) ); + $this->title->getUserPermissionsErrors( 'bogus', + $this->user ) ); } - function testPageRestrictions() { - global $wgUser, $wgContLang; + /** + * @todo This test method should be split up into separate test methods and + * data providers + */ + public function testPageRestrictions() { + global $wgContLang; $prefix = $wgContLang->getFormattedNsText( NS_PROJECT ); - $wgUser = $this->user; $this->setTitle( NS_MAIN ); $this->title->mRestrictionsLoaded = true; $this->setUserPerm( "edit" ); $this->title->mRestrictions = array( "bogus" => array( 'bogus', "sysop", "protect", "" ) ); - $this->assertEquals( array( ), - $this->title->getUserPermissionsErrors( 'edit', - $this->user ) ); + $this->assertEquals( array(), + $this->title->getUserPermissionsErrors( 'edit', + $this->user ) ); $this->assertEquals( true, - $this->title->quickUserCan( 'edit' ) ); + $this->title->quickUserCan( 'edit', $this->user ) ); $this->title->mRestrictions = array( "edit" => array( 'bogus', "sysop", "protect", "" ), - "bogus" => array( 'bogus', "sysop", "protect", "" ) ); + "bogus" => array( 'bogus', "sysop", "protect", "" ) ); $this->assertEquals( array( array( 'badaccess-group0' ), - array( 'protectedpagetext', 'bogus' ), - array( 'protectedpagetext', 'protect' ), - array( 'protectedpagetext', 'protect' ) ), - $this->title->getUserPermissionsErrors( 'bogus', - $this->user ) ); + array( 'protectedpagetext', 'bogus' ), + array( 'protectedpagetext', 'editprotected' ), + array( 'protectedpagetext', 'protect' ) ), + $this->title->getUserPermissionsErrors( 'bogus', + $this->user ) ); $this->assertEquals( array( array( 'protectedpagetext', 'bogus' ), - array( 'protectedpagetext', 'protect' ), - array( 'protectedpagetext', 'protect' ) ), - $this->title->getUserPermissionsErrors( 'edit', - $this->user ) ); + array( 'protectedpagetext', 'editprotected' ), + array( 'protectedpagetext', 'protect' ) ), + $this->title->getUserPermissionsErrors( 'edit', + $this->user ) ); $this->setUserPerm( "" ); $this->assertEquals( array( array( 'badaccess-group0' ), - array( 'protectedpagetext', 'bogus' ), - array( 'protectedpagetext', 'protect' ), - array( 'protectedpagetext', 'protect' ) ), - $this->title->getUserPermissionsErrors( 'bogus', - $this->user ) ); + array( 'protectedpagetext', 'bogus' ), + array( 'protectedpagetext', 'editprotected' ), + array( 'protectedpagetext', 'protect' ) ), + $this->title->getUserPermissionsErrors( 'bogus', + $this->user ) ); $this->assertEquals( array( array( 'badaccess-groups', "*, [[$prefix:Users|Users]]", 2 ), - array( 'protectedpagetext', 'bogus' ), - array( 'protectedpagetext', 'protect' ), - array( 'protectedpagetext', 'protect' ) ), - $this->title->getUserPermissionsErrors( 'edit', - $this->user ) ); + array( 'protectedpagetext', 'bogus' ), + array( 'protectedpagetext', 'editprotected' ), + array( 'protectedpagetext', 'protect' ) ), + $this->title->getUserPermissionsErrors( 'edit', + $this->user ) ); $this->setUserPerm( array( "edit", "editprotected" ) ); $this->assertEquals( array( array( 'badaccess-group0' ), - array( 'protectedpagetext', 'bogus' ), - array( 'protectedpagetext', 'protect' ), - array( 'protectedpagetext', 'protect' ) ), - $this->title->getUserPermissionsErrors( 'bogus', - $this->user ) ); - $this->assertEquals( array( ), - $this->title->getUserPermissionsErrors( 'edit', - $this->user ) ); + array( 'protectedpagetext', 'bogus' ), + array( 'protectedpagetext', 'protect' ) ), + $this->title->getUserPermissionsErrors( 'bogus', + $this->user ) ); + $this->assertEquals( array( + array( 'protectedpagetext', 'bogus' ), + array( 'protectedpagetext', 'protect' ) ), + $this->title->getUserPermissionsErrors( 'edit', + $this->user ) ); + $this->title->mCascadeRestriction = true; + $this->setUserPerm( "edit" ); $this->assertEquals( false, - $this->title->quickUserCan( 'bogus' ) ); + $this->title->quickUserCan( 'bogus', $this->user ) ); $this->assertEquals( false, - $this->title->quickUserCan( 'edit' ) ); + $this->title->quickUserCan( 'edit', $this->user ) ); $this->assertEquals( array( array( 'badaccess-group0' ), - array( 'protectedpagetext', 'bogus' ), - array( 'protectedpagetext', 'protect' ), - array( 'protectedpagetext', 'protect' ) ), - $this->title->getUserPermissionsErrors( 'bogus', - $this->user ) ); + array( 'protectedpagetext', 'bogus' ), + array( 'protectedpagetext', 'editprotected' ), + array( 'protectedpagetext', 'protect' ) ), + $this->title->getUserPermissionsErrors( 'bogus', + $this->user ) ); $this->assertEquals( array( array( 'protectedpagetext', 'bogus' ), - array( 'protectedpagetext', 'protect' ), - array( 'protectedpagetext', 'protect' ) ), - $this->title->getUserPermissionsErrors( 'edit', - $this->user ) ); + array( 'protectedpagetext', 'editprotected' ), + array( 'protectedpagetext', 'protect' ) ), + $this->title->getUserPermissionsErrors( 'edit', + $this->user ) ); + + $this->setUserPerm( array( "edit", "editprotected" ) ); + $this->assertEquals( false, + $this->title->quickUserCan( 'bogus', $this->user ) ); + $this->assertEquals( false, + $this->title->quickUserCan( 'edit', $this->user ) ); + $this->assertEquals( array( array( 'badaccess-group0' ), + array( 'protectedpagetext', 'bogus' ), + array( 'protectedpagetext', 'protect' ), + array( 'protectedpagetext', 'protect' ) ), + $this->title->getUserPermissionsErrors( 'bogus', + $this->user ) ); + $this->assertEquals( array( array( 'protectedpagetext', 'bogus' ), + array( 'protectedpagetext', 'protect' ), + array( 'protectedpagetext', 'protect' ) ), + $this->title->getUserPermissionsErrors( 'edit', + $this->user ) ); } - function testCascadingSourcesRestrictions() { - global $wgUser; - $wgUser = $this->user; + public function testCascadingSourcesRestrictions() { $this->setTitle( NS_MAIN, "test page" ); $this->setUserPerm( array( "edit", "bogus" ) ); @@ -523,22 +601,23 @@ class TitlePermissionTest extends MediaWikiLangTestCase { $this->title->mCascadingRestrictions = array( "bogus" => array( 'bogus', "sysop", "protect", "" ) ); $this->assertEquals( false, - $this->title->userCan( 'bogus' ) ); + $this->title->userCan( 'bogus', $this->user ) ); $this->assertEquals( array( array( "cascadeprotected", 2, "* [[:Bogus]]\n* [[:UnBogus]]\n" ), - array( "cascadeprotected", 2, "* [[:Bogus]]\n* [[:UnBogus]]\n" ) ), - $this->title->getUserPermissionsErrors( 'bogus', $this->user ) ); + array( "cascadeprotected", 2, "* [[:Bogus]]\n* [[:UnBogus]]\n" ), + array( "cascadeprotected", 2, "* [[:Bogus]]\n* [[:UnBogus]]\n" ) ), + $this->title->getUserPermissionsErrors( 'bogus', $this->user ) ); $this->assertEquals( true, - $this->title->userCan( 'edit' ) ); - $this->assertEquals( array( ), - $this->title->getUserPermissionsErrors( 'edit', $this->user ) ); - + $this->title->userCan( 'edit', $this->user ) ); + $this->assertEquals( array(), + $this->title->getUserPermissionsErrors( 'edit', $this->user ) ); } - function testActionPermissions() { - global $wgUser; - $wgUser = $this->user; - + /** + * @todo This test method should be split up into separate test methods and + * data providers + */ + public function testActionPermissions() { $this->setUserPerm( array( "createpage" ) ); $this->setTitle( NS_MAIN, "test page" ); $this->title->mTitleProtection['pt_create_perm'] = ''; @@ -548,111 +627,114 @@ class TitlePermissionTest extends MediaWikiLangTestCase { $this->title->mCascadeRestriction = false; $this->assertEquals( array( array( 'titleprotected', 'Useruser', 'test' ) ), - $this->title->getUserPermissionsErrors( 'create', $this->user ) ); + $this->title->getUserPermissionsErrors( 'create', $this->user ) ); $this->assertEquals( false, - $this->title->userCan( 'create' ) ); + $this->title->userCan( 'create', $this->user ) ); $this->title->mTitleProtection['pt_create_perm'] = 'sysop'; $this->setUserPerm( array( 'createpage', 'protect' ) ); - $this->assertEquals( array( ), - $this->title->getUserPermissionsErrors( 'create', $this->user ) ); - $this->assertEquals( true, - $this->title->userCan( 'create' ) ); + $this->assertEquals( array( array( 'titleprotected', 'Useruser', 'test' ) ), + $this->title->getUserPermissionsErrors( 'create', $this->user ) ); + $this->assertEquals( false, + $this->title->userCan( 'create', $this->user ) ); + $this->setUserPerm( array( 'createpage', 'editprotected' ) ); + $this->assertEquals( array(), + $this->title->getUserPermissionsErrors( 'create', $this->user ) ); + $this->assertEquals( true, + $this->title->userCan( 'create', $this->user ) ); $this->setUserPerm( array( 'createpage' ) ); $this->assertEquals( array( array( 'titleprotected', 'Useruser', 'test' ) ), - $this->title->getUserPermissionsErrors( 'create', $this->user ) ); + $this->title->getUserPermissionsErrors( 'create', $this->user ) ); $this->assertEquals( false, - $this->title->userCan( 'create' ) ); + $this->title->userCan( 'create', $this->user ) ); $this->setTitle( NS_MEDIA, "test page" ); $this->setUserPerm( array( "move" ) ); $this->assertEquals( false, - $this->title->userCan( 'move' ) ); + $this->title->userCan( 'move', $this->user ) ); $this->assertEquals( array( array( 'immobile-source-namespace', 'Media' ) ), - $this->title->getUserPermissionsErrors( 'move', $this->user ) ); + $this->title->getUserPermissionsErrors( 'move', $this->user ) ); - $this->setTitle( NS_MAIN, "test page" ); - $this->assertEquals( array( ), - $this->title->getUserPermissionsErrors( 'move', $this->user ) ); + $this->setTitle( NS_HELP, "test page" ); + $this->assertEquals( array(), + $this->title->getUserPermissionsErrors( 'move', $this->user ) ); $this->assertEquals( true, - $this->title->userCan( 'move' ) ); + $this->title->userCan( 'move', $this->user ) ); $this->title->mInterwiki = "no"; $this->assertEquals( array( array( 'immobile-source-page' ) ), - $this->title->getUserPermissionsErrors( 'move', $this->user ) ); + $this->title->getUserPermissionsErrors( 'move', $this->user ) ); $this->assertEquals( false, - $this->title->userCan( 'move' ) ); + $this->title->userCan( 'move', $this->user ) ); $this->setTitle( NS_MEDIA, "test page" ); $this->assertEquals( false, - $this->title->userCan( 'move-target' ) ); + $this->title->userCan( 'move-target', $this->user ) ); $this->assertEquals( array( array( 'immobile-target-namespace', 'Media' ) ), - $this->title->getUserPermissionsErrors( 'move-target', $this->user ) ); + $this->title->getUserPermissionsErrors( 'move-target', $this->user ) ); - $this->setTitle( NS_MAIN, "test page" ); - $this->assertEquals( array( ), - $this->title->getUserPermissionsErrors( 'move-target', $this->user ) ); + $this->setTitle( NS_HELP, "test page" ); + $this->assertEquals( array(), + $this->title->getUserPermissionsErrors( 'move-target', $this->user ) ); $this->assertEquals( true, - $this->title->userCan( 'move-target' ) ); + $this->title->userCan( 'move-target', $this->user ) ); $this->title->mInterwiki = "no"; $this->assertEquals( array( array( 'immobile-target-page' ) ), - $this->title->getUserPermissionsErrors( 'move-target', $this->user ) ); + $this->title->getUserPermissionsErrors( 'move-target', $this->user ) ); $this->assertEquals( false, - $this->title->userCan( 'move-target' ) ); - + $this->title->userCan( 'move-target', $this->user ) ); } - function testUserBlock() { - global $wgUser, $wgEmailConfirmToEdit, $wgEmailAuthentication; + public function testUserBlock() { + global $wgEmailConfirmToEdit, $wgEmailAuthentication; $wgEmailConfirmToEdit = true; $wgEmailAuthentication = true; - $wgUser = $this->user; $this->setUserPerm( array( "createpage", "move" ) ); - $this->setTitle( NS_MAIN, "test page" ); + $this->setTitle( NS_HELP, "test page" ); # $short $this->assertEquals( array( array( 'confirmedittext' ) ), - $this->title->getUserPermissionsErrors( 'move-target', $this->user ) ); + $this->title->getUserPermissionsErrors( 'move-target', $this->user ) ); $wgEmailConfirmToEdit = false; - $this->assertEquals( true, $this->title->userCan( 'move-target' ) ); + $this->assertEquals( true, $this->title->userCan( 'move-target', $this->user ) ); # $wgEmailConfirmToEdit && !$user->isEmailConfirmed() && $action != 'createaccount' - $this->assertEquals( array( ), - $this->title->getUserPermissionsErrors( 'move-target', - $this->user ) ); + $this->assertEquals( array(), + $this->title->getUserPermissionsErrors( 'move-target', + $this->user ) ); global $wgLang; $prev = time(); $now = time() + 120; $this->user->mBlockedby = $this->user->getId(); $this->user->mBlock = new Block( '127.0.8.1', 0, $this->user->getId(), - 'no reason given', $prev + 3600, 1, 0 ); + 'no reason given', $prev + 3600, 1, 0 ); $this->user->mBlock->mTimestamp = 0; $this->assertEquals( array( array( 'autoblockedtext', - '[[User:Useruser|Useruser]]', 'no reason given', '127.0.0.1', - 'Useruser', null, 'infinite', '127.0.8.1', - $wgLang->timeanddate( wfTimestamp( TS_MW, $prev ), true ) ) ), + '[[User:Useruser|Useruser]]', 'no reason given', '127.0.0.1', + 'Useruser', null, 'infinite', '127.0.8.1', + $wgLang->timeanddate( wfTimestamp( TS_MW, $prev ), true ) ) ), $this->title->getUserPermissionsErrors( 'move-target', - $this->user ) ); + $this->user ) ); - $this->assertEquals( false, $this->title->userCan( 'move-target' ) ); + $this->assertEquals( false, $this->title->userCan( 'move-target', $this->user ) ); // quickUserCan should ignore user blocks - $this->assertEquals( true, $this->title->quickUserCan( 'move-target' ) ); + $this->assertEquals( true, $this->title->quickUserCan( 'move-target', $this->user ) ); global $wgLocalTZoffset; $wgLocalTZoffset = -60; $this->user->mBlockedby = $this->user->getName(); - $this->user->mBlock = new Block( '127.0.8.1', 0, 1, 'no reason given', $now, 0, 10 ); + $this->user->mBlock = new Block( '127.0.8.1', 0, $this->user->getId(), + 'no reason given', $now, 0, 10 ); $this->assertEquals( array( array( 'blockedtext', - '[[User:Useruser|Useruser]]', 'no reason given', '127.0.0.1', - 'Useruser', null, '23:00, 31 December 1969', '127.0.8.1', - $wgLang->timeanddate( wfTimestamp( TS_MW, $now ), true ) ) ), + '[[User:Useruser|Useruser]]', 'no reason given', '127.0.0.1', + 'Useruser', null, '23:00, 31 December 1969', '127.0.8.1', + $wgLang->timeanddate( wfTimestamp( TS_MW, $now ), true ) ) ), $this->title->getUserPermissionsErrors( 'move-target', $this->user ) ); - # $action != 'read' && $action != 'createaccount' && $user->isBlockedFrom( $this ) # $user->blockedFor() == '' # $user->mBlock->mExpiry == 'infinity' diff --git a/tests/phpunit/includes/TitleTest.php b/tests/phpunit/includes/TitleTest.php index f61652df..6bfe5453 100644 --- a/tests/phpunit/includes/TitleTest.php +++ b/tests/phpunit/includes/TitleTest.php @@ -1,8 +1,27 @@ <?php +/** + * @group Database + * ^--- needed for language cache stuff + */ class TitleTest extends MediaWikiTestCase { + protected function setUp() { + parent::setUp(); - function testLegalChars() { + $this->setMwGlobals( array( + 'wgLanguageCode' => 'en', + 'wgContLang' => Language::factory( 'en' ), + // User language + 'wgLang' => Language::factory( 'en' ), + 'wgAllowUserJs' => false, + 'wgDefaultLanguageVariant' => false, + ) ); + } + + /** + * @covers Title::legalChars + */ + public function testLegalChars() { $titlechars = Title::legalChars(); foreach ( range( 1, 255 ) as $num ) { @@ -16,12 +35,160 @@ class TitleTest extends MediaWikiTestCase { } /** - * @dataProvider dataBug31100 + * See also mediawiki.Title.test.js + * @covers Title::secureAndSplit + * @todo This method should be split into 2 separate tests each with a provider */ - function testBug31100FixSpecialName( $text, $expectedParam ) { + public function testSecureAndSplit() { + // Valid + foreach ( array( + 'Sandbox', + 'A "B"', + 'A \'B\'', + '.com', + '~', + '"', + '\'', + 'Talk:Sandbox', + 'Talk:Foo:Sandbox', + 'File:Example.svg', + 'File_talk:Example.svg', + 'Foo/.../Sandbox', + 'Sandbox/...', + 'A~~', + // Length is 256 total, but only title part matters + 'Category:' . str_repeat( 'x', 248 ), + str_repeat( 'x', 252 ) + ) as $text ) { + $this->assertInstanceOf( 'Title', Title::newFromText( $text ), "Valid: $text" ); + } + + // Invalid + foreach ( array( + '', + '__ __', + ' __ ', + // Bad characters forbidden regardless of wgLegalTitleChars + 'A [ B', + 'A ] B', + 'A { B', + 'A } B', + 'A < B', + 'A > B', + 'A | B', + // URL encoding + 'A%20B', + 'A%23B', + 'A%2523B', + // XML/HTML character entity references + // Note: Commented out because they are not marked invalid by the PHP test as + // Title::newFromText runs Sanitizer::decodeCharReferencesAndNormalize first. + //'A é B', + //'A é B', + //'A é B', + // Subject of NS_TALK does not roundtrip to NS_MAIN + 'Talk:File:Example.svg', + // Directory navigation + '.', + '..', + './Sandbox', + '../Sandbox', + 'Foo/./Sandbox', + 'Foo/../Sandbox', + 'Sandbox/.', + 'Sandbox/..', + // Tilde + 'A ~~~ Name', + 'A ~~~~ Signature', + 'A ~~~~~ Timestamp', + str_repeat( 'x', 256 ), + // Namespace prefix without actual title + // ':', // bug 54044 + 'Talk:', + 'Category: ', + 'Category: #bar' + ) as $text ) { + $this->assertNull( Title::newFromText( $text ), "Invalid: $text" ); + } + } + + public static function provideConvertByteClassToUnicodeClass() { + return array( + array( + ' %!"$&\'()*,\\-.\\/0-9:;=?@A-Z\\\\^_`a-z~\\x80-\\xFF+', + ' %!"$&\'()*,\\-./0-9:;=?@A-Z\\\\\\^_`a-z~+\\u0080-\\uFFFF', + ), + array( + 'QWERTYf-\\xFF+', + 'QWERTYf-\\x7F+\\u0080-\\uFFFF', + ), + array( + 'QWERTY\\x66-\\xFD+', + 'QWERTYf-\\x7F+\\u0080-\\uFFFF', + ), + array( + 'QWERTYf-y+', + 'QWERTYf-y+', + ), + array( + 'QWERTYf-\\x80+', + 'QWERTYf-\\x7F+\\u0080-\\uFFFF', + ), + array( + 'QWERTY\\x66-\\x80+\\x23', + 'QWERTYf-\\x7F+#\\u0080-\\uFFFF', + ), + array( + 'QWERTY\\x66-\\x80+\\xD3', + 'QWERTYf-\\x7F+\\u0080-\\uFFFF', + ), + array( + '\\\\\\x99', + '\\\\\\u0080-\\uFFFF', + ), + array( + '-\\x99', + '\\-\\u0080-\\uFFFF', + ), + array( + 'QWERTY\\-\\x99', + 'QWERTY\\-\\u0080-\\uFFFF', + ), + array( + '\\\\x99', + '\\\\x99', + ), + array( + 'A-\\x9F', + 'A-\\x7F\\u0080-\\uFFFF', + ), + array( + '\\x66-\\x77QWERTY\\x88-\\x91FXZ', + 'f-wQWERTYFXZ\\u0080-\\uFFFF', + ), + array( + '\\x66-\\x99QWERTY\\xAA-\\xEEFXZ', + 'f-\\x7FQWERTYFXZ\\u0080-\\uFFFF', + ), + ); + } + + /** + * @dataProvider provideConvertByteClassToUnicodeClass + * @covers Title::convertByteClassToUnicodeClass + */ + public function testConvertByteClassToUnicodeClass( $byteClass, $unicodeClass ) { + $this->assertEquals( $unicodeClass, Title::convertByteClassToUnicodeClass( $byteClass ) ); + } + + /** + * @dataProvider provideBug31100 + * @covers Title::fixSpecialName + */ + public function testBug31100FixSpecialName( $text, $expectedParam ) { $title = Title::newFromText( $text ); $fixed = $title->fixSpecialName(); - $stuff = explode( '/', $fixed->getDbKey(), 2 ); + $stuff = explode( '/', $fixed->getDBkey(), 2 ); if ( count( $stuff ) == 2 ) { $par = $stuff[1]; } else { @@ -30,24 +197,25 @@ class TitleTest extends MediaWikiTestCase { $this->assertEquals( $expectedParam, $par, "Bug 31100 regression check: Title->fixSpecialName() should preserve parameter" ); } - function dataBug31100() { + public static function provideBug31100() { return array( array( 'Special:Version', null ), array( 'Special:Version/', '' ), array( 'Special:Version/param', 'param' ), ); } - + /** * Auth-less test of Title::isValidMoveOperation - * + * * @group Database * @param string $source * @param string $target - * @param array|string|true $expected Required error - * @dataProvider dataTestIsValidMoveOperation + * @param array|string|bool $expected Required error + * @dataProvider provideTestIsValidMoveOperation + * @covers Title::isValidMoveOperation */ - function testIsValidMoveOperation( $source, $target, $expected ) { + public function testIsValidMoveOperation( $source, $target, $expected ) { $title = Title::newFromText( $source ); $nt = Title::newFromText( $target ); $errors = $title->isValidMoveOperation( $nt, false ); @@ -60,42 +228,150 @@ class TitleTest extends MediaWikiTestCase { } } } - - function flattenErrorsArray( $errors ) { + + /** + * Provides test parameter values for testIsValidMoveOperation() + */ + public function dataTestIsValidMoveOperation() { + return array( + array( 'Test', 'Test', 'selfmove' ), + array( 'File:Test.jpg', 'Page', 'imagenocrossnamespace' ) + ); + } + + /** + * Auth-less test of Title::userCan + * + * @param array $whitelistRegexp + * @param string $source + * @param string $action + * @param array|string|bool $expected Required error + * + * @covers Title::checkReadPermissions + * @dataProvider dataWgWhitelistReadRegexp + */ + public function testWgWhitelistReadRegexp( $whitelistRegexp, $source, $action, $expected ) { + // $wgWhitelistReadRegexp must be an array. Since the provided test cases + // usually have only one regex, it is more concise to write the lonely regex + // as a string. Thus we cast to an array() to honor $wgWhitelistReadRegexp + // type requisite. + if ( is_string( $whitelistRegexp ) ) { + $whitelistRegexp = array( $whitelistRegexp ); + } + + $title = Title::newFromDBkey( $source ); + + global $wgGroupPermissions; + $oldPermissions = $wgGroupPermissions; + // Disallow all so we can ensure our regex works + $wgGroupPermissions = array(); + $wgGroupPermissions['*']['read'] = false; + + global $wgWhitelistRead; + $oldWhitelist = $wgWhitelistRead; + // Undo any LocalSettings explicite whitelists so they won't cause a + // failing test to succeed. Set it to some random non sense just + // to make sure we properly test Title::checkReadPermissions() + $wgWhitelistRead = array( 'some random non sense title' ); + + global $wgWhitelistReadRegexp; + $oldWhitelistRegexp = $wgWhitelistReadRegexp; + $wgWhitelistReadRegexp = $whitelistRegexp; + + // Just use $wgUser which in test is a user object for '127.0.0.1' + global $wgUser; + // Invalidate user rights cache to take in account $wgGroupPermissions + // change above. + $wgUser->clearInstanceCache(); + $errors = $title->userCan( $action, $wgUser ); + + // Restore globals + $wgGroupPermissions = $oldPermissions; + $wgWhitelistRead = $oldWhitelist; + $wgWhitelistReadRegexp = $oldWhitelistRegexp; + + if ( is_bool( $expected ) ) { + # Forge the assertion message depending on the assertion expectation + $allowableness = $expected + ? " should be allowed" + : " should NOT be allowed"; + $this->assertEquals( $expected, $errors, "User action '$action' on [[$source]] $allowableness." ); + } else { + $errors = $this->flattenErrorsArray( $errors ); + foreach ( (array)$expected as $error ) { + $this->assertContains( $error, $errors ); + } + } + } + + /** + * Provides test parameter values for testWgWhitelistReadRegexp() + */ + public function dataWgWhitelistReadRegexp() { + $ALLOWED = true; + $DISALLOWED = false; + + return array( + // Everything, if this doesn't work, we're really in trouble + array( '/.*/', 'Main_Page', 'read', $ALLOWED ), + array( '/.*/', 'Main_Page', 'edit', $DISALLOWED ), + + // We validate against the title name, not the db key + array( '/^Main_Page$/', 'Main_Page', 'read', $DISALLOWED ), + // Main page + array( '/^Main/', 'Main_Page', 'read', $ALLOWED ), + array( '/^Main.*/', 'Main_Page', 'read', $ALLOWED ), + // With spaces + array( '/Mic\sCheck/', 'Mic Check', 'read', $ALLOWED ), + // Unicode multibyte + // ...without unicode modifier + array( '/Unicode Test . Yes/', 'Unicode Test Ñ Yes', 'read', $DISALLOWED ), + // ...with unicode modifier + array( '/Unicode Test . Yes/u', 'Unicode Test Ñ Yes', 'read', $ALLOWED ), + // Case insensitive + array( '/MiC ChEcK/', 'mic check', 'read', $DISALLOWED ), + array( '/MiC ChEcK/i', 'mic check', 'read', $ALLOWED ), + + // From DefaultSettings.php: + array( "@^UsEr.*@i", 'User is banned', 'read', $ALLOWED ), + array( "@^UsEr.*@i", 'User:John Doe', 'read', $ALLOWED ), + + // With namespaces: + array( '/^Special:NewPages$/', 'Special:NewPages', 'read', $ALLOWED ), + array( null, 'Special:Newpages', 'read', $DISALLOWED ), + + ); + } + + public function flattenErrorsArray( $errors ) { $result = array(); foreach ( $errors as $error ) { $result[] = $error[0]; } + return $result; } - - function dataTestIsValidMoveOperation() { - return array( + + public static function provideTestIsValidMoveOperation() { + return array( array( 'Test', 'Test', 'selfmove' ), array( 'File:Test.jpg', 'Page', 'imagenocrossnamespace' ) ); } - - + /** - * @dataProvider provideCasesForGetpageviewlanguage + * @dataProvider provideGetPageViewLanguage + * @covers Title::getPageViewLanguage */ - function testGetpageviewlanguage( $expected, $titleText, $contLang, $lang, $variant, $msg='' ) { - // Save globals - global $wgContLang, $wgLang, $wgAllowUserJs, $wgLanguageCode, $wgDefaultLanguageVariant; - $save['wgContLang'] = $wgContLang; - $save['wgLang'] = $wgLang; - $save['wgAllowUserJs'] = $wgAllowUserJs; - $save['wgLanguageCode'] = $wgLanguageCode; - $save['wgDefaultLanguageVariant'] = $wgDefaultLanguageVariant; - - // Setup test environnement: - $wgContLang = Language::factory( $contLang ); - $wgLang = Language::factory( $lang ); - # To test out .js titles: - $wgAllowUserJs = true; + public function testGetPageViewLanguage( $expected, $titleText, $contLang, $lang, $variant, $msg = '' ) { + global $wgLanguageCode, $wgContLang, $wgLang, $wgDefaultLanguageVariant, $wgAllowUserJs; + + // Setup environnement for this test $wgLanguageCode = $contLang; + $wgContLang = Language::factory( $contLang ); + $wgLang = Language::factory( $lang ); $wgDefaultLanguageVariant = $variant; + $wgAllowUserJs = true; $title = Title::newFromText( $titleText ); $this->assertInstanceOf( 'Title', $title, @@ -105,16 +381,9 @@ class TitleTest extends MediaWikiTestCase { $title->getPageViewLanguage()->getCode(), $msg ); - - // Restore globals - $wgContLang = $save['wgContLang']; - $wgLang = $save['wgLang']; - $wgAllowUserJs = $save['wgAllowUserJs']; - $wgLanguageCode = $save['wgLanguageCode']; - $wgDefaultLanguageVariant = $save['wgDefaultLanguageVariant']; } - function provideCasesForGetpageviewlanguage() { + public static function provideGetPageViewLanguage() { # Format: # - expected # - Title name @@ -123,33 +392,94 @@ class TitleTest extends MediaWikiTestCase { # - wgDefaultLanguageVariant # - Optional message return array( - array( 'fr', 'Main_page', 'fr', 'fr', false ), - array( 'es', 'Main_page', 'es', 'zh-tw', false ), - array( 'zh', 'Main_page', 'zh', 'zh-tw', false ), - - array( 'es', 'Main_page', 'es', 'zh-tw', 'zh-cn' ), - array( 'es', 'MediaWiki:About', 'es', 'zh-tw', 'zh-cn' ), - array( 'es', 'MediaWiki:About/', 'es', 'zh-tw', 'zh-cn' ), - array( 'de', 'MediaWiki:About/de', 'es', 'zh-tw', 'zh-cn' ), - array( 'en', 'MediaWiki:Common.js', 'es', 'zh-tw', 'zh-cn' ), - array( 'en', 'MediaWiki:Common.css', 'es', 'zh-tw', 'zh-cn' ), - array( 'en', 'User:JohnDoe/Common.js', 'es', 'zh-tw', 'zh-cn' ), - array( 'en', 'User:JohnDoe/Monobook.css', 'es', 'zh-tw', 'zh-cn' ), - - array( 'zh-cn', 'Main_page', 'zh', 'zh-tw', 'zh-cn' ), - array( 'zh', 'MediaWiki:About', 'zh', 'zh-tw', 'zh-cn' ), - array( 'zh', 'MediaWiki:About/', 'zh', 'zh-tw', 'zh-cn' ), - array( 'de', 'MediaWiki:About/de', 'zh', 'zh-tw', 'zh-cn' ), - array( 'zh-cn', 'MediaWiki:About/zh-cn', 'zh', 'zh-tw', 'zh-cn' ), - array( 'zh-tw', 'MediaWiki:About/zh-tw', 'zh', 'zh-tw', 'zh-cn' ), - array( 'en', 'MediaWiki:Common.js', 'zh', 'zh-tw', 'zh-cn' ), - array( 'en', 'MediaWiki:Common.css', 'zh', 'zh-tw', 'zh-cn' ), - array( 'en', 'User:JohnDoe/Common.js', 'zh', 'zh-tw', 'zh-cn' ), - array( 'en', 'User:JohnDoe/Monobook.css', 'zh', 'zh-tw', 'zh-cn' ), - - array( 'zh-tw', 'Special:NewPages', 'es', 'zh-tw', 'zh-cn' ), - array( 'zh-tw', 'Special:NewPages', 'zh', 'zh-tw', 'zh-cn' ), + array( 'fr', 'Help:I_need_somebody', 'fr', 'fr', false ), + array( 'es', 'Help:I_need_somebody', 'es', 'zh-tw', false ), + array( 'zh', 'Help:I_need_somebody', 'zh', 'zh-tw', false ), + + array( 'es', 'Help:I_need_somebody', 'es', 'zh-tw', 'zh-cn' ), + array( 'es', 'MediaWiki:About', 'es', 'zh-tw', 'zh-cn' ), + array( 'es', 'MediaWiki:About/', 'es', 'zh-tw', 'zh-cn' ), + array( 'de', 'MediaWiki:About/de', 'es', 'zh-tw', 'zh-cn' ), + array( 'en', 'MediaWiki:Common.js', 'es', 'zh-tw', 'zh-cn' ), + array( 'en', 'MediaWiki:Common.css', 'es', 'zh-tw', 'zh-cn' ), + array( 'en', 'User:JohnDoe/Common.js', 'es', 'zh-tw', 'zh-cn' ), + array( 'en', 'User:JohnDoe/Monobook.css', 'es', 'zh-tw', 'zh-cn' ), + array( 'zh-cn', 'Help:I_need_somebody', 'zh', 'zh-tw', 'zh-cn' ), + array( 'zh', 'MediaWiki:About', 'zh', 'zh-tw', 'zh-cn' ), + array( 'zh', 'MediaWiki:About/', 'zh', 'zh-tw', 'zh-cn' ), + array( 'de', 'MediaWiki:About/de', 'zh', 'zh-tw', 'zh-cn' ), + array( 'zh-cn', 'MediaWiki:About/zh-cn', 'zh', 'zh-tw', 'zh-cn' ), + array( 'zh-tw', 'MediaWiki:About/zh-tw', 'zh', 'zh-tw', 'zh-cn' ), + array( 'en', 'MediaWiki:Common.js', 'zh', 'zh-tw', 'zh-cn' ), + array( 'en', 'MediaWiki:Common.css', 'zh', 'zh-tw', 'zh-cn' ), + array( 'en', 'User:JohnDoe/Common.js', 'zh', 'zh-tw', 'zh-cn' ), + array( 'en', 'User:JohnDoe/Monobook.css', 'zh', 'zh-tw', 'zh-cn' ), + + array( 'zh-tw', 'Special:NewPages', 'es', 'zh-tw', 'zh-cn' ), + array( 'zh-tw', 'Special:NewPages', 'zh', 'zh-tw', 'zh-cn' ), + + ); + } + + /** + * @dataProvider provideBaseTitleCases + * @covers Title::getBaseText + */ + public function testGetBaseText( $title, $expected, $msg = '' ) { + $title = Title::newFromText( $title ); + $this->assertEquals( $expected, + $title->getBaseText(), + $msg + ); + } + + public static function provideBaseTitleCases() { + return array( + # Title, expected base, optional message + array( 'User:John_Doe/subOne/subTwo', 'John Doe/subOne' ), + array( 'User:Foo/Bar/Baz', 'Foo/Bar' ), + ); + } + + /** + * @dataProvider provideRootTitleCases + * @covers Title::getRootText + */ + public function testGetRootText( $title, $expected, $msg = '' ) { + $title = Title::newFromText( $title ); + $this->assertEquals( $expected, + $title->getRootText(), + $msg + ); + } + + public static function provideRootTitleCases() { + return array( + # Title, expected base, optional message + array( 'User:John_Doe/subOne/subTwo', 'John Doe' ), + array( 'User:Foo/Bar/Baz', 'Foo' ), + ); + } + + /** + * @todo Handle $wgNamespacesWithSubpages cases + * @dataProvider provideSubpageTitleCases + * @covers Title::getSubpageText + */ + public function testGetSubpageText( $title, $expected, $msg = '' ) { + $title = Title::newFromText( $title ); + $this->assertEquals( $expected, + $title->getSubpageText(), + $msg + ); + } + + public static function provideSubpageTitleCases() { + return array( + # Title, expected base, optional message + array( 'User:John_Doe/subOne/subTwo', 'subTwo' ), + array( 'User:John_Doe/subOne', 'subOne' ), ); } } diff --git a/tests/phpunit/includes/UIDGeneratorTest.php b/tests/phpunit/includes/UIDGeneratorTest.php new file mode 100644 index 00000000..8f78ae51 --- /dev/null +++ b/tests/phpunit/includes/UIDGeneratorTest.php @@ -0,0 +1,98 @@ +<?php + +class UIDGeneratorTest extends MediaWikiTestCase { + + /** + * @dataProvider provider_testTimestampedUID + * @covers UIDGenerator::newTimestampedUID128 + * @covers UIDGenerator::newTimestampedUID88 + */ + public function testTimestampedUID( $method, $digitlen, $bits, $tbits, $hostbits ) { + $id = call_user_func( array( 'UIDGenerator', $method ) ); + $this->assertEquals( true, ctype_digit( $id ), "UID made of digit characters" ); + $this->assertLessThanOrEqual( $digitlen, strlen( $id ), + "UID has the right number of digits" ); + $this->assertLessThanOrEqual( $bits, strlen( wfBaseConvert( $id, 10, 2 ) ), + "UID has the right number of bits" ); + + $ids = array(); + for ( $i = 0; $i < 300; $i++ ) { + $ids[] = call_user_func( array( 'UIDGenerator', $method ) ); + } + + $lastId = array_shift( $ids ); + if ( $hostbits ) { + $lastHost = substr( wfBaseConvert( $lastId, 10, 2, $bits ), -$hostbits ); + } + + $this->assertArrayEquals( array_unique( $ids ), $ids, "All generated IDs are unique." ); + + foreach ( $ids as $id ) { + $id_bin = wfBaseConvert( $id, 10, 2 ); + $lastId_bin = wfBaseConvert( $lastId, 10, 2 ); + + $this->assertGreaterThanOrEqual( + substr( $id_bin, 0, $tbits ), + substr( $lastId_bin, 0, $tbits ), + "New ID timestamp ($id_bin) >= prior one ($lastId_bin)." ); + + if ( $hostbits ) { + $this->assertEquals( + substr( $id_bin, 0, -$hostbits ), + substr( $lastId_bin, 0, -$hostbits ), + "Host ID of ($id_bin) is same as prior one ($lastId_bin)." ); + } + + $lastId = $id; + } + } + + /** + * array( method, length, bits, hostbits ) + * NOTE: When adding a new method name here please update the covers tags for the tests! + */ + public static function provider_testTimestampedUID() { + return array( + array( 'newTimestampedUID128', 39, 128, 46, 48 ), + array( 'newTimestampedUID128', 39, 128, 46, 48 ), + array( 'newTimestampedUID88', 27, 88, 46, 32 ), + ); + } + + /** + * @covers UIDGenerator::newUUIDv4 + */ + public function testUUIDv4() { + for ( $i = 0; $i < 100; $i++ ) { + $id = UIDGenerator::newUUIDv4(); + $this->assertEquals( true, + preg_match( '!^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$!', $id ), + "UID $id has the right format" ); + } + } + + /** + * @covers UIDGenerator::newRawUUIDv4 + */ + public function testRawUUIDv4() { + for ( $i = 0; $i < 100; $i++ ) { + $id = UIDGenerator::newRawUUIDv4(); + $this->assertEquals( true, + preg_match( '!^[0-9a-f]{12}4[0-9a-f]{3}[89ab][0-9a-f]{15}$!', $id ), + "UID $id has the right format" ); + } + } + + /** + * @covers UIDGenerator::newRawUUIDv4 + */ + public function testRawUUIDv4QuickRand() { + for ( $i = 0; $i < 100; $i++ ) { + $id = UIDGenerator::newRawUUIDv4( UIDGenerator::QUICK_RAND ); + $this->assertEquals( true, + preg_match( '!^[0-9a-f]{12}4[0-9a-f]{3}[89ab][0-9a-f]{15}$!', $id ), + "UID $id has the right format" ); + } + } + +} diff --git a/tests/phpunit/includes/UserMailerTest.php b/tests/phpunit/includes/UserMailerTest.php new file mode 100644 index 00000000..278edfaa --- /dev/null +++ b/tests/phpunit/includes/UserMailerTest.php @@ -0,0 +1,14 @@ +<?php + +class UserMailerTest extends MediaWikiLangTestCase { + + /** + * @covers UserMailer::quotedPrintable + */ + public function testQuotedPrintable() { + $this->assertEquals( + "=?UTF-8?Q?=C4=88u=20legebla=3F?=", + UserMailer::quotedPrintable( "\xc4\x88u legebla?", "UTF-8" ) ); + } + +}
\ No newline at end of file diff --git a/tests/phpunit/includes/UserTest.php b/tests/phpunit/includes/UserTest.php index 7a424aef..ff33e825 100644 --- a/tests/phpunit/includes/UserTest.php +++ b/tests/phpunit/includes/UserTest.php @@ -7,22 +7,25 @@ define( 'NS_UNITTEST_TALK', 5601 ); * @group Database */ class UserTest extends MediaWikiTestCase { - protected $savedGroupPermissions, $savedRevokedPermissions; - /** * @var User */ protected $user; - public function setUp() { + protected function setUp() { parent::setUp(); - $this->savedGroupPermissions = $GLOBALS['wgGroupPermissions']; - $this->savedRevokedPermissions = $GLOBALS['wgRevokePermissions']; + $this->setMwGlobals( array( + 'wgGroupPermissions' => array(), + 'wgRevokePermissions' => array(), + ) ); $this->setUpPermissionGlobals(); - $this->setUpUser(); + + $this->user = new User; + $this->user->addGroup( 'unittesters' ); } + private function setUpPermissionGlobals() { global $wgGroupPermissions, $wgRevokePermissions; @@ -38,23 +41,21 @@ class UserTest extends MediaWikiTestCase { 'writetest' => true, 'modifytest' => true, ); + # Data for regular $wgRevokePermissions test $wgRevokePermissions['formertesters'] = array( 'runtest' => true, ); - } - private function setUpUser() { - $this->user = new User; - $this->user->addGroup( 'unittesters' ); - } - public function tearDown() { - parent::tearDown(); - - $GLOBALS['wgGroupPermissions'] = $this->savedGroupPermissions; - $GLOBALS['wgRevokePermissions'] = $this->savedRevokedPermissions; + # For the options test + $wgGroupPermissions['*'] = array( + 'editmyoptions' => true, + ); } + /** + * @covers User::getGroupPermissions + */ public function testGroupPermissions() { $rights = User::getGroupPermissions( array( 'unittesters' ) ); $this->assertContains( 'runtest', $rights ); @@ -68,6 +69,10 @@ class UserTest extends MediaWikiTestCase { $this->assertContains( 'modifytest', $rights ); $this->assertNotContains( 'nukeworld', $rights ); } + + /** + * @covers User::getGroupPermissions + */ public function testRevokePermissions() { $rights = User::getGroupPermissions( array( 'unittesters', 'formertesters' ) ); $this->assertNotContains( 'runtest', $rights ); @@ -76,6 +81,9 @@ class UserTest extends MediaWikiTestCase { $this->assertNotContains( 'nukeworld', $rights ); } + /** + * @covers User::getRights + */ public function testUserPermissions() { $rights = $this->user->getRights(); $this->assertContains( 'runtest', $rights ); @@ -86,6 +94,7 @@ class UserTest extends MediaWikiTestCase { /** * @dataProvider provideGetGroupsWithPermission + * @covers User::getGroupsWithPermission */ public function testGetGroupsWithPermission( $expected, $right ) { $result = User::getGroupsWithPermission( $right ); @@ -95,7 +104,7 @@ class UserTest extends MediaWikiTestCase { $this->assertEquals( $expected, $result, "Groups with permission $right" ); } - public function provideGetGroupsWithPermission() { + public static function provideGetGroupsWithPermission() { return array( array( array( 'unittesters', 'testwriters' ), @@ -118,25 +127,26 @@ class UserTest extends MediaWikiTestCase { /** * @dataProvider provideUserNames + * @covers User::isValidUserName */ public function testIsValidUserName( $username, $result, $message ) { $this->assertEquals( $this->user->isValidUserName( $username ), $result, $message ); } - public function provideUserNames() { + public static function provideUserNames() { return array( array( '', false, 'Empty string' ), array( ' ', false, 'Blank space' ), array( 'abcd', false, 'Starts with small letter' ), - array( 'Ab/cd', false, 'Contains slash' ), - array( 'Ab cd' , true, 'Whitespace' ), - array( '192.168.1.1', false, 'IP' ), + array( 'Ab/cd', false, 'Contains slash' ), + array( 'Ab cd', true, 'Whitespace' ), + array( '192.168.1.1', false, 'IP' ), array( 'User:Abcd', false, 'Reserved Namespace' ), - array( '12abcd232' , true , 'Starts with Numbers' ), - array( '?abcd' , true, 'Start with ? mark' ), + array( '12abcd232', true, 'Starts with Numbers' ), + array( '?abcd', true, 'Start with ? mark' ), array( '#abcd', false, 'Start with #' ), - array( 'Abcdകഖഗഘ', true, ' Mixed scripts' ), - array( 'ജോസ്തോമസ്', false, 'ZWNJ- Format control character' ), + array( 'Abcdകഖഗഘ', true, ' Mixed scripts' ), + array( 'ജോസ്തോമസ്', false, 'ZWNJ- Format control character' ), array( 'Ab cd', false, ' Ideographic space' ), ); } @@ -168,4 +178,60 @@ class UserTest extends MediaWikiTestCase { 'Each user rights (core/extensions) has a corresponding right- message.' ); } + + /** + * Test User::editCount + * @group medium + * @covers User::getEditCount + */ + public function testEditCount() { + $user = User::newFromName( 'UnitTestUser' ); + $user->loadDefaults(); + $user->addToDatabase(); + + // let the user have a few (3) edits + $page = WikiPage::factory( Title::newFromText( 'Help:UserTest_EditCount' ) ); + for ( $i = 0; $i < 3; $i++ ) { + $page->doEdit( (string)$i, 'test', 0, false, $user ); + } + + $user->clearInstanceCache(); + $this->assertEquals( 3, $user->getEditCount(), 'After three edits, the user edit count should be 3' ); + + // increase the edit count and clear the cache + $user->incEditCount(); + + $user->clearInstanceCache(); + $this->assertEquals( 4, $user->getEditCount(), 'After increasing the edit count manually, the user edit count should be 4' ); + } + + /** + * Test changing user options. + * @covers User::setOption + * @covers User::getOption + */ + public function testOptions() { + $user = User::newFromName( 'UnitTestUser' ); + $user->addToDatabase(); + + $user->setOption( 'someoption', 'test' ); + $user->setOption( 'cols', 200 ); + $user->saveSettings(); + + $user = User::newFromName( 'UnitTestUser' ); + $this->assertEquals( 'test', $user->getOption( 'someoption' ) ); + $this->assertEquals( 200, $user->getOption( 'cols' ) ); + } + + /** + * Bug 37963 + * Make sure defaults are loaded when setOption is called. + * @covers User::loadOptions + */ + public function testAnonOptions() { + global $wgDefaultUserOptions; + $this->user->setOption( 'someoption', 'test' ); + $this->assertEquals( $wgDefaultUserOptions['cols'], $this->user->getOption( 'cols' ) ); + $this->assertEquals( 'test', $this->user->getOption( 'someoption' ) ); + } } diff --git a/tests/phpunit/includes/WebRequestTest.php b/tests/phpunit/includes/WebRequestTest.php index 1fc0b4b3..f8ed14b6 100644 --- a/tests/phpunit/includes/WebRequestTest.php +++ b/tests/phpunit/includes/WebRequestTest.php @@ -1,26 +1,34 @@ <?php +/** + * @group WebRequest + */ class WebRequestTest extends MediaWikiTestCase { - static $oldServer; + protected $oldServer; - function setUp() { - self::$oldServer = $_SERVER; + protected function setUp() { + parent::setUp(); + + $this->oldServer = $_SERVER; } - function tearDown() { - $_SERVER = self::$oldServer; + protected function tearDown() { + $_SERVER = $this->oldServer; + + parent::tearDown(); } /** * @dataProvider provideDetectServer + * @covers WebRequest::detectServer */ - function testDetectServer( $expected, $input, $description ) { + public function testDetectServer( $expected, $input, $description ) { $_SERVER = $input; $result = WebRequest::detectServer(); $this->assertEquals( $expected, $result, $description ); } - function provideDetectServer() { + public static function provideDetectServer() { return array( array( 'http://x', @@ -96,18 +104,29 @@ class WebRequestTest extends MediaWikiTestCase { /** * @dataProvider provideGetIP + * @covers WebRequest::getIP */ - function testGetIP( $expected, $input, $squid, $private, $description ) { - global $wgSquidServersNoPurge, $wgUsePrivateIPs; + public function testGetIP( $expected, $input, $squid, $xffList, $private, $description ) { $_SERVER = $input; - $wgSquidServersNoPurge = $squid; - $wgUsePrivateIPs = $private; + $this->setMwGlobals( array( + 'wgSquidServersNoPurge' => $squid, + 'wgUsePrivateIPs' => $private, + 'wgHooks' => array( + 'IsTrustedProxy' => array( + function( &$ip, &$trusted ) use ( $xffList ) { + $trusted = $trusted || in_array( $ip, $xffList ); + return true; + } + ) + ) + ) ); + $request = new WebRequest(); $result = $request->getIP(); $this->assertEquals( $expected, $result, $description ); } - function provideGetIP() { + public static function provideGetIP() { return array( array( '127.0.0.1', @@ -115,6 +134,7 @@ class WebRequestTest extends MediaWikiTestCase { 'REMOTE_ADDR' => '127.0.0.1' ), array(), + array(), false, 'Simple IPv4' ), @@ -124,16 +144,29 @@ class WebRequestTest extends MediaWikiTestCase { 'REMOTE_ADDR' => '::1' ), array(), + array(), false, 'Simple IPv6' ), array( + '12.0.0.1', + array( + 'REMOTE_ADDR' => 'abcd:0001:002:03:4:555:6666:7777', + 'HTTP_X_FORWARDED_FOR' => '12.0.0.1, abcd:0001:002:03:4:555:6666:7777', + ), + array( 'ABCD:1:2:3:4:555:6666:7777' ), + array(), + false, + 'IPv6 normalisation' + ), + array( '12.0.0.3', array( 'REMOTE_ADDR' => '12.0.0.1', 'HTTP_X_FORWARDED_FOR' => '12.0.0.3, 12.0.0.2' ), array( '12.0.0.1', '12.0.0.2' ), + array(), false, 'With X-Forwaded-For' ), @@ -144,6 +177,7 @@ class WebRequestTest extends MediaWikiTestCase { 'HTTP_X_FORWARDED_FOR' => '12.0.0.3, 12.0.0.2' ), array(), + array(), false, 'With X-Forwaded-For and disallowed server' ), @@ -154,42 +188,101 @@ class WebRequestTest extends MediaWikiTestCase { 'HTTP_X_FORWARDED_FOR' => '12.0.0.3, 12.0.0.2' ), array( '12.0.0.1' ), + array(), false, 'With multiple X-Forwaded-For and only one allowed server' ), array( - '12.0.0.2', + '10.0.0.3', array( 'REMOTE_ADDR' => '12.0.0.2', - 'HTTP_X_FORWARDED_FOR' => '10.0.0.3, 12.0.0.2' + 'HTTP_X_FORWARDED_FOR' => '10.0.0.4, 10.0.0.3, 12.0.0.2' ), array( '12.0.0.1', '12.0.0.2' ), + array(), false, - 'With X-Forwaded-For and private IP' + 'With X-Forwaded-For and private IP (from cache proxy)' ), array( - '10.0.0.3', + '10.0.0.4', array( 'REMOTE_ADDR' => '12.0.0.2', - 'HTTP_X_FORWARDED_FOR' => '10.0.0.3, 12.0.0.2' + 'HTTP_X_FORWARDED_FOR' => '10.0.0.4, 10.0.0.3, 12.0.0.2' + ), + array( '12.0.0.1', '12.0.0.2', '10.0.0.3' ), + array(), + true, + 'With X-Forwaded-For and private IP (allowed)' + ), + array( + '10.0.0.4', + array( + 'REMOTE_ADDR' => '12.0.0.2', + 'HTTP_X_FORWARDED_FOR' => '10.0.0.4, 10.0.0.3, 12.0.0.2' ), array( '12.0.0.1', '12.0.0.2' ), + array( '10.0.0.3' ), true, 'With X-Forwaded-For and private IP (allowed)' ), + array( + '10.0.0.3', + array( + 'REMOTE_ADDR' => '12.0.0.2', + 'HTTP_X_FORWARDED_FOR' => '10.0.0.4, 10.0.0.3, 12.0.0.2' + ), + array( '12.0.0.1', '12.0.0.2' ), + array( '10.0.0.3' ), + false, + 'With X-Forwaded-For and private IP (disallowed)' + ), + array( + '12.0.0.3', + array( + 'REMOTE_ADDR' => '12.0.0.1', + 'HTTP_X_FORWARDED_FOR' => '12.0.0.3, 12.0.0.2' + ), + array(), + array( '12.0.0.1', '12.0.0.2' ), + false, + 'With X-Forwaded-For' + ), + array( + '12.0.0.2', + array( + 'REMOTE_ADDR' => '12.0.0.1', + 'HTTP_X_FORWARDED_FOR' => '12.0.0.3, 12.0.0.2' + ), + array(), + array( '12.0.0.1' ), + false, + 'With multiple X-Forwaded-For and only one allowed server' + ), + array( + '12.0.0.2', + array( + 'REMOTE_ADDR' => '12.0.0.2', + 'HTTP_X_FORWARDED_FOR' => '10.0.0.3, 12.0.0.2' + ), + array(), + array( '12.0.0.2' ), + false, + 'With X-Forwaded-For and private IP and hook (disallowed)' + ), ); } /** * @expectedException MWException + * @covers WebRequest::getIP */ - function testGetIpLackOfRemoteAddrThrowAnException() { + public function testGetIpLackOfRemoteAddrThrowAnException() { $request = new WebRequest(); # Next call throw an exception about lacking an IP $request->getIP(); } - function languageProvider() { + public static function provideLanguageData() { return array( array( '', array(), 'Empty Accept-Language header' ), array( 'en', array( 'en' => 1 ), 'One language' ), @@ -206,11 +299,12 @@ class WebRequestTest extends MediaWikiTestCase { } /** - * @dataProvider languageProvider + * @dataProvider provideLanguageData + * @covers WebRequest::getAcceptLang */ - function testAcceptLang($acceptLanguageHeader, $expectedLanguages, $description) { + public function testAcceptLang( $acceptLanguageHeader, $expectedLanguages, $description ) { $_SERVER = array( 'HTTP_ACCEPT_LANGUAGE' => $acceptLanguageHeader ); $request = new WebRequest(); - $this->assertSame( $request->getAcceptLang(), $expectedLanguages, $description); + $this->assertSame( $request->getAcceptLang(), $expectedLanguages, $description ); } } diff --git a/tests/phpunit/includes/WikiPageTest.php b/tests/phpunit/includes/WikiPageTest.php index 0e1e1ce8..e0d786b9 100644 --- a/tests/phpunit/includes/WikiPageTest.php +++ b/tests/phpunit/includes/WikiPageTest.php @@ -1,41 +1,45 @@ <?php -/** -* @group Database -* ^--- important, causes temporary tables to be used instead of the real database -* @group medium -**/ +/** + * @group ContentHandler + * @group Database + * ^--- important, causes temporary tables to be used instead of the real database + * @group medium + **/ class WikiPageTest extends MediaWikiLangTestCase { - var $pages_to_delete; + protected $pages_to_delete; - function __construct( $name = null, array $data = array(), $dataName = '' ) { + function __construct( $name = null, array $data = array(), $dataName = '' ) { parent::__construct( $name, $data, $dataName ); - $this->tablesUsed = array_merge ( $this->tablesUsed, - array( 'page', - 'revision', - 'text', - - 'recentchanges', - 'logging', - - 'page_props', - 'pagelinks', - 'categorylinks', - 'langlinks', - 'externallinks', - 'imagelinks', - 'templatelinks', - 'iwlinks' ) ); + $this->tablesUsed = array_merge( + $this->tablesUsed, + array( 'page', + 'revision', + 'text', + + 'recentchanges', + 'logging', + + 'page_props', + 'pagelinks', + 'categorylinks', + 'langlinks', + 'externallinks', + 'imagelinks', + 'templatelinks', + 'iwlinks' ) ); } - public function setUp() { + protected function setUp() { parent::setUp(); $this->pages_to_delete = array(); + + LinkCache::singleton()->clear(); # avoid cached redirect status, etc } - public function tearDown() { + protected function tearDown() { foreach ( $this->pages_to_delete as $p ) { /* @var $p WikiPage */ @@ -50,8 +54,16 @@ class WikiPageTest extends MediaWikiLangTestCase { parent::tearDown(); } - protected function newPage( $title ) { - if ( is_string( $title ) ) $title = Title::newFromText( $title ); + /** + * @param Title $title + * @param String $model + * @return WikiPage + */ + protected function newPage( $title, $model = null ) { + if ( is_string( $title ) ) { + $ns = $this->getDefaultWikitextNS(); + $title = Title::newFromText( $title, $ns ); + } $p = new WikiPage( $title ); @@ -60,31 +72,114 @@ class WikiPageTest extends MediaWikiLangTestCase { return $p; } + /** + * @param String|Title|WikiPage $page + * @param String $text + * @param int $model + * + * @return WikiPage + */ protected function createPage( $page, $text, $model = null ) { - if ( is_string( $page ) ) $page = Title::newFromText( $page ); - if ( $page instanceof Title ) $page = $this->newPage( $page ); + if ( is_string( $page ) || $page instanceof Title ) { + $page = $this->newPage( $page, $model ); + } - $page->doEdit( $text, "testing", EDIT_NEW ); + $content = ContentHandler::makeContent( $text, $page->getTitle(), $model ); + $page->doEditContent( $content, "testing", EDIT_NEW ); return $page; } + /** + * @covers WikiPage::doEditContent + */ + public function testDoEditContent() { + $page = $this->newPage( "WikiPageTest_testDoEditContent" ); + $title = $page->getTitle(); + + $content = ContentHandler::makeContent( "[[Lorem ipsum]] dolor sit amet, consetetur sadipscing elitr, sed diam " + . " nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat.", + $title, CONTENT_MODEL_WIKITEXT ); + + $page->doEditContent( $content, "[[testing]] 1" ); + + $this->assertTrue( $title->getArticleID() > 0, "Title object should have new page id" ); + $this->assertTrue( $page->getId() > 0, "WikiPage should have new page id" ); + $this->assertTrue( $title->exists(), "Title object should indicate that the page now exists" ); + $this->assertTrue( $page->exists(), "WikiPage object should indicate that the page now exists" ); + + $id = $page->getId(); + + # ------------------------ + $dbr = wfGetDB( DB_SLAVE ); + $res = $dbr->select( 'pagelinks', '*', array( 'pl_from' => $id ) ); + $n = $res->numRows(); + $res->free(); + + $this->assertEquals( 1, $n, 'pagelinks should contain one link from the page' ); + + # ------------------------ + $page = new WikiPage( $title ); + + $retrieved = $page->getContent(); + $this->assertTrue( $content->equals( $retrieved ), 'retrieved content doesn\'t equal original' ); + + # ------------------------ + $content = ContentHandler::makeContent( "At vero eos et accusam et justo duo [[dolores]] et ea rebum. " + . "Stet clita kasd [[gubergren]], no sea takimata sanctus est.", + $title, CONTENT_MODEL_WIKITEXT ); + + $page->doEditContent( $content, "testing 2" ); + + # ------------------------ + $page = new WikiPage( $title ); + + $retrieved = $page->getContent(); + $this->assertTrue( $content->equals( $retrieved ), 'retrieved content doesn\'t equal original' ); + + # ------------------------ + $dbr = wfGetDB( DB_SLAVE ); + $res = $dbr->select( 'pagelinks', '*', array( 'pl_from' => $id ) ); + $n = $res->numRows(); + $res->free(); + + $this->assertEquals( 2, $n, 'pagelinks should contain two links from the page' ); + } + + /** + * @covers WikiPage::doEdit + */ public function testDoEdit() { - $title = Title::newFromText( "WikiPageTest_testDoEdit" ); + $this->hideDeprecated( "WikiPage::doEdit" ); + $this->hideDeprecated( "WikiPage::getText" ); + $this->hideDeprecated( "Revision::getText" ); + + //NOTE: assume help namespace will default to wikitext + $title = Title::newFromText( "Help:WikiPageTest_testDoEdit" ); $page = $this->newPage( $title ); $text = "[[Lorem ipsum]] dolor sit amet, consetetur sadipscing elitr, sed diam " - . " nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat."; + . " nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat."; - $page->doEdit( $text, "testing 1" ); + $page->doEdit( $text, "[[testing]] 1" ); + $this->assertTrue( $title->getArticleID() > 0, "Title object should have new page id" ); + $this->assertTrue( $page->getId() > 0, "WikiPage should have new page id" ); $this->assertTrue( $title->exists(), "Title object should indicate that the page now exists" ); $this->assertTrue( $page->exists(), "WikiPage object should indicate that the page now exists" ); $id = $page->getId(); # ------------------------ + $dbr = wfGetDB( DB_SLAVE ); + $res = $dbr->select( 'pagelinks', '*', array( 'pl_from' => $id ) ); + $n = $res->numRows(); + $res->free(); + + $this->assertEquals( 1, $n, 'pagelinks should contain one link from the page' ); + + # ------------------------ $page = new WikiPage( $title ); $retrieved = $page->getText(); @@ -92,7 +187,7 @@ class WikiPageTest extends MediaWikiLangTestCase { # ------------------------ $text = "At vero eos et accusam et justo duo [[dolores]] et ea rebum. " - . "Stet clita kasd [[gubergren]], no sea takimata sanctus est."; + . "Stet clita kasd [[gubergren]], no sea takimata sanctus est."; $page->doEdit( $text, "testing 2" ); @@ -111,10 +206,16 @@ class WikiPageTest extends MediaWikiLangTestCase { $this->assertEquals( 2, $n, 'pagelinks should contain two links from the page' ); } + /** + * @covers WikiPage::doQuickEdit + */ public function testDoQuickEdit() { global $wgUser; - $page = $this->createPage( "WikiPageTest_testDoQuickEdit", "original text" ); + $this->hideDeprecated( "WikiPage::doQuickEdit" ); + + //NOTE: assume help namespace will default to wikitext + $page = $this->createPage( "Help:WikiPageTest_testDoQuickEdit", "original text" ); $text = "quick text"; $page->doQuickEdit( $text, $wgUser, "testing q" ); @@ -124,13 +225,35 @@ class WikiPageTest extends MediaWikiLangTestCase { $this->assertEquals( $text, $page->getText() ); } + /** + * @covers WikiPage::doQuickEditContent + */ + public function testDoQuickEditContent() { + global $wgUser; + + $page = $this->createPage( "WikiPageTest_testDoQuickEditContent", "original text", CONTENT_MODEL_WIKITEXT ); + + $content = ContentHandler::makeContent( "quick text", $page->getTitle(), CONTENT_MODEL_WIKITEXT ); + $page->doQuickEditContent( $content, $wgUser, "testing q" ); + + # --------------------- + $page = new WikiPage( $page->getTitle() ); + $this->assertTrue( $content->equals( $page->getContent() ) ); + } + + /** + * @covers WikiPage::doDeleteArticle + */ public function testDoDeleteArticle() { - $page = $this->createPage( "WikiPageTest_testDoDeleteArticle", "[[original text]] foo" ); + $page = $this->createPage( "WikiPageTest_testDoDeleteArticle", "[[original text]] foo", CONTENT_MODEL_WIKITEXT ); $id = $page->getId(); $page->doDeleteArticle( "testing deletion" ); + $this->assertFalse( $page->getTitle()->getArticleID() > 0, "Title object should now have page id 0" ); + $this->assertFalse( $page->getId() > 0, "WikiPage should now have page id 0" ); $this->assertFalse( $page->exists(), "WikiPage::exists should return false after page was deleted" ); + $this->assertNull( $page->getContent(), "WikiPage::getContent should return null after page was deleted" ); $this->assertFalse( $page->getText(), "WikiPage::getText should return false after page was deleted" ); $t = Title::newFromText( $page->getTitle()->getPrefixedText() ); @@ -145,8 +268,11 @@ class WikiPageTest extends MediaWikiLangTestCase { $this->assertEquals( 0, $n, 'pagelinks should contain no more links from the page' ); } + /** + * @covers WikiPage::doDeleteUpdates + */ public function testDoDeleteUpdates() { - $page = $this->createPage( "WikiPageTest_testDoDeleteArticle", "[[original text]] foo" ); + $page = $this->createPage( "WikiPageTest_testDoDeleteArticle", "[[original text]] foo", CONTENT_MODEL_WIKITEXT ); $id = $page->getId(); $page->doDeleteUpdates( $id ); @@ -160,6 +286,9 @@ class WikiPageTest extends MediaWikiLangTestCase { $this->assertEquals( 0, $n, 'pagelinks should contain no more links from the page' ); } + /** + * @covers WikiPage::getRevision + */ public function testGetRevision() { $page = $this->newPage( "WikiPageTest_testGetRevision" ); @@ -167,47 +296,107 @@ class WikiPageTest extends MediaWikiLangTestCase { $this->assertNull( $rev ); # ----------------- - $this->createPage( $page, "some text" ); + $this->createPage( $page, "some text", CONTENT_MODEL_WIKITEXT ); $rev = $page->getRevision(); $this->assertEquals( $page->getLatest(), $rev->getId() ); - $this->assertEquals( "some text", $rev->getText() ); + $this->assertEquals( "some text", $rev->getContent()->getNativeData() ); } + /** + * @covers WikiPage::getContent + */ + public function testGetContent() { + $page = $this->newPage( "WikiPageTest_testGetContent" ); + + $content = $page->getContent(); + $this->assertNull( $content ); + + # ----------------- + $this->createPage( $page, "some text", CONTENT_MODEL_WIKITEXT ); + + $content = $page->getContent(); + $this->assertEquals( "some text", $content->getNativeData() ); + } + + /** + * @covers WikiPage::getText + */ public function testGetText() { + $this->hideDeprecated( "WikiPage::getText" ); + $page = $this->newPage( "WikiPageTest_testGetText" ); $text = $page->getText(); $this->assertFalse( $text ); # ----------------- - $this->createPage( $page, "some text" ); + $this->createPage( $page, "some text", CONTENT_MODEL_WIKITEXT ); $text = $page->getText(); $this->assertEquals( "some text", $text ); } + /** + * @covers WikiPage::getRawText + */ public function testGetRawText() { + $this->hideDeprecated( "WikiPage::getRawText" ); + $page = $this->newPage( "WikiPageTest_testGetRawText" ); $text = $page->getRawText(); $this->assertFalse( $text ); # ----------------- - $this->createPage( $page, "some text" ); + $this->createPage( $page, "some text", CONTENT_MODEL_WIKITEXT ); $text = $page->getRawText(); $this->assertEquals( "some text", $text ); } - + /** + * @covers WikiPage::getContentModel + */ + public function testGetContentModel() { + global $wgContentHandlerUseDB; + + if ( !$wgContentHandlerUseDB ) { + $this->markTestSkipped( '$wgContentHandlerUseDB is disabled' ); + } + + $page = $this->createPage( "WikiPageTest_testGetContentModel", "some text", CONTENT_MODEL_JAVASCRIPT ); + + $page = new WikiPage( $page->getTitle() ); + $this->assertEquals( CONTENT_MODEL_JAVASCRIPT, $page->getContentModel() ); + } + + /** + * @covers WikiPage::getContentHandler + */ + public function testGetContentHandler() { + global $wgContentHandlerUseDB; + + if ( !$wgContentHandlerUseDB ) { + $this->markTestSkipped( '$wgContentHandlerUseDB is disabled' ); + } + + $page = $this->createPage( "WikiPageTest_testGetContentHandler", "some text", CONTENT_MODEL_JAVASCRIPT ); + + $page = new WikiPage( $page->getTitle() ); + $this->assertEquals( 'JavaScriptContentHandler', get_class( $page->getContentHandler() ) ); + } + + /** + * @covers WikiPage::exists + */ public function testExists() { $page = $this->newPage( "WikiPageTest_testExists" ); $this->assertFalse( $page->exists() ); # ----------------- - $this->createPage( $page, "some text" ); + $this->createPage( $page, "some text", CONTENT_MODEL_WIKITEXT ); $this->assertTrue( $page->exists() ); $page = new WikiPage( $page->getTitle() ); @@ -221,7 +410,7 @@ class WikiPageTest extends MediaWikiLangTestCase { $this->assertFalse( $page->exists() ); } - public function dataHasViewableContent() { + public static function provideHasViewableContent() { return array( array( 'WikiPageTest_testHasViewableContent', false, true ), array( 'Special:WikiPageTest_testHasViewableContent', false ), @@ -232,14 +421,15 @@ class WikiPageTest extends MediaWikiLangTestCase { } /** - * @dataProvider dataHasViewableContent + * @dataProvider provideHasViewableContent + * @covers WikiPage::hasViewableContent */ public function testHasViewableContent( $title, $viewable, $create = false ) { $page = $this->newPage( $title ); $this->assertEquals( $viewable, $page->hasViewableContent() ); if ( $create ) { - $this->createPage( $page, "some text" ); + $this->createPage( $page, "some text", CONTENT_MODEL_WIKITEXT ); $this->assertTrue( $page->hasViewableContent() ); $page = new WikiPage( $page->getTitle() ); @@ -247,18 +437,23 @@ class WikiPageTest extends MediaWikiLangTestCase { } } - public function dataGetRedirectTarget() { + public static function provideGetRedirectTarget() { return array( - array( 'WikiPageTest_testGetRedirectTarget_1', "hello world", null ), - array( 'WikiPageTest_testGetRedirectTarget_2', "#REDIRECT [[hello world]]", "Hello world" ), + array( 'WikiPageTest_testGetRedirectTarget_1', CONTENT_MODEL_WIKITEXT, "hello world", null ), + array( 'WikiPageTest_testGetRedirectTarget_2', CONTENT_MODEL_WIKITEXT, "#REDIRECT [[hello world]]", "Hello world" ), ); } /** - * @dataProvider dataGetRedirectTarget + * @dataProvider provideGetRedirectTarget + * @covers WikiPage::getRedirectTarget */ - public function testGetRedirectTarget( $title, $text, $target ) { - $page = $this->createPage( $title, $text ); + public function testGetRedirectTarget( $title, $model, $text, $target ) { + $page = $this->createPage( $title, $text, $model ); + + # sanity check, because this test seems to fail for no reason for some people. + $c = $page->getContent(); + $this->assertEquals( 'WikitextContent', get_class( $c ) ); # now, test the actual redirect $t = $page->getRedirectTarget(); @@ -266,143 +461,169 @@ class WikiPageTest extends MediaWikiLangTestCase { } /** - * @dataProvider dataGetRedirectTarget + * @dataProvider provideGetRedirectTarget + * @covers WikiPage::isRedirect */ - public function testIsRedirect( $title, $text, $target ) { - $page = $this->createPage( $title, $text ); + public function testIsRedirect( $title, $model, $text, $target ) { + $page = $this->createPage( $title, $text, $model ); $this->assertEquals( !is_null( $target ), $page->isRedirect() ); } - public function dataIsCountable() { + public static function provideIsCountable() { return array( // any array( 'WikiPageTest_testIsCountable', - '', - 'any', - true + CONTENT_MODEL_WIKITEXT, + '', + 'any', + true ), array( 'WikiPageTest_testIsCountable', - 'Foo', - 'any', - true + CONTENT_MODEL_WIKITEXT, + 'Foo', + 'any', + true ), // comma array( 'WikiPageTest_testIsCountable', - 'Foo', - 'comma', - false + CONTENT_MODEL_WIKITEXT, + 'Foo', + 'comma', + false ), array( 'WikiPageTest_testIsCountable', - 'Foo, bar', - 'comma', - true + CONTENT_MODEL_WIKITEXT, + 'Foo, bar', + 'comma', + true ), // link array( 'WikiPageTest_testIsCountable', - 'Foo', - 'link', - false + CONTENT_MODEL_WIKITEXT, + 'Foo', + 'link', + false ), array( 'WikiPageTest_testIsCountable', - 'Foo [[bar]]', - 'link', - true + CONTENT_MODEL_WIKITEXT, + 'Foo [[bar]]', + 'link', + true ), // redirects array( 'WikiPageTest_testIsCountable', - '#REDIRECT [[bar]]', - 'any', - false + CONTENT_MODEL_WIKITEXT, + '#REDIRECT [[bar]]', + 'any', + false ), array( 'WikiPageTest_testIsCountable', - '#REDIRECT [[bar]]', - 'comma', - false + CONTENT_MODEL_WIKITEXT, + '#REDIRECT [[bar]]', + 'comma', + false ), array( 'WikiPageTest_testIsCountable', - '#REDIRECT [[bar]]', - 'link', - false + CONTENT_MODEL_WIKITEXT, + '#REDIRECT [[bar]]', + 'link', + false ), // not a content namespace array( 'Talk:WikiPageTest_testIsCountable', - 'Foo', - 'any', - false + CONTENT_MODEL_WIKITEXT, + 'Foo', + 'any', + false ), array( 'Talk:WikiPageTest_testIsCountable', - 'Foo, bar', - 'comma', - false + CONTENT_MODEL_WIKITEXT, + 'Foo, bar', + 'comma', + false ), array( 'Talk:WikiPageTest_testIsCountable', - 'Foo [[bar]]', - 'link', - false + CONTENT_MODEL_WIKITEXT, + 'Foo [[bar]]', + 'link', + false ), // not a content namespace, different model array( 'MediaWiki:WikiPageTest_testIsCountable.js', - 'Foo', - 'any', - false + null, + 'Foo', + 'any', + false ), array( 'MediaWiki:WikiPageTest_testIsCountable.js', - 'Foo, bar', - 'comma', - false + null, + 'Foo, bar', + 'comma', + false ), array( 'MediaWiki:WikiPageTest_testIsCountable.js', - 'Foo [[bar]]', - 'link', - false + null, + 'Foo [[bar]]', + 'link', + false ), ); } /** - * @dataProvider dataIsCountable + * @dataProvider provideIsCountable + * @covers WikiPage::isCountable */ - public function testIsCountable( $title, $text, $mode, $expected ) { - global $wgArticleCountMethod; + public function testIsCountable( $title, $model, $text, $mode, $expected ) { + global $wgContentHandlerUseDB; - $old = $wgArticleCountMethod; - $wgArticleCountMethod = $mode; + $this->setMwGlobals( 'wgArticleCountMethod', $mode ); + + $title = Title::newFromText( $title ); + + if ( !$wgContentHandlerUseDB && $model && ContentHandler::getDefaultModelFor( $title ) != $model ) { + $this->markTestSkipped( "Can not use non-default content model $model for " + . $title->getPrefixedDBkey() . " with \$wgContentHandlerUseDB disabled." ); + } - $page = $this->createPage( $title, $text ); - $editInfo = $page->prepareTextForEdit( $page->getText() ); + $page = $this->createPage( $title, $text, $model ); + $hasLinks = wfGetDB( DB_SLAVE )->selectField( 'pagelinks', 1, + array( 'pl_from' => $page->getId() ), __METHOD__ ); + + $editInfo = $page->prepareContentForEdit( $page->getContent() ); $v = $page->isCountable(); $w = $page->isCountable( $editInfo ); - $wgArticleCountMethod = $old; $this->assertEquals( $expected, $v, "isCountable( null ) returned unexpected value " . var_export( $v, true ) - . " instead of " . var_export( $expected, true ) . " in mode `$mode` for text \"$text\"" ); + . " instead of " . var_export( $expected, true ) . " in mode `$mode` for text \"$text\"" ); $this->assertEquals( $expected, $w, "isCountable( \$editInfo ) returned unexpected value " . var_export( $v, true ) - . " instead of " . var_export( $expected, true ) . " in mode `$mode` for text \"$text\"" ); + . " instead of " . var_export( $expected, true ) . " in mode `$mode` for text \"$text\"" ); } - public function dataGetParserOutput() { + public static function provideGetParserOutput() { return array( - array("hello ''world''\n", "<p>hello <i>world</i></p>"), - // @todo: more...? + array( CONTENT_MODEL_WIKITEXT, "hello ''world''\n", "<p>hello <i>world</i></p>" ), + // @todo more...? ); } /** - * @dataProvider dataGetParserOutput + * @dataProvider provideGetParserOutput + * @covers WikiPage::getParserOutput */ - public function testGetParserOutput( $text, $expectedHtml ) { - $page = $this->createPage( 'WikiPageTest_testGetParserOutput', $text ); + public function testGetParserOutput( $model, $text, $expectedHtml ) { + $page = $this->createPage( 'WikiPageTest_testGetParserOutput', $text, $model ); - $opt = new ParserOptions(); + $opt = $page->makeParserOptions( 'canonical' ); $po = $page->getParserOutput( $opt ); $text = $po->getText(); @@ -410,9 +631,39 @@ class WikiPageTest extends MediaWikiLangTestCase { $text = preg_replace( '!\s*(</p>)!sm', '\1', $text ); # don't let tidy confuse us $this->assertEquals( $expectedHtml, $text ); + return $po; } + /** + * @covers WikiPage::getParserOutput + */ + public function testGetParserOutput_nonexisting() { + static $count = 0; + $count++; + + $page = new WikiPage( new Title( "WikiPageTest_testGetParserOutput_nonexisting_$count" ) ); + + $opt = new ParserOptions(); + $po = $page->getParserOutput( $opt ); + + $this->assertFalse( $po, "getParserOutput() shall return false for non-existing pages." ); + } + + /** + * @covers WikiPage::getParserOutput + */ + public function testGetParserOutput_badrev() { + $page = $this->createPage( 'WikiPageTest_testGetParserOutput', "dummy", CONTENT_MODEL_WIKITEXT ); + + $opt = new ParserOptions(); + $po = $page->getParserOutput( $opt, $page->getLatest() + 1234 ); + + // @todo would be neat to also test deleted revision + + $this->assertFalse( $po, "getParserOutput() shall return false for non-existing revisions." ); + } + static $sections = "Intro @@ -429,129 +680,147 @@ more stuff public function dataReplaceSection() { + //NOTE: assume the Help namespace to contain wikitext return array( - array( 'WikiPageTest_testReplaceSection', - WikiPageTest::$sections, - "0", - "No more", - null, - trim( preg_replace( '/^Intro/sm', 'No more', WikiPageTest::$sections ) ) - ), - array( 'WikiPageTest_testReplaceSection', - WikiPageTest::$sections, - "", - "No more", - null, - "No more" - ), - array( 'WikiPageTest_testReplaceSection', - WikiPageTest::$sections, - "2", - "== TEST ==\nmore fun", - null, - trim( preg_replace( '/^== test ==.*== foo ==/sm', "== TEST ==\nmore fun\n\n== foo ==", WikiPageTest::$sections ) ) - ), - array( 'WikiPageTest_testReplaceSection', - WikiPageTest::$sections, - "8", - "No more", - null, - trim( WikiPageTest::$sections ) - ), - array( 'WikiPageTest_testReplaceSection', - WikiPageTest::$sections, - "new", - "No more", - "New", - trim( WikiPageTest::$sections ) . "\n\n== New ==\n\nNo more" + array( 'Help:WikiPageTest_testReplaceSection', + CONTENT_MODEL_WIKITEXT, + WikiPageTest::$sections, + "0", + "No more", + null, + trim( preg_replace( '/^Intro/sm', 'No more', WikiPageTest::$sections ) ) + ), + array( 'Help:WikiPageTest_testReplaceSection', + CONTENT_MODEL_WIKITEXT, + WikiPageTest::$sections, + "", + "No more", + null, + "No more" + ), + array( 'Help:WikiPageTest_testReplaceSection', + CONTENT_MODEL_WIKITEXT, + WikiPageTest::$sections, + "2", + "== TEST ==\nmore fun", + null, + trim( preg_replace( '/^== test ==.*== foo ==/sm', + "== TEST ==\nmore fun\n\n== foo ==", + WikiPageTest::$sections ) ) + ), + array( 'Help:WikiPageTest_testReplaceSection', + CONTENT_MODEL_WIKITEXT, + WikiPageTest::$sections, + "8", + "No more", + null, + trim( WikiPageTest::$sections ) + ), + array( 'Help:WikiPageTest_testReplaceSection', + CONTENT_MODEL_WIKITEXT, + WikiPageTest::$sections, + "new", + "No more", + "New", + trim( WikiPageTest::$sections ) . "\n\n== New ==\n\nNo more" ), ); } /** * @dataProvider dataReplaceSection + * @covers WikiPage::replaceSection */ - public function testReplaceSection( $title, $text, $section, $with, $sectionTitle, $expected ) { - $page = $this->createPage( $title, $text ); + public function testReplaceSection( $title, $model, $text, $section, $with, $sectionTitle, $expected ) { + $this->hideDeprecated( "WikiPage::replaceSection" ); + + $page = $this->createPage( $title, $text, $model ); $text = $page->replaceSection( $section, $with, $sectionTitle ); $text = trim( $text ); $this->assertEquals( $expected, $text ); } - /* @todo FIXME: fix this! - public function testGetUndoText() { - global $wgDiff3; + /** + * @dataProvider dataReplaceSection + * @covers WikiPage::replaceSectionContent + */ + public function testReplaceSectionContent( $title, $model, $text, $section, $with, $sectionTitle, $expected ) { + $page = $this->createPage( $title, $text, $model ); - wfSuppressWarnings(); - $haveDiff3 = $wgDiff3 && file_exists( $wgDiff3 ); - wfRestoreWarnings(); + $content = ContentHandler::makeContent( $with, $page->getTitle(), $page->getContentModel() ); + $c = $page->replaceSectionContent( $section, $content, $sectionTitle ); - if( !$haveDiff3 ) { - $this->markTestSkipped( "diff3 not installed or not found" ); - return; - } + $this->assertEquals( $expected, is_null( $c ) ? null : trim( $c->getNativeData() ) ); + } - $text = "one"; - $page = $this->createPage( "WikiPageTest_testGetUndoText", $text ); - $rev1 = $page->getRevision(); + /* @todo FIXME: fix this! + public function testGetUndoText() { + $this->checkHasDiff3(); - $text .= "\n\ntwo"; - $page->doEditContent( ContentHandler::makeContent( $text, $page->getTitle() ), "adding section two"); - $rev2 = $page->getRevision(); + $text = "one"; + $page = $this->createPage( "WikiPageTest_testGetUndoText", $text ); + $rev1 = $page->getRevision(); - $text .= "\n\nthree"; - $page->doEditContent( ContentHandler::makeContent( $text, $page->getTitle() ), "adding section three"); - $rev3 = $page->getRevision(); + $text .= "\n\ntwo"; + $page->doEditContent( ContentHandler::makeContent( $text, $page->getTitle() ), "adding section two"); + $rev2 = $page->getRevision(); - $text .= "\n\nfour"; - $page->doEditContent( ContentHandler::makeContent( $text, $page->getTitle() ), "adding section four"); - $rev4 = $page->getRevision(); + $text .= "\n\nthree"; + $page->doEditContent( ContentHandler::makeContent( $text, $page->getTitle() ), "adding section three"); + $rev3 = $page->getRevision(); - $text .= "\n\nfive"; - $page->doEditContent( ContentHandler::makeContent( $text, $page->getTitle() ), "adding section five"); - $rev5 = $page->getRevision(); + $text .= "\n\nfour"; + $page->doEditContent( ContentHandler::makeContent( $text, $page->getTitle() ), "adding section four"); + $rev4 = $page->getRevision(); - $text .= "\n\nsix"; - $page->doEditContent( ContentHandler::makeContent( $text, $page->getTitle() ), "adding section six"); - $rev6 = $page->getRevision(); + $text .= "\n\nfive"; + $page->doEditContent( ContentHandler::makeContent( $text, $page->getTitle() ), "adding section five"); + $rev5 = $page->getRevision(); - $undo6 = $page->getUndoText( $rev6 ); - if ( $undo6 === false ) $this->fail( "getUndoText failed for rev6" ); - $this->assertEquals( "one\n\ntwo\n\nthree\n\nfour\n\nfive", $undo6 ); + $text .= "\n\nsix"; + $page->doEditContent( ContentHandler::makeContent( $text, $page->getTitle() ), "adding section six"); + $rev6 = $page->getRevision(); - $undo3 = $page->getUndoText( $rev4, $rev2 ); - if ( $undo3 === false ) $this->fail( "getUndoText failed for rev4..rev2" ); - $this->assertEquals( "one\n\ntwo\n\nfive", $undo3 ); + $undo6 = $page->getUndoText( $rev6 ); + if ( $undo6 === false ) $this->fail( "getUndoText failed for rev6" ); + $this->assertEquals( "one\n\ntwo\n\nthree\n\nfour\n\nfive", $undo6 ); - $undo2 = $page->getUndoText( $rev2 ); - if ( $undo2 === false ) $this->fail( "getUndoText failed for rev2" ); - $this->assertEquals( "one\n\nfive", $undo2 ); + $undo3 = $page->getUndoText( $rev4, $rev2 ); + if ( $undo3 === false ) $this->fail( "getUndoText failed for rev4..rev2" ); + $this->assertEquals( "one\n\ntwo\n\nfive", $undo3 ); + + $undo2 = $page->getUndoText( $rev2 ); + if ( $undo2 === false ) $this->fail( "getUndoText failed for rev2" ); + $this->assertEquals( "one\n\nfive", $undo2 ); } - */ + */ /** * @todo FIXME: this is a better rollback test than the one below, but it keeps failing in jenkins for some reason. */ public function broken_testDoRollback() { $admin = new User(); - $admin->setName("Admin"); + $admin->setName( "Admin" ); $text = "one"; $page = $this->newPage( "WikiPageTest_testDoRollback" ); - $page->doEdit( $text, "section one", EDIT_NEW, false, $admin ); + $page->doEditContent( ContentHandler::makeContent( $text, $page->getTitle() ), + "section one", EDIT_NEW, false, $admin ); $user1 = new User(); $user1->setName( "127.0.1.11" ); $text .= "\n\ntwo"; $page = new WikiPage( $page->getTitle() ); - $page->doEdit( $text, "adding section two", 0, false, $user1 ); + $page->doEditContent( ContentHandler::makeContent( $text, $page->getTitle() ), + "adding section two", 0, false, $user1 ); $user2 = new User(); $user2->setName( "127.0.2.13" ); $text .= "\n\nthree"; $page = new WikiPage( $page->getTitle() ); - $page->doEdit( $text, "adding section three", 0, false, $user2 ); + $page->doEditContent( ContentHandler::makeContent( $text, $page->getTitle() ), + "adding section three", 0, false, $user2 ); # we are having issues with doRollback spuriously failing. apparently the last revision somehow goes missing # or not committed under some circumstances. so, make sure the last revision has the right user name. @@ -578,27 +847,31 @@ more stuff } $page = new WikiPage( $page->getTitle() ); - $this->assertEquals( $rev2->getSha1(), $page->getRevision()->getSha1(), "rollback did not revert to the correct revision" ); - $this->assertEquals( "one\n\ntwo", $page->getText() ); + $this->assertEquals( $rev2->getSha1(), $page->getRevision()->getSha1(), + "rollback did not revert to the correct revision" ); + $this->assertEquals( "one\n\ntwo", $page->getContent()->getNativeData() ); } /** * @todo FIXME: the above rollback test is better, but it keeps failing in jenkins for some reason. + * @covers WikiPage::doRollback */ public function testDoRollback() { $admin = new User(); - $admin->setName("Admin"); + $admin->setName( "Admin" ); $text = "one"; $page = $this->newPage( "WikiPageTest_testDoRollback" ); - $page->doEdit( $text, "section one", EDIT_NEW, false, $admin ); + $page->doEditContent( ContentHandler::makeContent( $text, $page->getTitle(), CONTENT_MODEL_WIKITEXT ), + "section one", EDIT_NEW, false, $admin ); $rev1 = $page->getRevision(); $user1 = new User(); $user1->setName( "127.0.1.11" ); $text .= "\n\ntwo"; $page = new WikiPage( $page->getTitle() ); - $page->doEdit( $text, "adding section two", 0, false, $user1 ); + $page->doEditContent( ContentHandler::makeContent( $text, $page->getTitle(), CONTENT_MODEL_WIKITEXT ), + "adding section two", 0, false, $user1 ); # now, try the rollback $admin->addGroup( "sysop" ); #XXX: make the test user a sysop... @@ -610,11 +883,12 @@ more stuff } $page = new WikiPage( $page->getTitle() ); - $this->assertEquals( $rev1->getSha1(), $page->getRevision()->getSha1(), "rollback did not revert to the correct revision" ); - $this->assertEquals( "one", $page->getText() ); + $this->assertEquals( $rev1->getSha1(), $page->getRevision()->getSha1(), + "rollback did not revert to the correct revision" ); + $this->assertEquals( "one", $page->getContent()->getNativeData() ); } - public function dataGetAutosummary( ) { + public static function provideGetAutosummary() { return array( array( 'Hello there, world!', @@ -656,17 +930,21 @@ more stuff } /** - * @dataProvider dataGetAutoSummary + * @dataProvider provideGetAutoSummary + * @covers WikiPage::getAutosummary */ public function testGetAutosummary( $old, $new, $flags, $expected ) { + $this->hideDeprecated( "WikiPage::getAutosummary" ); + $page = $this->newPage( "WikiPageTest_testGetAutosummary" ); $summary = $page->getAutosummary( $old, $new, $flags ); - $this->assertTrue( (bool)preg_match( $expected, $summary ), "Autosummary didn't match expected pattern $expected: $summary" ); + $this->assertTrue( (bool)preg_match( $expected, $summary ), + "Autosummary didn't match expected pattern $expected: $summary" ); } - public function dataGetAutoDeleteReason( ) { + public static function provideGetAutoDeleteReason() { return array( array( array(), @@ -703,10 +981,10 @@ more stuff array( array( array( "first edit: " - . "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam " - . " nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. " - . "At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea " - . "takimata sanctus est Lorem ipsum dolor sit amet.'", null ), + . "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam " + . " nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. " + . "At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea " + . "takimata sanctus est Lorem ipsum dolor sit amet.'", null ), ), '/first edit:.*\.\.\."/', false @@ -725,60 +1003,72 @@ more stuff } /** - * @dataProvider dataGetAutoDeleteReason + * @dataProvider provideGetAutoDeleteReason + * @covers WikiPage::getAutoDeleteReason */ public function testGetAutoDeleteReason( $edits, $expectedResult, $expectedHistory ) { global $wgUser; - $page = $this->newPage( "WikiPageTest_testGetAutoDeleteReason" ); + //NOTE: assume Help namespace to contain wikitext + $page = $this->newPage( "Help:WikiPageTest_testGetAutoDeleteReason" ); $c = 1; foreach ( $edits as $edit ) { $user = new User(); - if ( !empty( $edit[1] ) ) $user->setName( $edit[1] ); - else $user = $wgUser; + if ( !empty( $edit[1] ) ) { + $user->setName( $edit[1] ); + } else { + $user = $wgUser; + } + + $content = ContentHandler::makeContent( $edit[0], $page->getTitle(), $page->getContentModel() ); - $page->doEdit( $edit[0], "test edit $c", $c < 2 ? EDIT_NEW : 0, false, $user ); + $page->doEditContent( $content, "test edit $c", $c < 2 ? EDIT_NEW : 0, false, $user ); $c += 1; } $reason = $page->getAutoDeleteReason( $hasHistory ); - if ( is_bool( $expectedResult ) || is_null( $expectedResult ) ) $this->assertEquals( $expectedResult, $reason ); - else $this->assertTrue( (bool)preg_match( $expectedResult, $reason ), "Autosummary didn't match expected pattern $expectedResult: $reason" ); + if ( is_bool( $expectedResult ) || is_null( $expectedResult ) ) { + $this->assertEquals( $expectedResult, $reason ); + } else { + $this->assertTrue( (bool)preg_match( $expectedResult, $reason ), + "Autosummary didn't match expected pattern $expectedResult: $reason" ); + } - $this->assertEquals( $expectedHistory, $hasHistory, "expected \$hasHistory to be " . var_export( $expectedHistory, true ) ); + $this->assertEquals( $expectedHistory, $hasHistory, + "expected \$hasHistory to be " . var_export( $expectedHistory, true ) ); $page->doDeleteArticle( "done" ); } - public function dataPreSaveTransform() { + public static function providePreSaveTransform() { return array( array( 'hello this is ~~~', - "hello this is [[Special:Contributions/127.0.0.1|127.0.0.1]]", + "hello this is [[Special:Contributions/127.0.0.1|127.0.0.1]]", ), array( 'hello \'\'this\'\' is <nowiki>~~~</nowiki>', - 'hello \'\'this\'\' is <nowiki>~~~</nowiki>', + 'hello \'\'this\'\' is <nowiki>~~~</nowiki>', ), ); } /** - * @dataProvider dataPreSaveTransform + * @dataProvider providePreSaveTransform + * @covers WikiPage::preSaveTransform */ public function testPreSaveTransform( $text, $expected ) { $this->hideDeprecated( 'WikiPage::preSaveTransform' ); $user = new User(); - $user->setName("127.0.0.1"); + $user->setName( "127.0.0.1" ); - $page = $this->newPage( "WikiPageTest_testPreloadTransform" ); + //NOTE: assume Help namespace to contain wikitext + $page = $this->newPage( "Help:WikiPageTest_testPreloadTransform" ); $text = $page->preSaveTransform( $text, $user ); $this->assertEquals( $expected, $text ); } - } - diff --git a/tests/phpunit/includes/WikiPageTest_ContentHandlerUseDB.php b/tests/phpunit/includes/WikiPageTest_ContentHandlerUseDB.php new file mode 100644 index 00000000..2a723e85 --- /dev/null +++ b/tests/phpunit/includes/WikiPageTest_ContentHandlerUseDB.php @@ -0,0 +1,53 @@ +<?php + +/** + * @group ContentHandler + * @group Database + * ^--- important, causes temporary tables to be used instead of the real database + */ +class WikiPageTest_ContentHandlerUseDB extends WikiPageTest { + + protected function setUp() { + parent::setUp(); + $this->setMwGlobals( 'wgContentHandlerUseDB', false ); + + $dbw = wfGetDB( DB_MASTER ); + + $page_table = $dbw->tableName( 'page' ); + $revision_table = $dbw->tableName( 'revision' ); + $archive_table = $dbw->tableName( 'archive' ); + + if ( $dbw->fieldExists( $page_table, 'page_content_model' ) ) { + $dbw->query( "alter table $page_table drop column page_content_model" ); + $dbw->query( "alter table $revision_table drop column rev_content_model" ); + $dbw->query( "alter table $revision_table drop column rev_content_format" ); + $dbw->query( "alter table $archive_table drop column ar_content_model" ); + $dbw->query( "alter table $archive_table drop column ar_content_format" ); + } + } + + /** + * @covers WikiPage::getContentModel + */ + public function testGetContentModel() { + $page = $this->createPage( "WikiPageTest_testGetContentModel", "some text", CONTENT_MODEL_JAVASCRIPT ); + + $page = new WikiPage( $page->getTitle() ); + + // NOTE: since the content model is not recorded in the database, + // we expect to get the default, namely CONTENT_MODEL_WIKITEXT + $this->assertEquals( CONTENT_MODEL_WIKITEXT, $page->getContentModel() ); + } + + /** + * @covers WikiPage::getContentHandler + */ + public function testGetContentHandler() { + $page = $this->createPage( "WikiPageTest_testGetContentHandler", "some text", CONTENT_MODEL_JAVASCRIPT ); + + // NOTE: since the content model is not recorded in the database, + // we expect to get the default, namely CONTENT_MODEL_WIKITEXT + $page = new WikiPage( $page->getTitle() ); + $this->assertEquals( 'WikitextContentHandler', get_class( $page->getContentHandler() ) ); + } +} diff --git a/tests/phpunit/includes/XmlJsTest.php b/tests/phpunit/includes/XmlJsTest.php index c5b411fb..161468e2 100644 --- a/tests/phpunit/includes/XmlJsTest.php +++ b/tests/phpunit/includes/XmlJsTest.php @@ -1,9 +1,24 @@ <?php + +/** + * @group Xml + */ class XmlJs extends MediaWikiTestCase { - public function testConstruction() { - $obj = new XmlJsCode( null ); - $this->assertNull( $obj->value ); - $obj = new XmlJsCode( '' ); - $this->assertSame( $obj->value, '' ); + + /** + * @covers XmlJsCode::__construct + * @dataProvider provideConstruction + */ + public function testConstruction( $value ) { + $obj = new XmlJsCode( $value ); + $this->assertEquals( $value, $obj->value ); } + + public function provideConstruction(){ + return array( + array( null ), + array( '' ), + ); + } + } diff --git a/tests/phpunit/includes/XmlSelectTest.php b/tests/phpunit/includes/XmlSelectTest.php index 2407c151..56d28b54 100644 --- a/tests/phpunit/includes/XmlSelectTest.php +++ b/tests/phpunit/includes/XmlSelectTest.php @@ -1,18 +1,31 @@ <?php -// TODO +/** + * @group Xml + */ class XmlSelectTest extends MediaWikiTestCase { + + /** + * @var XmlSelect + */ protected $select; protected function setUp() { + parent::setUp(); + $this->setMwGlobals( array( + 'wgWellFormedXml' => true, + ) ); $this->select = new XmlSelect(); } + protected function tearDown() { + parent::tearDown(); $this->select = null; } - ### START OF TESTS ### - + /** + * @covers XmlSelect::__construct + */ public function testConstructWithoutParameters() { $this->assertEquals( '<select></select>', $this->select->getHTML() ); } @@ -20,6 +33,7 @@ class XmlSelectTest extends MediaWikiTestCase { /** * Parameters are $name (false), $id (false), $default (false) * @dataProvider provideConstructionParameters + * @covers XmlSelect::__construct */ public function testConstructParameters( $name, $id, $default, $expected ) { $this->select = new XmlSelect( $name, $id, $default ); @@ -33,9 +47,8 @@ class XmlSelectTest extends MediaWikiTestCase { * - $id (default: false) * - $default (default: false) * Provides a fourth parameters representing the expected HTML output - * */ - public function provideConstructionParameters() { + public static function provideConstructionParameters() { return array( /** * Values are set following a 3-bit Gray code where two successive @@ -43,51 +56,68 @@ class XmlSelectTest extends MediaWikiTestCase { * See http://en.wikipedia.org/wiki/Gray_code */ # $name $id $default - array( false , false, false, '<select></select>' ), - array( false , false, 'foo', '<select></select>' ), - array( false , 'id' , 'foo', '<select id="id"></select>' ), - array( false , 'id' , false, '<select id="id"></select>' ), - array( 'name', 'id' , false, '<select name="name" id="id"></select>' ), - array( 'name', 'id' , 'foo', '<select name="name" id="id"></select>' ), - array( 'name', false, 'foo', '<select name="name"></select>' ), - array( 'name', false, false, '<select name="name"></select>' ), + array( false, false, false, '<select></select>' ), + array( false, false, 'foo', '<select></select>' ), + array( false, 'id', 'foo', '<select id="id"></select>' ), + array( false, 'id', false, '<select id="id"></select>' ), + array( 'name', 'id', false, '<select name="name" id="id"></select>' ), + array( 'name', 'id', 'foo', '<select name="name" id="id"></select>' ), + array( 'name', false, 'foo', '<select name="name"></select>' ), + array( 'name', false, false, '<select name="name"></select>' ), ); } - # Begin XmlSelect::addOption() similar to Xml::option + /** + * @covers XmlSelect::addOption + */ public function testAddOption() { $this->select->addOption( 'foo' ); $this->assertEquals( '<select><option value="foo">foo</option></select>', $this->select->getHTML() ); } + + /** + * @covers XmlSelect::addOption + */ public function testAddOptionWithDefault() { $this->select->addOption( 'foo', true ); $this->assertEquals( '<select><option value="1">foo</option></select>', $this->select->getHTML() ); } + + /** + * @covers XmlSelect::addOption + */ public function testAddOptionWithFalse() { $this->select->addOption( 'foo', false ); $this->assertEquals( '<select><option value="foo">foo</option></select>', $this->select->getHTML() ); } + + /** + * @covers XmlSelect::addOption + */ public function testAddOptionWithValueZero() { $this->select->addOption( 'foo', 0 ); $this->assertEquals( '<select><option value="0">foo</option></select>', $this->select->getHTML() ); } - # End XmlSelect::addOption() similar to Xml::option + /** + * @covers XmlSelect::setDefault + */ public function testSetDefault() { $this->select->setDefault( 'bar1' ); $this->select->addOption( 'foo1' ); $this->select->addOption( 'bar1' ); $this->select->addOption( 'foo2' ); $this->assertEquals( -'<select><option value="foo1">foo1</option>' . "\n" . -'<option value="bar1" selected="">bar1</option>' . "\n" . -'<option value="foo2">foo2</option></select>', $this->select->getHTML() ); + '<select><option value="foo1">foo1</option>' . "\n" . + '<option value="bar1" selected="">bar1</option>' . "\n" . + '<option value="foo2">foo2</option></select>', $this->select->getHTML() ); } /** * Adding default later on should set the correct selection or * raise an exception. * To handle this, we need to render the options in getHtml() + * @covers XmlSelect::setDefault */ public function testSetDefaultAfterAddingOptions() { $this->select->addOption( 'foo1' ); @@ -95,11 +125,15 @@ class XmlSelectTest extends MediaWikiTestCase { $this->select->addOption( 'foo2' ); $this->select->setDefault( 'bar1' ); # setting default after adding options $this->assertEquals( -'<select><option value="foo1">foo1</option>' . "\n" . -'<option value="bar1" selected="">bar1</option>' . "\n" . -'<option value="foo2">foo2</option></select>', $this->select->getHTML() ); + '<select><option value="foo1">foo1</option>' . "\n" . + '<option value="bar1" selected="">bar1</option>' . "\n" . + '<option value="foo2">foo2</option></select>', $this->select->getHTML() ); } + /** + * @covers XmlSelect::setAttribute + * @covers XmlSelect::getAttribute + */ public function testGetAttributes() { # create some attributes $this->select->setAttribute( 'dummy', 0x777 ); @@ -129,7 +163,7 @@ class XmlSelectTest extends MediaWikiTestCase { # verify string / integer $this->assertEquals( $this->select->getAttribute( '1911' ), - 'razor' + 'razor' ); $this->assertEquals( $this->select->getAttribute( 'dummy' ), diff --git a/tests/phpunit/includes/XmlTest.php b/tests/phpunit/includes/XmlTest.php index 93ed3dc7..8205029f 100644 --- a/tests/phpunit/includes/XmlTest.php +++ b/tests/phpunit/includes/XmlTest.php @@ -1,48 +1,44 @@ <?php +/** + * @group Xml + */ class XmlTest extends MediaWikiTestCase { - private static $oldLang; - private static $oldNamespaces; - public function setUp() { - global $wgLang, $wgContLang; + protected function setUp() { + parent::setUp(); - self::$oldLang = $wgLang; - $wgLang = Language::factory( 'en' ); - - // Hardcode namespaces during test runs, - // so that html output based on existing namespaces - // can be properly evaluated. - self::$oldNamespaces = $wgContLang->getNamespaces(); - $wgContLang->setNamespaces( array( + $langObj = Language::factory( 'en' ); + $langObj->setNamespaces( array( -2 => 'Media', -1 => 'Special', - 0 => '', - 1 => 'Talk', - 2 => 'User', - 3 => 'User_talk', - 4 => 'MyWiki', - 5 => 'MyWiki_Talk', - 6 => 'File', - 7 => 'File_talk', - 8 => 'MediaWiki', - 9 => 'MediaWiki_talk', - 10 => 'Template', - 11 => 'Template_talk', - 100 => 'Custom', - 101 => 'Custom_talk', + 0 => '', + 1 => 'Talk', + 2 => 'User', + 3 => 'User_talk', + 4 => 'MyWiki', + 5 => 'MyWiki_Talk', + 6 => 'File', + 7 => 'File_talk', + 8 => 'MediaWiki', + 9 => 'MediaWiki_talk', + 10 => 'Template', + 11 => 'Template_talk', + 100 => 'Custom', + 101 => 'Custom_talk', ) ); - } - public function tearDown() { - global $wgLang, $wgContLang; - $wgLang = self::$oldLang; - - $wgContLang->setNamespaces( self::$oldNamespaces ); + $this->setMwGlobals( array( + 'wgLang' => $langObj, + 'wgWellFormedXml' => true, + ) ); } + /** + * @covers Xml::expandAttributes + */ public function testExpandAttributes() { - $this->assertNull( Xml::expandAttributes(null), + $this->assertNull( Xml::expandAttributes( null ), 'Converting a null list of attributes' ); $this->assertEquals( '', Xml::expandAttributes( array() ), @@ -50,12 +46,18 @@ class XmlTest extends MediaWikiTestCase { ); } + /** + * @covers Xml::expandAttributes + */ public function testExpandAttributesException() { - $this->setExpectedException('MWException'); - Xml::expandAttributes('string'); + $this->setExpectedException( 'MWException' ); + Xml::expandAttributes( 'string' ); } - function testElementOpen() { + /** + * @covers Xml::element + */ + public function testElementOpen() { $this->assertEquals( '<element>', Xml::element( 'element', null, null ), @@ -63,7 +65,10 @@ class XmlTest extends MediaWikiTestCase { ); } - function testElementEmpty() { + /** + * @covers Xml::element + */ + public function testElementEmpty() { $this->assertEquals( '<element />', Xml::element( 'element', null, '' ), @@ -71,14 +76,21 @@ class XmlTest extends MediaWikiTestCase { ); } - function testElementInputCanHaveAValueOfZero() { + /** + * @covers Xml::input + */ + public function testElementInputCanHaveAValueOfZero() { $this->assertEquals( '<input name="name" value="0" />', Xml::input( 'name', false, 0 ), 'Input with a value of 0 (bug 23797)' ); } - function testElementEscaping() { + + /** + * @covers Xml::element + */ + public function testElementEscaping() { $this->assertEquals( '<element>hello <there> you & you</element>', Xml::element( 'element', null, 'hello <there> you & you' ), @@ -86,13 +98,19 @@ class XmlTest extends MediaWikiTestCase { ); } + /** + * @covers Xml::escapeTagsOnly + */ public function testEscapeTagsOnly() { $this->assertEquals( '"><', Xml::escapeTagsOnly( '"><' ), 'replace " > and < with their HTML entitites' ); } - function testElementAttributes() { + /** + * @covers Xml::element + */ + public function testElementAttributes() { $this->assertEquals( '<element key="value" <>="<>">', Xml::element( 'element', array( 'key' => 'value', '<>' => '<>' ), null ), @@ -100,7 +118,10 @@ class XmlTest extends MediaWikiTestCase { ); } - function testOpenElement() { + /** + * @covers Xml::openElement + */ + public function testOpenElement() { $this->assertEquals( '<element k="v">', Xml::openElement( 'element', array( 'k' => 'v' ) ), @@ -108,95 +129,100 @@ class XmlTest extends MediaWikiTestCase { ); } - function testCloseElement() { + /** + * @covers Xml::closeElement + */ + public function testCloseElement() { $this->assertEquals( '</element>', Xml::closeElement( 'element' ), 'closeElement() shortcut' ); } /** - * @group Broken + * @covers Xml::dateMenu */ - public function testDateMenu( ) { - $curYear = intval(gmdate('Y')); - $prevYear = $curYear - 1; + public function testDateMenu() { + $curYear = intval( gmdate( 'Y' ) ); + $prevYear = $curYear - 1; - $curMonth = intval(gmdate('n')); + $curMonth = intval( gmdate( 'n' ) ); $prevMonth = $curMonth - 1; - if( $prevMonth == 0 ) { $prevMonth = 12; } + if ( $prevMonth == 0 ) { + $prevMonth = 12; + } $nextMonth = $curMonth + 1; - if( $nextMonth == 13 ) { $nextMonth = 1; } + if ( $nextMonth == 13 ) { + $nextMonth = 1; + } $this->assertEquals( - '<label for="year">From year (and earlier):</label> <input name="year" size="4" value="2011" id="year" maxlength="4" /> <label for="month">From month (and earlier):</label> <select id="month" name="month" class="mw-month-selector"><option value="-1">all</option>' . "\n" . -'<option value="1">January</option>' . "\n" . -'<option value="2" selected="">February</option>' . "\n" . -'<option value="3">March</option>' . "\n" . -'<option value="4">April</option>' . "\n" . -'<option value="5">May</option>' . "\n" . -'<option value="6">June</option>' . "\n" . -'<option value="7">July</option>' . "\n" . -'<option value="8">August</option>' . "\n" . -'<option value="9">September</option>' . "\n" . -'<option value="10">October</option>' . "\n" . -'<option value="11">November</option>' . "\n" . -'<option value="12">December</option></select>', + '<label for="year">From year (and earlier):</label> <input id="year" maxlength="4" size="7" type="number" value="2011" name="year" /> <label for="month">From month (and earlier):</label> <select id="month" name="month" class="mw-month-selector"><option value="-1">all</option>' . "\n" . + '<option value="1">January</option>' . "\n" . + '<option value="2" selected="">February</option>' . "\n" . + '<option value="3">March</option>' . "\n" . + '<option value="4">April</option>' . "\n" . + '<option value="5">May</option>' . "\n" . + '<option value="6">June</option>' . "\n" . + '<option value="7">July</option>' . "\n" . + '<option value="8">August</option>' . "\n" . + '<option value="9">September</option>' . "\n" . + '<option value="10">October</option>' . "\n" . + '<option value="11">November</option>' . "\n" . + '<option value="12">December</option></select>', Xml::dateMenu( 2011, 02 ), "Date menu for february 2011" ); $this->assertEquals( - '<label for="year">From year (and earlier):</label> <input name="year" size="4" value="2011" id="year" maxlength="4" /> <label for="month">From month (and earlier):</label> <select id="month" name="month" class="mw-month-selector"><option value="-1">all</option>' . "\n" . -'<option value="1">January</option>' . "\n" . -'<option value="2">February</option>' . "\n" . -'<option value="3">March</option>' . "\n" . -'<option value="4">April</option>' . "\n" . -'<option value="5">May</option>' . "\n" . -'<option value="6">June</option>' . "\n" . -'<option value="7">July</option>' . "\n" . -'<option value="8">August</option>' . "\n" . -'<option value="9">September</option>' . "\n" . -'<option value="10">October</option>' . "\n" . -'<option value="11">November</option>' . "\n" . -'<option value="12">December</option></select>', - Xml::dateMenu( 2011, -1), + '<label for="year">From year (and earlier):</label> <input id="year" maxlength="4" size="7" type="number" value="2011" name="year" /> <label for="month">From month (and earlier):</label> <select id="month" name="month" class="mw-month-selector"><option value="-1">all</option>' . "\n" . + '<option value="1">January</option>' . "\n" . + '<option value="2">February</option>' . "\n" . + '<option value="3">March</option>' . "\n" . + '<option value="4">April</option>' . "\n" . + '<option value="5">May</option>' . "\n" . + '<option value="6">June</option>' . "\n" . + '<option value="7">July</option>' . "\n" . + '<option value="8">August</option>' . "\n" . + '<option value="9">September</option>' . "\n" . + '<option value="10">October</option>' . "\n" . + '<option value="11">November</option>' . "\n" . + '<option value="12">December</option></select>', + Xml::dateMenu( 2011, -1 ), "Date menu with negative month for 'All'" ); $this->assertEquals( Xml::dateMenu( $curYear, $curMonth ), - Xml::dateMenu( '' , $curMonth ), + Xml::dateMenu( '', $curMonth ), "Date menu year is the current one when not specified" ); - // @todo FIXME: next month can be in the next year - // test failing because it is now december + $wantedYear = $nextMonth == 1 ? $curYear : $prevYear; $this->assertEquals( - Xml::dateMenu( $prevYear, $nextMonth ), + Xml::dateMenu( $wantedYear, $nextMonth ), Xml::dateMenu( '', $nextMonth ), "Date menu next month is 11 months ago" ); - # @todo FIXME: Please note there is no year there! $this->assertEquals( - '<label for="year">From year (and earlier):</label> <input name="year" size="4" value="" id="year" maxlength="4" /> <label for="month">From month (and earlier):</label> <select id="month" name="month" class="mw-month-selector"><option value="-1">all</option>' . "\n" . -'<option value="1">January</option>' . "\n" . -'<option value="2">February</option>' . "\n" . -'<option value="3">March</option>' . "\n" . -'<option value="4">April</option>' . "\n" . -'<option value="5">May</option>' . "\n" . -'<option value="6">June</option>' . "\n" . -'<option value="7">July</option>' . "\n" . -'<option value="8">August</option>' . "\n" . -'<option value="9">September</option>' . "\n" . -'<option value="10">October</option>' . "\n" . -'<option value="11">November</option>' . "\n" . -'<option value="12">December</option></select>', + '<label for="year">From year (and earlier):</label> <input id="year" maxlength="4" size="7" type="number" name="year" /> <label for="month">From month (and earlier):</label> <select id="month" name="month" class="mw-month-selector"><option value="-1">all</option>' . "\n" . + '<option value="1">January</option>' . "\n" . + '<option value="2">February</option>' . "\n" . + '<option value="3">March</option>' . "\n" . + '<option value="4">April</option>' . "\n" . + '<option value="5">May</option>' . "\n" . + '<option value="6">June</option>' . "\n" . + '<option value="7">July</option>' . "\n" . + '<option value="8">August</option>' . "\n" . + '<option value="9">September</option>' . "\n" . + '<option value="10">October</option>' . "\n" . + '<option value="11">November</option>' . "\n" . + '<option value="12">December</option></select>', Xml::dateMenu( '', '' ), "Date menu with neither year or month" ); } - # - # textarea - # - function testTextareaNoContent() { + /** + * @covers Xml::textarea + */ + public function testTextareaNoContent() { $this->assertEquals( '<textarea name="name" id="name" cols="40" rows="5"></textarea>', Xml::textarea( 'name', '' ), @@ -204,7 +230,10 @@ class XmlTest extends MediaWikiTestCase { ); } - function testTextareaAttribs() { + /** + * @covers Xml::textarea + */ + public function testTextareaAttribs() { $this->assertEquals( '<textarea name="name" id="name" cols="20" rows="10"><txt></textarea>', Xml::textarea( 'name', '<txt>', 20, 10 ), @@ -212,17 +241,21 @@ class XmlTest extends MediaWikiTestCase { ); } - # - # input and label - # - function testLabelCreation() { + /** + * @covers Xml::label + */ + public function testLabelCreation() { $this->assertEquals( '<label for="id">name</label>', Xml::label( 'name', 'id' ), 'label() with no attribs' ); } - function testLabelAttributeCanOnlyBeClassOrTitle() { + + /** + * @covers Xml::label + */ + public function testLabelAttributeCanOnlyBeClassOrTitle() { $this->assertEquals( '<label for="id">name</label>', Xml::label( 'name', 'id', array( 'generated' => true ) ), @@ -241,17 +274,20 @@ class XmlTest extends MediaWikiTestCase { $this->assertEquals( '<label for="id" class="nice" title="nice tooltip">name</label>', Xml::label( 'name', 'id', array( - 'generated' => true, - 'class' => 'nice', - 'title' => 'nice tooltip', - 'anotherattr' => 'value', + 'generated' => true, + 'class' => 'nice', + 'title' => 'nice tooltip', + 'anotherattr' => 'value', ) ), 'label() skip all attributes but "class" and "title"' ); } - function testLanguageSelector() { + /** + * @covers Xml::languageSelector + */ + public function testLanguageSelector() { $select = Xml::languageSelector( 'en', true, null, array( 'id' => 'testlang' ), wfMessage( 'yourlanguage' ) ); $this->assertEquals( @@ -260,10 +296,10 @@ class XmlTest extends MediaWikiTestCase { ); } - # - # JS - # - function testEscapeJsStringSpecialChars() { + /** + * @covers Xml::escapeJsString + */ + public function testEscapeJsStringSpecialChars() { $this->assertEquals( '\\\\\r\n', Xml::escapeJsString( "\\\r\n" ), @@ -271,7 +307,10 @@ class XmlTest extends MediaWikiTestCase { ); } - function testEncodeJsVarBoolean() { + /** + * @covers Xml::encodeJsVar + */ + public function testEncodeJsVarBoolean() { $this->assertEquals( 'true', Xml::encodeJsVar( true ), @@ -279,7 +318,10 @@ class XmlTest extends MediaWikiTestCase { ); } - function testEncodeJsVarNull() { + /** + * @covers Xml::encodeJsVar + */ + public function testEncodeJsVarNull() { $this->assertEquals( 'null', Xml::encodeJsVar( null ), @@ -287,7 +329,10 @@ class XmlTest extends MediaWikiTestCase { ); } - function testEncodeJsVarArray() { + /** + * @covers Xml::encodeJsVar + */ + public function testEncodeJsVarArray() { $this->assertEquals( '["a",1]', Xml::encodeJsVar( array( 'a', 1 ) ), @@ -300,7 +345,10 @@ class XmlTest extends MediaWikiTestCase { ); } - function testEncodeJsVarObject() { + /** + * @covers Xml::encodeJsVar + */ + public function testEncodeJsVarObject() { $this->assertEquals( '{"a":"a","b":1}', Xml::encodeJsVar( (object)array( 'a' => 'a', 'b' => 1 ) ), @@ -308,7 +356,10 @@ class XmlTest extends MediaWikiTestCase { ); } - function testEncodeJsVarInt() { + /** + * @covers Xml::encodeJsVar + */ + public function testEncodeJsVarInt() { $this->assertEquals( '123456', Xml::encodeJsVar( 123456 ), @@ -316,7 +367,10 @@ class XmlTest extends MediaWikiTestCase { ); } - function testEncodeJsVarFloat() { + /** + * @covers Xml::encodeJsVar + */ + public function testEncodeJsVarFloat() { $this->assertEquals( '1.23456', Xml::encodeJsVar( 1.23456 ), @@ -324,7 +378,10 @@ class XmlTest extends MediaWikiTestCase { ); } - function testEncodeJsVarIntString() { + /** + * @covers Xml::encodeJsVar + */ + public function testEncodeJsVarIntString() { $this->assertEquals( '"123456"', Xml::encodeJsVar( '123456' ), @@ -332,7 +389,10 @@ class XmlTest extends MediaWikiTestCase { ); } - function testEncodeJsVarFloatString() { + /** + * @covers Xml::encodeJsVar + */ + public function testEncodeJsVarFloatString() { $this->assertEquals( '"1.23456"', Xml::encodeJsVar( '1.23456' ), diff --git a/tests/phpunit/includes/XmlTypeCheckTest.php b/tests/phpunit/includes/XmlTypeCheckTest.php new file mode 100644 index 00000000..8d6f1ed7 --- /dev/null +++ b/tests/phpunit/includes/XmlTypeCheckTest.php @@ -0,0 +1,30 @@ +<?php +/** + * PHPUnit tests for XMLTypeCheck. + * @author physikerwelt + * @group Xml + * @covers XMLTypeCheck + */ +class XmlTypeCheckTest extends MediaWikiTestCase { + const WELL_FORMED_XML = "<root><child /></root>"; + const MAL_FORMED_XML = "<root><child /></error>"; + + /** + * @covers XMLTypeCheck::newFromString + * @covers XMLTypeCheck::getRootElement + */ + public function testWellFormedXML() { + $testXML = XmlTypeCheck::newFromString( self::WELL_FORMED_XML ); + $this->assertTrue( $testXML->wellFormed ); + $this->assertEquals( 'root', $testXML->getRootElement() ); + } + + /** + * @covers XMLTypeCheck::newFromString + */ + public function testMalFormedXML() { + $testXML = XmlTypeCheck::newFromString( self::MAL_FORMED_XML ); + $this->assertFalse( $testXML->wellFormed ); + } + +} diff --git a/tests/phpunit/includes/ZipDirectoryReaderTest.php b/tests/phpunit/includes/ZipDirectoryReaderTest.php index d90a6950..2627a417 100644 --- a/tests/phpunit/includes/ZipDirectoryReaderTest.php +++ b/tests/phpunit/includes/ZipDirectoryReaderTest.php @@ -1,9 +1,15 @@ <?php +/** + * @covers ZipDirectoryReader + * NOTE: this test is more like an integration test than a unit test + */ class ZipDirectoryReaderTest extends MediaWikiTestCase { - var $zipDir, $entries; + protected $zipDir; + protected $entries; - function setUp() { + protected function setUp() { + parent::setUp(); $this->zipDir = __DIR__ . '/../data/zip'; } @@ -23,21 +29,21 @@ class ZipDirectoryReaderTest extends MediaWikiTestCase { $this->assertTrue( $status->isOK(), $assertMessage ); } - function testEmpty() { + public function testEmpty() { $this->readZipAssertSuccess( 'empty.zip', 'Empty zip' ); } - function testMultiDisk0() { - $this->readZipAssertError( 'split.zip', 'zip-unsupported', + public function testMultiDisk0() { + $this->readZipAssertError( 'split.zip', 'zip-unsupported', 'Split zip error' ); } - function testNoSignature() { - $this->readZipAssertError( 'nosig.zip', 'zip-wrong-format', + public function testNoSignature() { + $this->readZipAssertError( 'nosig.zip', 'zip-wrong-format', 'No signature should give "wrong format" error' ); } - function testSimple() { + public function testSimple() { $this->readZipAssertSuccess( 'class.zip', 'Simple ZIP' ); $this->assertEquals( $this->entries, array( array( 'name' => 'Class.class', @@ -46,33 +52,33 @@ class ZipDirectoryReaderTest extends MediaWikiTestCase { ) ) ); } - function testBadCentralEntrySignature() { + public function testBadCentralEntrySignature() { $this->readZipAssertError( 'wrong-central-entry-sig.zip', 'zip-bad', 'Bad central entry error' ); } - function testTrailingBytes() { + public function testTrailingBytes() { $this->readZipAssertError( 'trail.zip', 'zip-bad', 'Trailing bytes error' ); } - function testWrongCDStart() { - $this->readZipAssertError( 'wrong-cd-start-disk.zip', 'zip-unsupported', + public function testWrongCDStart() { + $this->readZipAssertError( 'wrong-cd-start-disk.zip', 'zip-unsupported', 'Wrong CD start disk error' ); } - function testCentralDirectoryGap() { + public function testCentralDirectoryGap() { $this->readZipAssertError( 'cd-gap.zip', 'zip-bad', 'CD gap error' ); } - function testCentralDirectoryTruncated() { + public function testCentralDirectoryTruncated() { $this->readZipAssertError( 'cd-truncated.zip', 'zip-bad', 'CD truncated error (should hit unpack() overrun)' ); } - function testLooksLikeZip64() { + public function testLooksLikeZip64() { $this->readZipAssertError( 'looks-like-zip64.zip', 'zip-unsupported', 'A file which looks like ZIP64 but isn\'t, should give error' ); } diff --git a/tests/phpunit/includes/api/ApiAccountCreationTest.php b/tests/phpunit/includes/api/ApiAccountCreationTest.php new file mode 100644 index 00000000..68f80ac9 --- /dev/null +++ b/tests/phpunit/includes/api/ApiAccountCreationTest.php @@ -0,0 +1,159 @@ +<?php + +/** + * @group Database + * @group API + * @group medium + */ +class ApiCreateAccountTest extends ApiTestCase { + function setUp() { + parent::setUp(); + LoginForm::setCreateaccountToken(); + $this->setMwGlobals( array( 'wgEnableEmail' => true ) ); + } + + /** + * Test the account creation API with a valid request. Also + * make sure the new account can log in and is valid. + * + * This test does multiple API requests so it might end up being + * a bit slow. Raise the default timeout. + * @group medium + */ + public function testValid() { + global $wgServer; + + if ( !isset( $wgServer ) ) { + $this->markTestIncomplete( 'This test needs $wgServer to be set in LocalSettings.php' ); + } + + $password = User::randomPassword(); + + $ret = $this->doApiRequest( array( + 'action' => 'createaccount', + 'name' => 'Apitestnew', + 'password' => $password, + 'email' => 'test@domain.test', + 'realname' => 'Test Name' + ) ); + + $result = $ret[0]; + $this->assertNotInternalType( 'bool', $result ); + $this->assertNotInternalType( 'null', $result['createaccount'] ); + + // Should first ask for token. + $a = $result['createaccount']; + $this->assertEquals( 'needtoken', $a['result'] ); + $token = $a['token']; + + // Finally create the account + $ret = $this->doApiRequest( + array( + 'action' => 'createaccount', + 'name' => 'Apitestnew', + 'password' => $password, + 'token' => $token, + 'email' => 'test@domain.test', + 'realname' => 'Test Name' + ), + $ret[2] + ); + + $result = $ret[0]; + $this->assertNotInternalType( 'bool', $result ); + $this->assertEquals( 'success', $result['createaccount']['result'] ); + + // Try logging in with the new user. + $ret = $this->doApiRequest( array( + 'action' => 'login', + 'lgname' => 'Apitestnew', + 'lgpassword' => $password, + ) ); + + $result = $ret[0]; + $this->assertNotInternalType( 'bool', $result ); + $this->assertNotInternalType( 'null', $result['login'] ); + + $a = $result['login']['result']; + $this->assertEquals( 'NeedToken', $a ); + $token = $result['login']['token']; + + $ret = $this->doApiRequest( + array( + 'action' => 'login', + 'lgtoken' => $token, + 'lgname' => 'Apitestnew', + 'lgpassword' => $password, + ), + $ret[2] + ); + + $result = $ret[0]; + + $this->assertNotInternalType( 'bool', $result ); + $a = $result['login']['result']; + + $this->assertEquals( 'Success', $a ); + + // log out to destroy the session + $ret = $this->doApiRequest( + array( + 'action' => 'logout', + ), + $ret[2] + ); + $this->assertEquals( array(), $ret[0] ); + } + + /** + * Make sure requests with no names are invalid. + * @expectedException UsageException + */ + public function testNoName() { + $this->doApiRequest( array( + 'action' => 'createaccount', + 'token' => LoginForm::getCreateaccountToken(), + 'password' => 'password', + ) ); + } + + /** + * Make sure requests with no password are invalid. + * @expectedException UsageException + */ + public function testNoPassword() { + $this->doApiRequest( array( + 'action' => 'createaccount', + 'name' => 'testName', + 'token' => LoginForm::getCreateaccountToken(), + ) ); + } + + /** + * Make sure requests with existing users are invalid. + * @expectedException UsageException + */ + public function testExistingUser() { + $this->doApiRequest( array( + 'action' => 'createaccount', + 'name' => 'Apitestsysop', + 'token' => LoginForm::getCreateaccountToken(), + 'password' => 'password', + 'email' => 'test@domain.test', + ) ); + } + + /** + * Make sure requests with invalid emails are invalid. + * @expectedException UsageException + */ + public function testInvalidEmail() { + $this->doApiRequest( array( + 'action' => 'createaccount', + 'name' => 'Test User', + 'token' => LoginForm::getCreateaccountToken(), + 'password' => 'password', + 'email' => 'invalid', + ) ); + } +} diff --git a/tests/phpunit/includes/api/ApiBlockTest.php b/tests/phpunit/includes/api/ApiBlockTest.php index 5dfceee8..8afb748a 100644 --- a/tests/phpunit/includes/api/ApiBlockTest.php +++ b/tests/phpunit/includes/api/ApiBlockTest.php @@ -3,10 +3,10 @@ /** * @group API * @group Database + * @group medium */ class ApiBlockTest extends ApiTestCase { - - function setUp() { + protected function setUp() { parent::setUp(); $this->doLogin(); } @@ -34,9 +34,8 @@ class ApiBlockTest extends ApiTestCase { * Which made the Block/Unblock API to actually verify the token * previously always considered valid (bug 34212). */ - function testMakeNormalBlock() { - - $data = $this->getTokens(); + public function testMakeNormalBlock() { + $tokens = $this->getTokens(); $user = User::newFromName( 'UTApiBlockee' ); @@ -44,44 +43,23 @@ class ApiBlockTest extends ApiTestCase { $this->markTestIncomplete( "The user UTApiBlockee does not exist" ); } - if( !isset( $data[0]['query']['pages'] ) ) { + if ( !array_key_exists( 'blocktoken', $tokens ) ) { $this->markTestIncomplete( "No block token found" ); } - $keys = array_keys( $data[0]['query']['pages'] ); - $key = array_pop( $keys ); - $pageinfo = $data[0]['query']['pages'][$key]; - - $data = $this->doApiRequest( array( + $this->doApiRequest( array( 'action' => 'block', 'user' => 'UTApiBlockee', 'reason' => 'Some reason', - 'token' => $pageinfo['blocktoken'] ), null, false, self::$users['sysop']->user ); + 'token' => $tokens['blocktoken'] ), null, false, self::$users['sysop']->user ); - $block = Block::newFromTarget('UTApiBlockee'); + $block = Block::newFromTarget( 'UTApiBlockee' ); $this->assertTrue( !is_null( $block ), 'Block is valid' ); $this->assertEquals( 'UTApiBlockee', (string)$block->getTarget() ); $this->assertEquals( 'Some reason', $block->mReason ); $this->assertEquals( 'infinity', $block->mExpiry ); - - } - - /** - * @dataProvider provideBlockUnblockAction - */ - function testGetTokenUsingABlockingAction( $action ) { - $data = $this->doApiRequest( - array( - 'action' => $action, - 'user' => 'UTApiBlockee', - 'gettoken' => '' ), - null, - false, - self::$users['sysop']->user - ); - $this->assertEquals( 34, strlen( $data[0][$action]["{$action}token"] ) ); } /** @@ -92,13 +70,13 @@ class ApiBlockTest extends ApiTestCase { * @dataProvider provideBlockUnblockAction * @expectedException UsageException */ - function testBlockingActionWithNoToken( $action ) { + public function testBlockingActionWithNoToken( $action ) { $this->doApiRequest( array( 'action' => $action, 'user' => 'UTApiBlockee', 'reason' => 'Some reason', - ), + ), null, false, self::$users['sysop']->user @@ -108,9 +86,9 @@ class ApiBlockTest extends ApiTestCase { /** * Just provide the 'block' and 'unblock' action to test both API calls */ - function provideBlockUnblockAction() { + public static function provideBlockUnblockAction() { return array( - array( 'block' ), + array( 'block' ), array( 'unblock' ), ); } diff --git a/tests/phpunit/includes/api/ApiEditPageTest.php b/tests/phpunit/includes/api/ApiEditPageTest.php index 5297d6da..0c49b12b 100644 --- a/tests/phpunit/includes/api/ApiEditPageTest.php +++ b/tests/phpunit/includes/api/ApiEditPageTest.php @@ -7,25 +7,54 @@ * * @group API * @group Database + * @group medium */ class ApiEditPageTest extends ApiTestCase { - function setUp() { + public function setUp() { + global $wgExtraNamespaces, $wgNamespaceContentModels, $wgContentHandlers, $wgContLang; + parent::setUp(); + + $wgExtraNamespaces[12312] = 'Dummy'; + $wgExtraNamespaces[12313] = 'Dummy_talk'; + + $wgNamespaceContentModels[12312] = "testing"; + $wgContentHandlers["testing"] = 'DummyContentHandlerForTesting'; + + MWNamespace::getCanonicalNamespaces( true ); # reset namespace cache + $wgContLang->resetNamespaces(); # reset namespace cache + $this->doLogin(); } - function testEdit( ) { - $name = 'ApiEditPageTest_testEdit'; + public function tearDown() { + global $wgExtraNamespaces, $wgNamespaceContentModels, $wgContentHandlers, $wgContLang; + + unset( $wgExtraNamespaces[12312] ); + unset( $wgExtraNamespaces[12313] ); + + unset( $wgNamespaceContentModels[12312] ); + unset( $wgContentHandlers["testing"] ); + + MWNamespace::getCanonicalNamespaces( true ); # reset namespace cache + $wgContLang->resetNamespaces(); # reset namespace cache + + parent::tearDown(); + } + + public function testEdit() { + $name = 'Help:ApiEditPageTest_testEdit'; // assume Help namespace to default to wikitext // -- test new page -------------------------------------------- $apiResult = $this->doApiRequestWithToken( array( - 'action' => 'edit', - 'title' => $name, - 'text' => 'some text', ) ); + 'action' => 'edit', + 'title' => $name, + 'text' => 'some text', + ) ); $apiResult = $apiResult[0]; - # Validate API result data + // Validate API result data $this->assertArrayHasKey( 'edit', $apiResult ); $this->assertArrayHasKey( 'result', $apiResult['edit'] ); $this->assertEquals( 'Success', $apiResult['edit']['result'] ); @@ -37,9 +66,10 @@ class ApiEditPageTest extends ApiTestCase { // -- test existing page, no change ---------------------------- $data = $this->doApiRequestWithToken( array( - 'action' => 'edit', - 'title' => $name, - 'text' => 'some text', ) ); + 'action' => 'edit', + 'title' => $name, + 'text' => 'some text', + ) ); $this->assertEquals( 'Success', $data[0]['edit']['result'] ); @@ -48,9 +78,10 @@ class ApiEditPageTest extends ApiTestCase { // -- test existing page, with change -------------------------- $data = $this->doApiRequestWithToken( array( - 'action' => 'edit', - 'title' => $name, - 'text' => 'different text' ) ); + 'action' => 'edit', + 'title' => $name, + 'text' => 'different text' + ) ); $this->assertEquals( 'Success', $data[0]['edit']['result'] ); @@ -66,19 +97,321 @@ class ApiEditPageTest extends ApiTestCase { ); } - function testEditAppend() { - $this->markTestIncomplete( "not yet implemented" ); + public function testNonTextEdit() { + $name = 'Dummy:ApiEditPageTest_testNonTextEdit'; + $data = serialize( 'some bla bla text' ); + + // -- test new page -------------------------------------------- + $apiResult = $this->doApiRequestWithToken( array( + 'action' => 'edit', + 'title' => $name, + 'text' => $data, ) ); + $apiResult = $apiResult[0]; + + // Validate API result data + $this->assertArrayHasKey( 'edit', $apiResult ); + $this->assertArrayHasKey( 'result', $apiResult['edit'] ); + $this->assertEquals( 'Success', $apiResult['edit']['result'] ); + + $this->assertArrayHasKey( 'new', $apiResult['edit'] ); + $this->assertArrayNotHasKey( 'nochange', $apiResult['edit'] ); + + $this->assertArrayHasKey( 'pageid', $apiResult['edit'] ); + + // validate resulting revision + $page = WikiPage::factory( Title::newFromText( $name ) ); + $this->assertEquals( "testing", $page->getContentModel() ); + $this->assertEquals( $data, $page->getContent()->serialize() ); } - function testEditSection() { - $this->markTestIncomplete( "not yet implemented" ); + public static function provideEditAppend() { + return array( + array( #0: append + 'foo', 'append', 'bar', "foobar" + ), + array( #1: prepend + 'foo', 'prepend', 'bar', "barfoo" + ), + array( #2: append to empty page + '', 'append', 'foo', "foo" + ), + array( #3: prepend to empty page + '', 'prepend', 'foo', "foo" + ), + array( #4: append to non-existing page + null, 'append', 'foo', "foo" + ), + array( #5: prepend to non-existing page + null, 'prepend', 'foo', "foo" + ), + ); } - function testUndo() { - $this->markTestIncomplete( "not yet implemented" ); + /** + * @dataProvider provideEditAppend + */ + public function testEditAppend( $text, $op, $append, $expected ) { + static $count = 0; + $count++; + + // assume NS_HELP defaults to wikitext + $name = "Help:ApiEditPageTest_testEditAppend_$count"; + + // -- create page (or not) ----------------------------------------- + if ( $text !== null ) { + if ( $text === '' ) { + // can't create an empty page, so create it with some content + $this->doApiRequestWithToken( array( + 'action' => 'edit', + 'title' => $name, + 'text' => '(dummy)', ) ); + } + + list( $re ) = $this->doApiRequestWithToken( array( + 'action' => 'edit', + 'title' => $name, + 'text' => $text, ) ); + + $this->assertEquals( 'Success', $re['edit']['result'] ); // sanity + } + + // -- try append/prepend -------------------------------------------- + list( $re ) = $this->doApiRequestWithToken( array( + 'action' => 'edit', + 'title' => $name, + $op . 'text' => $append, ) ); + + $this->assertEquals( 'Success', $re['edit']['result'] ); + + // -- validate ----------------------------------------------------- + $page = new WikiPage( Title::newFromText( $name ) ); + $content = $page->getContent(); + $this->assertNotNull( $content, 'Page should have been created' ); + + $text = $content->getNativeData(); + + $this->assertEquals( $expected, $text ); } - function testEditNonText() { - $this->markTestIncomplete( "not yet implemented" ); + /** + * Test editing of sections + */ + public function testEditSection() { + $name = 'Help:ApiEditPageTest_testEditSection'; + $page = WikiPage::factory( Title::newFromText( $name ) ); + $text = "==section 1==\ncontent 1\n==section 2==\ncontent2"; + // Preload the page with some text + $page->doEditContent( ContentHandler::makeContent( $text, $page->getTitle() ), 'summary' ); + + list( $re ) = $this->doApiRequestWithToken( array( + 'action' => 'edit', + 'title' => $name, + 'section' => '1', + 'text' => "==section 1==\nnew content 1", + ) ); + $this->assertEquals( 'Success', $re['edit']['result'] ); + $newtext = WikiPage::factory( Title::newFromText( $name) )->getContent( Revision::RAW )->getNativeData(); + $this->assertEquals( $newtext, "==section 1==\nnew content 1\n\n==section 2==\ncontent2" ); + + // Test that we raise a 'nosuchsection' error + try { + $this->doApiRequestWithToken( array( + 'action' => 'edit', + 'title' => $name, + 'section' => '9999', + 'text' => 'text', + ) ); + $this->fail( "Should have raised a UsageException" ); + } catch ( UsageException $e ) { + $this->assertEquals( $e->getCodeString(), 'nosuchsection' ); + } + } + + /** + * Test action=edit§ion=new + * Run it twice so we test adding a new section on a + * page that doesn't exist (bug 52830) and one that + * does exist + */ + public function testEditNewSection() { + $name = 'Help:ApiEditPageTest_testEditNewSection'; + + // Test on a page that does not already exist + $this->assertFalse( Title::newFromText( $name )->exists() ); + list( $re ) = $this->doApiRequestWithToken( array( + 'action' => 'edit', + 'title' => $name, + 'section' => 'new', + 'text' => 'test', + 'summary' => 'header', + )); + + $this->assertEquals( 'Success', $re['edit']['result'] ); + // Check the page text is correct + $text = WikiPage::factory( Title::newFromText( $name ) )->getContent( Revision::RAW )->getNativeData(); + $this->assertEquals( $text, "== header ==\n\ntest" ); + + // Now on one that does + $this->assertTrue( Title::newFromText( $name )->exists() ); + list( $re2 ) = $this->doApiRequestWithToken( array( + 'action' => 'edit', + 'title' => $name, + 'section' => 'new', + 'text' => 'test', + 'summary' => 'header', + )); + + $this->assertEquals( 'Success', $re2['edit']['result'] ); + $text = WikiPage::factory( Title::newFromText( $name ) )->getContent( Revision::RAW )->getNativeData(); + $this->assertEquals( $text, "== header ==\n\ntest\n\n== header ==\n\ntest" ); + } + + public function testEditConflict() { + static $count = 0; + $count++; + + // assume NS_HELP defaults to wikitext + $name = "Help:ApiEditPageTest_testEditConflict_$count"; + $title = Title::newFromText( $name ); + + $page = WikiPage::factory( $title ); + + // base edit + $page->doEditContent( new WikitextContent( "Foo" ), + "testing 1", EDIT_NEW, false, self::$users['sysop']->user ); + $this->forceRevisionDate( $page, '20120101000000' ); + $baseTime = $page->getRevision()->getTimestamp(); + + // conflicting edit + $page->doEditContent( new WikitextContent( "Foo bar" ), + "testing 2", EDIT_UPDATE, $page->getLatest(), self::$users['uploader']->user ); + $this->forceRevisionDate( $page, '20120101020202' ); + + // try to save edit, expect conflict + try { + $this->doApiRequestWithToken( array( + 'action' => 'edit', + 'title' => $name, + 'text' => 'nix bar!', + 'basetimestamp' => $baseTime, + ), null, self::$users['sysop']->user ); + + $this->fail( 'edit conflict expected' ); + } catch ( UsageException $ex ) { + $this->assertEquals( 'editconflict', $ex->getCodeString() ); + } + } + + public function testEditConflict_redirect() { + static $count = 0; + $count++; + + // assume NS_HELP defaults to wikitext + $name = "Help:ApiEditPageTest_testEditConflict_redirect_$count"; + $title = Title::newFromText( $name ); + $page = WikiPage::factory( $title ); + + $rname = "Help:ApiEditPageTest_testEditConflict_redirect_r$count"; + $rtitle = Title::newFromText( $rname ); + $rpage = WikiPage::factory( $rtitle ); + + // base edit for content + $page->doEditContent( new WikitextContent( "Foo" ), + "testing 1", EDIT_NEW, false, self::$users['sysop']->user ); + $this->forceRevisionDate( $page, '20120101000000' ); + $baseTime = $page->getRevision()->getTimestamp(); + + // base edit for redirect + $rpage->doEditContent( new WikitextContent( "#REDIRECT [[$name]]" ), + "testing 1", EDIT_NEW, false, self::$users['sysop']->user ); + $this->forceRevisionDate( $rpage, '20120101000000' ); + + // conflicting edit to redirect + $rpage->doEditContent( new WikitextContent( "#REDIRECT [[$name]]\n\n[[Category:Test]]" ), + "testing 2", EDIT_UPDATE, $page->getLatest(), self::$users['uploader']->user ); + $this->forceRevisionDate( $rpage, '20120101020202' ); + + // try to save edit; should work, because we follow the redirect + list( $re, , ) = $this->doApiRequestWithToken( array( + 'action' => 'edit', + 'title' => $rname, + 'text' => 'nix bar!', + 'basetimestamp' => $baseTime, + 'redirect' => true, + ), null, self::$users['sysop']->user ); + + $this->assertEquals( 'Success', $re['edit']['result'], + "no edit conflict expected when following redirect" ); + + // try again, without following the redirect. Should fail. + try { + $this->doApiRequestWithToken( array( + 'action' => 'edit', + 'title' => $rname, + 'text' => 'nix bar!', + 'basetimestamp' => $baseTime, + ), null, self::$users['sysop']->user ); + + $this->fail( 'edit conflict expected' ); + } catch ( UsageException $ex ) { + $this->assertEquals( 'editconflict', $ex->getCodeString() ); + } + } + + public function testEditConflict_bug41990() { + static $count = 0; + $count++; + + /* + * bug 41990: if the target page has a newer revision than the redirect, then editing the + * redirect while specifying 'redirect' and *not* specifying 'basetimestamp' erroneously + * caused an edit conflict to be detected. + */ + + // assume NS_HELP defaults to wikitext + $name = "Help:ApiEditPageTest_testEditConflict_redirect_bug41990_$count"; + $title = Title::newFromText( $name ); + $page = WikiPage::factory( $title ); + + $rname = "Help:ApiEditPageTest_testEditConflict_redirect_bug41990_r$count"; + $rtitle = Title::newFromText( $rname ); + $rpage = WikiPage::factory( $rtitle ); + + // base edit for content + $page->doEditContent( new WikitextContent( "Foo" ), + "testing 1", EDIT_NEW, false, self::$users['sysop']->user ); + $this->forceRevisionDate( $page, '20120101000000' ); + + // base edit for redirect + $rpage->doEditContent( new WikitextContent( "#REDIRECT [[$name]]" ), + "testing 1", EDIT_NEW, false, self::$users['sysop']->user ); + $this->forceRevisionDate( $rpage, '20120101000000' ); + $baseTime = $rpage->getRevision()->getTimestamp(); + + // new edit to content + $page->doEditContent( new WikitextContent( "Foo bar" ), + "testing 2", EDIT_UPDATE, $page->getLatest(), self::$users['uploader']->user ); + $this->forceRevisionDate( $rpage, '20120101020202' ); + + // try to save edit; should work, following the redirect. + list( $re, , ) = $this->doApiRequestWithToken( array( + 'action' => 'edit', + 'title' => $rname, + 'text' => 'nix bar!', + 'redirect' => true, + ), null, self::$users['sysop']->user ); + + $this->assertEquals( 'Success', $re['edit']['result'], + "no edit conflict expected here" ); + } + + protected function forceRevisionDate( WikiPage $page, $timestamp ) { + $dbw = wfGetDB( DB_MASTER ); + + $dbw->update( 'revision', + array( 'rev_timestamp' => $dbw->timestamp( $timestamp ) ), + array( 'rev_id' => $page->getLatest() ) ); + + $page->clear(); } } diff --git a/tests/phpunit/includes/api/ApiOptionsTest.php b/tests/phpunit/includes/api/ApiOptionsTest.php index 5243fca1..ad1e73ab 100644 --- a/tests/phpunit/includes/api/ApiOptionsTest.php +++ b/tests/phpunit/includes/api/ApiOptionsTest.php @@ -3,48 +3,44 @@ /** * @group API * @group Database + * @group medium */ class ApiOptionsTest extends MediaWikiLangTestCase { - private $mTested, $mApiMainMock, $mUserMock, $mContext, $mSession; + private $mTested, $mUserMock, $mContext, $mSession; private $mOldGetPreferencesHooks = false; private static $Success = array( 'options' => 'success' ); - function setUp() { + protected function setUp() { parent::setUp(); $this->mUserMock = $this->getMockBuilder( 'User' ) ->disableOriginalConstructor() ->getMock(); - $this->mApiMainMock = $this->getMockBuilder( 'ApiBase' ) - ->disableOriginalConstructor() - ->getMock(); + // Set up groups and rights + $this->mUserMock->expects( $this->any() ) + ->method( 'getEffectiveGroups' )->will( $this->returnValue( array( '*', 'user' ) ) ); + $this->mUserMock->expects( $this->any() ) + ->method( 'isAllowed' )->will( $this->returnValue( true ) ); - // Set up groups + // Set up callback for User::getOptionKinds $this->mUserMock->expects( $this->any() ) - ->method( 'getEffectiveGroups' )->will( $this->returnValue( array( '*', 'user')) ); + ->method( 'getOptionKinds' )->will( $this->returnCallback( array( $this, 'getOptionKinds' ) ) ); // Create a new context $this->mContext = new DerivativeContext( new RequestContext() ); $this->mContext->getContext()->setTitle( Title::newFromText( 'Test' ) ); $this->mContext->setUser( $this->mUserMock ); - $this->mApiMainMock->expects( $this->any() ) - ->method( 'getContext' ) - ->will( $this->returnValue( $this->mContext ) ); - - $this->mApiMainMock->expects( $this->any() ) - ->method( 'getResult' ) - ->will( $this->returnValue( new ApiResult( $this->mApiMainMock ) ) ); - + $main = new ApiMain( $this->mContext ); // Empty session $this->mSession = array(); - $this->mTested = new ApiOptions( $this->mApiMainMock, 'options' ); + $this->mTested = new ApiOptions( $main, 'options' ); global $wgHooks; if ( !isset( $wgHooks['GetPreferences'] ) ) { @@ -54,7 +50,7 @@ class ApiOptionsTest extends MediaWikiLangTestCase { $wgHooks['GetPreferences'][] = array( $this, 'hookGetPreferences' ); } - public function tearDown() { + protected function tearDown() { global $wgHooks; if ( $this->mOldGetPreferencesHooks !== false ) { @@ -66,6 +62,8 @@ class ApiOptionsTest extends MediaWikiLangTestCase { } public function hookGetPreferences( $user, &$preferences ) { + $preferences = array(); + foreach ( array( 'name', 'willBeNull', 'willBeEmpty', 'willBeHappy' ) as $k ) { $preferences[$k] = array( 'type' => 'text', @@ -74,9 +72,56 @@ class ApiOptionsTest extends MediaWikiLangTestCase { ); } + $preferences['testmultiselect'] = array( + 'type' => 'multiselect', + 'options' => array( + 'Test' => array( + '<span dir="auto">Some HTML here for option 1</span>' => 'opt1', + '<span dir="auto">Some HTML here for option 2</span>' => 'opt2', + '<span dir="auto">Some HTML here for option 3</span>' => 'opt3', + '<span dir="auto">Some HTML here for option 4</span>' => 'opt4', + ), + ), + 'section' => 'test', + 'label' => ' ', + 'prefix' => 'testmultiselect-', + 'default' => array(), + ); + return true; } + public function getOptionKinds( IContextSource $context, $options = null ) { + // Match with above. + $kinds = array( + 'name' => 'registered', + 'willBeNull' => 'registered', + 'willBeEmpty' => 'registered', + 'willBeHappy' => 'registered', + 'testmultiselect-opt1' => 'registered-multiselect', + 'testmultiselect-opt2' => 'registered-multiselect', + 'testmultiselect-opt3' => 'registered-multiselect', + 'testmultiselect-opt4' => 'registered-multiselect', + ); + + if ( $options === null ) { + return $kinds; + } + + $mapping = array(); + foreach ( $options as $key => $value ) { + if ( isset( $kinds[$key] ) ) { + $mapping[$key] = $kinds[$key]; + } elseif ( substr( $key, 0, 7 ) === 'userjs-' ) { + $mapping[$key] = 'userjs'; + } else { + $mapping[$key] = 'unused'; + } + } + + return $mapping; + } + private function getSampleRequest( $custom = array() ) { $request = array( 'token' => '123ABC', @@ -84,12 +129,14 @@ class ApiOptionsTest extends MediaWikiLangTestCase { 'optionname' => null, 'optionvalue' => null, ); + return array_merge( $request, $custom ); } private function executeQuery( $request ) { $this->mContext->setRequest( new FauxRequest( $request, true, $this->mSession ) ); $this->mTested->execute(); + return $this->mTested->getResult()->getData(); } @@ -114,6 +161,7 @@ class ApiOptionsTest extends MediaWikiLangTestCase { } catch ( UsageException $e ) { $this->assertEquals( 'notloggedin', $e->getCodeString() ); $this->assertEquals( 'Anonymous users cannot change preferences', $e->getMessage() ); + return; } $this->fail( "UsageException was not thrown" ); @@ -127,6 +175,7 @@ class ApiOptionsTest extends MediaWikiLangTestCase { } catch ( UsageException $e ) { $this->assertEquals( 'nooptionname', $e->getCodeString() ); $this->assertEquals( 'The optionname parameter must be set', $e->getMessage() ); + return; } $this->fail( "UsageException was not thrown" ); @@ -149,6 +198,7 @@ class ApiOptionsTest extends MediaWikiLangTestCase { } catch ( UsageException $e ) { $this->assertEquals( 'nochanges', $e->getCodeString() ); $this->assertEquals( 'No changes were requested', $e->getMessage() ); + return; } $this->fail( "UsageException was not thrown" ); @@ -156,7 +206,8 @@ class ApiOptionsTest extends MediaWikiLangTestCase { public function testReset() { $this->mUserMock->expects( $this->once() ) - ->method( 'resetOptions' ); + ->method( 'resetOptions' ) + ->with( $this->equalTo( array( 'all' ) ) ); $this->mUserMock->expects( $this->never() ) ->method( 'setOption' ); @@ -171,6 +222,24 @@ class ApiOptionsTest extends MediaWikiLangTestCase { $this->assertEquals( self::$Success, $response ); } + public function testResetKinds() { + $this->mUserMock->expects( $this->once() ) + ->method( 'resetOptions' ) + ->with( $this->equalTo( array( 'registered' ) ) ); + + $this->mUserMock->expects( $this->never() ) + ->method( 'setOption' ); + + $this->mUserMock->expects( $this->once() ) + ->method( 'saveSettings' ); + + $request = $this->getSampleRequest( array( 'reset' => '', 'resetkinds' => 'registered' ) ); + + $response = $this->executeQuery( $request ); + + $this->assertEquals( self::$Success, $response ); + } + public function testOptionWithValue() { $this->mUserMock->expects( $this->never() ) ->method( 'resetOptions' ); @@ -195,7 +264,7 @@ class ApiOptionsTest extends MediaWikiLangTestCase { $this->mUserMock->expects( $this->once() ) ->method( 'setOption' ) - ->with( $this->equalTo( 'name' ), $this->equalTo( null ) ); + ->with( $this->equalTo( 'name' ), $this->identicalTo( null ) ); $this->mUserMock->expects( $this->once() ) ->method( 'saveSettings' ); @@ -210,24 +279,24 @@ class ApiOptionsTest extends MediaWikiLangTestCase { $this->mUserMock->expects( $this->never() ) ->method( 'resetOptions' ); - $this->mUserMock->expects( $this->at( 1 ) ) + $this->mUserMock->expects( $this->at( 2 ) ) ->method( 'getOptions' ); - $this->mUserMock->expects( $this->at( 2 ) ) + $this->mUserMock->expects( $this->at( 4 ) ) ->method( 'setOption' ) - ->with( $this->equalTo( 'willBeNull' ), $this->equalTo( null ) ); + ->with( $this->equalTo( 'willBeNull' ), $this->identicalTo( null ) ); - $this->mUserMock->expects( $this->at( 3 ) ) + $this->mUserMock->expects( $this->at( 5 ) ) ->method( 'getOptions' ); - $this->mUserMock->expects( $this->at( 4 ) ) + $this->mUserMock->expects( $this->at( 6 ) ) ->method( 'setOption' ) ->with( $this->equalTo( 'willBeEmpty' ), $this->equalTo( '' ) ); - $this->mUserMock->expects( $this->at( 5 ) ) + $this->mUserMock->expects( $this->at( 7 ) ) ->method( 'getOptions' ); - $this->mUserMock->expects( $this->at( 6 ) ) + $this->mUserMock->expects( $this->at( 8 ) ) ->method( 'setOption' ) ->with( $this->equalTo( 'willBeHappy' ), $this->equalTo( 'Happy' ) ); @@ -245,17 +314,17 @@ class ApiOptionsTest extends MediaWikiLangTestCase { $this->mUserMock->expects( $this->once() ) ->method( 'resetOptions' ); - $this->mUserMock->expects( $this->at( 2 ) ) + $this->mUserMock->expects( $this->at( 4 ) ) ->method( 'getOptions' ); - $this->mUserMock->expects( $this->at( 3 ) ) + $this->mUserMock->expects( $this->at( 5 ) ) ->method( 'setOption' ) ->with( $this->equalTo( 'willBeHappy' ), $this->equalTo( 'Happy' ) ); - $this->mUserMock->expects( $this->at( 4 ) ) + $this->mUserMock->expects( $this->at( 6 ) ) ->method( 'getOptions' ); - $this->mUserMock->expects( $this->at( 5 ) ) + $this->mUserMock->expects( $this->at( 7 ) ) ->method( 'setOption' ) ->with( $this->equalTo( 'name' ), $this->equalTo( 'value' ) ); @@ -273,4 +342,79 @@ class ApiOptionsTest extends MediaWikiLangTestCase { $this->assertEquals( self::$Success, $response ); } + + public function testMultiSelect() { + $this->mUserMock->expects( $this->never() ) + ->method( 'resetOptions' ); + + $this->mUserMock->expects( $this->at( 3 ) ) + ->method( 'setOption' ) + ->with( $this->equalTo( 'testmultiselect-opt1' ), $this->identicalTo( true ) ); + + $this->mUserMock->expects( $this->at( 4 ) ) + ->method( 'setOption' ) + ->with( $this->equalTo( 'testmultiselect-opt2' ), $this->identicalTo( null ) ); + + $this->mUserMock->expects( $this->at( 5 ) ) + ->method( 'setOption' ) + ->with( $this->equalTo( 'testmultiselect-opt3' ), $this->identicalTo( false ) ); + + $this->mUserMock->expects( $this->at( 6 ) ) + ->method( 'setOption' ) + ->with( $this->equalTo( 'testmultiselect-opt4' ), $this->identicalTo( false ) ); + + $this->mUserMock->expects( $this->once() ) + ->method( 'saveSettings' ); + + $request = $this->getSampleRequest( array( + 'change' => 'testmultiselect-opt1=1|testmultiselect-opt2|testmultiselect-opt3=|testmultiselect-opt4=0' + ) ); + + $response = $this->executeQuery( $request ); + + $this->assertEquals( self::$Success, $response ); + } + + public function testUnknownOption() { + $this->mUserMock->expects( $this->never() ) + ->method( 'resetOptions' ); + + $this->mUserMock->expects( $this->never() ) + ->method( 'saveSettings' ); + + $request = $this->getSampleRequest( array( + 'change' => 'unknownOption=1' + ) ); + + $response = $this->executeQuery( $request ); + + $this->assertEquals( array( + 'options' => 'success', + 'warnings' => array( + 'options' => array( + '*' => "Validation error for 'unknownOption': not a valid preference" + ) + ) + ), $response ); + } + + public function testUserjsOption() { + $this->mUserMock->expects( $this->never() ) + ->method( 'resetOptions' ); + + $this->mUserMock->expects( $this->at( 3 ) ) + ->method( 'setOption' ) + ->with( $this->equalTo( 'userjs-option' ), $this->equalTo( '1' ) ); + + $this->mUserMock->expects( $this->once() ) + ->method( 'saveSettings' ); + + $request = $this->getSampleRequest( array( + 'change' => 'userjs-option=1' + ) ); + + $response = $this->executeQuery( $request ); + + $this->assertEquals( self::$Success, $response ); + } } diff --git a/tests/phpunit/includes/api/ApiParseTest.php b/tests/phpunit/includes/api/ApiParseTest.php new file mode 100644 index 00000000..2d714e65 --- /dev/null +++ b/tests/phpunit/includes/api/ApiParseTest.php @@ -0,0 +1,29 @@ +<?php + +/** + * @group API + * @group Database + * @group medium + */ +class ApiParseTest extends ApiTestCase { + + protected function setUp() { + parent::setUp(); + $this->doLogin(); + } + + public function testParseNonexistentPage() { + $somePage = mt_rand(); + + try { + $this->doApiRequest( array( + 'action' => 'parse', + 'page' => $somePage ) ); + + $this->fail( "API did not return an error when parsing a nonexistent page" ); + } catch ( UsageException $ex ) { + $this->assertEquals( 'missingtitle', $ex->getCodeString(), + "Parse request for nonexistent page must give 'missingtitle' error: " . var_export( $ex->getMessageArray(), true ) ); + } + } +} diff --git a/tests/phpunit/includes/api/ApiPurgeTest.php b/tests/phpunit/includes/api/ApiPurgeTest.php index 2566c6cd..28b5ff4d 100644 --- a/tests/phpunit/includes/api/ApiPurgeTest.php +++ b/tests/phpunit/includes/api/ApiPurgeTest.php @@ -3,10 +3,11 @@ /** * @group API * @group Database + * @group medium */ class ApiPurgeTest extends ApiTestCase { - function setUp() { + protected function setUp() { parent::setUp(); $this->doLogin(); } @@ -14,7 +15,7 @@ class ApiPurgeTest extends ApiTestCase { /** * @group Broken */ - function testPurgeMainPage() { + public function testPurgeMainPage() { if ( !Title::newFromText( 'UTPage' )->exists() ) { $this->markTestIncomplete( "The article [[UTPage]] does not exist" ); } @@ -32,9 +33,8 @@ class ApiPurgeTest extends ApiTestCase { "Purge request for three articles should give back three results received: " . var_export( $data[0]['purge'], true ) ); $pages = array( 'UTPage' => 'purged', $somePage => 'missing', '%5D' => 'invalid' ); - foreach( $data[0]['purge'] as $v ) { + foreach ( $data[0]['purge'] as $v ) { $this->assertArrayHasKey( $pages[$v['title']], $v ); } } - } diff --git a/tests/phpunit/includes/api/ApiTest.php b/tests/phpunit/includes/api/ApiTest.php index c3eacd5b..472f8c4a 100644 --- a/tests/phpunit/includes/api/ApiTest.php +++ b/tests/phpunit/includes/api/ApiTest.php @@ -3,37 +3,38 @@ /** * @group API * @group Database + * @group medium */ class ApiTest extends ApiTestCase { - function testRequireOnlyOneParameterDefault() { + public function testRequireOnlyOneParameterDefault() { $mock = new MockApi(); $this->assertEquals( null, $mock->requireOnlyOneParameter( array( "filename" => "foo.txt", - "enablechunks" => false ), "filename", "enablechunks" ) ); + "enablechunks" => false ), "filename", "enablechunks" ) ); } /** * @expectedException UsageException */ - function testRequireOnlyOneParameterZero() { + public function testRequireOnlyOneParameterZero() { $mock = new MockApi(); $this->assertEquals( null, $mock->requireOnlyOneParameter( array( "filename" => "foo.txt", - "enablechunks" => 0 ), "filename", "enablechunks" ) ); + "enablechunks" => 0 ), "filename", "enablechunks" ) ); } /** * @expectedException UsageException */ - function testRequireOnlyOneParameterTrue() { + public function testRequireOnlyOneParameterTrue() { $mock = new MockApi(); $this->assertEquals( null, $mock->requireOnlyOneParameter( array( "filename" => "foo.txt", - "enablechunks" => true ), "filename", "enablechunks" ) ); + "enablechunks" => true ), "filename", "enablechunks" ) ); } /** @@ -42,8 +43,7 @@ class ApiTest extends ApiTestCase { * * @expectedException UsageException */ - function testApi() { - + public function testApi() { $api = new ApiMain( new FauxRequest( array( 'action' => 'help', 'format' => 'xml' ) ) ); @@ -61,14 +61,14 @@ class ApiTest extends ApiTestCase { /** * Test result of attempted login with an empty username */ - function testApiLoginNoName() { + public function testApiLoginNoName() { $data = $this->doApiRequest( array( 'action' => 'login', 'lgname' => '', 'lgpassword' => self::$users['sysop']->password, ) ); $this->assertEquals( 'NoName', $data[0]['login']['result'] ); } - function testApiLoginBadPass() { + public function testApiLoginBadPass() { global $wgServer; $user = self::$users['sysop']; @@ -81,8 +81,7 @@ class ApiTest extends ApiTestCase { "action" => "login", "lgname" => $user->username, "lgpassword" => "bad", - ) - ); + ) ); $result = $ret[0]; @@ -92,12 +91,14 @@ class ApiTest extends ApiTestCase { $token = $result["login"]["token"]; - $ret = $this->doApiRequest( array( - "action" => "login", - "lgtoken" => $token, - "lgname" => $user->username, - "lgpassword" => "badnowayinhell", - ), $ret[2] + $ret = $this->doApiRequest( + array( + "action" => "login", + "lgtoken" => $token, + "lgname" => $user->username, + "lgpassword" => "badnowayinhell", + ), + $ret[2] ); $result = $ret[0]; @@ -108,7 +109,7 @@ class ApiTest extends ApiTestCase { $this->assertEquals( "WrongPass", $a ); } - function testApiLoginGoodPass() { + public function testApiLoginGoodPass() { global $wgServer; if ( !isset( $wgServer ) ) { @@ -119,9 +120,9 @@ class ApiTest extends ApiTestCase { $user->user->logOut(); $ret = $this->doApiRequest( array( - "action" => "login", - "lgname" => $user->username, - "lgpassword" => $user->password, + "action" => "login", + "lgname" => $user->username, + "lgpassword" => $user->password, ) ); @@ -133,12 +134,14 @@ class ApiTest extends ApiTestCase { $this->assertEquals( "NeedToken", $a ); $token = $result["login"]["token"]; - $ret = $this->doApiRequest( array( - "action" => "login", - "lgtoken" => $token, - "lgname" => $user->username, - "lgpassword" => $user->password, - ), $ret[2] + $ret = $this->doApiRequest( + array( + "action" => "login", + "lgtoken" => $token, + "lgname" => $user->username, + "lgpassword" => $user->password, + ), + $ret[2] ); $result = $ret[0]; @@ -152,8 +155,8 @@ class ApiTest extends ApiTestCase { /** * @group Broken */ - function testApiGotCookie() { - $this->markTestIncomplete( "The server can't do external HTTP requests, and the internal one won't give cookies" ); + public function testApiGotCookie() { + $this->markTestIncomplete( "The server can't do external HTTP requests, and the internal one won't give cookies" ); global $wgServer, $wgScriptPath; @@ -165,8 +168,11 @@ class ApiTest extends ApiTestCase { $req = MWHttpRequest::factory( self::$apiUrl . "?action=login&format=xml", array( "method" => "POST", "postData" => array( - "lgname" => $user->username, - "lgpassword" => $user->password ) ) ); + "lgname" => $user->username, + "lgpassword" => $user->password + ) + ) + ); $req->execute(); libxml_use_internal_errors( true ); @@ -195,27 +201,7 @@ class ApiTest extends ApiTestCase { return $cj; } - /** - * @todo Finish filling me out...what are we trying to test here? - */ - function testApiListPages() { - global $wgServer; - if ( !isset( $wgServer ) ) { - $this->markTestIncomplete( 'This test needs $wgServer to be set in LocalSettings.php' ); - } - - $ret = $this->doApiRequest( array( - 'action' => 'query', - 'prop' => 'revisions', - 'titles' => 'Main Page', - 'rvprop' => 'timestamp|user|comment|content', - ) ); - - $result = $ret[0]['query']['pages']; - $this->markTestIncomplete( "Somebody needs to finish loving me" ); - } - - function testRunLogin() { + public function testRunLogin() { $sysopUser = self::$users['sysop']; $data = $this->doApiRequest( array( 'action' => 'login', @@ -237,44 +223,37 @@ class ApiTest extends ApiTestCase { $this->assertArrayHasKey( "result", $data[0]['login'] ); $this->assertEquals( "Success", $data[0]['login']['result'] ); $this->assertArrayHasKey( 'lgtoken', $data[0]['login'] ); - + return $data; } - - function testGettingToken() { + + public function testGettingToken() { foreach ( self::$users as $user ) { $this->runTokenTest( $user ); } } function runTokenTest( $user ) { - - $data = $this->getTokenList( $user ); - - $this->assertArrayHasKey( 'query', $data[0] ); - $this->assertArrayHasKey( 'pages', $data[0]['query'] ); - $keys = array_keys( $data[0]['query']['pages'] ); - $key = array_pop( $keys ); + $tokens = $this->getTokenList( $user ); $rights = $user->user->getRights(); - $this->assertArrayHasKey( $key, $data[0]['query']['pages'] ); - $this->assertArrayHasKey( 'edittoken', $data[0]['query']['pages'][$key] ); - $this->assertArrayHasKey( 'movetoken', $data[0]['query']['pages'][$key] ); + $this->assertArrayHasKey( 'edittoken', $tokens ); + $this->assertArrayHasKey( 'movetoken', $tokens ); if ( isset( $rights['delete'] ) ) { - $this->assertArrayHasKey( 'deletetoken', $data[0]['query']['pages'][$key] ); + $this->assertArrayHasKey( 'deletetoken', $tokens ); } if ( isset( $rights['block'] ) ) { - $this->assertArrayHasKey( 'blocktoken', $data[0]['query']['pages'][$key] ); - $this->assertArrayHasKey( 'unblocktoken', $data[0]['query']['pages'][$key] ); + $this->assertArrayHasKey( 'blocktoken', $tokens ); + $this->assertArrayHasKey( 'unblocktoken', $tokens ); } if ( isset( $rights['protect'] ) ) { - $this->assertArrayHasKey( 'protecttoken', $data[0]['query']['pages'][$key] ); + $this->assertArrayHasKey( 'protecttoken', $tokens ); } - return $data; + return $tokens; } } diff --git a/tests/phpunit/includes/api/ApiTestCase.php b/tests/phpunit/includes/api/ApiTestCase.php index b84292e3..94ef9c68 100644 --- a/tests/phpunit/includes/api/ApiTestCase.php +++ b/tests/phpunit/includes/api/ApiTestCase.php @@ -1,4 +1,4 @@ -<?php +<?php abstract class ApiTestCase extends MediaWikiLangTestCase { protected static $apiUrl; @@ -8,15 +8,13 @@ abstract class ApiTestCase extends MediaWikiLangTestCase { */ protected $apiContext; - function setUp() { - global $wgContLang, $wgAuth, $wgMemc, $wgRequest, $wgUser, $wgServer; + protected function setUp() { + global $wgServer; parent::setUp(); self::$apiUrl = $wgServer . wfScript( 'api' ); - $wgMemc = new EmptyBagOStuff(); - $wgContLang = Language::factory( 'en' ); - $wgAuth = new StubObject( 'wgAuth', 'AuthPlugin' ); - $wgRequest = new FauxRequest( array() ); + + ApiQueryInfo::resetTokenCache(); // tokens are invalid because we cleared the session self::$users = array( 'sysop' => new TestUser( @@ -33,21 +31,56 @@ abstract class ApiTestCase extends MediaWikiLangTestCase { ) ); - $wgUser = self::$users['sysop']->user; + $this->setMwGlobals( array( + 'wgMemc' => new EmptyBagOStuff(), + 'wgAuth' => new StubObject( 'wgAuth', 'AuthPlugin' ), + 'wgRequest' => new FauxRequest( array() ), + 'wgUser' => self::$users['sysop']->user, + ) ); $this->apiContext = new ApiTestContext(); + } + + /** + * Edits or creates a page/revision + * @param $pageName string page title + * @param $text string content of the page + * @param $summary string optional summary string for the revision + * @param $defaultNs int optional namespace id + * @return array as returned by WikiPage::doEditContent() + */ + protected function editPage( $pageName, $text, $summary = '', $defaultNs = NS_MAIN ) { + $title = Title::newFromText( $pageName, $defaultNs ); + $page = WikiPage::factory( $title ); + return $page->doEditContent( ContentHandler::makeContent( $text, $title ), $summary ); } - protected function doApiRequest( Array $params, Array $session = null, $appendModule = false, User $user = null ) { + /** + * Does the API request and returns the result. + * + * The returned value is an array containing + * - the result data (array) + * - the request (WebRequest) + * - the session data of the request (array) + * - if $appendModule is true, the Api module $module + * + * @param array $params + * @param array|null $session + * @param bool $appendModule + * @param User|null $user + * + * @return array + */ + protected function doApiRequest( array $params, array $session = null, $appendModule = false, User $user = null ) { global $wgRequest, $wgUser; if ( is_null( $session ) ) { - # re-use existing global session by default + // re-use existing global session by default $session = $wgRequest->getSessionArray(); } - # set up global environment + // set up global environment if ( $user ) { $wgUser = $user; } @@ -55,21 +88,22 @@ abstract class ApiTestCase extends MediaWikiLangTestCase { $wgRequest = new FauxRequest( $params, true, $session ); RequestContext::getMain()->setRequest( $wgRequest ); - # set up local environment + // set up local environment $context = $this->apiContext->newTestContext( $wgRequest, $wgUser ); $module = new ApiMain( $context, true ); - # run it! + // run it! $module->execute(); - # construct result + // construct result $results = array( $module->getResultData(), $context->getRequest(), $context->getRequest()->getSessionArray() ); - if( $appendModule ) { + + if ( $appendModule ) { $results[] = $module; } @@ -83,8 +117,10 @@ abstract class ApiTestCase extends MediaWikiLangTestCase { * @param $params Array: key-value API params * @param $session Array|null: session array * @param $user User|null A User object for the context + * @return result of the API call + * @throws Exception in case wsToken is not set in the session */ - protected function doApiRequestWithToken( Array $params, Array $session = null, User $user = null ) { + protected function doApiRequestWithToken( array $params, array $session = null, User $user = null ) { global $wgRequest; if ( $session === null ) { @@ -96,42 +132,67 @@ abstract class ApiTestCase extends MediaWikiLangTestCase { $session['wsEditToken'] = $session['wsToken']; // add token to request parameters $params['token'] = md5( $session['wsToken'] ) . User::EDIT_TOKEN_SUFFIX; + return $this->doApiRequest( $params, $session, false, $user ); } else { throw new Exception( "request data not in right format" ); } } - protected function doLogin() { + protected function doLogin( $user = 'sysop' ) { + if ( !array_key_exists( $user, self::$users ) ) { + throw new MWException( "Can not log in to undefined user $user" ); + } + $data = $this->doApiRequest( array( 'action' => 'login', - 'lgname' => self::$users['sysop']->username, - 'lgpassword' => self::$users['sysop']->password ) ); + 'lgname' => self::$users[ $user ]->username, + 'lgpassword' => self::$users[ $user ]->password ) ); $token = $data[0]['login']['token']; - $data = $this->doApiRequest( array( - 'action' => 'login', - 'lgtoken' => $token, - 'lgname' => self::$users['sysop']->username, - 'lgpassword' => self::$users['sysop']->password - ), $data[2] ); + $data = $this->doApiRequest( + array( + 'action' => 'login', + 'lgtoken' => $token, + 'lgname' => self::$users[ $user ]->username, + 'lgpassword' => self::$users[ $user ]->password, + ), + $data[2] + ); return $data; } protected function getTokenList( $user, $session = null ) { $data = $this->doApiRequest( array( - 'action' => 'query', - 'titles' => 'Main Page', - 'intoken' => 'edit|delete|protect|move|block|unblock|watch', - 'prop' => 'info' ), $session, false, $user->user ); - return $data; + 'action' => 'tokens', + 'type' => 'edit|delete|protect|move|block|unblock|watch' + ), $session, false, $user->user ); + + if ( !array_key_exists( 'tokens', $data[0] ) ) { + throw new MWException( 'Api failed to return a token list' ); + } + + return $data[0]['tokens']; + } + + public function testApiTestGroup() { + $groups = PHPUnit_Util_Test::getGroups( get_class( $this ) ); + $constraint = PHPUnit_Framework_Assert::logicalOr( + $this->contains( 'medium' ), + $this->contains( 'large' ) + ); + $this->assertThat( $groups, $constraint, + 'ApiTestCase::setUp can be slow, tests must be "medium" or "large"' + ); } } class UserWrapper { - public $userName, $password, $user; + public $userName; + public $password; + public $user; public function __construct( $userName, $password, $group = '' ) { $this->userName = $userName; @@ -153,10 +214,14 @@ class UserWrapper { } class MockApi extends ApiBase { - public function execute() { } - public function getVersion() { } + public function execute() { + } - public function __construct() { } + public function getVersion() { + } + + public function __construct() { + } public function getAllowedParams() { return array( @@ -182,6 +247,7 @@ class ApiTestContext extends RequestContext { if ( $user !== null ) { $context->setUser( $user ); } + return $context; } } diff --git a/tests/phpunit/includes/api/ApiTestCaseUpload.php b/tests/phpunit/includes/api/ApiTestCaseUpload.php index 39c79547..7e18b6ed 100644 --- a/tests/phpunit/includes/api/ApiTestCaseUpload.php +++ b/tests/phpunit/includes/api/ApiTestCaseUpload.php @@ -8,19 +8,23 @@ abstract class ApiTestCaseUpload extends ApiTestCase { /** * Fixture -- run before every test */ - public function setUp() { - global $wgEnableUploads, $wgEnableAPI; + protected function setUp() { parent::setUp(); - $wgEnableUploads = true; - $wgEnableAPI = true; + $this->setMwGlobals( array( + 'wgEnableUploads' => true, + 'wgEnableAPI' => true, + ) ); + wfSetupSession(); $this->clearFakeUploads(); } - public function tearDown() { + protected function tearDown() { $this->clearTempUpload(); + + parent::tearDown(); } /** @@ -43,7 +47,8 @@ abstract class ApiTestCaseUpload extends ApiTestCase { // see if it now doesn't exist; reload $title = Title::newFromText( $title->getText(), NS_FILE ); } - return ! ( $title && $title instanceof Title && $title->exists() ); + + return !( $title && $title instanceof Title && $title->exists() ); } /** @@ -54,7 +59,6 @@ abstract class ApiTestCaseUpload extends ApiTestCase { return $this->deleteFileByTitle( Title::newFromText( $fileName, NS_FILE ) ); } - /** * Helper function -- given a file on the filesystem, find matching content in the db (and associated articles) and remove them. * @param $filePath String: path to file on the filesystem @@ -66,6 +70,7 @@ abstract class ApiTestCaseUpload extends ApiTestCase { foreach ( $dupes as $dupe ) { $success &= $this->deleteFileByTitle( $dupe->getTitle() ); } + return $success; } @@ -81,7 +86,7 @@ abstract class ApiTestCaseUpload extends ApiTestCase { $tmpName = tempnam( wfTempDir(), "" ); if ( !file_exists( $filePath ) ) { throw new Exception( "$filePath doesn't exist!" ); - }; + } if ( !copy( $filePath, $tmpName ) ) { throw new Exception( "couldn't copy $filePath to $tmpName" ); @@ -93,43 +98,43 @@ abstract class ApiTestCaseUpload extends ApiTestCase { throw new Exception( "couldn't stat $tmpName" ); } - $_FILES[ $fieldName ] = array( - 'name' => $fileName, - 'type' => $type, - 'tmp_name' => $tmpName, - 'size' => $size, - 'error' => null + $_FILES[$fieldName] = array( + 'name' => $fileName, + 'type' => $type, + 'tmp_name' => $tmpName, + 'size' => $size, + 'error' => null ); return true; - } - function fakeUploadChunk( $fieldName, $fileName, $type, & $chunkData ){ + + function fakeUploadChunk( $fieldName, $fileName, $type, & $chunkData ) { $tmpName = tempnam( wfTempDir(), "" ); - // copy the chunk data to temp location: + // copy the chunk data to temp location: if ( !file_put_contents( $tmpName, $chunkData ) ) { throw new Exception( "couldn't copy chunk data to $tmpName" ); } - + clearstatcache(); $size = filesize( $tmpName ); if ( $size === false ) { throw new Exception( "couldn't stat $tmpName" ); } - - $_FILES[ $fieldName ] = array( - 'name' => $fileName, - 'type' => $type, - 'tmp_name' => $tmpName, - 'size' => $size, - 'error' => null + + $_FILES[$fieldName] = array( + 'name' => $fileName, + 'type' => $type, + 'tmp_name' => $tmpName, + 'size' => $size, + 'error' => null ); } function clearTempUpload() { - if( isset( $_FILES['file']['tmp_name'] ) ) { + if ( isset( $_FILES['file']['tmp_name'] ) ) { $tmp = $_FILES['file']['tmp_name']; - if( file_exists( $tmp ) ) { + if ( file_exists( $tmp ) ) { unlink( $tmp ); } } @@ -141,8 +146,4 @@ abstract class ApiTestCaseUpload extends ApiTestCase { function clearFakeUploads() { $_FILES = array(); } - - - - } diff --git a/tests/phpunit/includes/api/ApiUploadTest.php b/tests/phpunit/includes/api/ApiUploadTest.php index 642fed05..1540af55 100644 --- a/tests/phpunit/includes/api/ApiUploadTest.php +++ b/tests/phpunit/includes/api/ApiUploadTest.php @@ -16,7 +16,7 @@ // TODO: port the other Upload tests, and other API tests to this framework -require_once( 'ApiTestCaseUpload.php' ); +require_once 'ApiTestCaseUpload.php'; /** * @group Database @@ -27,12 +27,11 @@ require_once( 'ApiTestCaseUpload.php' ); * This is pretty sucky... needs to be prettified. */ class ApiUploadTest extends ApiTestCaseUpload { - /** * Testing login * XXX this is a funny way of getting session context */ - function testLogin() { + public function testLogin() { $user = self::$users['uploader']; $params = array( @@ -59,8 +58,8 @@ class ApiUploadTest extends ApiTestCaseUpload { $this->assertArrayHasKey( 'lgtoken', $result['login'] ); $this->assertNotEmpty( $session, 'API Login must return a session' ); - return $session; + return $session; } /** @@ -107,8 +106,7 @@ class ApiUploadTest extends ApiTestCaseUpload { try { $randomImageGenerator = new RandomImageGenerator(); $filePaths = $randomImageGenerator->writeImages( 1, $extension, wfTempDir() ); - } - catch ( Exception $e ) { + } catch ( Exception $e ) { $this->markTestIncomplete( $e->getMessage() ); } @@ -119,8 +117,7 @@ class ApiUploadTest extends ApiTestCaseUpload { $this->deleteFileByFileName( $fileName ); $this->deleteFileByContent( $filePath ); - - if (! $this->fakeUploadFile( 'file', $fileName, $mimeType, $filePath ) ) { + if ( !$this->fakeUploadFile( 'file', $fileName, $mimeType, $filePath ) ) { $this->markTestIncomplete( "Couldn't upload file!\n" ); } @@ -129,7 +126,7 @@ class ApiUploadTest extends ApiTestCaseUpload { 'filename' => $fileName, 'file' => 'dummy content', 'comment' => 'dummy comment', - 'text' => "This is the page text for $fileName", + 'text' => "This is the page text for $fileName", ); $exception = false; @@ -141,7 +138,7 @@ class ApiUploadTest extends ApiTestCaseUpload { } $this->assertTrue( isset( $result['upload'] ) ); $this->assertEquals( 'Success', $result['upload']['result'] ); - $this->assertEquals( $fileSize, ( int )$result['upload']['imageinfo']['size'] ); + $this->assertEquals( $fileSize, (int)$result['upload']['imageinfo']['size'] ); $this->assertEquals( $mimeType, $result['upload']['imageinfo']['mime'] ); $this->assertFalse( $exception ); @@ -162,7 +159,7 @@ class ApiUploadTest extends ApiTestCaseUpload { $this->deleteFileByFileName( $fileName ); - if (! $this->fakeUploadFile( 'file', $fileName, $mimeType, $filePath ) ) { + if ( !$this->fakeUploadFile( 'file', $fileName, $mimeType, $filePath ) ) { $this->markTestIncomplete( "Couldn't upload file!\n" ); } @@ -171,7 +168,7 @@ class ApiUploadTest extends ApiTestCaseUpload { 'filename' => $fileName, 'file' => 'dummy content', 'comment' => 'dummy comment', - 'text' => "This is the page text for $fileName", + 'text' => "This is the page text for $fileName", ); $exception = false; @@ -199,8 +196,7 @@ class ApiUploadTest extends ApiTestCaseUpload { try { $randomImageGenerator = new RandomImageGenerator(); $filePaths = $randomImageGenerator->writeImages( 2, $extension, wfTempDir() ); - } - catch ( Exception $e ) { + } catch ( Exception $e ) { $this->markTestIncomplete( $e->getMessage() ); } @@ -216,12 +212,12 @@ class ApiUploadTest extends ApiTestCaseUpload { 'filename' => $fileName, 'file' => 'dummy content', 'comment' => 'dummy comment', - 'text' => "This is the page text for $fileName", + 'text' => "This is the page text for $fileName", ); // first upload .... should succeed - if (! $this->fakeUploadFile( 'file', $fileName, $mimeType, $filePaths[0] ) ) { + if ( !$this->fakeUploadFile( 'file', $fileName, $mimeType, $filePaths[0] ) ) { $this->markTestIncomplete( "Couldn't upload file!\n" ); } @@ -238,7 +234,7 @@ class ApiUploadTest extends ApiTestCaseUpload { // second upload with the same name (but different content) - if (! $this->fakeUploadFile( 'file', $fileName, $mimeType, $filePaths[1] ) ) { + if ( !$this->fakeUploadFile( 'file', $fileName, $mimeType, $filePaths[1] ) ) { $this->markTestIncomplete( "Couldn't upload file!\n" ); } @@ -272,8 +268,7 @@ class ApiUploadTest extends ApiTestCaseUpload { try { $randomImageGenerator = new RandomImageGenerator(); $filePaths = $randomImageGenerator->writeImages( 1, $extension, wfTempDir() ); - } - catch ( Exception $e ) { + } catch ( Exception $e ) { $this->markTestIncomplete( $e->getMessage() ); } @@ -292,16 +287,16 @@ class ApiUploadTest extends ApiTestCaseUpload { 'filename' => $fileNames[0], 'file' => 'dummy content', 'comment' => 'dummy comment', - 'text' => "This is the page text for " . $fileNames[0], + 'text' => "This is the page text for " . $fileNames[0], ); - if (! $this->fakeUploadFile( 'file', $fileNames[0], $mimeType, $filePaths[0] ) ) { + if ( !$this->fakeUploadFile( 'file', $fileNames[0], $mimeType, $filePaths[0] ) ) { $this->markTestIncomplete( "Couldn't upload file!\n" ); } $exception = false; try { - list( $result, $request, $session ) = $this->doApiRequestWithToken( $params, $session, + list( $result, , $session ) = $this->doApiRequestWithToken( $params, $session, self::$users['uploader']->user ); } catch ( UsageException $e ) { $exception = true; @@ -310,10 +305,9 @@ class ApiUploadTest extends ApiTestCaseUpload { $this->assertEquals( 'Success', $result['upload']['result'] ); $this->assertFalse( $exception ); - // second upload with the same content (but different name) - if (! $this->fakeUploadFile( 'file', $fileNames[1], $mimeType, $filePaths[0] ) ) { + if ( !$this->fakeUploadFile( 'file', $fileNames[1], $mimeType, $filePaths[0] ) ) { $this->markTestIncomplete( "Couldn't upload file!\n" ); } @@ -322,12 +316,12 @@ class ApiUploadTest extends ApiTestCaseUpload { 'filename' => $fileNames[1], 'file' => 'dummy content', 'comment' => 'dummy comment', - 'text' => "This is the page text for " . $fileNames[1], + 'text' => "This is the page text for " . $fileNames[1], ); $exception = false; try { - list( $result, $request, $session ) = $this->doApiRequestWithToken( $params, $session, + list( $result ) = $this->doApiRequestWithToken( $params, $session, self::$users['uploader']->user ); // FIXME: leaks a temporary file } catch ( UsageException $e ) { $exception = true; @@ -344,13 +338,13 @@ class ApiUploadTest extends ApiTestCaseUpload { unlink( $filePaths[0] ); } - /** * @depends testLogin */ public function testUploadStash( $session ) { - global $wgUser; - $wgUser = self::$users['uploader']->user; // @todo FIXME: still used somewhere + $this->setMwGlobals( array( + 'wgUser' => self::$users['uploader']->user, // @todo FIXME: still used somewhere + ) ); $extension = 'png'; $mimeType = 'image/png'; @@ -358,8 +352,7 @@ class ApiUploadTest extends ApiTestCaseUpload { try { $randomImageGenerator = new RandomImageGenerator(); $filePaths = $randomImageGenerator->writeImages( 1, $extension, wfTempDir() ); - } - catch ( Exception $e ) { + } catch ( Exception $e ) { $this->markTestIncomplete( $e->getMessage() ); } @@ -370,22 +363,22 @@ class ApiUploadTest extends ApiTestCaseUpload { $this->deleteFileByFileName( $fileName ); $this->deleteFileByContent( $filePath ); - if (! $this->fakeUploadFile( 'file', $fileName, $mimeType, $filePath ) ) { + if ( !$this->fakeUploadFile( 'file', $fileName, $mimeType, $filePath ) ) { $this->markTestIncomplete( "Couldn't upload file!\n" ); } $params = array( 'action' => 'upload', - 'stash' => 1, + 'stash' => 1, 'filename' => $fileName, 'file' => 'dummy content', 'comment' => 'dummy comment', - 'text' => "This is the page text for $fileName", + 'text' => "This is the page text for $fileName", ); $exception = false; try { - list( $result, $request, $session ) = $this->doApiRequestWithToken( $params, $session, + list( $result, , $session ) = $this->doApiRequestWithToken( $params, $session, self::$users['uploader']->user ); // FIXME: leaks a temporary file } catch ( UsageException $e ) { $exception = true; @@ -393,7 +386,7 @@ class ApiUploadTest extends ApiTestCaseUpload { $this->assertFalse( $exception ); $this->assertTrue( isset( $result['upload'] ) ); $this->assertEquals( 'Success', $result['upload']['result'] ); - $this->assertEquals( $fileSize, ( int )$result['upload']['imageinfo']['size'] ); + $this->assertEquals( $fileSize, (int)$result['upload']['imageinfo']['size'] ); $this->assertEquals( $mimeType, $result['upload']['imageinfo']['mime'] ); $this->assertTrue( isset( $result['upload']['filekey'] ) ); $this->assertEquals( $result['upload']['sessionkey'], $result['upload']['filekey'] ); @@ -408,13 +401,13 @@ class ApiUploadTest extends ApiTestCaseUpload { 'filekey' => $filekey, 'filename' => $fileName, 'comment' => 'dummy comment', - 'text' => "This is the page text for $fileName, altered", + 'text' => "This is the page text for $fileName, altered", ); $this->clearFakeUploads(); $exception = false; try { - list( $result, $request, $session ) = $this->doApiRequestWithToken( $params, $session, + list( $result ) = $this->doApiRequestWithToken( $params, $session, self::$users['uploader']->user ); } catch ( UsageException $e ) { $exception = true; @@ -427,15 +420,15 @@ class ApiUploadTest extends ApiTestCaseUpload { $this->deleteFileByFilename( $fileName ); unlink( $filePath ); } - - + /** * @depends testLogin */ public function testUploadChunks( $session ) { - global $wgUser; - $wgUser = self::$users['uploader']->user; // @todo FIXME: still used somewhere - + $this->setMwGlobals( array( + 'wgUser' => self::$users['uploader']->user, // @todo FIXME: still used somewhere + ) ); + $chunkSize = 1048576; // Download a large image file // ( using RandomImageGenerator for large files is not stable ) @@ -444,11 +437,10 @@ class ApiUploadTest extends ApiTestCaseUpload { $filePath = wfTempDir() . '/Oberaargletscher_from_Oberaar.jpg'; try { // Only download if the file is not avaliable in the temp location: - if( !is_file( $filePath ) ){ - copy($url, $filePath); + if ( !is_file( $filePath ) ) { + copy( $url, $filePath ); } - } - catch ( Exception $e ) { + } catch ( Exception $e ) { $this->markTestIncomplete( $e->getMessage() ); } @@ -458,44 +450,44 @@ class ApiUploadTest extends ApiTestCaseUpload { $this->deleteFileByFileName( $fileName ); $this->deleteFileByContent( $filePath ); - // Base upload params: + // Base upload params: $params = array( 'action' => 'upload', - 'stash' => 1, + 'stash' => 1, 'filename' => $fileName, 'filesize' => $fileSize, 'offset' => 0, ); - + // Upload chunks $chunkSessionKey = false; $resultOffset = 0; - // Open the file: - $handle = @fopen ($filePath, "r"); - if( $handle === false ){ + // Open the file: + $handle = @fopen( $filePath, "r" ); + if ( $handle === false ) { $this->markTestIncomplete( "could not open file: $filePath" ); } - while (!feof ($handle)) { + while ( !feof( $handle ) ) { // Get the current chunk $chunkData = @fread( $handle, $chunkSize ); // Upload the current chunk into the $_FILE object: $this->fakeUploadChunk( 'chunk', 'blob', $mimeType, $chunkData ); - + // Check for chunkSessionKey - if( !$chunkSessionKey ){ + if ( !$chunkSessionKey ) { // Upload fist chunk ( and get the session key ) try { - list( $result, $request, $session ) = $this->doApiRequestWithToken( $params, $session, + list( $result, , $session ) = $this->doApiRequestWithToken( $params, $session, self::$users['uploader']->user ); } catch ( UsageException $e ) { $this->markTestIncomplete( $e->getMessage() ); } - // Make sure we got a valid chunk continue: + // Make sure we got a valid chunk continue: $this->assertTrue( isset( $result['upload'] ) ); $this->assertTrue( isset( $result['upload']['filekey'] ) ); - // If we don't get a session key mark test incomplete. - if( ! isset( $result['upload']['filekey'] ) ){ + // If we don't get a session key mark test incomplete. + if ( !isset( $result['upload']['filekey'] ) ) { $this->markTestIncomplete( "no filekey provided" ); } $chunkSessionKey = $result['upload']['filekey']; @@ -513,17 +505,17 @@ class ApiUploadTest extends ApiTestCaseUpload { $this->assertEquals( $resultOffset, $params['offset'] ); // Upload current chunk try { - list( $result, $request, $session ) = $this->doApiRequestWithToken( $params, $session, + list( $result, , $session ) = $this->doApiRequestWithToken( $params, $session, self::$users['uploader']->user ); } catch ( UsageException $e ) { $this->markTestIncomplete( $e->getMessage() ); } - // Make sure we got a valid chunk continue: + // Make sure we got a valid chunk continue: $this->assertTrue( isset( $result['upload'] ) ); $this->assertTrue( isset( $result['upload']['filekey'] ) ); - - // Check if we were on the last chunk: - if( $params['offset'] + $chunkSize >= $fileSize ){ + + // Check if we were on the last chunk: + if ( $params['offset'] + $chunkSize >= $fileSize ) { $this->assertEquals( 'Success', $result['upload']['result'] ); break; } else { @@ -531,11 +523,11 @@ class ApiUploadTest extends ApiTestCaseUpload { // update $resultOffset $resultOffset = $result['upload']['offset']; } - } - fclose ($handle); - + } + fclose( $handle ); + // Check that we got a valid file result: - wfDebug( __METHOD__ . " hohoh filesize {$fileSize} info {$result['upload']['imageinfo']['size']}\n\n"); + wfDebug( __METHOD__ . " hohoh filesize {$fileSize} info {$result['upload']['imageinfo']['size']}\n\n" ); $this->assertEquals( $fileSize, $result['upload']['imageinfo']['size'] ); $this->assertEquals( $mimeType, $result['upload']['imageinfo']['mime'] ); $this->assertTrue( isset( $result['upload']['filekey'] ) ); @@ -547,12 +539,12 @@ class ApiUploadTest extends ApiTestCaseUpload { 'filekey' => $filekey, 'filename' => $fileName, 'comment' => 'dummy comment', - 'text' => "This is the page text for $fileName, altered", + 'text' => "This is the page text for $fileName, altered", ); $this->clearFakeUploads(); $exception = false; try { - list( $result, $request, $session ) = $this->doApiRequestWithToken( $params, $session, + list( $result ) = $this->doApiRequestWithToken( $params, $session, self::$users['uploader']->user ); } catch ( UsageException $e ) { $exception = true; @@ -563,7 +555,7 @@ class ApiUploadTest extends ApiTestCaseUpload { // clean up $this->deleteFileByFilename( $fileName ); - // don't remove downloaded temporary file for fast subquent tests. + // don't remove downloaded temporary file for fast subquent tests. //unlink( $filePath ); } } diff --git a/tests/phpunit/includes/api/ApiWatchTest.php b/tests/phpunit/includes/api/ApiWatchTest.php index d2e98152..028ea9ff 100644 --- a/tests/phpunit/includes/api/ApiWatchTest.php +++ b/tests/phpunit/includes/api/ApiWatchTest.php @@ -3,35 +3,29 @@ /** * @group API * @group Database + * @group medium * @todo This test suite is severly broken and need a full review */ class ApiWatchTest extends ApiTestCase { - - function setUp() { + protected function setUp() { parent::setUp(); $this->doLogin(); } function getTokens() { - $data = $this->getTokenList( self::$users['sysop'] ); - - $keys = array_keys( $data[0]['query']['pages'] ); - $key = array_pop( $keys ); - $pageinfo = $data[0]['query']['pages'][$key]; - - return $pageinfo; + return $this->getTokenList( self::$users['sysop'] ); } /** */ - function testWatchEdit() { - $pageinfo = $this->getTokens(); + public function testWatchEdit() { + $tokens = $this->getTokens(); $data = $this->doApiRequest( array( 'action' => 'edit', - 'title' => 'UTPage', + 'title' => 'Help:UTPage', // Help namespace is hopefully wikitext 'text' => 'new text', - 'token' => $pageinfo['edittoken'], + 'token' => $tokens['edittoken'], 'watchlist' => 'watch' ) ); $this->assertArrayHasKey( 'edit', $data[0] ); $this->assertArrayHasKey( 'result', $data[0]['edit'] ); @@ -43,9 +37,8 @@ class ApiWatchTest extends ApiTestCase { /** * @depends testWatchEdit */ - function testWatchClear() { - - $pageinfo = $this->getTokens(); + public function testWatchClear() { + $tokens = $this->getTokens(); $data = $this->doApiRequest( array( 'action' => 'query', @@ -59,7 +52,7 @@ class ApiWatchTest extends ApiTestCase { 'action' => 'watch', 'title' => $page['title'], 'unwatch' => true, - 'token' => $pageinfo['watchtoken'] ) ); + 'token' => $tokens['watchtoken'] ) ); } } $data = $this->doApiRequest( array( @@ -74,14 +67,13 @@ class ApiWatchTest extends ApiTestCase { /** */ - function testWatchProtect() { - - $pageinfo = $this->getTokens(); + public function testWatchProtect() { + $tokens = $this->getTokens(); $data = $this->doApiRequest( array( 'action' => 'protect', - 'token' => $pageinfo['protecttoken'], - 'title' => 'UTPage', + 'token' => $tokens['protecttoken'], + 'title' => 'Help:UTPage', 'protections' => 'edit=sysop', 'watchlist' => 'unwatch' ) ); @@ -93,18 +85,17 @@ class ApiWatchTest extends ApiTestCase { /** */ - function testGetRollbackToken() { + public function testGetRollbackToken() { + $this->getTokens(); - $pageinfo = $this->getTokens(); - - if ( !Title::newFromText( 'UTPage' )->exists() ) { - $this->markTestSkipped( "The article [[UTPage]] does not exist" ); //TODO: just create it? + if ( !Title::newFromText( 'Help:UTPage' )->exists() ) { + $this->markTestSkipped( "The article [[Help:UTPage]] does not exist" ); //TODO: just create it? } $data = $this->doApiRequest( array( 'action' => 'query', 'prop' => 'revisions', - 'titles' => 'UTPage', + 'titles' => 'Help:UTPage', 'rvtoken' => 'rollback' ) ); $this->assertArrayHasKey( 'query', $data[0] ); @@ -113,7 +104,7 @@ class ApiWatchTest extends ApiTestCase { $key = array_pop( $keys ); if ( isset( $data[0]['query']['pages'][$key]['missing'] ) ) { - $this->markTestSkipped( "Target page (UTPage) doesn't exist" ); + $this->markTestSkipped( "Target page (Help:UTPage) doesn't exist" ); } $this->assertArrayHasKey( 'pageid', $data[0]['query']['pages'][$key] ); @@ -130,7 +121,7 @@ class ApiWatchTest extends ApiTestCase { * * @depends testGetRollbackToken */ - function testWatchRollback( $data ) { + public function testWatchRollback( $data ) { $keys = array_keys( $data[0]['query']['pages'] ); $key = array_pop( $keys ); $pageinfo = $data[0]['query']['pages'][$key]; @@ -139,38 +130,19 @@ class ApiWatchTest extends ApiTestCase { try { $data = $this->doApiRequest( array( 'action' => 'rollback', - 'title' => 'UTPage', + 'title' => 'Help:UTPage', 'user' => $revinfo['user'], 'token' => $pageinfo['rollbacktoken'], 'watchlist' => 'watch' ) ); $this->assertArrayHasKey( 'rollback', $data[0] ); $this->assertArrayHasKey( 'title', $data[0]['rollback'] ); - } catch( UsageException $ue ) { - if( $ue->getCodeString() == 'onlyauthor' ) { - $this->markTestIncomplete( "Only one author to 'UTPage', cannot test rollback" ); + } catch ( UsageException $ue ) { + if ( $ue->getCodeString() == 'onlyauthor' ) { + $this->markTestIncomplete( "Only one author to 'Help:UTPage', cannot test rollback" ); } else { $this->fail( "Received error '" . $ue->getCodeString() . "'" ); } } } - - /** - */ - function testWatchDelete() { - $pageinfo = $this->getTokens(); - - $data = $this->doApiRequest( array( - 'action' => 'delete', - 'token' => $pageinfo['deletetoken'], - 'title' => 'UTPage' ) ); - $this->assertArrayHasKey( 'delete', $data[0] ); - $this->assertArrayHasKey( 'title', $data[0]['delete'] ); - - $data = $this->doApiRequest( array( - 'action' => 'query', - 'list' => 'watchlist' ) ); - - $this->markTestIncomplete( 'This test needs to verify the deleted article was added to the users watchlist' ); - } } diff --git a/tests/phpunit/includes/api/PrefixUniquenessTest.php b/tests/phpunit/includes/api/PrefixUniquenessTest.php index 69b01ea7..d9be85e3 100644 --- a/tests/phpunit/includes/api/PrefixUniquenessTest.php +++ b/tests/phpunit/includes/api/PrefixUniquenessTest.php @@ -1,14 +1,15 @@ <?php /** - * Checks that all API query modules, core and extensions, have unique prefixes + * Checks that all API query modules, core and extensions, have unique prefixes. + * * @group API */ class PrefixUniquenessTest extends MediaWikiTestCase { public function testPrefixes() { $main = new ApiMain( new FauxRequest() ); $query = new ApiQuery( $main, 'foo', 'bar' ); - $modules = $query->getModules(); + $modules = $query->getModuleManager()->getNamesWithClasses(); $prefixes = array(); foreach ( $modules as $name => $class ) { diff --git a/tests/phpunit/includes/api/RandomImageGenerator.php b/tests/phpunit/includes/api/RandomImageGenerator.php index 8b6a3849..59756b21 100644 --- a/tests/phpunit/includes/api/RandomImageGenerator.php +++ b/tests/phpunit/includes/api/RandomImageGenerator.php @@ -27,14 +27,14 @@ class RandomImageGenerator { private $dictionaryFile; - private $minWidth = 400 ; - private $maxWidth = 800 ; - private $minHeight = 400 ; - private $maxHeight = 800 ; - private $shapesToDraw = 5 ; + private $minWidth = 400; + private $maxWidth = 800; + private $minHeight = 400; + private $maxHeight = 800; + private $shapesToDraw = 5; /** - * Orientations: 0th row, 0th column, EXIF orientation code, rotation 2x2 matrix that is opposite of orientation + * Orientations: 0th row, 0th column, Exif orientation code, rotation 2x2 matrix that is opposite of orientation * n.b. we do not handle the 'flipped' orientations, which is why there is no entry for 2, 4, 5, or 7. Those * seem to be rare in real images anyway * (we also would need a non-symmetric shape for the images to test those, like a letter F) @@ -76,11 +76,13 @@ class RandomImageGenerator { // find the dictionary file, to generate random names if ( !isset( $this->dictionaryFile ) ) { - foreach ( array( + foreach ( + array( '/usr/share/dict/words', '/usr/dict/words', - __DIR__ . '/words.txt' ) - as $dictionaryFile ) { + __DIR__ . '/words.txt' + ) as $dictionaryFile + ) { if ( is_file( $dictionaryFile ) and is_readable( $dictionaryFile ) ) { $this->dictionaryFile = $dictionaryFile; break; @@ -103,9 +105,10 @@ class RandomImageGenerator { function writeImages( $number, $format = 'jpg', $dir = null ) { $filenames = $this->getRandomFilenames( $number, $format, $dir ); $imageWriteMethod = $this->getImageWriteMethod( $format ); - foreach( $filenames as $filename ) { + foreach ( $filenames as $filename ) { $this->{$imageWriteMethod}( $this->getImageSpec(), $format, $filename ); } + return $filenames; } @@ -144,7 +147,7 @@ class RandomImageGenerator { $dir = getcwd(); } $filenames = array(); - foreach( $this->getRandomWordPairs( $number ) as $pair ) { + foreach ( $this->getRandomWordPairs( $number ) as $pair ) { $basename = $pair[0] . '_' . $pair[1]; if ( !is_null( $extension ) ) { $basename .= '.' . $extension; @@ -154,7 +157,6 @@ class RandomImageGenerator { } return $filenames; - } @@ -181,20 +183,19 @@ class RandomImageGenerator { } $originX = mt_rand( -1 * $radius, $spec['width'] + $radius ); $originY = mt_rand( -1 * $radius, $spec['height'] + $radius ); - $angle = mt_rand( 0, ( 3.141592/2 ) * $radius ) / $radius; + $angle = mt_rand( 0, ( 3.141592 / 2 ) * $radius ) / $radius; $legDeltaX = round( $radius * sin( $angle ) ); $legDeltaY = round( $radius * cos( $angle ) ); $draw = array(); $draw['fill'] = $this->getRandomColor(); $draw['shape'] = array( - array( 'x' => $originX, 'y' => $originY - $radius ), - array( 'x' => $originX + $legDeltaX, 'y' => $originY + $legDeltaY ), - array( 'x' => $originX - $legDeltaX, 'y' => $originY + $legDeltaY ), - array( 'x' => $originX, 'y' => $originY - $radius ) + array( 'x' => $originX, 'y' => $originY - $radius ), + array( 'x' => $originX + $legDeltaX, 'y' => $originY + $legDeltaY ), + array( 'x' => $originX - $legDeltaX, 'y' => $originY + $legDeltaY ), + array( 'x' => $originX, 'y' => $originY - $radius ) ); $draws[] = $draw; - } $spec['draws'] = $draws; @@ -214,6 +215,7 @@ class RandomImageGenerator { foreach ( $shape as $point ) { $points[] = $point['x'] . ',' . $point['y']; } + return join( " ", $points ); } @@ -235,12 +237,13 @@ class RandomImageGenerator { $shape = $g->addChild( 'polygon' ); $shape->addAttribute( 'fill', $drawSpec['fill'] ); $shape->addAttribute( 'points', self::shapePointsToString( $drawSpec['shape'] ) ); - }; - if ( ! $fh = fopen( $filename, 'w' ) ) { + } + + if ( !$fh = fopen( $filename, 'w' ) ) { throw new Exception( "couldn't open $filename for writing" ); } fwrite( $fh, $svg->asXML() ); - if ( !fclose($fh) ) { + if ( !fclose( $fh ) ) { throw new Exception( "couldn't close $filename" ); } } @@ -262,7 +265,7 @@ class RandomImageGenerator { */ $orientation = self::$orientations[0]; // default is normal orientation if ( $format == 'jpg' ) { - $orientation = self::$orientations[ array_rand( self::$orientations ) ]; + $orientation = self::$orientations[array_rand( self::$orientations )]; $spec = self::rotateImageSpec( $spec, $orientation['counterRotation'] ); } @@ -301,7 +304,7 @@ class RandomImageGenerator { /** * Given an image specification, produce rotated version - * This is used when simulating a rotated image capture with EXIF orientation + * This is used when simulating a rotated image capture with Exif orientation * @param $spec Object returned by getImageSpec * @param $matrix 2x2 transformation matrix * @return transformed Spec @@ -321,12 +324,12 @@ class RandomImageGenerator { $tSpec['height'] = abs( $dims['y'] ); $tSpec['fill'] = $spec['fill']; $tSpec['draws'] = array(); - foreach( $spec['draws'] as $draw ) { + foreach ( $spec['draws'] as $draw ) { $tDraw = array( 'fill' => $draw['fill'], 'shape' => array() ); - foreach( $draw['shape'] as $point ) { + foreach ( $draw['shape'] as $point ) { $tPoint = self::matrixMultiply2x2( $matrix, $point['x'], $point['y'] ); $tPoint['x'] += $correctionX; $tPoint['y'] += $correctionY; @@ -334,6 +337,7 @@ class RandomImageGenerator { } $tSpec['draws'][] = $tDraw; } + return $tSpec; } @@ -357,7 +361,7 @@ class RandomImageGenerator { * * Sample command line: * $ convert -size 100x60 xc:rgb(90,87,45) \ - * -draw 'fill rgb(12,34,56) polygon 41,39 44,57 50,57 41,39' \ + * -draw 'fill rgb(12,34,56) polygon 41,39 44,57 50,57 41,39' \ * -draw 'fill rgb(99,123,231) circle 59,39 56,57' \ * -draw 'fill rgb(240,12,32) circle 50,21 50,3' filename.png * @@ -370,7 +374,7 @@ class RandomImageGenerator { $args = array(); $args[] = "-size " . wfEscapeShellArg( $spec['width'] . 'x' . $spec['height'] ); $args[] = wfEscapeShellArg( "xc:" . $spec['fill'] ); - foreach( $spec['draws'] as $draw ) { + foreach ( $spec['draws'] as $draw ) { $fill = $draw['fill']; $polygon = self::shapePointsToString( $draw['shape'] ); $drawCommand = "fill $fill polygon $polygon"; @@ -381,6 +385,7 @@ class RandomImageGenerator { $command = wfEscapeShellArg( $wgImageMagickConvertCommand ) . " " . implode( " ", $args ); $retval = null; wfShellExec( $command, $retval ); + return ( $retval === 0 ); } @@ -391,10 +396,11 @@ class RandomImageGenerator { */ public function getRandomColor() { $components = array(); - for ($i = 0; $i <= 2; $i++ ) { + for ( $i = 0; $i <= 2; $i++ ) { $components[] = mt_rand( 0, 255 ); } - return 'rgb(' . join(', ', $components) . ')'; + + return 'rgb(' . join( ', ', $components ) . ')'; } /** @@ -408,13 +414,13 @@ class RandomImageGenerator { // construct pairs of words $pairs = array(); $count = count( $lines ); - for( $i = 0; $i < $count; $i += 2 ) { - $pairs[] = array( $lines[$i], $lines[$i+1] ); + for ( $i = 0; $i < $count; $i += 2 ) { + $pairs[] = array( $lines[$i], $lines[$i + 1] ); } + return $pairs; } - /** * Return N random lines from a file * @@ -438,17 +444,17 @@ class RandomImageGenerator { */ $fh = fopen( $filepath, "r" ); if ( !$fh ) { - throw new Exception( "couldn't open $filepath" ); + throw new Exception( "couldn't open $filepath" ); } $line_number = 0; $max_index = $number_desired - 1; - while( !feof( $fh ) ) { + while ( !feof( $fh ) ) { $line = fgets( $fh ); if ( $line !== false ) { $line_number++; $line = trim( $line ); if ( mt_rand( 0, $line_number ) <= $max_index ) { - $lines[ mt_rand( 0, $max_index ) ] = $line; + $lines[mt_rand( 0, $max_index )] = $line; } } } @@ -459,5 +465,4 @@ class RandomImageGenerator { return $lines; } - } diff --git a/tests/phpunit/includes/api/format/ApiFormatPhpTest.php b/tests/phpunit/includes/api/format/ApiFormatPhpTest.php index 8209f591..a0bbb2dc 100644 --- a/tests/phpunit/includes/api/format/ApiFormatPhpTest.php +++ b/tests/phpunit/includes/api/format/ApiFormatPhpTest.php @@ -3,17 +3,15 @@ /** * @group API * @group Database + * @group medium */ class ApiFormatPhpTest extends ApiFormatTestBase { - function testValidPhpSyntax() { - + public function testValidPhpSyntax() { + $data = $this->apiRequest( 'php', array( 'action' => 'query', 'meta' => 'siteinfo' ) ); - + $this->assertInternalType( 'array', unserialize( $data ) ); - $this->assertGreaterThan( 0, count( (array) $data ) ); - - + $this->assertGreaterThan( 0, count( (array)$data ) ); } - } diff --git a/tests/phpunit/includes/api/format/ApiFormatTestBase.php b/tests/phpunit/includes/api/format/ApiFormatTestBase.php index a0b7b020..153f2cf4 100644 --- a/tests/phpunit/includes/api/format/ApiFormatTestBase.php +++ b/tests/phpunit/includes/api/format/ApiFormatTestBase.php @@ -5,7 +5,7 @@ abstract class ApiFormatTestBase extends ApiTestCase { $data = parent::doApiRequest( $params, $data, true ); $module = $data[3]; - + $printer = $module->createPrinterByName( $format ); $printer->setUnescapeAmps( false ); @@ -14,7 +14,7 @@ abstract class ApiFormatTestBase extends ApiTestCase { ob_start(); $printer->execute(); $out = ob_get_clean(); - + $printer->closePrinter(); return $out; diff --git a/tests/phpunit/includes/api/generateRandomImages.php b/tests/phpunit/includes/api/generateRandomImages.php index ee345623..87f5c4c0 100644 --- a/tests/phpunit/includes/api/generateRandomImages.php +++ b/tests/phpunit/includes/api/generateRandomImages.php @@ -5,12 +5,9 @@ * @file */ -// Evaluate the include path relative to this file -$IP = dirname( dirname( dirname( dirname( __DIR__ ) ) ) ); - // Start up MediaWiki in command-line mode -require_once( "$IP/maintenance/Maintenance.php" ); -require( __DIR__ . "/RandomImageGenerator.php" ); +require_once __DIR__ . "/../../../../maintenance/Maintenance.php"; +require __DIR__ . "/RandomImageGenerator.php"; class GenerateRandomImages extends Maintenance { @@ -46,6 +43,4 @@ class GenerateRandomImages extends Maintenance { } $maintClass = 'GenerateRandomImages'; -require( RUN_MAINTENANCE_IF_MAIN ); - - +require RUN_MAINTENANCE_IF_MAIN; diff --git a/tests/phpunit/includes/api/query/ApiQueryBasicTest.php b/tests/phpunit/includes/api/query/ApiQueryBasicTest.php new file mode 100644 index 00000000..1a2aa832 --- /dev/null +++ b/tests/phpunit/includes/api/query/ApiQueryBasicTest.php @@ -0,0 +1,395 @@ +<?php +/** + * + * + * Created on Feb 6, 2013 + * + * Copyright © 2013 Yuri Astrakhan "<Firstname><Lastname>@gmail.com" + * + * 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 + * + * @file + */ + +require_once 'ApiQueryTestBase.php'; + +/** These tests validate basic functionality of the api query module + * + * @group API + * @group Database + * @group medium + */ +class ApiQueryBasicTest extends ApiQueryTestBase { + /** + * Create a set of pages. These must not change, otherwise the tests might give wrong results. + * @see MediaWikiTestCase::addDBData() + */ + function addDBData() { + try { + if ( Title::newFromText( 'AQBT-All' )->exists() ) { + return; + } + + // Ordering is important, as it will be returned in the same order as stored in the index + $this->editPage( 'AQBT-All', '[[Category:AQBT-Cat]] [[AQBT-Links]] {{AQBT-T}}' ); + $this->editPage( 'AQBT-Categories', '[[Category:AQBT-Cat]]' ); + $this->editPage( 'AQBT-Links', '[[AQBT-All]] [[AQBT-Categories]] [[AQBT-Templates]]' ); + $this->editPage( 'AQBT-Templates', '{{AQBT-T}}' ); + $this->editPage( 'AQBT-T', 'Content', '', NS_TEMPLATE ); + + // Refresh due to the bug with listing transclusions as links if they don't exist + $this->editPage( 'AQBT-All', '[[Category:AQBT-Cat]] [[AQBT-Links]] {{AQBT-T}}' ); + $this->editPage( 'AQBT-Templates', '{{AQBT-T}}' ); + } catch ( Exception $e ) { + $this->exceptionFromAddDBData = $e; + } + } + + private static $links = array( + array( 'prop' => 'links', 'titles' => 'AQBT-All' ), + array( 'pages' => array( + '1' => array( + 'pageid' => 1, + 'ns' => 0, + 'title' => 'AQBT-All', + 'links' => array( + array( 'ns' => 0, 'title' => 'AQBT-Links' ), + ) + ) + ) ) + ); + + private static $templates = array( + array( 'prop' => 'templates', 'titles' => 'AQBT-All' ), + array( 'pages' => array( + '1' => array( + 'pageid' => 1, + 'ns' => 0, + 'title' => 'AQBT-All', + 'templates' => array( + array( 'ns' => 10, 'title' => 'Template:AQBT-T' ), + ) + ) + ) ) + ); + + private static $categories = array( + array( 'prop' => 'categories', 'titles' => 'AQBT-All' ), + array( 'pages' => array( + '1' => array( + 'pageid' => 1, + 'ns' => 0, + 'title' => 'AQBT-All', + 'categories' => array( + array( 'ns' => 14, 'title' => 'Category:AQBT-Cat' ), + ) + ) + ) ) + ); + + private static $allpages = array( + array( 'list' => 'allpages', 'apprefix' => 'AQBT-' ), + array( 'allpages' => array( + array( 'pageid' => 1, 'ns' => 0, 'title' => 'AQBT-All' ), + array( 'pageid' => 2, 'ns' => 0, 'title' => 'AQBT-Categories' ), + array( 'pageid' => 3, 'ns' => 0, 'title' => 'AQBT-Links' ), + array( 'pageid' => 4, 'ns' => 0, 'title' => 'AQBT-Templates' ), + ) ) + ); + + private static $alllinks = array( + array( 'list' => 'alllinks', 'alprefix' => 'AQBT-' ), + array( 'alllinks' => array( + array( 'ns' => 0, 'title' => 'AQBT-All' ), + array( 'ns' => 0, 'title' => 'AQBT-Categories' ), + array( 'ns' => 0, 'title' => 'AQBT-Links' ), + array( 'ns' => 0, 'title' => 'AQBT-Templates' ), + ) ) + ); + + private static $alltransclusions = array( + array( 'list' => 'alltransclusions', 'atprefix' => 'AQBT-' ), + array( 'alltransclusions' => array( + array( 'ns' => 10, 'title' => 'Template:AQBT-T' ), + array( 'ns' => 10, 'title' => 'Template:AQBT-T' ), + ) ) + ); + + private static $allcategories = array( + array( 'list' => 'allcategories', 'acprefix' => 'AQBT-' ), + array( 'allcategories' => array( + array( '*' => 'AQBT-Cat' ), + ) ) + ); + + private static $backlinks = array( + array( 'list' => 'backlinks', 'bltitle' => 'AQBT-Links' ), + array( 'backlinks' => array( + array( 'pageid' => 1, 'ns' => 0, 'title' => 'AQBT-All' ), + ) ) + ); + + private static $embeddedin = array( + array( 'list' => 'embeddedin', 'eititle' => 'Template:AQBT-T' ), + array( 'embeddedin' => array( + array( 'pageid' => 1, 'ns' => 0, 'title' => 'AQBT-All' ), + array( 'pageid' => 4, 'ns' => 0, 'title' => 'AQBT-Templates' ), + ) ) + ); + + private static $categorymembers = array( + array( 'list' => 'categorymembers', 'cmtitle' => 'Category:AQBT-Cat' ), + array( 'categorymembers' => array( + array( 'pageid' => 1, 'ns' => 0, 'title' => 'AQBT-All' ), + array( 'pageid' => 2, 'ns' => 0, 'title' => 'AQBT-Categories' ), + ) ) + ); + + private static $generatorAllpages = array( + array( 'generator' => 'allpages', 'gapprefix' => 'AQBT-' ), + array( 'pages' => array( + '1' => array( + 'pageid' => 1, + 'ns' => 0, + 'title' => 'AQBT-All' ), + '2' => array( + 'pageid' => 2, + 'ns' => 0, + 'title' => 'AQBT-Categories' ), + '3' => array( + 'pageid' => 3, + 'ns' => 0, + 'title' => 'AQBT-Links' ), + '4' => array( + 'pageid' => 4, + 'ns' => 0, + 'title' => 'AQBT-Templates' ), + ) ) + ); + + private static $generatorLinks = array( + array( 'generator' => 'links', 'titles' => 'AQBT-Links' ), + array( 'pages' => array( + '1' => array( + 'pageid' => 1, + 'ns' => 0, + 'title' => 'AQBT-All' ), + '2' => array( + 'pageid' => 2, + 'ns' => 0, + 'title' => 'AQBT-Categories' ), + '4' => array( + 'pageid' => 4, + 'ns' => 0, + 'title' => 'AQBT-Templates' ), + ) ) + ); + + private static $generatorLinksPropLinks = array( + array( 'prop' => 'links' ), + array( 'pages' => array( + '1' => array( 'links' => array( + array( 'ns' => 0, 'title' => 'AQBT-Links' ), + ) ) + ) ) + ); + + private static $generatorLinksPropTemplates = array( + array( 'prop' => 'templates' ), + array( 'pages' => array( + '1' => array( 'templates' => array( + array( 'ns' => 10, 'title' => 'Template:AQBT-T' ) ) ), + '4' => array( 'templates' => array( + array( 'ns' => 10, 'title' => 'Template:AQBT-T' ) ) ), + ) ) + ); + + /** + * Test basic props + */ + public function testProps() { + $this->check( self::$links ); + $this->check( self::$templates ); + $this->check( self::$categories ); + } + + /** + * Test basic lists + */ + public function testLists() { + $this->check( self::$allpages ); + $this->check( self::$alllinks ); + $this->check( self::$alltransclusions ); + // This test is temporarily disabled until a sqlite bug is fixed + // $this->check( self::$allcategories ); + $this->check( self::$backlinks ); + $this->check( self::$embeddedin ); + $this->check( self::$categorymembers ); + } + + /** + * Test basic lists + */ + public function testAllTogether() { + + // All props together + $this->check( $this->merge( + self::$links, + self::$templates, + self::$categories + ) ); + + // All lists together + $this->check( $this->merge( + self::$allpages, + self::$alllinks, + self::$alltransclusions, + // This test is temporarily disabled until a sqlite bug is fixed + // self::$allcategories, + self::$backlinks, + self::$embeddedin, + self::$categorymembers + ) ); + + // All props+lists together + $this->check( $this->merge( + self::$links, + self::$templates, + self::$categories, + self::$allpages, + self::$alllinks, + self::$alltransclusions, + // This test is temporarily disabled until a sqlite bug is fixed + // self::$allcategories, + self::$backlinks, + self::$embeddedin, + self::$categorymembers + ) ); + } + + /** + * Test basic lists + */ + public function testGenerator() { + // generator=allpages + $this->check( self::$generatorAllpages ); + // generator=allpages & list=allpages + $this->check( $this->merge( + self::$generatorAllpages, + self::$allpages ) ); + // generator=links + $this->check( self::$generatorLinks ); + // generator=links & prop=links + $this->check( $this->merge( + self::$generatorLinks, + self::$generatorLinksPropLinks ) ); + // generator=links & prop=templates + $this->check( $this->merge( + self::$generatorLinks, + self::$generatorLinksPropTemplates ) ); + // generator=links & prop=links|templates + $this->check( $this->merge( + self::$generatorLinks, + self::$generatorLinksPropLinks, + self::$generatorLinksPropTemplates ) ); + // generator=links & prop=links|templates & list=allpages|... + $this->check( $this->merge( + self::$generatorLinks, + self::$generatorLinksPropLinks, + self::$generatorLinksPropTemplates, + self::$allpages, + self::$alllinks, + self::$alltransclusions, + // This test is temporarily disabled until a sqlite bug is fixed + // self::$allcategories, + self::$backlinks, + self::$embeddedin, + self::$categorymembers ) ); + } + + /** + * Test bug 51821 + */ + public function testGeneratorRedirects() { + $this->editPage( 'AQBT-Target', 'test' ); + $this->editPage( 'AQBT-Redir', '#REDIRECT [[AQBT-Target]]' ); + $this->check( array( + array( 'generator' => 'backlinks', 'gbltitle' => 'AQBT-Target', 'redirects' => '1' ), + array( + 'redirects' => array( + array( + 'from' => 'AQBT-Redir', + 'to' => 'AQBT-Target', + ) + ), + 'pages' => array( + '6' => array( + 'pageid' => 6, + 'ns' => 0, + 'title' => 'AQBT-Target', + ) + ), + ) + ) ); + } + + /** + * Recursively merges the expected values in the $item into the $all + */ + private function mergeExpected( &$all, $item ) { + foreach ( $item as $k => $v ) { + if ( array_key_exists( $k, $all ) ) { + if ( is_array( $all[$k] ) ) { + $this->mergeExpected( $all[$k], $v ); + } else { + $this->assertEquals( $all[$k], $v ); + } + } else { + $all[$k] = $v; + } + } + } + + /** + * Recursively compare arrays, ignoring mismatches in numeric key and pageids. + * @param $expected array expected values + * @param $result array returned values + */ + private function assertQueryResults( $expected, $result ) { + reset( $expected ); + reset( $result ); + while ( true ) { + $e = each( $expected ); + $r = each( $result ); + // If either of the arrays is shorter, abort. If both are done, success. + $this->assertEquals( (bool)$e, (bool)$r ); + if ( !$e ) { + break; // done + } + // continue only if keys are identical or both keys are numeric + $this->assertTrue( $e['key'] === $r['key'] || ( is_numeric( $e['key'] ) && is_numeric( $r['key'] ) ) ); + // don't compare pageids + if ( $e['key'] !== 'pageid' ) { + // If values are arrays, compare recursively, otherwise compare with === + if ( is_array( $e['value'] ) && is_array( $r['value'] ) ) { + $this->assertQueryResults( $e['value'], $r['value'] ); + } else { + $this->assertEquals( $e['value'], $r['value'] ); + } + } + } + } +} diff --git a/tests/phpunit/includes/api/query/ApiQueryContinue2Test.php b/tests/phpunit/includes/api/query/ApiQueryContinue2Test.php new file mode 100644 index 00000000..4d5ddbae --- /dev/null +++ b/tests/phpunit/includes/api/query/ApiQueryContinue2Test.php @@ -0,0 +1,68 @@ +<?php +/** + * Copyright © 2013 Yuri Astrakhan "<Firstname><Lastname>@gmail.com" + * + * 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 3 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 + */ + +require_once 'ApiQueryContinueTestBase.php'; + +/** + * @group API + * @group Database + * @group medium + */ +class ApiQueryContinue2Test extends ApiQueryContinueTestBase { + /** + * Create a set of pages. These must not change, otherwise the tests might give wrong results. + * @see MediaWikiTestCase::addDBData() + */ + function addDBData() { + try { + $this->editPage( 'AQCT73462-A', '**AQCT73462-A** [[AQCT73462-B]] [[AQCT73462-C]]' ); + $this->editPage( 'AQCT73462-B', '[[AQCT73462-A]] **AQCT73462-B** [[AQCT73462-C]]' ); + $this->editPage( 'AQCT73462-C', '[[AQCT73462-A]] [[AQCT73462-B]] **AQCT73462-C**' ); + $this->editPage( 'AQCT73462-A', '**AQCT73462-A** [[AQCT73462-B]] [[AQCT73462-C]]' ); + $this->editPage( 'AQCT73462-B', '[[AQCT73462-A]] **AQCT73462-B** [[AQCT73462-C]]' ); + $this->editPage( 'AQCT73462-C', '[[AQCT73462-A]] [[AQCT73462-B]] **AQCT73462-C**' ); + } catch ( Exception $e ) { + $this->exceptionFromAddDBData = $e; + } + } + + /** + * @medium + */ + public function testA() { + $this->mVerbose = false; + $mk = function ( $g, $p, $gDir ) { + return array( + 'generator' => 'allpages', + 'gapprefix' => 'AQCT73462-', + 'prop' => 'links', + 'gaplimit' => "$g", + 'pllimit' => "$p", + 'gapdir' => $gDir ? "ascending" : "descending", + ); + }; + // generator + 1 prop + 1 list + $data = $this->query( $mk( 99, 99, true ), 1, 'g1p', false ); + $this->checkC( $data, $mk( 1, 1, true ), 6, 'g1p-11t' ); + $this->checkC( $data, $mk( 2, 2, true ), 3, 'g1p-22t' ); + $this->checkC( $data, $mk( 1, 1, false ), 6, 'g1p-11f' ); + $this->checkC( $data, $mk( 2, 2, false ), 3, 'g1p-22f' ); + } +} diff --git a/tests/phpunit/includes/api/query/ApiQueryContinueTest.php b/tests/phpunit/includes/api/query/ApiQueryContinueTest.php new file mode 100644 index 00000000..f494e9ca --- /dev/null +++ b/tests/phpunit/includes/api/query/ApiQueryContinueTest.php @@ -0,0 +1,313 @@ +<?php +/** + * Copyright © 2013 Yuri Astrakhan "<Firstname><Lastname>@gmail.com" + * + * 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 + */ + +require_once 'ApiQueryContinueTestBase.php'; + +/** + * These tests validate the new continue functionality of the api query module by + * doing multiple requests with varying parameters, merging the results, and checking + * that the result matches the full data received in one no-limits call. + * + * @group API + * @group Database + * @group medium + */ +class ApiQueryContinueTest extends ApiQueryContinueTestBase { + /** + * Create a set of pages. These must not change, otherwise the tests might give wrong results. + * @see MediaWikiTestCase::addDBData() + */ + function addDBData() { + try { + $this->editPage( 'Template:AQCT-T1', '**Template:AQCT-T1**' ); + $this->editPage( 'Template:AQCT-T2', '**Template:AQCT-T2**' ); + $this->editPage( 'Template:AQCT-T3', '**Template:AQCT-T3**' ); + $this->editPage( 'Template:AQCT-T4', '**Template:AQCT-T4**' ); + $this->editPage( 'Template:AQCT-T5', '**Template:AQCT-T5**' ); + + $this->editPage( 'AQCT-1', '**AQCT-1** {{AQCT-T2}} {{AQCT-T3}} {{AQCT-T4}} {{AQCT-T5}}' ); + $this->editPage( 'AQCT-2', '[[AQCT-1]] **AQCT-2** {{AQCT-T3}} {{AQCT-T4}} {{AQCT-T5}}' ); + $this->editPage( 'AQCT-3', '[[AQCT-1]] [[AQCT-2]] **AQCT-3** {{AQCT-T4}} {{AQCT-T5}}' ); + $this->editPage( 'AQCT-4', '[[AQCT-1]] [[AQCT-2]] [[AQCT-3]] **AQCT-4** {{AQCT-T5}}' ); + $this->editPage( 'AQCT-5', '[[AQCT-1]] [[AQCT-2]] [[AQCT-3]] [[AQCT-4]] **AQCT-5**' ); + } catch ( Exception $e ) { + $this->exceptionFromAddDBData = $e; + } + } + + /** + * Test smart continue - list=allpages + * @medium + */ + public function test1List() { + $this->mVerbose = false; + $mk = function ( $l ) { + return array( + 'list' => 'allpages', + 'apprefix' => 'AQCT-', + 'aplimit' => "$l", + ); + }; + $data = $this->query( $mk( 99 ), 1, '1L', false ); + + // 1 list + $this->checkC( $data, $mk( 1 ), 5, '1L-1' ); + $this->checkC( $data, $mk( 2 ), 3, '1L-2' ); + $this->checkC( $data, $mk( 3 ), 2, '1L-3' ); + $this->checkC( $data, $mk( 4 ), 2, '1L-4' ); + $this->checkC( $data, $mk( 5 ), 1, '1L-5' ); + } + + /** + * Test smart continue - list=allpages|alltransclusions + * @medium + */ + public function test2Lists() { + $this->mVerbose = false; + $mk = function ( $l1, $l2 ) { + return array( + 'list' => 'allpages|alltransclusions', + 'apprefix' => 'AQCT-', + 'atprefix' => 'AQCT-', + 'atunique' => '', + 'aplimit' => "$l1", + 'atlimit' => "$l2", + ); + }; + // 2 lists + $data = $this->query( $mk( 99, 99 ), 1, '2L', false ); + $this->checkC( $data, $mk( 1, 1 ), 5, '2L-11' ); + $this->checkC( $data, $mk( 2, 2 ), 3, '2L-22' ); + $this->checkC( $data, $mk( 3, 3 ), 2, '2L-33' ); + $this->checkC( $data, $mk( 4, 4 ), 2, '2L-44' ); + $this->checkC( $data, $mk( 5, 5 ), 1, '2L-55' ); + } + + /** + * Test smart continue - generator=allpages, prop=links + * @medium + */ + public function testGen1Prop() { + $this->mVerbose = false; + $mk = function ( $g, $p ) { + return array( + 'generator' => 'allpages', + 'gapprefix' => 'AQCT-', + 'gaplimit' => "$g", + 'prop' => 'links', + 'pllimit' => "$p", + ); + }; + // generator + 1 prop + $data = $this->query( $mk( 99, 99 ), 1, 'G1P', false ); + $this->checkC( $data, $mk( 1, 1 ), 11, 'G1P-11' ); + $this->checkC( $data, $mk( 2, 2 ), 6, 'G1P-22' ); + $this->checkC( $data, $mk( 3, 3 ), 4, 'G1P-33' ); + $this->checkC( $data, $mk( 4, 4 ), 3, 'G1P-44' ); + $this->checkC( $data, $mk( 5, 5 ), 2, 'G1P-55' ); + } + + /** + * Test smart continue - generator=allpages, prop=links|templates + * @medium + */ + public function testGen2Prop() { + $this->mVerbose = false; + $mk = function ( $g, $p1, $p2 ) { + return array( + 'generator' => 'allpages', + 'gapprefix' => 'AQCT-', + 'gaplimit' => "$g", + 'prop' => 'links|templates', + 'pllimit' => "$p1", + 'tllimit' => "$p2", + ); + }; + // generator + 2 props + $data = $this->query( $mk( 99, 99, 99 ), 1, 'G2P', false ); + $this->checkC( $data, $mk( 1, 1, 1 ), 16, 'G2P-111' ); + $this->checkC( $data, $mk( 2, 2, 2 ), 9, 'G2P-222' ); + $this->checkC( $data, $mk( 3, 3, 3 ), 6, 'G2P-333' ); + $this->checkC( $data, $mk( 4, 4, 4 ), 4, 'G2P-444' ); + $this->checkC( $data, $mk( 5, 5, 5 ), 2, 'G2P-555' ); + $this->checkC( $data, $mk( 5, 1, 1 ), 10, 'G2P-511' ); + $this->checkC( $data, $mk( 4, 2, 2 ), 7, 'G2P-422' ); + $this->checkC( $data, $mk( 2, 3, 3 ), 7, 'G2P-233' ); + $this->checkC( $data, $mk( 2, 4, 4 ), 5, 'G2P-244' ); + $this->checkC( $data, $mk( 1, 5, 5 ), 5, 'G2P-155' ); + } + + /** + * Test smart continue - generator=allpages, prop=links, list=alltransclusions + * @medium + */ + public function testGen1Prop1List() { + $this->mVerbose = false; + $mk = function ( $g, $p, $l ) { + return array( + 'generator' => 'allpages', + 'gapprefix' => 'AQCT-', + 'gaplimit' => "$g", + 'prop' => 'links', + 'pllimit' => "$p", + 'list' => 'alltransclusions', + 'atprefix' => 'AQCT-', + 'atunique' => '', + 'atlimit' => "$l", + ); + }; + // generator + 1 prop + 1 list + $data = $this->query( $mk( 99, 99, 99 ), 1, 'G1P1L', false ); + $this->checkC( $data, $mk( 1, 1, 1 ), 11, 'G1P1L-111' ); + $this->checkC( $data, $mk( 2, 2, 2 ), 6, 'G1P1L-222' ); + $this->checkC( $data, $mk( 3, 3, 3 ), 4, 'G1P1L-333' ); + $this->checkC( $data, $mk( 4, 4, 4 ), 3, 'G1P1L-444' ); + $this->checkC( $data, $mk( 5, 5, 5 ), 2, 'G1P1L-555' ); + $this->checkC( $data, $mk( 5, 5, 1 ), 4, 'G1P1L-551' ); + $this->checkC( $data, $mk( 5, 5, 2 ), 2, 'G1P1L-552' ); + } + + /** + * Test smart continue - generator=allpages, prop=links|templates, + * list=alllinks|alltransclusions, meta=siteinfo + * @medium + */ + public function testGen2Prop2List1Meta() { + $this->mVerbose = false; + $mk = function ( $g, $p1, $p2, $l1, $l2 ) { + return array( + 'generator' => 'allpages', + 'gapprefix' => 'AQCT-', + 'gaplimit' => "$g", + 'prop' => 'links|templates', + 'pllimit' => "$p1", + 'tllimit' => "$p2", + 'list' => 'alllinks|alltransclusions', + 'alprefix' => 'AQCT-', + 'alunique' => '', + 'allimit' => "$l1", + 'atprefix' => 'AQCT-', + 'atunique' => '', + 'atlimit' => "$l2", + 'meta' => 'siteinfo', + 'siprop' => 'namespaces', + ); + }; + // generator + 1 prop + 1 list + $data = $this->query( $mk( 99, 99, 99, 99, 99 ), 1, 'G2P2L1M', false ); + $this->checkC( $data, $mk( 1, 1, 1, 1, 1 ), 16, 'G2P2L1M-11111' ); + $this->checkC( $data, $mk( 2, 2, 2, 2, 2 ), 9, 'G2P2L1M-22222' ); + $this->checkC( $data, $mk( 3, 3, 3, 3, 3 ), 6, 'G2P2L1M-33333' ); + $this->checkC( $data, $mk( 4, 4, 4, 4, 4 ), 4, 'G2P2L1M-44444' ); + $this->checkC( $data, $mk( 5, 5, 5, 5, 5 ), 2, 'G2P2L1M-55555' ); + $this->checkC( $data, $mk( 5, 5, 5, 1, 1 ), 4, 'G2P2L1M-55511' ); + $this->checkC( $data, $mk( 5, 5, 5, 2, 2 ), 2, 'G2P2L1M-55522' ); + $this->checkC( $data, $mk( 5, 1, 1, 5, 5 ), 10, 'G2P2L1M-51155' ); + $this->checkC( $data, $mk( 5, 2, 2, 5, 5 ), 5, 'G2P2L1M-52255' ); + } + + /** + * Test smart continue - generator=templates, prop=templates + * @medium + */ + public function testSameGenAndProp() { + $this->mVerbose = false; + $mk = function ( $g, $gDir, $p, $pDir ) { + return array( + 'titles' => 'AQCT-1', + 'generator' => 'templates', + 'gtllimit' => "$g", + 'gtldir' => $gDir ? 'ascending' : 'descending', + 'prop' => 'templates', + 'tllimit' => "$p", + 'tldir' => $pDir ? 'ascending' : 'descending', + ); + }; + // generator + 1 prop + $data = $this->query( $mk( 99, true, 99, true ), 1, 'G=P', false ); + + $this->checkC( $data, $mk( 1, true, 1, true ), 4, 'G=P-1t1t' ); + $this->checkC( $data, $mk( 2, true, 2, true ), 2, 'G=P-2t2t' ); + $this->checkC( $data, $mk( 3, true, 3, true ), 2, 'G=P-3t3t' ); + $this->checkC( $data, $mk( 1, true, 3, true ), 4, 'G=P-1t3t' ); + $this->checkC( $data, $mk( 3, true, 1, true ), 2, 'G=P-3t1t' ); + + $this->checkC( $data, $mk( 1, true, 1, false ), 4, 'G=P-1t1f' ); + $this->checkC( $data, $mk( 2, true, 2, false ), 2, 'G=P-2t2f' ); + $this->checkC( $data, $mk( 3, true, 3, false ), 2, 'G=P-3t3f' ); + $this->checkC( $data, $mk( 1, true, 3, false ), 4, 'G=P-1t3f' ); + $this->checkC( $data, $mk( 3, true, 1, false ), 2, 'G=P-3t1f' ); + + $this->checkC( $data, $mk( 1, false, 1, true ), 4, 'G=P-1f1t' ); + $this->checkC( $data, $mk( 2, false, 2, true ), 2, 'G=P-2f2t' ); + $this->checkC( $data, $mk( 3, false, 3, true ), 2, 'G=P-3f3t' ); + $this->checkC( $data, $mk( 1, false, 3, true ), 4, 'G=P-1f3t' ); + $this->checkC( $data, $mk( 3, false, 1, true ), 2, 'G=P-3f1t' ); + + $this->checkC( $data, $mk( 1, false, 1, false ), 4, 'G=P-1f1f' ); + $this->checkC( $data, $mk( 2, false, 2, false ), 2, 'G=P-2f2f' ); + $this->checkC( $data, $mk( 3, false, 3, false ), 2, 'G=P-3f3f' ); + $this->checkC( $data, $mk( 1, false, 3, false ), 4, 'G=P-1f3f' ); + $this->checkC( $data, $mk( 3, false, 1, false ), 2, 'G=P-3f1f' ); + } + + /** + * Test smart continue - generator=allpages, list=allpages + * @medium + */ + public function testSameGenList() { + $this->mVerbose = false; + $mk = function ( $g, $gDir, $l, $pDir ) { + return array( + 'generator' => 'allpages', + 'gapprefix' => 'AQCT-', + 'gaplimit' => "$g", + 'gapdir' => $gDir ? 'ascending' : 'descending', + 'list' => 'allpages', + 'apprefix' => 'AQCT-', + 'aplimit' => "$l", + 'apdir' => $pDir ? 'ascending' : 'descending', + ); + }; + // generator + 1 list + $data = $this->query( $mk( 99, true, 99, true ), 1, 'G=L', false ); + + $this->checkC( $data, $mk( 1, true, 1, true ), 5, 'G=L-1t1t' ); + $this->checkC( $data, $mk( 2, true, 2, true ), 3, 'G=L-2t2t' ); + $this->checkC( $data, $mk( 3, true, 3, true ), 2, 'G=L-3t3t' ); + $this->checkC( $data, $mk( 1, true, 3, true ), 5, 'G=L-1t3t' ); + $this->checkC( $data, $mk( 3, true, 1, true ), 5, 'G=L-3t1t' ); + $this->checkC( $data, $mk( 1, true, 1, false ), 5, 'G=L-1t1f' ); + $this->checkC( $data, $mk( 2, true, 2, false ), 3, 'G=L-2t2f' ); + $this->checkC( $data, $mk( 3, true, 3, false ), 2, 'G=L-3t3f' ); + $this->checkC( $data, $mk( 1, true, 3, false ), 5, 'G=L-1t3f' ); + $this->checkC( $data, $mk( 3, true, 1, false ), 5, 'G=L-3t1f' ); + $this->checkC( $data, $mk( 1, false, 1, true ), 5, 'G=L-1f1t' ); + $this->checkC( $data, $mk( 2, false, 2, true ), 3, 'G=L-2f2t' ); + $this->checkC( $data, $mk( 3, false, 3, true ), 2, 'G=L-3f3t' ); + $this->checkC( $data, $mk( 1, false, 3, true ), 5, 'G=L-1f3t' ); + $this->checkC( $data, $mk( 3, false, 1, true ), 5, 'G=L-3f1t' ); + $this->checkC( $data, $mk( 1, false, 1, false ), 5, 'G=L-1f1f' ); + $this->checkC( $data, $mk( 2, false, 2, false ), 3, 'G=L-2f2f' ); + $this->checkC( $data, $mk( 3, false, 3, false ), 2, 'G=L-3f3f' ); + $this->checkC( $data, $mk( 1, false, 3, false ), 5, 'G=L-1f3f' ); + $this->checkC( $data, $mk( 3, false, 1, false ), 5, 'G=L-3f1f' ); + } +} diff --git a/tests/phpunit/includes/api/query/ApiQueryContinueTestBase.php b/tests/phpunit/includes/api/query/ApiQueryContinueTestBase.php new file mode 100644 index 00000000..fbb1e640 --- /dev/null +++ b/tests/phpunit/includes/api/query/ApiQueryContinueTestBase.php @@ -0,0 +1,209 @@ +<?php +/** + * + * + * Created on Jan 1, 2013 + * + * Copyright © 2013 Yuri Astrakhan "<Firstname><Lastname>@gmail.com" + * + * 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 + * + * @file + */ + +require_once 'ApiQueryTestBase.php'; + +abstract class ApiQueryContinueTestBase extends ApiQueryTestBase { + + /** + * Enable to print in-depth debugging info during the test run + */ + protected $mVerbose = false; + + /** + * Run query() and compare against expected values + */ + protected function checkC( $expected, $params, $expectedCount, $id, $continue = true ) { + $result = $this->query( $params, $expectedCount, $id, $continue ); + $this->assertResult( $expected, $result, $id ); + } + + /** + * Run query in a loop until no more values are available + * @param array $params api parameters + * @param int $expectedCount max number of iterations + * @param string $id unit test id + * @param boolean $useContinue true to use smart continue + * @return mixed: merged results data array + * @throws Exception + */ + protected function query( $params, $expectedCount, $id, $useContinue = true ) { + if ( isset( $params['action'] ) ) { + $this->assertEquals( 'query', $params['action'], 'Invalid query action' ); + } else { + $params['action'] = 'query'; + } + if ( $useContinue && !isset( $params['continue'] ) ) { + $params['continue'] = ''; + } + $count = 0; + $result = array(); + $continue = array(); + do { + $request = array_merge( $params, $continue ); + uksort( $request, function ( $a, $b ) { + // put 'continue' params at the end - lazy method + $a = strpos( $a, 'continue' ) !== false ? 'zzz ' . $a : $a; + $b = strpos( $b, 'continue' ) !== false ? 'zzz ' . $b : $b; + + return strcmp( $a, $b ); + } ); + $reqStr = http_build_query( $request ); + //$reqStr = str_replace( '&', ' & ', $reqStr ); + $this->assertLessThan( $expectedCount, $count, "$id more data: $reqStr" ); + if ( $this->mVerbose ) { + print "$id (#$count): $reqStr\n"; + } + try { + $data = $this->doApiRequest( $request ); + } catch ( Exception $e ) { + throw new Exception( "$id on $count", 0, $e ); + } + $data = $data[0]; + if ( isset( $data['warnings'] ) ) { + $warnings = json_encode( $data['warnings'] ); + $this->fail( "$id Warnings on #$count in $reqStr\n$warnings" ); + } + $this->assertArrayHasKey( 'query', $data, "$id no 'query' on #$count in $reqStr" ); + if ( isset( $data['continue'] ) ) { + $continue = $data['continue']; + unset( $data['continue'] ); + } else { + $continue = array(); + } + if ( $this->mVerbose ) { + $this->printResult( $data ); + } + $this->mergeResult( $result, $data ); + $count++; + if ( empty( $continue ) ) { + // $this->assertEquals( $expectedCount, $count, "$id finished early" ); + if ( $expectedCount > $count ) { + print "***** $id Finished early in $count turns. $expectedCount was expected\n"; + } + + return $result; + } elseif ( !$useContinue ) { + $this->assertFalse( 'Non-smart query must be requested all at once' ); + } + } while ( true ); + } + + private function printResult( $data ) { + $q = $data['query']; + $print = array(); + if ( isset( $q['pages'] ) ) { + foreach ( $q['pages'] as $p ) { + $m = $p['title']; + if ( isset( $p['links'] ) ) { + $m .= '/[' . implode( ',', array_map( + function ( $v ) { + return $v['title']; + }, + $p['links'] ) ) . ']'; + } + if ( isset( $p['categories'] ) ) { + $m .= '/(' . implode( ',', array_map( + function ( $v ) { + return str_replace( 'Category:', '', $v['title'] ); + }, + $p['categories'] ) ) . ')'; + } + $print[] = $m; + } + } + if ( isset( $q['allcategories'] ) ) { + $print[] = '*Cats/(' . implode( ',', array_map( + function ( $v ) { + return $v['*']; + }, + $q['allcategories'] ) ) . ')'; + } + self::GetItems( $q, 'allpages', 'Pages', $print ); + self::GetItems( $q, 'alllinks', 'Links', $print ); + self::GetItems( $q, 'alltransclusions', 'Trnscl', $print ); + print ' ' . implode( ' ', $print ) . "\n"; + } + + private static function GetItems( $q, $moduleName, $name, &$print ) { + if ( isset( $q[$moduleName] ) ) { + $print[] = "*$name/[" . implode( ',', + array_map( function ( $v ) { + return $v['title']; + }, + $q[$moduleName] ) ) . ']'; + } + } + + /** + * Recursively merge the new result returned from the query to the previous results. + * @param mixed $results + * @param mixed $newResult + * @param bool $numericIds If true, treat keys as ids to be merged instead of appending + */ + protected function mergeResult( &$results, $newResult, $numericIds = false ) { + $this->assertEquals( is_array( $results ), is_array( $newResult ), 'Type of result and data do not match' ); + if ( !is_array( $results ) ) { + $this->assertEquals( $results, $newResult, 'Repeated result must be the same as before' ); + } else { + $sort = null; + foreach ( $newResult as $key => $value ) { + if ( !$numericIds && $sort === null ) { + if ( !is_array( $value ) ) { + $sort = false; + } elseif ( array_key_exists( 'title', $value ) ) { + $sort = function ( $a, $b ) { + return strcmp( $a['title'], $b['title'] ); + }; + } else { + $sort = false; + } + } + $keyExists = array_key_exists( $key, $results ); + if ( is_numeric( $key ) ) { + if ( $numericIds ) { + if ( !$keyExists ) { + $results[$key] = $value; + } else { + $this->mergeResult( $results[$key], $value ); + } + } else { + $results[] = $value; + } + } elseif ( !$keyExists ) { + $results[$key] = $value; + } else { + $this->mergeResult( $results[$key], $value, $key === 'pages' ); + } + } + if ( $numericIds ) { + ksort( $results, SORT_NUMERIC ); + } elseif ( $sort !== null && $sort !== false ) { + uasort( $results, $sort ); + } + } + } +} diff --git a/tests/phpunit/includes/api/query/ApiQueryRevisionsTest.php b/tests/phpunit/includes/api/query/ApiQueryRevisionsTest.php new file mode 100644 index 00000000..1bca2256 --- /dev/null +++ b/tests/phpunit/includes/api/query/ApiQueryRevisionsTest.php @@ -0,0 +1,39 @@ +<?php + +/** + * @group API + * @group Database + * @group medium + */ +class ApiQueryRevisionsTest extends ApiTestCase { + + /** + * @group medium + */ + public function testContentComesWithContentModelAndFormat() { + $pageName = 'Help:' . __METHOD__; + $title = Title::newFromText( $pageName ); + $page = WikiPage::factory( $title ); + $page->doEdit( 'Some text', 'inserting content' ); + + $apiResult = $this->doApiRequest( array( + 'action' => 'query', + 'prop' => 'revisions', + 'titles' => $pageName, + 'rvprop' => 'content', + ) ); + $this->assertArrayHasKey( 'query', $apiResult[0] ); + $this->assertArrayHasKey( 'pages', $apiResult[0]['query'] ); + foreach ( $apiResult[0]['query']['pages'] as $page ) { + $this->assertArrayHasKey( 'revisions', $page ); + foreach ( $page['revisions'] as $revision ) { + $this->assertArrayHasKey( 'contentformat', $revision, + 'contentformat should be included when asking content so client knows how to interpret it' + ); + $this->assertArrayHasKey( 'contentmodel', $revision, + 'contentmodel should be included when asking content so client knows how to interpret it' + ); + } + } + } +} diff --git a/tests/phpunit/includes/api/ApiQueryTest.php b/tests/phpunit/includes/api/query/ApiQueryTest.php index a4b9dc70..f5645555 100644 --- a/tests/phpunit/includes/api/ApiQueryTest.php +++ b/tests/phpunit/includes/api/query/ApiQueryTest.php @@ -3,15 +3,16 @@ /** * @group API * @group Database + * @group medium */ class ApiQueryTest extends ApiTestCase { - function setUp() { + protected function setUp() { parent::setUp(); $this->doLogin(); } - function testTitlesGetNormalized() { + public function testTitlesGetNormalized() { global $wgMetaNamespace; @@ -19,12 +20,11 @@ class ApiQueryTest extends ApiTestCase { 'action' => 'query', 'titles' => 'Project:articleA|article_B' ) ); - $this->assertArrayHasKey( 'query', $data[0] ); $this->assertArrayHasKey( 'normalized', $data[0]['query'] ); // Forge a normalized title - $to = Title::newFromText( $wgMetaNamespace.':ArticleA' ); + $to = Title::newFromText( $wgMetaNamespace . ':ArticleA' ); $this->assertEquals( array( @@ -41,12 +41,11 @@ class ApiQueryTest extends ApiTestCase { ), $data[0]['query']['normalized'][1] ); - } - function testTitlesAreRejectedIfInvalid() { + public function testTitlesAreRejectedIfInvalid() { $title = false; - while( !$title || Title::newFromText( $title )->exists() ) { + while ( !$title || Title::newFromText( $title )->exists() ) { $title = md5( mt_rand( 0, 10000 ) + rand( 0, 999000 ) ); } @@ -64,5 +63,4 @@ class ApiQueryTest extends ApiTestCase { $this->assertArrayHasKey( 'missing', $data[0]['query']['pages'][-2] ); $this->assertArrayHasKey( 'invalid', $data[0]['query']['pages'][-1] ); } - } diff --git a/tests/phpunit/includes/api/query/ApiQueryTestBase.php b/tests/phpunit/includes/api/query/ApiQueryTestBase.php new file mode 100644 index 00000000..8ee8ea96 --- /dev/null +++ b/tests/phpunit/includes/api/query/ApiQueryTestBase.php @@ -0,0 +1,150 @@ +<?php +/** + * + * + * Created on Feb 10, 2013 + * + * Copyright © 2013 Yuri Astrakhan "<Firstname><Lastname>@gmail.com" + * + * 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 3 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 + * + * @file + */ + +/** This class has some common functionality for testing query module + */ +abstract class ApiQueryTestBase extends ApiTestCase { + + const PARAM_ASSERT = <<<STR +Each parameter must be an array of two elements, +first - an array of params to the API call, +and the second array - expected results as returned by the API +STR; + + /** + * Merges all requests parameter + expected values into one + * @param ... list of arrays, each of which contains exactly two + * @return array + */ + protected function merge( /*...*/ ) { + $request = array(); + $expected = array(); + foreach ( func_get_args() as $v ) { + list( $req, $exp ) = $this->validateRequestExpectedPair( $v ); + $request = array_merge_recursive( $request, $req ); + $this->mergeExpected( $expected, $exp ); + } + + return array( $request, $expected ); + } + + /** + * Check that the parameter is a valid two element array, + * with the first element being API request and the second - expected result + */ + private function validateRequestExpectedPair( $v ) { + $this->assertType( 'array', $v, self::PARAM_ASSERT ); + $this->assertEquals( 2, count( $v ), self::PARAM_ASSERT ); + $this->assertArrayHasKey( 0, $v, self::PARAM_ASSERT ); + $this->assertArrayHasKey( 1, $v, self::PARAM_ASSERT ); + $this->assertType( 'array', $v[0], self::PARAM_ASSERT ); + $this->assertType( 'array', $v[1], self::PARAM_ASSERT ); + + return $v; + } + + /** + * Recursively merges the expected values in the $item into the $all + */ + private function mergeExpected( &$all, $item ) { + foreach ( $item as $k => $v ) { + if ( array_key_exists( $k, $all ) ) { + if ( is_array( $all[$k] ) ) { + $this->mergeExpected( $all[$k], $v ); + } else { + $this->assertEquals( $all[$k], $v ); + } + } else { + $all[$k] = $v; + } + } + } + + /** + * Checks that the request's result matches the expected results. + * @param $values array is a two element array( request, expected_results ) + * @throws Exception + */ + protected function check( $values ) { + list( $req, $exp ) = $this->validateRequestExpectedPair( $values ); + if ( !array_key_exists( 'action', $req ) ) { + $req['action'] = 'query'; + } + foreach ( $req as &$val ) { + if ( is_array( $val ) ) { + $val = implode( '|', array_unique( $val ) ); + } + } + $result = $this->doApiRequest( $req ); + $this->assertResult( array( 'query' => $exp ), $result[0], $req ); + } + + protected function assertResult( $exp, $result, $message = '' ) { + try { + $this->assertResultRecursive( $exp, $result ); + } catch ( Exception $e ) { + if ( is_array( $message ) ) { + $message = http_build_query( $message ); + } + print "\nRequest: $message\n"; + print "\nExpected:\n"; + print_r( $exp ); + print "\nResult:\n"; + print_r( $result ); + throw $e; // rethrow it + } + } + + /** + * Recursively compare arrays, ignoring mismatches in numeric key and pageids. + * @param $expected array expected values + * @param $result array returned values + */ + private function assertResultRecursive( $expected, $result ) { + reset( $expected ); + reset( $result ); + while ( true ) { + $e = each( $expected ); + $r = each( $result ); + // If either of the arrays is shorter, abort. If both are done, success. + $this->assertEquals( (bool)$e, (bool)$r ); + if ( !$e ) { + break; // done + } + // continue only if keys are identical or both keys are numeric + $this->assertTrue( $e['key'] === $r['key'] || ( is_numeric( $e['key'] ) && is_numeric( $r['key'] ) ) ); + // don't compare pageids + if ( $e['key'] !== 'pageid' ) { + // If values are arrays, compare recursively, otherwise compare with === + if ( is_array( $e['value'] ) && is_array( $r['value'] ) ) { + $this->assertResultRecursive( $e['value'], $r['value'] ); + } else { + $this->assertEquals( $e['value'], $r['value'] ); + } + } + } + } +} diff --git a/tests/phpunit/includes/cache/GenderCacheTest.php b/tests/phpunit/includes/cache/GenderCacheTest.php index a8b987e2..ce2db5d7 100644 --- a/tests/phpunit/includes/cache/GenderCacheTest.php +++ b/tests/phpunit/includes/cache/GenderCacheTest.php @@ -6,7 +6,7 @@ */ class GenderCacheTest extends MediaWikiLangTestCase { - function setUp() { + protected function setUp() { global $wgDefaultUserOptions; parent::setUp(); //ensure the correct default gender @@ -15,7 +15,7 @@ class GenderCacheTest extends MediaWikiLangTestCase { function addDBData() { $user = User::newFromName( 'UTMale' ); - if( $user->getID() == 0 ) { + if ( $user->getID() == 0 ) { $user->addToDatabase(); $user->setPassword( 'UTMalePassword' ); } @@ -24,7 +24,7 @@ class GenderCacheTest extends MediaWikiLangTestCase { $user->saveSettings(); $user = User::newFromName( 'UTFemale' ); - if( $user->getID() == 0 ) { + if ( $user->getID() == 0 ) { $user->addToDatabase(); $user->setPassword( 'UTFemalePassword' ); } @@ -33,7 +33,7 @@ class GenderCacheTest extends MediaWikiLangTestCase { $user->saveSettings(); $user = User::newFromName( 'UTDefaultGender' ); - if( $user->getID() == 0 ) { + if ( $user->getID() == 0 ) { $user->addToDatabase(); $user->setPassword( 'UTDefaultGenderPassword' ); } @@ -45,9 +45,10 @@ class GenderCacheTest extends MediaWikiLangTestCase { /** * test usernames * - * @dataProvider dataUserName + * @dataProvider provideUserGenders + * @covers GenderCache::getGenderOf */ - function testUserName( $username, $expectedGender ) { + public function testUserName( $username, $expectedGender ) { $genderCache = GenderCache::singleton(); $gender = $genderCache->getGenderOf( $username ); $this->assertEquals( $gender, $expectedGender, "GenderCache normal" ); @@ -56,16 +57,17 @@ class GenderCacheTest extends MediaWikiLangTestCase { /** * genderCache should work with user objects, too * - * @dataProvider dataUserName + * @dataProvider provideUserGenders + * @covers GenderCache::getGenderOf */ - function testUserObjects( $username, $expectedGender ) { + public function testUserObjects( $username, $expectedGender ) { $genderCache = GenderCache::singleton(); $user = User::newFromName( $username ); $gender = $genderCache->getGenderOf( $user ); $this->assertEquals( $gender, $expectedGender, "GenderCache normal" ); } - function dataUserName() { + public static function provideUserGenders() { return array( array( 'UTMale', 'male' ), array( 'UTFemale', 'female' ), @@ -81,15 +83,16 @@ class GenderCacheTest extends MediaWikiLangTestCase { * test strip of subpages to avoid unnecessary queries * against the never existing username * - * @dataProvider dataStripSubpages + * @dataProvider provideStripSubpages + * @covers GenderCache::getGenderOf */ - function testStripSubpages( $pageWithSubpage, $expectedGender ) { + public function testStripSubpages( $pageWithSubpage, $expectedGender ) { $genderCache = GenderCache::singleton(); $gender = $genderCache->getGenderOf( $pageWithSubpage ); $this->assertEquals( $gender, $expectedGender, "GenderCache must strip of subpages" ); } - function dataStripSubpages() { + public static function provideStripSubpages() { return array( array( 'UTMale/subpage', 'male' ), array( 'UTFemale/subpage', 'female' ), diff --git a/tests/phpunit/includes/cache/MessageCacheTest.php b/tests/phpunit/includes/cache/MessageCacheTest.php new file mode 100644 index 00000000..803acf73 --- /dev/null +++ b/tests/phpunit/includes/cache/MessageCacheTest.php @@ -0,0 +1,128 @@ +<?php + +/** + * @group Database + * @group Cache + * @covers MessageCache + */ +class MessageCacheTest extends MediaWikiLangTestCase { + + protected function setUp() { + parent::setUp(); + $this->configureLanguages(); + MessageCache::singleton()->enable(); + } + + /** + * Helper function -- setup site language for testing + */ + protected function configureLanguages() { + // for the test, we need the content language to be anything but English, + // let's choose e.g. German (de) + $langCode = 'de'; + $langObj = Language::factory( $langCode ); + + $this->setMwGlobals( array( + 'wgLanguageCode' => $langCode, + 'wgLang' => $langObj, + 'wgContLang' => $langObj, + ) ); + } + + function addDBData() { + $this->configureLanguages(); + + // Set up messages and fallbacks ab -> ru -> de + $this->makePage( 'FallbackLanguageTest-Full', 'ab' ); + $this->makePage( 'FallbackLanguageTest-Full', 'ru' ); + $this->makePage( 'FallbackLanguageTest-Full', 'de' ); + + // Fallbacks where ab does not exist + $this->makePage( 'FallbackLanguageTest-Partial', 'ru' ); + $this->makePage( 'FallbackLanguageTest-Partial', 'de' ); + + // Fallback to the content language + $this->makePage( 'FallbackLanguageTest-ContLang', 'de' ); + + // Add customizations for an existing message. + $this->makePage( 'sunday', 'ru' ); + + // Full key tests -- always want russian + $this->makePage( 'MessageCacheTest-FullKeyTest', 'ab' ); + $this->makePage( 'MessageCacheTest-FullKeyTest', 'ru' ); + + // In content language -- get base if no derivative + $this->makePage( 'FallbackLanguageTest-NoDervContLang', 'de', 'de/none', false ); + } + + /** + * Helper function for addDBData -- adds a simple page to the database + * + * @param string $title Title of page to be created + * @param string $lang Language and content of the created page + * @param string|null $content Content of the created page, or null for a generic string + * @param bool $createSubPage Set to false if a root page should be created + */ + protected function makePage( $title, $lang, $content = null, $createSubPage = true ) { + global $wgContLang; + + if ( $content === null ) { + $content = $lang; + } + if ( $lang !== $wgContLang->getCode() || $createSubPage ) { + $title = "$title/$lang"; + } + + $title = Title::newFromText( $title, NS_MEDIAWIKI ); + $wikiPage = new WikiPage( $title ); + $contentHandler = ContentHandler::makeContent( $content, $title ); + $wikiPage->doEditContent( $contentHandler, "$lang translation test case" ); + } + + /** + * Test message fallbacks, bug #1495 + * + * @dataProvider provideMessagesForFallback + */ + public function testMessageFallbacks( $message, $lang, $expectedContent ) { + $result = MessageCache::singleton()->get( $message, true, $lang ); + $this->assertEquals( $expectedContent, $result, "Message fallback failed." ); + } + + function provideMessagesForFallback() { + return array( + array( 'FallbackLanguageTest-Full', 'ab', 'ab' ), + array( 'FallbackLanguageTest-Partial', 'ab', 'ru' ), + array( 'FallbackLanguageTest-ContLang', 'ab', 'de' ), + array( 'FallbackLanguageTest-None', 'ab', false ), + + // Existing message with customizations on the fallbacks + array( 'sunday', 'ab', 'амҽыш' ), + + // bug 46579 + array( 'FallbackLanguageTest-NoDervContLang', 'de', 'de/none' ), + // UI language different from content language should only use de/none as last option + array( 'FallbackLanguageTest-NoDervContLang', 'fit', 'de/none' ), + ); + } + + /** + * There's a fallback case where the message key is given as fully qualified -- this + * should ignore the passed $lang and use the language from the key + * + * @dataProvider provideMessagesForFullKeys + */ + public function testFullKeyBehaviour( $message, $lang, $expectedContent ) { + $result = MessageCache::singleton()->get( $message, true, $lang, true ); + $this->assertEquals( $expectedContent, $result, "Full key message fallback failed." ); + } + + function provideMessagesForFullKeys() { + return array( + array( 'MessageCacheTest-FullKeyTest/ru', 'ru', 'ru' ), + array( 'MessageCacheTest-FullKeyTest/ru', 'ab', 'ru' ), + array( 'MessageCacheTest-FullKeyTest/ru/foo', 'ru', false ), + ); + } + +} diff --git a/tests/phpunit/includes/cache/ProcessCacheLRUTest.php b/tests/phpunit/includes/cache/ProcessCacheLRUTest.php index 30bfb124..d3793d83 100644 --- a/tests/phpunit/includes/cache/ProcessCacheLRUTest.php +++ b/tests/phpunit/includes/cache/ProcessCacheLRUTest.php @@ -24,7 +24,7 @@ class ProcessCacheLRUTest extends MediaWikiTestCase { */ function fillCache( &$cache, $numEntries ) { // Fill cache with three values - for( $i=1; $i<=$numEntries; $i++) { + for ( $i = 1; $i <= $numEntries; $i++ ) { $cache->set( "cache-key-$i", "prop-$i", "value-$i" ); } } @@ -36,10 +36,10 @@ class ProcessCacheLRUTest extends MediaWikiTestCase { function getExpectedCache( $cacheMaxEntries, $entryToFill ) { $expected = array(); - if( $entryToFill === 0 ) { + if ( $entryToFill === 0 ) { # The cache is empty! return array(); - } elseif( $entryToFill <= $cacheMaxEntries ) { + } elseif ( $entryToFill <= $cacheMaxEntries ) { # Cache is not fully filled $firstKey = 1; } else { @@ -47,21 +47,22 @@ class ProcessCacheLRUTest extends MediaWikiTestCase { $firstKey = 1 + $entryToFill - $cacheMaxEntries; } - $lastKey = $entryToFill; + $lastKey = $entryToFill; - for( $i=$firstKey; $i<=$lastKey; $i++ ) { + for ( $i = $firstKey; $i <= $lastKey; $i++ ) { $expected["cache-key-$i"] = array( "prop-$i" => "value-$i" ); } + return $expected; } /** * Highlight diff between assertEquals and assertNotSame */ - function testPhpUnitArrayEquality() { + public function testPhpUnitArrayEquality() { $one = array( 'A' => 1, 'B' => 2 ); $two = array( 'B' => 2, 'A' => 1 ); - $this->assertEquals( $one, $two ); // == + $this->assertEquals( $one, $two ); // == $this->assertNotSame( $one, $two ); // === } @@ -69,14 +70,14 @@ class ProcessCacheLRUTest extends MediaWikiTestCase { * @dataProvider provideInvalidConstructorArg * @expectedException MWException */ - function testConstructorGivenInvalidValue( $maxSize ) { - $c = new ProcessCacheLRUTestable( $maxSize ); + public function testConstructorGivenInvalidValue( $maxSize ) { + new ProcessCacheLRUTestable( $maxSize ); } /** * Value which are forbidden by the constructor */ - function provideInvalidConstructorArg() { + public static function provideInvalidConstructorArg() { return array( array( null ), array( array() ), @@ -87,7 +88,7 @@ class ProcessCacheLRUTest extends MediaWikiTestCase { ); } - function testAddAndGetAKey() { + public function testAddAndGetAKey() { $oneCache = new ProcessCacheLRUTestable( 1 ); $this->assertCacheEmpty( $oneCache ); @@ -98,7 +99,7 @@ class ProcessCacheLRUTest extends MediaWikiTestCase { $this->assertEquals( 'value1', $oneCache->get( 'cache-key', 'prop1' ) ); } - function testDeleteOldKey() { + public function testDeleteOldKey() { $oneCache = new ProcessCacheLRUTestable( 1 ); $this->assertCacheEmpty( $oneCache ); @@ -116,37 +117,35 @@ class ProcessCacheLRUTest extends MediaWikiTestCase { * @param $cacheMaxEntries Maximum entry the created cache will hold * @param $entryToFill Number of entries to insert in the created cache. */ - function testFillingCache( $cacheMaxEntries, $entryToFill, $msg = '' ) { + public function testFillingCache( $cacheMaxEntries, $entryToFill, $msg = '' ) { $cache = new ProcessCacheLRUTestable( $cacheMaxEntries ); - $this->fillCache( $cache, $entryToFill); + $this->fillCache( $cache, $entryToFill ); $this->assertSame( $this->getExpectedCache( $cacheMaxEntries, $entryToFill ), $cache->getCache(), "Filling a $cacheMaxEntries entries cache with $entryToFill entries" ); - } /** * Provider for testFillingCache */ - function provideCacheFilling() { + public static function provideCacheFilling() { // ($cacheMaxEntries, $entryToFill, $msg='') return array( - array( 1, 0 ), - array( 1, 1 ), - array( 1, 2 ), # overflow + array( 1, 0 ), + array( 1, 1 ), + array( 1, 2 ), # overflow array( 5, 33 ), # overflow ); - } /** * Create a cache with only one remaining entry then update * the first inserted entry. Should bump it to the top. */ - function testReplaceExistingKeyShouldBumpEntryToTop() { + public function testReplaceExistingKeyShouldBumpEntryToTop() { $maxEntries = 3; $cache = new ProcessCacheLRUTestable( $maxEntries ); @@ -165,9 +164,9 @@ class ProcessCacheLRUTest extends MediaWikiTestCase { ); } - function testRecentlyAccessedKeyStickIn() { + public function testRecentlyAccessedKeyStickIn() { $cache = new ProcessCacheLRUTestable( 2 ); - $cache->set( 'first' , 'prop1', 'value1' ); + $cache->set( 'first', 'prop1', 'value1' ); $cache->set( 'second', 'prop2', 'value2' ); // Get first @@ -184,7 +183,7 @@ class ProcessCacheLRUTest extends MediaWikiTestCase { * Given a cache having 1,2,3 as key, updating 2 should bump 2 to * the top of the queue with the new value: 1,3,2* (* = updated). */ - function testReplaceExistingKeyInAFullCacheShouldBumpToTop() { + public function testReplaceExistingKeyInAFullCacheShouldBumpToTop() { $maxEntries = 3; $cache = new ProcessCacheLRUTestable( $maxEntries ); @@ -205,7 +204,7 @@ class ProcessCacheLRUTest extends MediaWikiTestCase { ); } - function testBumpExistingKeyToTop() { + public function testBumpExistingKeyToTop() { $cache = new ProcessCacheLRUTestable( 3 ); $this->fillCache( $cache, 3 ); @@ -219,9 +218,7 @@ class ProcessCacheLRUTest extends MediaWikiTestCase { ), $cache->getCache() ); - } - } /** @@ -233,6 +230,7 @@ class ProcessCacheLRUTestable extends ProcessCacheLRU { public function getCache() { return $this->cache; } + public function getEntriesCount() { return count( $this->cache ); } diff --git a/tests/phpunit/includes/content/ContentHandlerTest.php b/tests/phpunit/includes/content/ContentHandlerTest.php new file mode 100644 index 00000000..aedf594d --- /dev/null +++ b/tests/phpunit/includes/content/ContentHandlerTest.php @@ -0,0 +1,451 @@ +<?php + +/** + * @group ContentHandler + * @group Database + * + * @note Declare that we are using the database, because otherwise we'll fail in the "databaseless" test run. + * This is because the LinkHolderArray used by the parser needs database access. + * + */ +class ContentHandlerTest extends MediaWikiTestCase { + + public function setUp() { + global $wgContLang; + parent::setUp(); + + $this->setMwGlobals( array( + 'wgExtraNamespaces' => array( + 12312 => 'Dummy', + 12313 => 'Dummy_talk', + ), + // The below tests assume that namespaces not mentioned here (Help, User, MediaWiki, ..) + // default to CONTENT_MODEL_WIKITEXT. + 'wgNamespaceContentModels' => array( + 12312 => 'testing', + ), + 'wgContentHandlers' => array( + CONTENT_MODEL_WIKITEXT => 'WikitextContentHandler', + CONTENT_MODEL_JAVASCRIPT => 'JavaScriptContentHandler', + CONTENT_MODEL_CSS => 'CssContentHandler', + CONTENT_MODEL_TEXT => 'TextContentHandler', + 'testing' => 'DummyContentHandlerForTesting', + ), + ) ); + + // Reset namespace cache + MWNamespace::getCanonicalNamespaces( true ); + $wgContLang->resetNamespaces(); + } + + public function tearDown() { + global $wgContLang; + + // Reset namespace cache + MWNamespace::getCanonicalNamespaces( true ); + $wgContLang->resetNamespaces(); + + parent::tearDown(); + } + + public static function dataGetDefaultModelFor() { + return array( + array( 'Help:Foo', CONTENT_MODEL_WIKITEXT ), + array( 'Help:Foo.js', CONTENT_MODEL_WIKITEXT ), + array( 'Help:Foo/bar.js', CONTENT_MODEL_WIKITEXT ), + array( 'User:Foo', CONTENT_MODEL_WIKITEXT ), + array( 'User:Foo.js', CONTENT_MODEL_WIKITEXT ), + array( 'User:Foo/bar.js', CONTENT_MODEL_JAVASCRIPT ), + array( 'User:Foo/bar.css', CONTENT_MODEL_CSS ), + array( 'User talk:Foo/bar.css', CONTENT_MODEL_WIKITEXT ), + array( 'User:Foo/bar.js.xxx', CONTENT_MODEL_WIKITEXT ), + array( 'User:Foo/bar.xxx', CONTENT_MODEL_WIKITEXT ), + array( 'MediaWiki:Foo.js', CONTENT_MODEL_JAVASCRIPT ), + array( 'MediaWiki:Foo.css', CONTENT_MODEL_CSS ), + array( 'MediaWiki:Foo.JS', CONTENT_MODEL_WIKITEXT ), + array( 'MediaWiki:Foo.CSS', CONTENT_MODEL_WIKITEXT ), + array( 'MediaWiki:Foo.css.xxx', CONTENT_MODEL_WIKITEXT ), + ); + } + + /** + * @dataProvider dataGetDefaultModelFor + * @covers ContentHandler::getDefaultModelFor + */ + public function testGetDefaultModelFor( $title, $expectedModelId ) { + $title = Title::newFromText( $title ); + $this->assertEquals( $expectedModelId, ContentHandler::getDefaultModelFor( $title ) ); + } + + /** + * @dataProvider dataGetDefaultModelFor + * @covers ContentHandler::getForTitle + */ + public function testGetForTitle( $title, $expectedContentModel ) { + $title = Title::newFromText( $title ); + $handler = ContentHandler::getForTitle( $title ); + $this->assertEquals( $expectedContentModel, $handler->getModelID() ); + } + + public static function dataGetLocalizedName() { + return array( + array( null, null ), + array( "xyzzy", null ), + + // XXX: depends on content language + array( CONTENT_MODEL_JAVASCRIPT, '/javascript/i' ), + ); + } + + /** + * @dataProvider dataGetLocalizedName + * @covers ContentHandler::getLocalizedName + */ + public function testGetLocalizedName( $id, $expected ) { + $name = ContentHandler::getLocalizedName( $id ); + + if ( $expected ) { + $this->assertNotNull( $name, "no name found for content model $id" ); + $this->assertTrue( preg_match( $expected, $name ) > 0, + "content model name for #$id did not match pattern $expected" + ); + } else { + $this->assertEquals( $id, $name, "localization of unknown model $id should have " + . "fallen back to use the model id directly." + ); + } + } + + public static function dataGetPageLanguage() { + global $wgLanguageCode; + + return array( + array( "Main", $wgLanguageCode ), + array( "Dummy:Foo", $wgLanguageCode ), + array( "MediaWiki:common.js", 'en' ), + array( "User:Foo/common.js", 'en' ), + array( "MediaWiki:common.css", 'en' ), + array( "User:Foo/common.css", 'en' ), + array( "User:Foo", $wgLanguageCode ), + + array( CONTENT_MODEL_JAVASCRIPT, 'javascript' ), + ); + } + + /** + * @dataProvider dataGetPageLanguage + * @covers ContentHandler::getPageLanguage + */ + public function testGetPageLanguage( $title, $expected ) { + if ( is_string( $title ) ) { + $title = Title::newFromText( $title ); + } + + $expected = wfGetLangObj( $expected ); + + $handler = ContentHandler::getForTitle( $title ); + $lang = $handler->getPageLanguage( $title ); + + $this->assertEquals( $expected->getCode(), $lang->getCode() ); + } + + public static function dataGetContentText_Null() { + return array( + array( 'fail' ), + array( 'serialize' ), + array( 'ignore' ), + ); + } + + /** + * @dataProvider dataGetContentText_Null + * @covers ContentHandler::getContentText + */ + public function testGetContentText_Null( $contentHandlerTextFallback ) { + $this->setMwGlobals( 'wgContentHandlerTextFallback', $contentHandlerTextFallback ); + + $content = null; + + $text = ContentHandler::getContentText( $content ); + $this->assertEquals( '', $text ); + } + + public static function dataGetContentText_TextContent() { + return array( + array( 'fail' ), + array( 'serialize' ), + array( 'ignore' ), + ); + } + + /** + * @dataProvider dataGetContentText_TextContent + * @covers ContentHandler::getContentText + */ + public function testGetContentText_TextContent( $contentHandlerTextFallback ) { + $this->setMwGlobals( 'wgContentHandlerTextFallback', $contentHandlerTextFallback ); + + $content = new WikitextContent( "hello world" ); + + $text = ContentHandler::getContentText( $content ); + $this->assertEquals( $content->getNativeData(), $text ); + } + + /** + * ContentHandler::getContentText should have thrown an exception for non-text Content object + * @expectedException MWException + * @covers ContentHandler::getContentText + */ + public function testGetContentText_NonTextContent_fail() { + $this->setMwGlobals( 'wgContentHandlerTextFallback', 'fail' ); + + $content = new DummyContentForTesting( "hello world" ); + + ContentHandler::getContentText( $content ); + } + + /** + * @covers ContentHandler::getContentText + */ + public function testGetContentText_NonTextContent_serialize() { + $this->setMwGlobals( 'wgContentHandlerTextFallback', 'serialize' ); + + $content = new DummyContentForTesting( "hello world" ); + + $text = ContentHandler::getContentText( $content ); + $this->assertEquals( $content->serialize(), $text ); + } + + /** + * @covers ContentHandler::getContentText + */ + public function testGetContentText_NonTextContent_ignore() { + $this->setMwGlobals( 'wgContentHandlerTextFallback', 'ignore' ); + + $content = new DummyContentForTesting( "hello world" ); + + $text = ContentHandler::getContentText( $content ); + $this->assertNull( $text ); + } + + /* + public static function makeContent( $text, Title $title, $modelId = null, $format = null ) {} + */ + + public static function dataMakeContent() { + return array( + array( 'hallo', 'Help:Test', null, null, CONTENT_MODEL_WIKITEXT, 'hallo', false ), + array( 'hallo', 'MediaWiki:Test.js', null, null, CONTENT_MODEL_JAVASCRIPT, 'hallo', false ), + array( serialize( 'hallo' ), 'Dummy:Test', null, null, "testing", 'hallo', false ), + + array( 'hallo', 'Help:Test', null, CONTENT_FORMAT_WIKITEXT, CONTENT_MODEL_WIKITEXT, 'hallo', false ), + array( 'hallo', 'MediaWiki:Test.js', null, CONTENT_FORMAT_JAVASCRIPT, CONTENT_MODEL_JAVASCRIPT, 'hallo', false ), + array( serialize( 'hallo' ), 'Dummy:Test', null, "testing", "testing", 'hallo', false ), + + array( 'hallo', 'Help:Test', CONTENT_MODEL_CSS, null, CONTENT_MODEL_CSS, 'hallo', false ), + array( 'hallo', 'MediaWiki:Test.js', CONTENT_MODEL_CSS, null, CONTENT_MODEL_CSS, 'hallo', false ), + array( serialize( 'hallo' ), 'Dummy:Test', CONTENT_MODEL_CSS, null, CONTENT_MODEL_CSS, serialize( 'hallo' ), false ), + + array( 'hallo', 'Help:Test', CONTENT_MODEL_WIKITEXT, "testing", null, null, true ), + array( 'hallo', 'MediaWiki:Test.js', CONTENT_MODEL_CSS, "testing", null, null, true ), + array( 'hallo', 'Dummy:Test', CONTENT_MODEL_JAVASCRIPT, "testing", null, null, true ), + ); + } + + /** + * @dataProvider dataMakeContent + * @covers ContentHandler::makeContent + */ + public function testMakeContent( $data, $title, $modelId, $format, $expectedModelId, $expectedNativeData, $shouldFail ) { + $title = Title::newFromText( $title ); + + try { + $content = ContentHandler::makeContent( $data, $title, $modelId, $format ); + + if ( $shouldFail ) { + $this->fail( "ContentHandler::makeContent should have failed!" ); + } + + $this->assertEquals( $expectedModelId, $content->getModel(), 'bad model id' ); + $this->assertEquals( $expectedNativeData, $content->getNativeData(), 'bads native data' ); + } catch ( MWException $ex ) { + if ( !$shouldFail ) { + $this->fail( "ContentHandler::makeContent failed unexpectedly: " . $ex->getMessage() ); + } else { + // dummy, so we don't get the "test did not perform any assertions" message. + $this->assertTrue( true ); + } + } + } + + /* + public function testSupportsSections() { + $this->markTestIncomplete( "not yet implemented" ); + } + */ + + /** + * @covers ContentHandler::runLegacyHooks + */ + public function testRunLegacyHooks() { + Hooks::register( 'testRunLegacyHooks', __CLASS__ . '::dummyHookHandler' ); + + $content = new WikitextContent( 'test text' ); + $ok = ContentHandler::runLegacyHooks( 'testRunLegacyHooks', array( 'foo', &$content, 'bar' ), false ); + + $this->assertTrue( $ok, "runLegacyHooks should have returned true" ); + $this->assertEquals( "TEST TEXT", $content->getNativeData() ); + } + + public static function dummyHookHandler( $foo, &$text, $bar ) { + if ( $text === null || $text === false ) { + return false; + } + + $text = strtoupper( $text ); + + return true; + } +} + +class DummyContentHandlerForTesting extends ContentHandler { + + public function __construct( $dataModel ) { + parent::__construct( $dataModel, array( "testing" ) ); + } + + /** + * Serializes Content object of the type supported by this ContentHandler. + * + * @param Content $content the Content object to serialize + * @param null $format the desired serialization format + * @return String serialized form of the content + */ + public function serializeContent( Content $content, $format = null ) { + return $content->serialize(); + } + + /** + * Unserializes a Content object of the type supported by this ContentHandler. + * + * @param $blob String serialized form of the content + * @param null $format the format used for serialization + * @return Content the Content object created by deserializing $blob + */ + public function unserializeContent( $blob, $format = null ) { + $d = unserialize( $blob ); + + return new DummyContentForTesting( $d ); + } + + /** + * Creates an empty Content object of the type supported by this ContentHandler. + * + */ + public function makeEmptyContent() { + return new DummyContentForTesting( '' ); + } +} + +class DummyContentForTesting extends AbstractContent { + + public function __construct( $data ) { + parent::__construct( "testing" ); + + $this->data = $data; + } + + public function serialize( $format = null ) { + return serialize( $this->data ); + } + + /** + * @return String a string representing the content in a way useful for building a full text search index. + * If no useful representation exists, this method returns an empty string. + */ + public function getTextForSearchIndex() { + return ''; + } + + /** + * @return String the wikitext to include when another page includes this content, or false if the content is not + * includable in a wikitext page. + */ + public function getWikitextForTransclusion() { + return false; + } + + /** + * Returns a textual representation of the content suitable for use in edit summaries and log messages. + * + * @param int $maxlength Maximum length of the summary text. + * @return string The summary text. + */ + public function getTextForSummary( $maxlength = 250 ) { + return ''; + } + + /** + * Returns native represenation of the data. Interpretation depends on the data model used, + * as given by getDataModel(). + * + * @return mixed the native representation of the content. Could be a string, a nested array + * structure, an object, a binary blob... anything, really. + */ + public function getNativeData() { + return $this->data; + } + + /** + * returns the content's nominal size in bogo-bytes. + * + * @return int + */ + public function getSize() { + return strlen( $this->data ); + } + + /** + * Return a copy of this Content object. The following must be true for the object returned + * if $copy = $original->copy() + * + * * get_class($original) === get_class($copy) + * * $original->getModel() === $copy->getModel() + * * $original->equals( $copy ) + * + * If and only if the Content object is imutable, the copy() method can and should + * return $this. That is, $copy === $original may be true, but only for imutable content + * objects. + * + * @return Content. A copy of this object. + */ + public function copy() { + return $this; + } + + /** + * Returns true if this content is countable as a "real" wiki page, provided + * that it's also in a countable location (e.g. a current revision in the main namespace). + * + * @param boolean $hasLinks if it is known whether this content contains links, provide this information here, + * to avoid redundant parsing to find out. + * @return boolean + */ + public function isCountable( $hasLinks = null ) { + return false; + } + + /** + * @param Title $title + * @param null $revId + * @param null|ParserOptions $options + * @param boolean $generateHtml whether to generate Html (default: true). If false, + * the result of calling getText() on the ParserOutput object returned by + * this method is undefined. + * + * @return ParserOutput + */ + public function getParserOutput( Title $title, $revId = null, ParserOptions $options = null, $generateHtml = true ) { + return new ParserOutput( $this->getNativeData() ); + } +} diff --git a/tests/phpunit/includes/content/CssContentTest.php b/tests/phpunit/includes/content/CssContentTest.php new file mode 100644 index 00000000..bd6d41fe --- /dev/null +++ b/tests/phpunit/includes/content/CssContentTest.php @@ -0,0 +1,87 @@ +<?php + +/** + * @group ContentHandler + * @group Database + * ^--- needed, because we do need the database to test link updates + */ +class CssContentTest extends MediaWikiTestCase { + + protected function setUp() { + parent::setUp(); + + // Anon user + $user = new User(); + $user->setName( '127.0.0.1' ); + + $this->setMwGlobals( array( + 'wgUser' => $user, + 'wgTextModelsToParse' => array( + CONTENT_MODEL_CSS, + ) + ) ); + } + + public function newContent( $text ) { + return new CssContent( $text ); + } + + public static function dataGetParserOutput() { + return array( + array( + 'MediaWiki:Test.css', + null, + "hello <world>\n", + "<pre class=\"mw-code mw-css\" dir=\"ltr\">\nhello <world>\n\n</pre>" + ), + array( + 'MediaWiki:Test.css', + null, + "/* hello [[world]] */\n", + "<pre class=\"mw-code mw-css\" dir=\"ltr\">\n/* hello [[world]] */\n\n</pre>", + array( + 'Links' => array( + array( 'World' => 0 ) + ) + ) + ), + + // TODO: more...? + ); + } + + /** + * @covers CssContent::getModel + */ + public function testGetModel() { + $content = $this->newContent( 'hello world.' ); + + $this->assertEquals( CONTENT_MODEL_CSS, $content->getModel() ); + } + + /** + * @covers CssContent::getContentHandler + */ + public function testGetContentHandler() { + $content = $this->newContent( 'hello world.' ); + + $this->assertEquals( CONTENT_MODEL_CSS, $content->getContentHandler()->getModelID() ); + } + + public static function dataEquals() { + return array( + array( new CssContent( 'hallo' ), null, false ), + array( new CssContent( 'hallo' ), new CssContent( 'hallo' ), true ), + array( new CssContent( 'hallo' ), new WikitextContent( 'hallo' ), false ), + array( new CssContent( 'hallo' ), new CssContent( 'HALLO' ), false ), + ); + } + + /** + * @dataProvider dataEquals + * @covers CssContent::equals + */ + public function testEquals( Content $a, Content $b = null, $equal = false ) { + $this->assertEquals( $equal, $a->equals( $b ) ); + } +} diff --git a/tests/phpunit/includes/content/JavaScriptContentTest.php b/tests/phpunit/includes/content/JavaScriptContentTest.php new file mode 100644 index 00000000..c8616ff0 --- /dev/null +++ b/tests/phpunit/includes/content/JavaScriptContentTest.php @@ -0,0 +1,287 @@ +<?php + +/** + * @group ContentHandler + * @group Database + * ^--- needed, because we do need the database to test link updates + */ +class JavaScriptContentTest extends TextContentTest { + + public function newContent( $text ) { + return new JavaScriptContent( $text ); + } + + public static function dataGetParserOutput() { + return array( + array( + 'MediaWiki:Test.js', + null, + "hello <world>\n", + "<pre class=\"mw-code mw-js\" dir=\"ltr\">\nhello <world>\n\n</pre>" + ), + array( + 'MediaWiki:Test.js', + null, + "hello(); // [[world]]\n", + "<pre class=\"mw-code mw-js\" dir=\"ltr\">\nhello(); // [[world]]\n\n</pre>", + array( + 'Links' => array( + array( 'World' => 0 ) + ) + ) + ), + + // TODO: more...? + ); + } + + // XXX: Unused function + public static function dataGetSection() { + return array( + array( WikitextContentTest::$sections, + '0', + null + ), + array( WikitextContentTest::$sections, + '2', + null + ), + array( WikitextContentTest::$sections, + '8', + null + ), + ); + } + + // XXX: Unused function + public static function dataReplaceSection() { + return array( + array( WikitextContentTest::$sections, + '0', + 'No more', + null, + null + ), + array( WikitextContentTest::$sections, + '', + 'No more', + null, + null + ), + array( WikitextContentTest::$sections, + '2', + "== TEST ==\nmore fun", + null, + null + ), + array( WikitextContentTest::$sections, + '8', + 'No more', + null, + null + ), + array( WikitextContentTest::$sections, + 'new', + 'No more', + 'New', + null + ), + ); + } + + /** + * @covers JavaScriptContent::addSectionHeader + */ + public function testAddSectionHeader() { + $content = $this->newContent( 'hello world' ); + $c = $content->addSectionHeader( 'test' ); + + $this->assertTrue( $content->equals( $c ) ); + } + + // XXX: currently, preSaveTransform is applied to scripts. this may change or become optional. + public static function dataPreSaveTransform() { + return array( + array( 'hello this is ~~~', + "hello this is [[Special:Contributions/127.0.0.1|127.0.0.1]]", + ), + array( 'hello \'\'this\'\' is <nowiki>~~~</nowiki>', + 'hello \'\'this\'\' is <nowiki>~~~</nowiki>', + ), + array( " Foo \n ", + " Foo", + ), + ); + } + + public static function dataPreloadTransform() { + return array( + array( 'hello this is ~~~', + 'hello this is ~~~', + ), + array( 'hello \'\'this\'\' is <noinclude>foo</noinclude><includeonly>bar</includeonly>', + 'hello \'\'this\'\' is <noinclude>foo</noinclude><includeonly>bar</includeonly>', + ), + ); + } + + public static function dataGetRedirectTarget() { + return array( + array( '#REDIRECT [[Test]]', + null, + ), + array( '#REDIRECT Test', + null, + ), + array( '* #REDIRECT [[Test]]', + null, + ), + ); + } + + /** + * @todo Test needs database! + */ + /* + public function getRedirectChain() { + $text = $this->getNativeData(); + return Title::newFromRedirectArray( $text ); + } + */ + + /** + * @todo Test needs database! + */ + /* + public function getUltimateRedirectTarget() { + $text = $this->getNativeData(); + return Title::newFromRedirectRecurse( $text ); + } + */ + + public static function dataIsCountable() { + return array( + array( '', + null, + 'any', + true + ), + array( 'Foo', + null, + 'any', + true + ), + array( 'Foo', + null, + 'comma', + false + ), + array( 'Foo, bar', + null, + 'comma', + false + ), + array( 'Foo', + null, + 'link', + false + ), + array( 'Foo [[bar]]', + null, + 'link', + false + ), + array( 'Foo', + true, + 'link', + false + ), + array( 'Foo [[bar]]', + false, + 'link', + false + ), + array( '#REDIRECT [[bar]]', + true, + 'any', + true + ), + array( '#REDIRECT [[bar]]', + true, + 'comma', + false + ), + array( '#REDIRECT [[bar]]', + true, + 'link', + false + ), + ); + } + + public static function dataGetTextForSummary() { + return array( + array( "hello\nworld.", + 16, + 'hello world.', + ), + array( 'hello world.', + 8, + 'hello...', + ), + array( '[[hello world]].', + 8, + '[[hel...', + ), + ); + } + + /** + * @covers JavaScriptContent::matchMagicWord + */ + public function testMatchMagicWord() { + $mw = MagicWord::get( "staticredirect" ); + + $content = $this->newContent( "#REDIRECT [[FOO]]\n__STATICREDIRECT__" ); + $this->assertFalse( $content->matchMagicWord( $mw ), "should not have matched magic word, since it's not wikitext" ); + } + + /** + * @covers JavaScriptContent::updateRedirect + */ + public function testUpdateRedirect() { + $target = Title::newFromText( "testUpdateRedirect_target" ); + + $content = $this->newContent( "#REDIRECT [[Someplace]]" ); + $newContent = $content->updateRedirect( $target ); + + $this->assertTrue( $content->equals( $newContent ), "content should be unchanged since it's not wikitext" ); + } + + /** + * @covers JavaScriptContent::getModel + */ + public function testGetModel() { + $content = $this->newContent( "hello world." ); + + $this->assertEquals( CONTENT_MODEL_JAVASCRIPT, $content->getModel() ); + } + + /** + * @covers JavaScriptContent::getContentHandler + */ + public function testGetContentHandler() { + $content = $this->newContent( "hello world." ); + + $this->assertEquals( CONTENT_MODEL_JAVASCRIPT, $content->getContentHandler()->getModelID() ); + } + + public static function dataEquals() { + return array( + array( new JavaScriptContent( "hallo" ), null, false ), + array( new JavaScriptContent( "hallo" ), new JavaScriptContent( "hallo" ), true ), + array( new JavaScriptContent( "hallo" ), new CssContent( "hallo" ), false ), + array( new JavaScriptContent( "hallo" ), new JavaScriptContent( "HALLO" ), false ), + ); + } +} diff --git a/tests/phpunit/includes/content/TextContentTest.php b/tests/phpunit/includes/content/TextContentTest.php new file mode 100644 index 00000000..a1f099f3 --- /dev/null +++ b/tests/phpunit/includes/content/TextContentTest.php @@ -0,0 +1,458 @@ +<?php + +/** + * @group ContentHandler + * @group Database + * ^--- needed, because we do need the database to test link updates + */ +class TextContentTest extends MediaWikiLangTestCase { + protected $context; + + protected function setUp() { + parent::setUp(); + + // Anon user + $user = new User(); + $user->setName( '127.0.0.1' ); + + $this->setMwGlobals( array( + 'wgUser' => $user, + 'wgTextModelsToParse' => array( + CONTENT_MODEL_WIKITEXT, + CONTENT_MODEL_CSS, + CONTENT_MODEL_JAVASCRIPT, + ), + 'wgUseTidy' => false, + 'wgAlwaysUseTidy' => false, + ) ); + + $this->context = new RequestContext( new FauxRequest() ); + $this->context->setTitle( Title::newFromText( 'Test' ) ); + $this->context->setUser( $user ); + } + + public function newContent( $text ) { + return new TextContent( $text ); + } + + public static function dataGetParserOutput() { + return array( + array( + 'TextContentTest_testGetParserOutput', + CONTENT_MODEL_TEXT, + "hello ''world'' & [[stuff]]\n", "hello ''world'' & [[stuff]]", + array( + 'Links' => array() + ) + ), + // TODO: more...? + ); + } + + /** + * @dataProvider dataGetParserOutput + * @covers TextContent::getParserOutput + */ + public function testGetParserOutput( $title, $model, $text, $expectedHtml, $expectedFields = null ) { + $title = Title::newFromText( $title ); + $content = ContentHandler::makeContent( $text, $title, $model ); + + $po = $content->getParserOutput( $title ); + + $html = $po->getText(); + $html = preg_replace( '#<!--.*?-->#sm', '', $html ); // strip comments + + $this->assertEquals( $expectedHtml, trim( $html ) ); + + if ( $expectedFields ) { + foreach ( $expectedFields as $field => $exp ) { + $f = 'get' . ucfirst( $field ); + $v = call_user_func( array( $po, $f ) ); + + if ( is_array( $exp ) ) { + $this->assertArrayEquals( $exp, $v ); + } else { + $this->assertEquals( $exp, $v ); + } + } + } + + // TODO: assert more properties + } + + public static function dataPreSaveTransform() { + return array( + array( + #0: no signature resolution + 'hello this is ~~~', + 'hello this is ~~~', + ), + array( + #1: rtrim + " Foo \n ", + ' Foo', + ), + ); + } + + /** + * @dataProvider dataPreSaveTransform + * @covers TextContent::preSaveTransform + */ + public function testPreSaveTransform( $text, $expected ) { + global $wgContLang; + + $options = ParserOptions::newFromUserAndLang( $this->context->getUser(), $wgContLang ); + + $content = $this->newContent( $text ); + $content = $content->preSaveTransform( $this->context->getTitle(), $this->context->getUser(), $options ); + + $this->assertEquals( $expected, $content->getNativeData() ); + } + + public static function dataPreloadTransform() { + return array( + array( + 'hello this is ~~~', + 'hello this is ~~~', + ), + ); + } + + /** + * @dataProvider dataPreloadTransform + * @covers TextContent::preloadTransform + */ + public function testPreloadTransform( $text, $expected ) { + global $wgContLang; + $options = ParserOptions::newFromUserAndLang( $this->context->getUser(), $wgContLang ); + + $content = $this->newContent( $text ); + $content = $content->preloadTransform( $this->context->getTitle(), $options ); + + $this->assertEquals( $expected, $content->getNativeData() ); + } + + public static function dataGetRedirectTarget() { + return array( + array( '#REDIRECT [[Test]]', + null, + ), + ); + } + + /** + * @dataProvider dataGetRedirectTarget + * @covers TextContent::getRedirectTarget + */ + public function testGetRedirectTarget( $text, $expected ) { + $content = $this->newContent( $text ); + $t = $content->getRedirectTarget(); + + if ( is_null( $expected ) ) { + $this->assertNull( $t, "text should not have generated a redirect target: $text" ); + } else { + $this->assertEquals( $expected, $t->getPrefixedText() ); + } + } + + /** + * @dataProvider dataGetRedirectTarget + * @covers TextContent::isRedirect + */ + public function testIsRedirect( $text, $expected ) { + $content = $this->newContent( $text ); + + $this->assertEquals( !is_null( $expected ), $content->isRedirect() ); + } + + /** + * @todo Test needs database! Should be done by a test class in the Database group. + */ + /* + public function getRedirectChain() { + $text = $this->getNativeData(); + return Title::newFromRedirectArray( $text ); + } + */ + + /** + * @todo Test needs database! Should be done by a test class in the Database group. + */ + /* + public function getUltimateRedirectTarget() { + $text = $this->getNativeData(); + return Title::newFromRedirectRecurse( $text ); + } + */ + + public static function dataIsCountable() { + return array( + array( '', + null, + 'any', + true + ), + array( 'Foo', + null, + 'any', + true + ), + array( 'Foo', + null, + 'comma', + false + ), + array( 'Foo, bar', + null, + 'comma', + false + ), + ); + } + + /** + * @dataProvider dataIsCountable + * @group Database + * @covers TextContent::isCountable + */ + public function testIsCountable( $text, $hasLinks, $mode, $expected ) { + $this->setMwGlobals( 'wgArticleCountMethod', $mode ); + + $content = $this->newContent( $text ); + + $v = $content->isCountable( $hasLinks, $this->context->getTitle() ); + + $this->assertEquals( $expected, $v, 'isCountable() returned unexpected value ' . var_export( $v, true ) + . ' instead of ' . var_export( $expected, true ) . " in mode `$mode` for text \"$text\"" ); + } + + public static function dataGetTextForSummary() { + return array( + array( "hello\nworld.", + 16, + 'hello world.', + ), + array( 'hello world.', + 8, + 'hello...', + ), + array( '[[hello world]].', + 8, + '[[hel...', + ), + ); + } + + /** + * @dataProvider dataGetTextForSummary + * @covers TextContent::getTextForSummary + */ + public function testGetTextForSummary( $text, $maxlength, $expected ) { + $content = $this->newContent( $text ); + + $this->assertEquals( $expected, $content->getTextForSummary( $maxlength ) ); + } + + /** + * @covers TextContent::getTextForSearchIndex + */ + public function testGetTextForSearchIndex() { + $content = $this->newContent( 'hello world.' ); + + $this->assertEquals( 'hello world.', $content->getTextForSearchIndex() ); + } + + /** + * @covers TextContent::copy + */ + public function testCopy() { + $content = $this->newContent( 'hello world.' ); + $copy = $content->copy(); + + $this->assertTrue( $content->equals( $copy ), 'copy must be equal to original' ); + $this->assertEquals( 'hello world.', $copy->getNativeData() ); + } + + /** + * @covers TextContent::getSize + */ + public function testGetSize() { + $content = $this->newContent( 'hello world.' ); + + $this->assertEquals( 12, $content->getSize() ); + } + + /** + * @covers TextContent::getNativeData + */ + public function testGetNativeData() { + $content = $this->newContent( 'hello world.' ); + + $this->assertEquals( 'hello world.', $content->getNativeData() ); + } + + /** + * @covers TextContent::getWikitextForTransclusion + */ + public function testGetWikitextForTransclusion() { + $content = $this->newContent( 'hello world.' ); + + $this->assertEquals( 'hello world.', $content->getWikitextForTransclusion() ); + } + + /** + * @covers TextContent::getModel + */ + public function testGetModel() { + $content = $this->newContent( "hello world." ); + + $this->assertEquals( CONTENT_MODEL_TEXT, $content->getModel() ); + } + + /** + * @covers TextContent::getContentHandler + */ + public function testGetContentHandler() { + $content = $this->newContent( "hello world." ); + + $this->assertEquals( CONTENT_MODEL_TEXT, $content->getContentHandler()->getModelID() ); + } + + public static function dataIsEmpty() { + return array( + array( '', true ), + array( ' ', false ), + array( '0', false ), + array( 'hallo welt.', false ), + ); + } + + /** + * @dataProvider dataIsEmpty + * @covers TextContent::isEmpty + */ + public function testIsEmpty( $text, $empty ) { + $content = $this->newContent( $text ); + + $this->assertEquals( $empty, $content->isEmpty() ); + } + + public static function dataEquals() { + return array( + array( new TextContent( "hallo" ), null, false ), + array( new TextContent( "hallo" ), new TextContent( "hallo" ), true ), + array( new TextContent( "hallo" ), new JavaScriptContent( "hallo" ), false ), + array( new TextContent( "hallo" ), new WikitextContent( "hallo" ), false ), + array( new TextContent( "hallo" ), new TextContent( "HALLO" ), false ), + ); + } + + /** + * @dataProvider dataEquals + * @covers TextContent::equals + */ + public function testEquals( Content $a, Content $b = null, $equal = false ) { + $this->assertEquals( $equal, $a->equals( $b ) ); + } + + public static function dataGetDeletionUpdates() { + return array( + array( "TextContentTest_testGetSecondaryDataUpdates_1", + CONTENT_MODEL_TEXT, "hello ''world''\n", + array() + ), + array( "TextContentTest_testGetSecondaryDataUpdates_2", + CONTENT_MODEL_TEXT, "hello [[world test 21344]]\n", + array() + ), + // TODO: more...? + ); + } + + /** + * @dataProvider dataGetDeletionUpdates + * @covers TextContent::getDeletionUpdates + */ + public function testDeletionUpdates( $title, $model, $text, $expectedStuff ) { + $ns = $this->getDefaultWikitextNS(); + $title = Title::newFromText( $title, $ns ); + + $content = ContentHandler::makeContent( $text, $title, $model ); + + $page = WikiPage::factory( $title ); + $page->doEditContent( $content, '' ); + + $updates = $content->getDeletionUpdates( $page ); + + // make updates accessible by class name + foreach ( $updates as $update ) { + $class = get_class( $update ); + $updates[$class] = $update; + } + + if ( !$expectedStuff ) { + $this->assertTrue( true ); // make phpunit happy + return; + } + + foreach ( $expectedStuff as $class => $fieldValues ) { + $this->assertArrayHasKey( $class, $updates, "missing an update of type $class" ); + + $update = $updates[$class]; + + foreach ( $fieldValues as $field => $value ) { + $v = $update->$field; #if the field doesn't exist, just crash and burn + $this->assertEquals( $value, $v, "unexpected value for field $field in instance of $class" ); + } + } + + $page->doDeleteArticle( '' ); + } + + public static function provideConvert() { + return array( + array( // #0 + 'Hallo Welt', + CONTENT_MODEL_WIKITEXT, + 'lossless', + 'Hallo Welt' + ), + array( // #1 + 'Hallo Welt', + CONTENT_MODEL_WIKITEXT, + 'lossless', + 'Hallo Welt' + ), + array( // #1 + 'Hallo Welt', + CONTENT_MODEL_CSS, + 'lossless', + 'Hallo Welt' + ), + array( // #1 + 'Hallo Welt', + CONTENT_MODEL_JAVASCRIPT, + 'lossless', + 'Hallo Welt' + ), + ); + } + + /** + * @dataProvider provideConvert + * @covers TextContent::convert + */ + public function testConvert( $text, $model, $lossy, $expectedNative ) { + $content = $this->newContent( $text ); + + $converted = $content->convert( $model, $lossy ); + + if ( $expectedNative === false ) { + $this->assertFalse( $converted, "conversion to $model was expected to fail!" ); + } else { + $this->assertInstanceOf( 'Content', $converted ); + $this->assertEquals( $expectedNative, $converted->getNativeData() ); + } + } +} diff --git a/tests/phpunit/includes/content/WikitextContentHandlerTest.php b/tests/phpunit/includes/content/WikitextContentHandlerTest.php new file mode 100644 index 00000000..75a72784 --- /dev/null +++ b/tests/phpunit/includes/content/WikitextContentHandlerTest.php @@ -0,0 +1,227 @@ +<?php + +/** + * @group ContentHandler + */ +class WikitextContentHandlerTest extends MediaWikiLangTestCase { + + /** + * @var ContentHandler + */ + var $handler; + + public function setUp() { + parent::setUp(); + + $this->handler = ContentHandler::getForModelID( CONTENT_MODEL_WIKITEXT ); + } + + /** + * @covers WikitextContentHandler::serializeContent + */ + public function testSerializeContent() { + $content = new WikitextContent( 'hello world' ); + + $this->assertEquals( 'hello world', $this->handler->serializeContent( $content ) ); + $this->assertEquals( 'hello world', $this->handler->serializeContent( $content, CONTENT_FORMAT_WIKITEXT ) ); + + try { + $this->handler->serializeContent( $content, 'dummy/foo' ); + $this->fail( "serializeContent() should have failed on unknown format" ); + } catch ( MWException $e ) { + // ok, as expected + } + } + + /** + * @covers WikitextContentHandler::unserializeContent + */ + public function testUnserializeContent() { + $content = $this->handler->unserializeContent( 'hello world' ); + $this->assertEquals( 'hello world', $content->getNativeData() ); + + $content = $this->handler->unserializeContent( 'hello world', CONTENT_FORMAT_WIKITEXT ); + $this->assertEquals( 'hello world', $content->getNativeData() ); + + try { + $this->handler->unserializeContent( 'hello world', 'dummy/foo' ); + $this->fail( "unserializeContent() should have failed on unknown format" ); + } catch ( MWException $e ) { + // ok, as expected + } + } + + /** + * @covers WikitextContentHandler::makeEmptyContent + */ + public function testMakeEmptyContent() { + $content = $this->handler->makeEmptyContent(); + + $this->assertTrue( $content->isEmpty() ); + $this->assertEquals( '', $content->getNativeData() ); + } + + public static function dataIsSupportedFormat() { + return array( + array( null, true ), + array( CONTENT_FORMAT_WIKITEXT, true ), + array( 99887766, false ), + ); + } + + /** + * @dataProvider provideMakeRedirectContent + * @param Title|string $title Title object or string for Title::newFromText() + * @param string $expected Serialized form of the content object built + * @covers WikitextContentHandler::makeRedirectContent + */ + public function testMakeRedirectContent( $title, $expected ) { + global $wgContLang; + $wgContLang->resetNamespaces(); + + if ( is_string( $title ) ) { + $title = Title::newFromText( $title ); + } + $content = $this->handler->makeRedirectContent( $title ); + $this->assertEquals( $expected, $content->serialize() ); + } + + public static function provideMakeRedirectContent() { + return array( + array( 'Hello', '#REDIRECT [[Hello]]' ), + array( 'Template:Hello', '#REDIRECT [[Template:Hello]]' ), + array( 'Hello#section', '#REDIRECT [[Hello#section]]' ), + array( 'user:john_doe#section', '#REDIRECT [[User:John doe#section]]' ), + array( 'MEDIAWIKI:FOOBAR', '#REDIRECT [[MediaWiki:FOOBAR]]' ), + array( 'Category:Foo', '#REDIRECT [[:Category:Foo]]' ), + array( Title::makeTitle( NS_MAIN, 'en:Foo' ), '#REDIRECT [[en:Foo]]' ), + array( Title::makeTitle( NS_MAIN, 'Foo', '', 'en' ), '#REDIRECT [[:en:Foo]]' ), + array( Title::makeTitle( NS_MAIN, 'Bar', 'fragment', 'google' ), '#REDIRECT [[google:Bar#fragment]]' ), + ); + } + + /** + * @dataProvider dataIsSupportedFormat + * @covers WikitextContentHandler::isSupportedFormat + */ + public function testIsSupportedFormat( $format, $supported ) { + $this->assertEquals( $supported, $this->handler->isSupportedFormat( $format ) ); + } + + public static function dataMerge3() { + return array( + array( + "first paragraph + + second paragraph\n", + + "FIRST paragraph + + second paragraph\n", + + "first paragraph + + SECOND paragraph\n", + + "FIRST paragraph + + SECOND paragraph\n", + ), + + array( "first paragraph + second paragraph\n", + + "Bla bla\n", + + "Blubberdibla\n", + + false, + ), + ); + } + + /** + * @dataProvider dataMerge3 + * @covers WikitextContentHandler::merge3 + */ + public function testMerge3( $old, $mine, $yours, $expected ) { + $this->checkHasDiff3(); + + // test merge + $oldContent = new WikitextContent( $old ); + $myContent = new WikitextContent( $mine ); + $yourContent = new WikitextContent( $yours ); + + $merged = $this->handler->merge3( $oldContent, $myContent, $yourContent ); + + $this->assertEquals( $expected, $merged ? $merged->getNativeData() : $merged ); + } + + public static function dataGetAutosummary() { + return array( + array( + 'Hello there, world!', + '#REDIRECT [[Foo]]', + 0, + '/^Redirected page .*Foo/' + ), + + array( + null, + 'Hello world!', + EDIT_NEW, + '/^Created page .*Hello/' + ), + + array( + 'Hello there, world!', + '', + 0, + '/^Blanked/' + ), + + array( + 'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut + labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et + ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.', + 'Hello world!', + 0, + '/^Replaced .*Hello/' + ), + + array( + 'foo', + 'bar', + 0, + '/^$/' + ), + ); + } + + /** + * @dataProvider dataGetAutosummary + * @covers WikitextContentHandler::getAutosummary + */ + public function testGetAutosummary( $old, $new, $flags, $expected ) { + $oldContent = is_null( $old ) ? null : new WikitextContent( $old ); + $newContent = is_null( $new ) ? null : new WikitextContent( $new ); + + $summary = $this->handler->getAutosummary( $oldContent, $newContent, $flags ); + + $this->assertTrue( (bool)preg_match( $expected, $summary ), "Autosummary didn't match expected pattern $expected: $summary" ); + } + + /** + * @todo Text case requires database, should be done by a test class in the Database group + */ + /* + public function testGetAutoDeleteReason( Title $title, &$hasHistory ) {} + */ + + /** + * @todo Text case requires database, should be done by a test class in the Database group + */ + /* + public function testGetUndoContent( Revision $current, Revision $undo, Revision $undoafter = null ) {} + */ +} diff --git a/tests/phpunit/includes/content/WikitextContentTest.php b/tests/phpunit/includes/content/WikitextContentTest.php new file mode 100644 index 00000000..9f20073d --- /dev/null +++ b/tests/phpunit/includes/content/WikitextContentTest.php @@ -0,0 +1,404 @@ +<?php + +/** + * @group ContentHandler + * + * @group Database + * ^--- needed, because we do need the database to test link updates + */ +class WikitextContentTest extends TextContentTest { + static $sections = "Intro + +== stuff == +hello world + +== test == +just a test + +== foo == +more stuff +"; + + public function newContent( $text ) { + return new WikitextContent( $text ); + } + + public static function dataGetParserOutput() { + return array( + array( + "WikitextContentTest_testGetParserOutput", + CONTENT_MODEL_WIKITEXT, + "hello ''world''\n", + "<p>hello <i>world</i>\n</p>" + ), + // TODO: more...? + ); + } + + public static function dataGetSecondaryDataUpdates() { + return array( + array( "WikitextContentTest_testGetSecondaryDataUpdates_1", + CONTENT_MODEL_WIKITEXT, "hello ''world''\n", + array( + 'LinksUpdate' => array( + 'mRecursive' => true, + 'mLinks' => array() + ) + ) + ), + array( "WikitextContentTest_testGetSecondaryDataUpdates_2", + CONTENT_MODEL_WIKITEXT, "hello [[world test 21344]]\n", + array( + 'LinksUpdate' => array( + 'mRecursive' => true, + 'mLinks' => array( + array( 'World_test_21344' => 0 ) + ) + ) + ) + ), + // TODO: more...? + ); + } + + /** + * @dataProvider dataGetSecondaryDataUpdates + * @group Database + * @covers WikitextContent::getSecondaryDataUpdates + */ + public function testGetSecondaryDataUpdates( $title, $model, $text, $expectedStuff ) { + $ns = $this->getDefaultWikitextNS(); + $title = Title::newFromText( $title, $ns ); + + $content = ContentHandler::makeContent( $text, $title, $model ); + + $page = WikiPage::factory( $title ); + $page->doEditContent( $content, '' ); + + $updates = $content->getSecondaryDataUpdates( $title ); + + // make updates accessible by class name + foreach ( $updates as $update ) { + $class = get_class( $update ); + $updates[$class] = $update; + } + + foreach ( $expectedStuff as $class => $fieldValues ) { + $this->assertArrayHasKey( $class, $updates, "missing an update of type $class" ); + + $update = $updates[$class]; + + foreach ( $fieldValues as $field => $value ) { + $v = $update->$field; #if the field doesn't exist, just crash and burn + $this->assertEquals( $value, $v, "unexpected value for field $field in instance of $class" ); + } + } + + $page->doDeleteArticle( '' ); + } + + public static function dataGetSection() { + return array( + array( WikitextContentTest::$sections, + "0", + "Intro" + ), + array( WikitextContentTest::$sections, + "2", + "== test == +just a test" + ), + array( WikitextContentTest::$sections, + "8", + false + ), + ); + } + + /** + * @dataProvider dataGetSection + * @covers WikitextContent::getSection + */ + public function testGetSection( $text, $sectionId, $expectedText ) { + $content = $this->newContent( $text ); + + $sectionContent = $content->getSection( $sectionId ); + if ( is_object( $sectionContent ) ) { + $sectionText = $sectionContent->getNativeData(); + } else { + $sectionText = $sectionContent; + } + + $this->assertEquals( $expectedText, $sectionText ); + } + + public static function dataReplaceSection() { + return array( + array( WikitextContentTest::$sections, + "0", + "No more", + null, + trim( preg_replace( '/^Intro/sm', 'No more', WikitextContentTest::$sections ) ) + ), + array( WikitextContentTest::$sections, + "", + "No more", + null, + "No more" + ), + array( WikitextContentTest::$sections, + "2", + "== TEST ==\nmore fun", + null, + trim( preg_replace( '/^== test ==.*== foo ==/sm', "== TEST ==\nmore fun\n\n== foo ==", WikitextContentTest::$sections ) ) + ), + array( WikitextContentTest::$sections, + "8", + "No more", + null, + WikitextContentTest::$sections + ), + array( WikitextContentTest::$sections, + "new", + "No more", + "New", + trim( WikitextContentTest::$sections ) . "\n\n\n== New ==\n\nNo more" + ), + ); + } + + /** + * @dataProvider dataReplaceSection + * @covers WikitextContent::replaceSection + */ + public function testReplaceSection( $text, $section, $with, $sectionTitle, $expected ) { + $content = $this->newContent( $text ); + $c = $content->replaceSection( $section, $this->newContent( $with ), $sectionTitle ); + + $this->assertEquals( $expected, is_null( $c ) ? null : $c->getNativeData() ); + } + + /** + * @covers WikitextContent::addSectionHeader + */ + public function testAddSectionHeader() { + $content = $this->newContent( 'hello world' ); + $content = $content->addSectionHeader( 'test' ); + + $this->assertEquals( "== test ==\n\nhello world", $content->getNativeData() ); + } + + public static function dataPreSaveTransform() { + return array( + array( 'hello this is ~~~', + "hello this is [[Special:Contributions/127.0.0.1|127.0.0.1]]", + ), + array( 'hello \'\'this\'\' is <nowiki>~~~</nowiki>', + 'hello \'\'this\'\' is <nowiki>~~~</nowiki>', + ), + array( // rtrim + " Foo \n ", + " Foo", + ), + ); + } + + public static function dataPreloadTransform() { + return array( + array( 'hello this is ~~~', + "hello this is ~~~", + ), + array( 'hello \'\'this\'\' is <noinclude>foo</noinclude><includeonly>bar</includeonly>', + 'hello \'\'this\'\' is bar', + ), + ); + } + + public static function dataGetRedirectTarget() { + return array( + array( '#REDIRECT [[Test]]', + 'Test', + ), + array( '#REDIRECT Test', + null, + ), + array( '* #REDIRECT [[Test]]', + null, + ), + ); + } + + public static function dataGetTextForSummary() { + return array( + array( "hello\nworld.", + 16, + 'hello world.', + ), + array( 'hello world.', + 8, + 'hello...', + ), + array( '[[hello world]].', + 8, + 'hel...', + ), + ); + } + + /** + * @todo Test needs database! Should be done by a test class in the Database group. + */ + /* + public function getRedirectChain() { + $text = $this->getNativeData(); + return Title::newFromRedirectArray( $text ); + } + */ + + /** + * @todo Test needs database! Should be done by a test class in the Database group. + */ + /* + public function getUltimateRedirectTarget() { + $text = $this->getNativeData(); + return Title::newFromRedirectRecurse( $text ); + } + */ + + public static function dataIsCountable() { + return array( + array( '', + null, + 'any', + true + ), + array( 'Foo', + null, + 'any', + true + ), + array( 'Foo', + null, + 'comma', + false + ), + array( 'Foo, bar', + null, + 'comma', + true + ), + array( 'Foo', + null, + 'link', + false + ), + array( 'Foo [[bar]]', + null, + 'link', + true + ), + array( 'Foo', + true, + 'link', + true + ), + array( 'Foo [[bar]]', + false, + 'link', + false + ), + array( '#REDIRECT [[bar]]', + true, + 'any', + false + ), + array( '#REDIRECT [[bar]]', + true, + 'comma', + false + ), + array( '#REDIRECT [[bar]]', + true, + 'link', + false + ), + ); + } + + /** + * @covers WikitextContent::matchMagicWord + */ + public function testMatchMagicWord() { + $mw = MagicWord::get( "staticredirect" ); + + $content = $this->newContent( "#REDIRECT [[FOO]]\n__STATICREDIRECT__" ); + $this->assertTrue( $content->matchMagicWord( $mw ), "should have matched magic word" ); + + $content = $this->newContent( "#REDIRECT [[FOO]]" ); + $this->assertFalse( $content->matchMagicWord( $mw ), "should not have matched magic word" ); + } + + /** + * @covers WikitextContent::updateRedirect + */ + public function testUpdateRedirect() { + $target = Title::newFromText( "testUpdateRedirect_target" ); + + // test with non-redirect page + $content = $this->newContent( "hello world." ); + $newContent = $content->updateRedirect( $target ); + + $this->assertTrue( $content->equals( $newContent ), "content should be unchanged" ); + + // test with actual redirect + $content = $this->newContent( "#REDIRECT [[Someplace]]" ); + $newContent = $content->updateRedirect( $target ); + + $this->assertFalse( $content->equals( $newContent ), "content should have changed" ); + $this->assertTrue( $newContent->isRedirect(), "new content should be a redirect" ); + + $this->assertEquals( $target->getFullText(), $newContent->getRedirectTarget()->getFullText() ); + } + + /** + * @covers WikitextContent::getModel + */ + public function testGetModel() { + $content = $this->newContent( "hello world." ); + + $this->assertEquals( CONTENT_MODEL_WIKITEXT, $content->getModel() ); + } + + /** + * @covers WikitextContent::getContentHandler + */ + public function testGetContentHandler() { + $content = $this->newContent( "hello world." ); + + $this->assertEquals( CONTENT_MODEL_WIKITEXT, $content->getContentHandler()->getModelID() ); + } + + public static function dataEquals() { + return array( + array( new WikitextContent( "hallo" ), null, false ), + array( new WikitextContent( "hallo" ), new WikitextContent( "hallo" ), true ), + array( new WikitextContent( "hallo" ), new JavaScriptContent( "hallo" ), false ), + array( new WikitextContent( "hallo" ), new TextContent( "hallo" ), false ), + array( new WikitextContent( "hallo" ), new WikitextContent( "HALLO" ), false ), + ); + } + + public static function dataGetDeletionUpdates() { + return array( + array( "WikitextContentTest_testGetSecondaryDataUpdates_1", + CONTENT_MODEL_WIKITEXT, "hello ''world''\n", + array( 'LinksDeletionUpdate' => array() ) + ), + array( "WikitextContentTest_testGetSecondaryDataUpdates_2", + CONTENT_MODEL_WIKITEXT, "hello [[world test 21344]]\n", + array( 'LinksDeletionUpdate' => array() ) + ), + // @todo more...? + ); + } +} diff --git a/tests/phpunit/includes/db/DatabaseMysqlBaseTest.php b/tests/phpunit/includes/db/DatabaseMysqlBaseTest.php new file mode 100644 index 00000000..ba63c091 --- /dev/null +++ b/tests/phpunit/includes/db/DatabaseMysqlBaseTest.php @@ -0,0 +1,209 @@ +<?php +/** + * Holds tests for DatabaseMysqlBase MediaWiki class. + * + * @section LICENSE + * 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 + * + * @file + * @author Antoine Musso + * @author Bryan Davis + * @copyright © 2013 Antoine Musso + * @copyright © 2013 Bryan Davis + * @copyright © 2013 Wikimedia Foundation Inc. + */ + +/** + * Fake class around abstract class so we can call concrete methods. + */ +class FakeDatabaseMysqlBase extends DatabaseMysqlBase { + // From DatabaseBase + protected function closeConnection() {} + protected function doQuery( $sql ) {} + + // From DatabaseMysql + protected function mysqlConnect( $realServer ) {} + protected function mysqlFreeResult( $res ) {} + protected function mysqlFetchObject( $res ) {} + protected function mysqlFetchArray( $res ) {} + protected function mysqlNumRows( $res ) {} + protected function mysqlNumFields( $res ) {} + protected function mysqlFieldName( $res, $n ) {} + protected function mysqlDataSeek( $res, $row ) {} + protected function mysqlError( $conn = null ) {} + protected function mysqlFetchField( $res, $n ) {} + protected function mysqlPing() {} + + // From interface DatabaseType + function insertId() {} + function lastErrno() {} + function affectedRows() {} + function getServerVersion() {} +} + +class DatabaseMysqlBaseTest extends MediaWikiTestCase { + + /** + * @dataProvider provideDiapers + * @covers DatabaseMysqlBase::addIdentifierQuotes + */ + public function testAddIdentifierQuotes( $expected, $in ) { + $db = new FakeDatabaseMysqlBase(); + $quoted = $db->addIdentifierQuotes( $in ); + $this->assertEquals($expected, $quoted); + } + + + /** + * Feeds testAddIdentifierQuotes + * + * Named per bug 20281 convention. + */ + function provideDiapers() { + return array( + // Format: expected, input + array( '``', '' ), + + // Yeah I really hate loosely typed PHP idiocies nowadays + array( '``', null ), + + // Dear codereviewer, guess what addIdentifierQuotes() + // will return with thoses: + array( '``', false ), + array( '`1`', true ), + + // We never know what could happen + array( '`0`', 0 ), + array( '`1`', 1 ), + + // Whatchout! Should probably use something more meaningful + array( "`'`", "'" ), # single quote + array( '`"`', '"' ), # double quote + array( '````', '`' ), # backtick + array( '`’`', '’' ), # apostrophe (look at your encyclopedia) + + // sneaky NUL bytes are lurking everywhere + array( '``', "\0" ), + array( '`xyzzy`', "\0x\0y\0z\0z\0y\0" ), + + // unicode chars + array( + self::createUnicodeString( '`\u0001a\uFFFFb`' ), + self::createUnicodeString( '\u0001a\uFFFFb' ) + ), + array( + self::createUnicodeString( '`\u0001\uFFFF`' ), + self::createUnicodeString( '\u0001\u0000\uFFFF\u0000' ) + ), + array( '`☃`', '☃' ), + array( '`メインページ`', 'メインページ' ), + array( '`Басты_бет`', 'Басты_бет' ), + + // Real world: + array( '`Alix`', 'Alix' ), # while( ! $recovered ) { sleep(); } + array( '`Backtick: ```', 'Backtick: `' ), + array( '`This is a test`', 'This is a test' ), + ); + } + + private static function createUnicodeString($str) { + return json_decode( '"' . $str . '"' ); + } + + function getMockForViews() { + $db = $this->getMockBuilder( 'DatabaseMysql' ) + ->disableOriginalConstructor() + ->setMethods( array( 'fetchRow', 'query' ) ) + ->getMock(); + + $db->expects( $this->any() ) + ->method( 'query' ) + ->with( $this->anything() ) + ->will( + $this->returnValue( null ) + ); + + $db->expects( $this->any() ) + ->method( 'fetchRow' ) + ->with( $this->anything() ) + ->will( $this->onConsecutiveCalls( + array( 'Tables_in_' => 'view1' ), + array( 'Tables_in_' => 'view2' ), + array( 'Tables_in_' => 'myview' ), + false # no more rows + )); + return $db; + } + /** + * @covers DatabaseMysqlBase::listViews + */ + function testListviews() { + $db = $this->getMockForViews(); + + // The first call populate an internal cache of views + $this->assertEquals( array( 'view1', 'view2', 'myview'), + $db->listViews() ); + $this->assertEquals( array( 'view1', 'view2', 'myview'), + $db->listViews() ); + + // Prefix filtering + $this->assertEquals( array( 'view1', 'view2' ), + $db->listViews( 'view' ) ); + $this->assertEquals( array( 'myview' ), + $db->listViews( 'my' ) ); + $this->assertEquals( array(), + $db->listViews( 'UNUSED_PREFIX' ) ); + $this->assertEquals( array( 'view1', 'view2', 'myview'), + $db->listViews( '' ) ); + } + + /** + * @covers DatabaseMysqlBase::isView + * @dataProvider provideViewExistanceChecks + */ + function testIsView( $isView, $viewName ) { + $db = $this->getMockForViews(); + + switch( $isView ) { + case true: + $this->assertTrue( $db->isView( $viewName ), + "$viewName should be considered a view" ); + break; + + case false: + $this->assertFalse( $db->isView( $viewName ), + "$viewName has not been defined as a view" ); + break; + } + + } + + function provideViewExistanceChecks() { + return array( + // format: whether it is a view, view name + array( true, 'view1' ), + array( true, 'view2' ), + array( true, 'myview' ), + + array( false, 'user' ), + + array( false, 'view10' ), + array( false, 'my' ), + array( false, 'OH_MY_GOD' ), # they killed kenny! + ); + } + +} diff --git a/tests/phpunit/includes/db/DatabaseSQLTest.php b/tests/phpunit/includes/db/DatabaseSQLTest.php index e37cd445..bdd567e7 100644 --- a/tests/phpunit/includes/db/DatabaseSQLTest.php +++ b/tests/phpunit/includes/db/DatabaseSQLTest.php @@ -2,34 +2,44 @@ /** * Test the abstract database layer - * Using Mysql for the sql at the moment TODO - * - * @group Database + * This is a non DBMS depending test. */ class DatabaseSQLTest extends MediaWikiTestCase { - public function setUp() { - // TODO support other DBMS or find another way to do it - if( $this->db->getType() !== 'mysql' ) { - $this->markTestSkipped( 'No mysql database' ); - } + /** + * @var DatabaseTestHelper + */ + private $database; + + protected function setUp() { + parent::setUp(); + $this->database = new DatabaseTestHelper( __CLASS__ ); + } + + protected function assertLastSql( $sqlText ) { + $this->assertEquals( + $this->database->getLastSqls(), + $sqlText + ); } /** - * @dataProvider dataSelectSQLText + * @dataProvider provideSelect + * @covers DatabaseBase::select */ - function testSelectSQLText( $sql, $sqlText ) { - $this->assertEquals( trim( $this->db->selectSQLText( - isset( $sql['tables'] ) ? $sql['tables'] : array(), - isset( $sql['fields'] ) ? $sql['fields'] : array(), + public function testSelect( $sql, $sqlText ) { + $this->database->select( + $sql['tables'], + $sql['fields'], isset( $sql['conds'] ) ? $sql['conds'] : array(), __METHOD__, isset( $sql['options'] ) ? $sql['options'] : array(), isset( $sql['join_conds'] ) ? $sql['join_conds'] : array() - ) ), $sqlText ); + ); + $this->assertLastSql( $sqlText ); } - function dataSelectSQLText() { + public static function provideSelect() { return array( array( array( @@ -37,9 +47,9 @@ class DatabaseSQLTest extends MediaWikiTestCase { 'fields' => array( 'field', 'alias' => 'field2' ), 'conds' => array( 'alias' => 'text' ), ), - "SELECT field,field2 AS alias " . - "FROM `unittest_table` " . - "WHERE alias = 'text'" + "SELECT field,field2 AS alias " . + "FROM table " . + "WHERE alias = 'text'" ), array( array( @@ -48,11 +58,11 @@ class DatabaseSQLTest extends MediaWikiTestCase { 'conds' => array( 'alias' => 'text' ), 'options' => array( 'LIMIT' => 1, 'ORDER BY' => 'field' ), ), - "SELECT field,field2 AS alias " . - "FROM `unittest_table` " . - "WHERE alias = 'text' " . - "ORDER BY field " . - "LIMIT 1" + "SELECT field,field2 AS alias " . + "FROM table " . + "WHERE alias = 'text' " . + "ORDER BY field " . + "LIMIT 1" ), array( array( @@ -62,13 +72,13 @@ class DatabaseSQLTest extends MediaWikiTestCase { 'options' => array( 'LIMIT' => 1, 'ORDER BY' => 'field' ), 'join_conds' => array( 't2' => array( 'LEFT JOIN', 'tid = t2.id' - )), + ) ), ), - "SELECT tid,field,field2 AS alias,t2.id " . - "FROM `unittest_table` LEFT JOIN `unittest_table2` `t2` ON ((tid = t2.id)) " . - "WHERE alias = 'text' " . - "ORDER BY field " . - "LIMIT 1" + "SELECT tid,field,field2 AS alias,t2.id " . + "FROM table LEFT JOIN table2 t2 ON ((tid = t2.id)) " . + "WHERE alias = 'text' " . + "ORDER BY field " . + "LIMIT 1" ), array( array( @@ -78,13 +88,13 @@ class DatabaseSQLTest extends MediaWikiTestCase { 'options' => array( 'LIMIT' => 1, 'GROUP BY' => 'field', 'HAVING' => 'COUNT(*) > 1' ), 'join_conds' => array( 't2' => array( 'LEFT JOIN', 'tid = t2.id' - )), + ) ), ), - "SELECT tid,field,field2 AS alias,t2.id " . - "FROM `unittest_table` LEFT JOIN `unittest_table2` `t2` ON ((tid = t2.id)) " . - "WHERE alias = 'text' " . - "GROUP BY field HAVING COUNT(*) > 1 " . - "LIMIT 1" + "SELECT tid,field,field2 AS alias,t2.id " . + "FROM table LEFT JOIN table2 t2 ON ((tid = t2.id)) " . + "WHERE alias = 'text' " . + "GROUP BY field HAVING COUNT(*) > 1 " . + "LIMIT 1" ), array( array( @@ -94,29 +104,466 @@ class DatabaseSQLTest extends MediaWikiTestCase { 'options' => array( 'LIMIT' => 1, 'GROUP BY' => array( 'field', 'field2' ), 'HAVING' => array( 'COUNT(*) > 1', 'field' => 1 ) ), 'join_conds' => array( 't2' => array( 'LEFT JOIN', 'tid = t2.id' - )), + ) ), + ), + "SELECT tid,field,field2 AS alias,t2.id " . + "FROM table LEFT JOIN table2 t2 ON ((tid = t2.id)) " . + "WHERE alias = 'text' " . + "GROUP BY field,field2 HAVING (COUNT(*) > 1) AND field = '1' " . + "LIMIT 1" + ), + array( + array( + 'tables' => array( 'table' ), + 'fields' => array( 'alias' => 'field' ), + 'conds' => array( 'alias' => array( 1, 2, 3, 4 ) ), + ), + "SELECT field AS alias " . + "FROM table " . + "WHERE alias IN ('1','2','3','4')" + ), + ); + } + + /** + * @dataProvider provideUpdate + * @covers DatabaseBase::update + */ + public function testUpdate( $sql, $sqlText ) { + $this->database->update( + $sql['table'], + $sql['values'], + $sql['conds'], + __METHOD__, + isset( $sql['options'] ) ? $sql['options'] : array() + ); + $this->assertLastSql( $sqlText ); + } + + public static function provideUpdate() { + return array( + array( + array( + 'table' => 'table', + 'values' => array( 'field' => 'text', 'field2' => 'text2' ), + 'conds' => array( 'alias' => 'text' ), + ), + "UPDATE table " . + "SET field = 'text'" . + ",field2 = 'text2' " . + "WHERE alias = 'text'" + ), + array( + array( + 'table' => 'table', + 'values' => array( 'field = other', 'field2' => 'text2' ), + 'conds' => array( 'id' => '1' ), + ), + "UPDATE table " . + "SET field = other" . + ",field2 = 'text2' " . + "WHERE id = '1'" + ), + array( + array( + 'table' => 'table', + 'values' => array( 'field = other', 'field2' => 'text2' ), + 'conds' => '*', + ), + "UPDATE table " . + "SET field = other" . + ",field2 = 'text2'" + ), + ); + } + + /** + * @dataProvider provideDelete + * @covers DatabaseBase::delete + */ + public function testDelete( $sql, $sqlText ) { + $this->database->delete( + $sql['table'], + $sql['conds'], + __METHOD__ + ); + $this->assertLastSql( $sqlText ); + } + + public static function provideDelete() { + return array( + array( + array( + 'table' => 'table', + 'conds' => array( 'alias' => 'text' ), + ), + "DELETE FROM table " . + "WHERE alias = 'text'" + ), + array( + array( + 'table' => 'table', + 'conds' => '*', + ), + "DELETE FROM table" + ), + ); + } + + /** + * @dataProvider provideUpsert + * @covers DatabaseBase::upsert + */ + public function testUpsert( $sql, $sqlText ) { + $this->database->upsert( + $sql['table'], + $sql['rows'], + $sql['uniqueIndexes'], + $sql['set'], + __METHOD__ + ); + $this->assertLastSql( $sqlText ); + } + + public static function provideUpsert() { + return array( + array( + array( + 'table' => 'upsert_table', + 'rows' => array( 'field' => 'text', 'field2' => 'text2' ), + 'uniqueIndexes' => array( 'field' ), + 'set' => array( 'field' => 'set' ), + ), + "BEGIN; " . + "UPDATE upsert_table " . + "SET field = 'set' " . + "WHERE ((field = 'text')); " . + "INSERT IGNORE INTO upsert_table " . + "(field,field2) " . + "VALUES ('text','text2'); " . + "COMMIT" + ), + ); + } + + /** + * @dataProvider provideDeleteJoin + * @covers DatabaseBase::deleteJoin + */ + public function testDeleteJoin( $sql, $sqlText ) { + $this->database->deleteJoin( + $sql['delTable'], + $sql['joinTable'], + $sql['delVar'], + $sql['joinVar'], + $sql['conds'], + __METHOD__ + ); + $this->assertLastSql( $sqlText ); + } + + public static function provideDeleteJoin() { + return array( + array( + array( + 'delTable' => 'table', + 'joinTable' => 'table_join', + 'delVar' => 'field', + 'joinVar' => 'field_join', + 'conds' => array( 'alias' => 'text' ), + ), + "DELETE FROM table " . + "WHERE field IN (" . + "SELECT field_join FROM table_join WHERE alias = 'text'" . + ")" + ), + array( + array( + 'delTable' => 'table', + 'joinTable' => 'table_join', + 'delVar' => 'field', + 'joinVar' => 'field_join', + 'conds' => '*', + ), + "DELETE FROM table " . + "WHERE field IN (" . + "SELECT field_join FROM table_join " . + ")" + ), + ); + } + + /** + * @dataProvider provideInsert + * @covers DatabaseBase::insert + */ + public function testInsert( $sql, $sqlText ) { + $this->database->insert( + $sql['table'], + $sql['rows'], + __METHOD__, + isset( $sql['options'] ) ? $sql['options'] : array() + ); + $this->assertLastSql( $sqlText ); + } + + public static function provideInsert() { + return array( + array( + array( + 'table' => 'table', + 'rows' => array( 'field' => 'text', 'field2' => 2 ), + ), + "INSERT INTO table " . + "(field,field2) " . + "VALUES ('text','2')" + ), + array( + array( + 'table' => 'table', + 'rows' => array( 'field' => 'text', 'field2' => 2 ), + 'options' => 'IGNORE', + ), + "INSERT IGNORE INTO table " . + "(field,field2) " . + "VALUES ('text','2')" + ), + array( + array( + 'table' => 'table', + 'rows' => array( + array( 'field' => 'text', 'field2' => 2 ), + array( 'field' => 'multi', 'field2' => 3 ), + ), + 'options' => 'IGNORE', + ), + "INSERT IGNORE INTO table " . + "(field,field2) " . + "VALUES " . + "('text','2')," . + "('multi','3')" + ), + ); + } + + /** + * @dataProvider provideInsertSelect + * @covers DatabaseBase::insertSelect + */ + public function testInsertSelect( $sql, $sqlText ) { + $this->database->insertSelect( + $sql['destTable'], + $sql['srcTable'], + $sql['varMap'], + $sql['conds'], + __METHOD__, + isset( $sql['insertOptions'] ) ? $sql['insertOptions'] : array(), + isset( $sql['selectOptions'] ) ? $sql['selectOptions'] : array() + ); + $this->assertLastSql( $sqlText ); + } + + public static function provideInsertSelect() { + return array( + array( + array( + 'destTable' => 'insert_table', + 'srcTable' => 'select_table', + 'varMap' => array( 'field_insert' => 'field_select', 'field' => 'field2' ), + 'conds' => '*', + ), + "INSERT INTO insert_table " . + "(field_insert,field) " . + "SELECT field_select,field2 " . + "FROM select_table" + ), + array( + array( + 'destTable' => 'insert_table', + 'srcTable' => 'select_table', + 'varMap' => array( 'field_insert' => 'field_select', 'field' => 'field2' ), + 'conds' => array( 'field' => 2 ), + ), + "INSERT INTO insert_table " . + "(field_insert,field) " . + "SELECT field_select,field2 " . + "FROM select_table " . + "WHERE field = '2'" + ), + array( + array( + 'destTable' => 'insert_table', + 'srcTable' => 'select_table', + 'varMap' => array( 'field_insert' => 'field_select', 'field' => 'field2' ), + 'conds' => array( 'field' => 2 ), + 'insertOptions' => 'IGNORE', + 'selectOptions' => array( 'ORDER BY' => 'field' ), ), - "SELECT tid,field,field2 AS alias,t2.id " . - "FROM `unittest_table` LEFT JOIN `unittest_table2` `t2` ON ((tid = t2.id)) " . - "WHERE alias = 'text' " . - "GROUP BY field,field2 HAVING (COUNT(*) > 1) AND field = '1' " . - "LIMIT 1" + "INSERT IGNORE INTO insert_table " . + "(field_insert,field) " . + "SELECT field_select,field2 " . + "FROM select_table " . + "WHERE field = '2' " . + "ORDER BY field" ), ); } /** - * @dataProvider dataConditional + * @dataProvider provideReplace + * @covers DatabaseBase::replace */ - function testConditional( $sql, $sqlText ) { - $this->assertEquals( trim( $this->db->conditional( + public function testReplace( $sql, $sqlText ) { + $this->database->replace( + $sql['table'], + $sql['uniqueIndexes'], + $sql['rows'], + __METHOD__ + ); + $this->assertLastSql( $sqlText ); + } + + public static function provideReplace() { + return array( + array( + array( + 'table' => 'replace_table', + 'uniqueIndexes' => array( 'field' ), + 'rows' => array( 'field' => 'text', 'field2' => 'text2' ), + ), + "DELETE FROM replace_table " . + "WHERE ( field='text' ); " . + "INSERT INTO replace_table " . + "(field,field2) " . + "VALUES ('text','text2')" + ), + array( + array( + 'table' => 'module_deps', + 'uniqueIndexes' => array( array( 'md_module', 'md_skin' ) ), + 'rows' => array( + 'md_module' => 'module', + 'md_skin' => 'skin', + 'md_deps' => 'deps', + ), + ), + "DELETE FROM module_deps " . + "WHERE ( md_module='module' AND md_skin='skin' ); " . + "INSERT INTO module_deps " . + "(md_module,md_skin,md_deps) " . + "VALUES ('module','skin','deps')" + ), + array( + array( + 'table' => 'module_deps', + 'uniqueIndexes' => array( array( 'md_module', 'md_skin' ) ), + 'rows' => array( + array( + 'md_module' => 'module', + 'md_skin' => 'skin', + 'md_deps' => 'deps', + ), array( + 'md_module' => 'module2', + 'md_skin' => 'skin2', + 'md_deps' => 'deps2', + ), + ), + ), + "DELETE FROM module_deps " . + "WHERE ( md_module='module' AND md_skin='skin' ); " . + "INSERT INTO module_deps " . + "(md_module,md_skin,md_deps) " . + "VALUES ('module','skin','deps'); " . + "DELETE FROM module_deps " . + "WHERE ( md_module='module2' AND md_skin='skin2' ); " . + "INSERT INTO module_deps " . + "(md_module,md_skin,md_deps) " . + "VALUES ('module2','skin2','deps2')" + ), + array( + array( + 'table' => 'module_deps', + 'uniqueIndexes' => array( 'md_module', 'md_skin' ), + 'rows' => array( + array( + 'md_module' => 'module', + 'md_skin' => 'skin', + 'md_deps' => 'deps', + ), array( + 'md_module' => 'module2', + 'md_skin' => 'skin2', + 'md_deps' => 'deps2', + ), + ), + ), + "DELETE FROM module_deps " . + "WHERE ( md_module='module' ) OR ( md_skin='skin' ); " . + "INSERT INTO module_deps " . + "(md_module,md_skin,md_deps) " . + "VALUES ('module','skin','deps'); " . + "DELETE FROM module_deps " . + "WHERE ( md_module='module2' ) OR ( md_skin='skin2' ); " . + "INSERT INTO module_deps " . + "(md_module,md_skin,md_deps) " . + "VALUES ('module2','skin2','deps2')" + ), + array( + array( + 'table' => 'module_deps', + 'uniqueIndexes' => array(), + 'rows' => array( + 'md_module' => 'module', + 'md_skin' => 'skin', + 'md_deps' => 'deps', + ), + ), + "INSERT INTO module_deps " . + "(md_module,md_skin,md_deps) " . + "VALUES ('module','skin','deps')" + ), + ); + } + + /** + * @dataProvider provideNativeReplace + * @covers DatabaseBase::nativeReplace + */ + public function testNativeReplace( $sql, $sqlText ) { + $this->database->nativeReplace( + $sql['table'], + $sql['rows'], + __METHOD__ + ); + $this->assertLastSql( $sqlText ); + } + + public static function provideNativeReplace() { + return array( + array( + array( + 'table' => 'replace_table', + 'rows' => array( 'field' => 'text', 'field2' => 'text2' ), + ), + "REPLACE INTO replace_table " . + "(field,field2) " . + "VALUES ('text','text2')" + ), + ); + } + + /** + * @dataProvider provideConditional + * @covers DatabaseBase::conditional + */ + public function testConditional( $sql, $sqlText ) { + $this->assertEquals( trim( $this->database->conditional( $sql['conds'], $sql['true'], $sql['false'] ) ), $sqlText ); } - function dataConditional() { + public static function provideConditional() { return array( array( array( @@ -144,4 +591,131 @@ class DatabaseSQLTest extends MediaWikiTestCase { ), ); } -}
\ No newline at end of file + + /** + * @dataProvider provideBuildConcat + * @covers DatabaseBase::buildConcat + */ + public function testBuildConcat( $stringList, $sqlText ) { + $this->assertEquals( trim( $this->database->buildConcat( + $stringList + ) ), $sqlText ); + } + + public static function provideBuildConcat() { + return array( + array( + array( 'field', 'field2' ), + "CONCAT(field,field2)" + ), + array( + array( "'test'", 'field2' ), + "CONCAT('test',field2)" + ), + ); + } + + /** + * @dataProvider provideBuildLike + * @covers DatabaseBase::buildLike + */ + public function testBuildLike( $array, $sqlText ) { + $this->assertEquals( trim( $this->database->buildLike( + $array + ) ), $sqlText ); + } + + public static function provideBuildLike() { + return array( + array( + 'text', + "LIKE 'text'" + ), + array( + array( 'text', new LikeMatch( '%' ) ), + "LIKE 'text%'" + ), + array( + array( 'text', new LikeMatch( '%' ), 'text2' ), + "LIKE 'text%text2'" + ), + array( + array( 'text', new LikeMatch( '_' ) ), + "LIKE 'text_'" + ), + ); + } + + /** + * @dataProvider provideUnionQueries + * @covers DatabaseBase::unionQueries + */ + public function testUnionQueries( $sql, $sqlText ) { + $this->assertEquals( trim( $this->database->unionQueries( + $sql['sqls'], + $sql['all'] + ) ), $sqlText ); + } + + public static function provideUnionQueries() { + return array( + array( + array( + 'sqls' => array( 'RAW SQL', 'RAW2SQL' ), + 'all' => true, + ), + "(RAW SQL) UNION ALL (RAW2SQL)" + ), + array( + array( + 'sqls' => array( 'RAW SQL', 'RAW2SQL' ), + 'all' => false, + ), + "(RAW SQL) UNION (RAW2SQL)" + ), + array( + array( + 'sqls' => array( 'RAW SQL', 'RAW2SQL', 'RAW3SQL' ), + 'all' => false, + ), + "(RAW SQL) UNION (RAW2SQL) UNION (RAW3SQL)" + ), + ); + } + + /** + * @covers DatabaseBase::commit + */ + public function testTransactionCommit() { + $this->database->begin( __METHOD__ ); + $this->database->commit( __METHOD__ ); + $this->assertLastSql( 'BEGIN; COMMIT' ); + } + + /** + * @covers DatabaseBase::rollback + */ + public function testTransactionRollback() { + $this->database->begin( __METHOD__ ); + $this->database->rollback( __METHOD__ ); + $this->assertLastSql( 'BEGIN; ROLLBACK' ); + } + + /** + * @covers DatabaseBase::dropTable + */ + public function testDropTable() { + $this->database->setExistingTables( array( 'table' ) ); + $this->database->dropTable( 'table', __METHOD__ ); + $this->assertLastSql( 'DROP TABLE table' ); + } + + /** + * @covers DatabaseBase::dropTable + */ + public function testDropNonExistingTable() { + $this->assertFalse( + $this->database->dropTable( 'non_existing', __METHOD__ ) + ); + } +} diff --git a/tests/phpunit/includes/db/DatabaseSqliteTest.php b/tests/phpunit/includes/db/DatabaseSqliteTest.php index d226598b..70ee9465 100644 --- a/tests/phpunit/includes/db/DatabaseSqliteTest.php +++ b/tests/phpunit/includes/db/DatabaseSqliteTest.php @@ -3,16 +3,20 @@ class MockDatabaseSqlite extends DatabaseSqliteStandalone { var $lastQuery; - function __construct( ) { + function __construct() { parent::__construct( ':memory:' ); } function query( $sql, $fname = '', $tempIgnore = false ) { $this->lastQuery = $sql; + return true; } - function replaceVars( $s ) { + /** + * Override parent visibility to public + */ + public function replaceVars( $s ) { return parent::replaceVars( $s ); } } @@ -20,11 +24,18 @@ class MockDatabaseSqlite extends DatabaseSqliteStandalone { /** * @group sqlite * @group Database + * @group medium */ class DatabaseSqliteTest extends MediaWikiTestCase { + + /** + * @var MockDatabaseSqlite + */ var $db; - public function setUp() { + protected function setUp() { + parent::setUp(); + if ( !Sqlite::isPresent() ) { $this->markTestSkipped( 'No SQLite support detected' ); } @@ -42,8 +53,8 @@ class DatabaseSqliteTest extends MediaWikiTestCase { private function assertResultIs( $expected, $res ) { $this->assertNotNull( $res ); $i = 0; - foreach( $res as $row ) { - foreach( $expected[$i] as $key => $value ) { + foreach ( $res as $row ) { + foreach ( $expected[$i] as $key => $value ) { $this->assertTrue( isset( $row->$key ) ); $this->assertEquals( $value, $row->$key ); } @@ -52,44 +63,97 @@ class DatabaseSqliteTest extends MediaWikiTestCase { $this->assertEquals( count( $expected ), $i, 'Unexpected number of rows' ); } + public static function provideAddQuotes() { + return array( + array( // #0: empty + '', "''" + ), + array( // #1: simple + 'foo bar', "'foo bar'" + ), + array( // #2: including quote + 'foo\'bar', "'foo''bar'" + ), + array( // #3: including \0 (must be represented as hex, per https://bugs.php.net/bug.php?id=63419) + "x\0y", + "x'780079'", + ), + array( // #4: blob object (must be represented as hex) + new Blob( "hello" ), + "x'68656c6c6f'", + ), + ); + } + + /** + * @dataProvider provideAddQuotes() + * @covers DatabaseSqlite::addQuotes + */ + public function testAddQuotes( $value, $expected ) { + // check quoting + $db = new DatabaseSqliteStandalone( ':memory:' ); + $this->assertEquals( $expected, $db->addQuotes( $value ), 'string not quoted as expected' ); + + // ok, quoting works as expected, now try a round trip. + $re = $db->query( 'select ' . $db->addQuotes( $value ) ); + + $this->assertTrue( $re !== false, 'query failed' ); + + if ( $row = $re->fetchRow() ) { + if ( $value instanceof Blob ) { + $value = $value->fetch(); + } + + $this->assertEquals( $value, $row[0], 'string mangled by the database' ); + } else { + $this->fail( 'query returned no result' ); + } + } + + /** + * @covers DatabaseSqlite::replaceVars + */ public function testReplaceVars() { $this->assertEquals( 'foo', $this->replaceVars( 'foo' ), "Don't break anything accidentally" ); $this->assertEquals( "CREATE TABLE /**/foo (foo_key INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, " - . "foo_bar TEXT, foo_name TEXT NOT NULL DEFAULT '', foo_int INTEGER, foo_int2 INTEGER );", + . "foo_bar TEXT, foo_name TEXT NOT NULL DEFAULT '', foo_int INTEGER, foo_int2 INTEGER );", $this->replaceVars( "CREATE TABLE /**/foo (foo_key int unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT, foo_bar char(13), foo_name varchar(255) binary NOT NULL DEFAULT '', foo_int tinyint ( 8 ), foo_int2 int(16) ) ENGINE=MyISAM;" ) - ); + ); $this->assertEquals( "CREATE TABLE foo ( foo1 REAL, foo2 REAL, foo3 REAL );", $this->replaceVars( "CREATE TABLE foo ( foo1 FLOAT, foo2 DOUBLE( 1,10), foo3 DOUBLE PRECISION );" ) - ); + ); $this->assertEquals( "CREATE TABLE foo ( foo_binary1 BLOB, foo_binary2 BLOB );", $this->replaceVars( "CREATE TABLE foo ( foo_binary1 binary(16), foo_binary2 varbinary(32) );" ) - ); + ); $this->assertEquals( "CREATE TABLE text ( text_foo TEXT );", $this->replaceVars( "CREATE TABLE text ( text_foo tinytext );" ), 'Table name changed' - ); + ); $this->assertEquals( "CREATE TABLE foo ( foobar INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL );", - $this->replaceVars("CREATE TABLE foo ( foobar INT PRIMARY KEY NOT NULL AUTO_INCREMENT );" ) - ); + $this->replaceVars( "CREATE TABLE foo ( foobar INT PRIMARY KEY NOT NULL AUTO_INCREMENT );" ) + ); $this->assertEquals( "CREATE TABLE foo ( foobar INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL );", - $this->replaceVars("CREATE TABLE foo ( foobar INT PRIMARY KEY AUTO_INCREMENT NOT NULL );" ) - ); + $this->replaceVars( "CREATE TABLE foo ( foobar INT PRIMARY KEY AUTO_INCREMENT NOT NULL );" ) + ); $this->assertEquals( "CREATE TABLE enums( enum1 TEXT, myenum TEXT)", $this->replaceVars( "CREATE TABLE enums( enum1 ENUM('A', 'B'), myenum ENUM ('X', 'Y'))" ) - ); + ); $this->assertEquals( "ALTER TABLE foo ADD COLUMN foo_bar INTEGER DEFAULT 42", $this->replaceVars( "ALTER TABLE foo\nADD COLUMN foo_bar int(10) unsigned DEFAULT 42" ) - ); + ); } + /** + * @covers DatabaseSqlite::tableName + */ public function testTableName() { // @todo Moar! $db = new DatabaseSqliteStandalone( ':memory:' ); @@ -100,6 +164,9 @@ class DatabaseSqliteTest extends MediaWikiTestCase { $this->assertEquals( 'foobar', $db->tableName( 'bar' ) ); } + /** + * @covers DatabaseSqlite::duplicateTableStructure + */ public function testDuplicateTableStructure() { $db = new DatabaseSqliteStandalone( ':memory:' ); $db->query( 'CREATE TABLE foo(foo, barfoo)' ); @@ -121,6 +188,9 @@ class DatabaseSqliteTest extends MediaWikiTestCase { ); } + /** + * @covers DatabaseSqlite::duplicateTableStructure + */ public function testDuplicateTableStructureVirtual() { $db = new DatabaseSqliteStandalone( ':memory:' ); if ( $db->getFulltextSearchModule() != 'FTS3' ) { @@ -141,6 +211,9 @@ class DatabaseSqliteTest extends MediaWikiTestCase { ); } + /** + * @covers DatabaseSqlite::deleteJoin + */ public function testDeleteJoin() { $db = new DatabaseSqliteStandalone( ':memory:' ); $db->query( 'CREATE TABLE a (a_1)', __METHOD__ ); @@ -180,10 +253,10 @@ class DatabaseSqliteTest extends MediaWikiTestCase { /** * Runs upgrades of older databases and compares results with current schema - * @todo: currently only checks list of tables + * @todo Currently only checks list of tables */ public function testUpgrades() { - global $IP, $wgVersion; + global $IP, $wgVersion, $wgProfileToDatabase; // Versions tested $versions = array( @@ -202,6 +275,9 @@ class DatabaseSqliteTest extends MediaWikiTestCase { $currentDB = new DatabaseSqliteStandalone( ':memory:' ); $currentDB->sourceFile( "$IP/maintenance/tables.sql" ); + if ( $wgProfileToDatabase ) { + $currentDB->sourceFile( "$IP/maintenance/sqlite/archives/patch-profiling.sql" ); + } $currentTables = $this->getTables( $currentDB ); sort( $currentTables ); @@ -250,13 +326,19 @@ class DatabaseSqliteTest extends MediaWikiTestCase { } } + /** + * @covers DatabaseSqlite::insertId + */ public function testInsertIdType() { $db = new DatabaseSqliteStandalone( ':memory:' ); - $this->assertInstanceOf( 'ResultWrapper', - $db->query( 'CREATE TABLE a ( a_1 )', __METHOD__ ), "Database creationg" ); - $this->assertTrue( $db->insert( 'a', array( 'a_1' => 10 ), __METHOD__ ), - "Insertion worked" ); - $this->assertEquals( "integer", gettype( $db->insertId() ), "Actual typecheck" ); + + $databaseCreation = $db->query( 'CREATE TABLE a ( a_1 )', __METHOD__ ); + $this->assertInstanceOf( 'ResultWrapper', $databaseCreation, "Database creation" ); + + $insertion = $db->insert( 'a', array( 'a_1' => 10 ), __METHOD__ ); + $this->assertTrue( $insertion, "Insertion worked" ); + + $this->assertInternalType( 'integer', $db->insertId(), "Actual typecheck" ); $this->assertTrue( $db->close(), "closing database" ); } @@ -272,12 +354,14 @@ class DatabaseSqliteTest extends MediaWikiTestCase { $db->sourceFile( "$IP/tests/phpunit/data/db/sqlite/tables-$version.sql" ); $updater = DatabaseUpdater::newForDB( $db, false, $maint ); $updater->doUpdates( array( 'core' ) ); + return $db; } private function getTables( $db ) { $list = array_flip( $db->listTables() ); $excluded = array( + 'external_user', // removed from core in 1.22 'math', // moved out of core in 1.18 'trackbacks', // removed from core in 1.19 'searchindex', @@ -293,6 +377,7 @@ class DatabaseSqliteTest extends MediaWikiTestCase { } $list = array_flip( $list ); sort( $list ); + return $list; } @@ -304,6 +389,7 @@ class DatabaseSqliteTest extends MediaWikiTestCase { $cols[$col->name] = $col; } ksort( $cols ); + return $cols; } @@ -321,6 +407,15 @@ class DatabaseSqliteTest extends MediaWikiTestCase { $indexes[$index->name] = $index; } ksort( $indexes ); + return $indexes; } + + public function testCaseInsensitiveLike() { + // TODO: Test this for all databases + $db = new DatabaseSqliteStandalone( ':memory:' ); + $res = $db->query( 'SELECT "a" LIKE "A" AS a' ); + $row = $res->fetchRow(); + $this->assertFalse( (bool)$row['a'] ); + } } diff --git a/tests/phpunit/includes/db/DatabaseTest.php b/tests/phpunit/includes/db/DatabaseTest.php index 379ffb17..301fc990 100644 --- a/tests/phpunit/includes/db/DatabaseTest.php +++ b/tests/phpunit/includes/db/DatabaseTest.php @@ -5,20 +5,28 @@ * @group DatabaseBase */ class DatabaseTest extends MediaWikiTestCase { - var $db, $functionTest = false; + /** + * @var DatabaseBase + */ + var $db; + var $functionTest = false; - function setUp() { + protected function setUp() { + parent::setUp(); $this->db = wfGetDB( DB_MASTER ); } - function tearDown() { + protected function tearDown() { + parent::tearDown(); if ( $this->functionTest ) { $this->dropFunctions(); $this->functionTest = false; } } - - function testAddQuotesNull() { + /** + * @covers DatabaseBase::dropTable + */ + public function testAddQuotesNull() { $check = "NULL"; if ( $this->db->getType() === 'sqlite' || $this->db->getType() === 'oracle' ) { $check = "''"; @@ -26,7 +34,7 @@ class DatabaseTest extends MediaWikiTestCase { $this->assertEquals( $check, $this->db->addQuotes( null ) ); } - function testAddQuotesInt() { + public function testAddQuotesInt() { # returning just "1234" should be ok too, though... # maybe $this->assertEquals( @@ -34,20 +42,20 @@ class DatabaseTest extends MediaWikiTestCase { $this->db->addQuotes( 1234 ) ); } - function testAddQuotesFloat() { + public function testAddQuotesFloat() { # returning just "1234.5678" would be ok too, though $this->assertEquals( "'1234.5678'", $this->db->addQuotes( 1234.5678 ) ); } - function testAddQuotesString() { + public function testAddQuotesString() { $this->assertEquals( "'string'", $this->db->addQuotes( 'string' ) ); } - function testAddQuotesStringQuote() { + public function testAddQuotesStringQuote() { $check = "'string''s cause trouble'"; if ( $this->db->getType() === 'mysql' ) { $check = "'string\'s cause trouble'"; @@ -82,36 +90,46 @@ class DatabaseTest extends MediaWikiTestCase { $quote = ''; } elseif ( $this->db->getType() === 'mysql' ) { $quote = '`'; + } elseif ( $this->db->getType() === 'oracle' ) { + $quote = '/*Q*/'; } else { $quote = '"'; } if ( $database !== null ) { - $database = $quote . $database . $quote . '.'; + if ( $this->db->getType() === 'oracle' ) { + $database = $quote . $database . '.'; + } else { + $database = $quote . $database . $quote . '.'; + } } if ( $prefix === null ) { $prefix = $this->dbPrefix(); } - return $database . $quote . $prefix . $table . $quote; + if ( $this->db->getType() === 'oracle' ) { + return strtoupper($database . $quote . $prefix . $table); + } else { + return $database . $quote . $prefix . $table . $quote; + } } - function testTableNameLocal() { + public function testTableNameLocal() { $this->assertEquals( $this->prefixAndQuote( 'tablename' ), $this->db->tableName( 'tablename' ) ); } - function testTableNameRawLocal() { + public function testTableNameRawLocal() { $this->assertEquals( $this->prefixAndQuote( 'tablename', null, null, 'raw' ), $this->db->tableName( 'tablename', 'raw' ) ); } - function testTableNameShared() { + public function testTableNameShared() { $this->assertEquals( $this->prefixAndQuote( 'tablename', 'sharedatabase', 'sh_' ), $this->getSharedTableName( 'tablename', 'sharedatabase', 'sh_' ) @@ -123,7 +141,7 @@ class DatabaseTest extends MediaWikiTestCase { ); } - function testTableNameRawShared() { + public function testTableNameRawShared() { $this->assertEquals( $this->prefixAndQuote( 'tablename', 'sharedatabase', 'sh_', 'raw' ), $this->getSharedTableName( 'tablename', 'sharedatabase', 'sh_', 'raw' ) @@ -135,21 +153,21 @@ class DatabaseTest extends MediaWikiTestCase { ); } - function testTableNameForeign() { + public function testTableNameForeign() { $this->assertEquals( $this->prefixAndQuote( 'tablename', 'databasename', '' ), $this->db->tableName( 'databasename.tablename' ) ); } - function testTableNameRawForeign() { + public function testTableNameRawForeign() { $this->assertEquals( $this->prefixAndQuote( 'tablename', 'databasename', '', 'raw' ), $this->db->tableName( 'databasename.tablename', 'raw' ) ); } - function testFillPreparedEmpty() { + public function testFillPreparedEmpty() { $sql = $this->db->fillPrepared( 'SELECT * FROM interwiki', array() ); $this->assertEquals( @@ -157,7 +175,7 @@ class DatabaseTest extends MediaWikiTestCase { $sql ); } - function testFillPreparedQuestion() { + public function testFillPreparedQuestion() { $sql = $this->db->fillPrepared( 'SELECT * FROM cur WHERE cur_namespace=? AND cur_title=?', array( 4, "Snicker's_paradox" ) ); @@ -169,7 +187,7 @@ class DatabaseTest extends MediaWikiTestCase { $this->assertEquals( $check, $sql ); } - function testFillPreparedBang() { + public function testFillPreparedBang() { $sql = $this->db->fillPrepared( 'SELECT user_id FROM ! WHERE user_name=?', array( '"user"', "Slash's Dot" ) ); @@ -181,7 +199,7 @@ class DatabaseTest extends MediaWikiTestCase { $this->assertEquals( $check, $sql ); } - function testFillPreparedRaw() { + public function testFillPreparedRaw() { $sql = $this->db->fillPrepared( "SELECT * FROM cur WHERE cur_title='This_\\&_that,_WTF\\?\\!'", array( '"user"', "Slash's Dot" ) ); @@ -190,10 +208,7 @@ class DatabaseTest extends MediaWikiTestCase { $sql ); } - /** - * @group Broken - */ - function testStoredFunctions() { + public function testStoredFunctions() { if ( !in_array( wfGetDB( DB_MASTER )->getType(), array( 'mysql', 'postgres' ) ) ) { $this->markTestSkipped( 'MySQL or Postgres required' ); } @@ -207,9 +222,13 @@ class DatabaseTest extends MediaWikiTestCase { private function dropFunctions() { $this->db->query( 'DROP FUNCTION IF EXISTS mw_test_function' - . ( $this->db->getType() == 'postgres' ? '()' : '' ) + . ( $this->db->getType() == 'postgres' ? '()' : '' ) ); } -} - + public function testUnknownTableCorruptsResults() { + $res = $this->db->select( 'page', '*', array( 'page_id' => 1 ) ); + $this->assertFalse( $this->db->tableExists( 'foobarbaz' ) ); + $this->assertInternalType( 'int', $res->numRows() ); + } +} diff --git a/tests/phpunit/includes/db/DatabaseTestHelper.php b/tests/phpunit/includes/db/DatabaseTestHelper.php new file mode 100644 index 00000000..790f273c --- /dev/null +++ b/tests/phpunit/includes/db/DatabaseTestHelper.php @@ -0,0 +1,166 @@ +<?php + +/** + * Helper for testing the methods from the DatabaseBase class + * @since 1.22 + */ +class DatabaseTestHelper extends DatabaseBase { + + /** + * __CLASS__ of the test suite, + * used to determine, if the function name is passed every time to query() + */ + protected $testName = array(); + + /** + * Array of lastSqls passed to query(), + * This is an array since some methods in DatabaseBase can do more than one + * query. Cleared when calling getLastSqls(). + */ + protected $lastSqls = array(); + + /** + * Array of tables to be considered as existing by tableExist() + * Use setExistingTables() to alter. + */ + protected $tablesExists; + + public function __construct( $testName ) { + $this->testName = $testName; + } + + /** + * Returns SQL queries grouped by '; ' + * Clear the list of queries that have been done so far. + */ + public function getLastSqls() { + $lastSqls = implode( '; ', $this->lastSqls ); + $this->lastSqls = array(); + + return $lastSqls; + } + + public function setExistingTables( $tablesExists ) { + $this->tablesExists = (array)$tablesExists; + } + + protected function addSql( $sql ) { + // clean up spaces before and after some words and the whole string + $this->lastSqls[] = trim( preg_replace( + '/\s{2,}(?=FROM|WHERE|GROUP BY|ORDER BY|LIMIT)|(?<=SELECT|INSERT|UPDATE)\s{2,}/', + ' ', $sql + ) ); + } + + protected function checkFunctionName( $fname ) { + if ( substr( $fname, 0, strlen( $this->testName ) ) !== $this->testName ) { + throw new MWException( 'function name does not start with test class. ' . + $fname . ' vs. ' . $this->testName . '. ' . + 'Please provide __METHOD__ to database methods.' ); + } + } + + function strencode( $s ) { + // Choose apos to avoid handling of escaping double quotes in quoted text + return str_replace( "'", "\'", $s ); + } + + public function addIdentifierQuotes( $s ) { + // no escaping to avoid handling of double quotes in quoted text + return $s; + } + + public function query( $sql, $fname = '', $tempIgnore = false ) { + $this->checkFunctionName( $fname ); + $this->addSql( $sql ); + + return parent::query( $sql, $fname, $tempIgnore ); + } + + public function tableExists( $table, $fname = __METHOD__ ) { + $this->checkFunctionName( $fname ); + + return in_array( $table, (array)$this->tablesExists ); + } + + // Redeclare parent method to make it public + public function nativeReplace( $table, $rows, $fname ) { + return parent::nativeReplace( $table, $rows, $fname ); + } + + function getType() { + return 'test'; + } + + function open( $server, $user, $password, $dbName ) { + return false; + } + + function fetchObject( $res ) { + return false; + } + + function fetchRow( $res ) { + return false; + } + + function numRows( $res ) { + return -1; + } + + function numFields( $res ) { + return -1; + } + + function fieldName( $res, $n ) { + return 'test'; + } + + function insertId() { + return -1; + } + + function dataSeek( $res, $row ) { + /* nop */ + } + + function lastErrno() { + return -1; + } + + function lastError() { + return 'test'; + } + + function fieldInfo( $table, $field ) { + return false; + } + + function indexInfo( $table, $index, $fname = 'Database::indexInfo' ) { + return false; + } + + function affectedRows() { + return -1; + } + + function getSoftwareLink() { + return 'test'; + } + + function getServerVersion() { + return 'test'; + } + + function getServerInfo() { + return 'test'; + } + + protected function closeConnection() { + return false; + } + + protected function doQuery( $sql ) { + return array(); + } +} diff --git a/tests/phpunit/includes/db/ORMRowTest.php b/tests/phpunit/includes/db/ORMRowTest.php index 9dcaf2b3..27d4d0e8 100644 --- a/tests/phpunit/includes/db/ORMRowTest.php +++ b/tests/phpunit/includes/db/ORMRowTest.php @@ -43,19 +43,19 @@ abstract class ORMRowTest extends \MediaWikiTestCase { * @since 1.20 * @return string */ - protected abstract function getRowClass(); + abstract protected function getRowClass(); /** * @since 1.20 * @return IORMTable */ - protected abstract function getTableInstance(); + abstract protected function getTableInstance(); /** * @since 1.20 * @return array */ - public abstract function constructorTestProvider(); + abstract public function constructorTestProvider(); /** * @since 1.20 @@ -76,6 +76,7 @@ abstract class ORMRowTest extends \MediaWikiTestCase { */ protected function getRowInstance( array $data, $loadDefaults ) { $class = $this->getRowClass(); + return new $class( $this->getTableInstance(), $data, $loadDefaults ); } @@ -136,7 +137,7 @@ abstract class ORMRowTest extends \MediaWikiTestCase { /** * @dataProvider constructorTestProvider */ - public function testSave( array $data, $loadDefaults ) { + public function testSaveAndRemove( array $data, $loadDefaults ) { $item = $this->getRowInstance( $data, $loadDefaults ); $this->assertTrue( $item->save() ); @@ -151,15 +152,6 @@ abstract class ORMRowTest extends \MediaWikiTestCase { $this->assertEquals( $id, $item->getId() ); $this->verifyFields( $item, $data ); - } - - /** - * @dataProvider constructorTestProvider - */ - public function testRemove( array $data, $loadDefaults ) { - $item = $this->getRowInstance( $data, $loadDefaults ); - - $this->assertTrue( $item->save() ); $this->assertTrue( $item->remove() ); @@ -231,4 +223,4 @@ abstract class ORMRowTest extends \MediaWikiTestCase { // TODO: test all of the methods! -}
\ No newline at end of file +} diff --git a/tests/phpunit/includes/db/ORMTableTest.php b/tests/phpunit/includes/db/ORMTableTest.php new file mode 100644 index 00000000..e583d1bc --- /dev/null +++ b/tests/phpunit/includes/db/ORMTableTest.php @@ -0,0 +1,146 @@ +<?php +/** + * Abstract class to construct tests for ORMTable deriving classes. + * + * 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 + * + * @file + * @since 1.21 + * + * @ingroup Test + * + * @group ORM + * @group Database + * + * @licence GNU GPL v2+ + * @author Jeroen De Dauw < jeroendedauw@gmail.com > + * @author Daniel Kinzler + */ +class ORMTableTest extends MediaWikiTestCase { + + /** + * @since 1.21 + * @return string + */ + protected function getTableClass() { + return 'PageORMTableForTesting'; + } + + /** + * @since 1.21 + * @return IORMTable + */ + public function getTable() { + $class = $this->getTableClass(); + + return $class::singleton(); + } + + /** + * @since 1.21 + * @return string + */ + public function getRowClass() { + return $this->getTable()->getRowClass(); + } + + /** + * @since 1.21 + */ + public function testSingleton() { + $class = $this->getTableClass(); + + $this->assertInstanceOf( $class, $class::singleton() ); + $this->assertTrue( $class::singleton() === $class::singleton() ); + } + + /** + * @since 1.21 + */ + public function testIgnoreErrorsOverride() { + $table = $this->getTable(); + + $db = $table->getReadDbConnection(); + $db->ignoreErrors( true ); + + try { + $table->rawSelect( "this is invalid" ); + $this->fail( "An invalid query should trigger a DBQueryError even if ignoreErrors is enabled." ); + } catch ( DBQueryError $ex ) { + $this->assertTrue( true, "just making phpunit happy" ); + } + + $db->ignoreErrors( false ); + } +} + +/** + * Dummy ORM table for testing, reading Title objects from the page table. + * + * @since 1.21 + */ + +class PageORMTableForTesting extends ORMTable { + + /** + * @see ORMTable::getName + * + * @return string + */ + public function getName() { + return 'page'; + } + + /** + * @see ORMTable::getRowClass + * + * @return string + */ + public function getRowClass() { + return 'Title'; + } + + /** + * @see ORMTable::newRow + * + * @return IORMRow + */ + public function newRow( array $data, $loadDefaults = false ) { + return Title::makeTitle( $data['namespace'], $data['title'] ); + } + + /** + * @see ORMTable::getFields + * + * @return array + */ + public function getFields() { + return array( + 'id' => 'int', + 'namespace' => 'int', + 'title' => 'str', + ); + } + + /** + * @see ORMTable::getFieldPrefix + * + * @return string + */ + protected function getFieldPrefix() { + return 'page_'; + } +} diff --git a/tests/phpunit/includes/db/TestORMRowTest.php b/tests/phpunit/includes/db/TestORMRowTest.php index afd1cb80..f65642b8 100644 --- a/tests/phpunit/includes/db/TestORMRowTest.php +++ b/tests/phpunit/includes/db/TestORMRowTest.php @@ -58,35 +58,62 @@ class TestORMRowTest extends ORMRowTest { return TestORMTable::singleton(); } - public function setUp() { + protected function setUp() { parent::setUp(); $dbw = wfGetDB( DB_MASTER ); $isSqlite = $GLOBALS['wgDBtype'] === 'sqlite'; + $isPostgres = $GLOBALS['wgDBtype'] === 'postgres'; $idField = $isSqlite ? 'INTEGER' : 'INT unsigned'; $primaryKey = $isSqlite ? 'PRIMARY KEY AUTOINCREMENT' : 'auto_increment PRIMARY KEY'; - $dbw->query( - 'CREATE TABLE IF NOT EXISTS ' . $dbw->tableName( 'orm_test' ) . '( - test_id ' . $idField . ' NOT NULL ' . $primaryKey . ', - test_name VARCHAR(255) NOT NULL, - test_age TINYINT unsigned NOT NULL, - test_height FLOAT NOT NULL, - test_awesome TINYINT unsigned NOT NULL, - test_stuff BLOB NOT NULL, - test_moarstuff BLOB NOT NULL, - test_time varbinary(14) NOT NULL - );' - ); + if ( $isPostgres ) { + $dbw->query( + 'CREATE TABLE IF NOT EXISTS ' . $dbw->tableName( 'orm_test' ) . "( + test_id serial PRIMARY KEY, + test_name TEXT NOT NULL DEFAULT '', + test_age INTEGER NOT NULL DEFAULT 0, + test_height REAL NOT NULL DEFAULT 0, + test_awesome INTEGER NOT NULL DEFAULT 0, + test_stuff BYTEA, + test_moarstuff BYTEA, + test_time TIMESTAMPTZ + );", + __METHOD__ + ); + } else { + $dbw->query( + 'CREATE TABLE IF NOT EXISTS ' . $dbw->tableName( 'orm_test' ) . '( + test_id ' . $idField . ' NOT NULL ' . $primaryKey . ', + test_name VARCHAR(255) NOT NULL, + test_age TINYINT unsigned NOT NULL, + test_height FLOAT NOT NULL, + test_awesome TINYINT unsigned NOT NULL, + test_stuff BLOB NOT NULL, + test_moarstuff BLOB NOT NULL, + test_time varbinary(14) NOT NULL + );', + __METHOD__ + ); + } + } + + protected function tearDown() { + $dbw = wfGetDB( DB_MASTER ); + $dbw->dropTable( 'orm_test', __METHOD__ ); + + parent::tearDown(); } public function constructorTestProvider() { + $dbw = wfGetDB( DB_MASTER ); return array( array( array( 'name' => 'Foobar', + 'time' => $dbw->timestamp( '20120101020202' ), 'age' => 42, 'height' => 9000.1, 'awesome' => true, @@ -98,9 +125,25 @@ class TestORMRowTest extends ORMRowTest { ); } + /** + * @since 1.21 + * @return array + */ + protected function getMockValues() { + return array( + 'id' => 1, + 'str' => 'foobar4645645', + 'int' => 42, + 'float' => 4.2, + 'bool' => '', + 'array' => array( 42, 'foobar' ), + 'blob' => new stdClass() + ); + } } -class TestORMRow extends ORMRow {} +class TestORMRow extends ORMRow { +} class TestORMTable extends ORMTable { @@ -155,7 +198,7 @@ class TestORMTable extends ORMTable { 'awesome' => 'bool', 'stuff' => 'array', 'moarstuff' => 'blob', - 'time' => 'int', // TS_MW + 'time' => 'str', // TS_MW ); } @@ -169,6 +212,4 @@ class TestORMTable extends ORMTable { protected function getFieldPrefix() { return 'test_'; } - - } diff --git a/tests/phpunit/includes/debug/MWDebugTest.php b/tests/phpunit/includes/debug/MWDebugTest.php index 246b2918..6926b1c8 100644 --- a/tests/phpunit/includes/debug/MWDebugTest.php +++ b/tests/phpunit/includes/debug/MWDebugTest.php @@ -3,10 +3,11 @@ class MWDebugTest extends MediaWikiTestCase { - function setUp() { + protected function setUp() { + parent::setUp(); // Make sure MWDebug class is enabled static $MWDebugEnabled = false; - if( !$MWDebugEnabled ) { + if ( !$MWDebugEnabled ) { MWDebug::init(); $MWDebugEnabled = true; } @@ -15,33 +16,36 @@ class MWDebugTest extends MediaWikiTestCase { wfSuppressWarnings(); } - function tearDown() { + protected function tearDown() { wfRestoreWarnings(); + parent::tearDown(); } - function testAddLog() { + public function testAddLog() { MWDebug::log( 'logging a string' ); - $this->assertEquals( array( array( - 'msg' => 'logging a string', - 'type' => 'log', - 'caller' => __METHOD__ , + $this->assertEquals( + array( array( + 'msg' => 'logging a string', + 'type' => 'log', + 'caller' => __METHOD__, ) ), MWDebug::getLog() ); } - function testAddWarning() { + public function testAddWarning() { MWDebug::warning( 'Warning message' ); - $this->assertEquals( array( array( - 'msg' => 'Warning message', - 'type' => 'warn', - 'caller' => 'MWDebugTest::testAddWarning', + $this->assertEquals( + array( array( + 'msg' => 'Warning message', + 'type' => 'warn', + 'caller' => 'MWDebugTest::testAddWarning', ) ), MWDebug::getLog() ); } - function testAvoidDuplicateDeprecations() { + public function testAvoidDuplicateDeprecations() { MWDebug::deprecated( 'wfOldFunction', '1.0', 'component' ); MWDebug::deprecated( 'wfOldFunction', '1.0', 'component' ); @@ -52,7 +56,7 @@ class MWDebugTest extends MediaWikiTestCase { ); } - function testAvoidNonConsecutivesDuplicateDeprecations() { + public function testAvoidNonConsecutivesDuplicateDeprecations() { MWDebug::deprecated( 'wfOldFunction', '1.0', 'component' ); MWDebug::warning( 'some warning' ); MWDebug::log( 'we could have logged something too' ); diff --git a/tests/phpunit/includes/filerepo/FileBackendTest.php b/tests/phpunit/includes/filebackend/FileBackendTest.php index a2dc5c6c..fcfa724f 100644 --- a/tests/phpunit/includes/filerepo/FileBackendTest.php +++ b/tests/phpunit/includes/filebackend/FileBackendTest.php @@ -6,14 +6,21 @@ * @group medium */ class FileBackendTest extends MediaWikiTestCase { - private $backend, $multiBackend; + + /** @var FileBackend */ + private $backend; + /** @var FileBackendMultiWrite */ + private $multiBackend; + /** @var FSFileBackend */ + public $singleBackend; private $filesToPrune = array(); private static $backendToUse; - function setUp() { + protected function setUp() { global $wgFileBackends; parent::setUp(); - $tmpPrefix = wfTempDir() . '/filebackend-unittest-' . time() . '-' . mt_rand(); + $uniqueId = time() . '-' . mt_rand(); + $tmpPrefix = wfTempDir() . '/filebackend-unittest-' . $uniqueId; if ( $this->getCliArg( 'use-filebackend=' ) ) { if ( self::$backendToUse ) { $this->singleBackend = self::$backendToUse; @@ -36,32 +43,34 @@ class FileBackendTest extends MediaWikiTestCase { } } else { $this->singleBackend = new FSFileBackend( array( - 'name' => 'localtesting', + 'name' => 'localtesting', 'lockManager' => 'fsLockManager', #'parallelize' => 'implicit', + 'wikiId' => wfWikiID() . $uniqueId, 'containerPaths' => array( 'unittest-cont1' => "{$tmpPrefix}-localtesting-cont1", 'unittest-cont2' => "{$tmpPrefix}-localtesting-cont2" ) ) ); } $this->multiBackend = new FileBackendMultiWrite( array( - 'name' => 'localtesting', + 'name' => 'localtesting', 'lockManager' => 'fsLockManager', 'parallelize' => 'implicit', - 'backends' => array( + 'wikiId' => wfWikiId() . $uniqueId, + 'backends' => array( array( - 'name' => 'localmutlitesting1', - 'class' => 'FSFileBackend', - 'lockManager' => 'nullLockManager', + 'name' => 'localmultitesting1', + 'class' => 'FSFileBackend', + 'lockManager' => 'nullLockManager', 'containerPaths' => array( 'unittest-cont1' => "{$tmpPrefix}-localtestingmulti1-cont1", 'unittest-cont2' => "{$tmpPrefix}-localtestingmulti1-cont2" ), 'isMultiMaster' => false ), array( - 'name' => 'localmutlitesting2', - 'class' => 'FSFileBackend', - 'lockManager' => 'nullLockManager', + 'name' => 'localmultitesting2', + 'class' => 'FSFileBackend', + 'lockManager' => 'nullLockManager', 'containerPaths' => array( 'unittest-cont1' => "{$tmpPrefix}-localtestingmulti2-cont1", 'unittest-cont2' => "{$tmpPrefix}-localtestingmulti2-cont2" ), @@ -72,7 +81,7 @@ class FileBackendTest extends MediaWikiTestCase { $this->filesToPrune = array(); } - private function baseStorePath() { + private static function baseStorePath() { return 'mwstore://localtesting'; } @@ -82,13 +91,14 @@ class FileBackendTest extends MediaWikiTestCase { /** * @dataProvider provider_testIsStoragePath + * @covers FileBackend::isStoragePath */ public function testIsStoragePath( $path, $isStorePath ) { $this->assertEquals( $isStorePath, FileBackend::isStoragePath( $path ), "FileBackend::isStoragePath on path '$path'" ); } - function provider_testIsStoragePath() { + public static function provider_testIsStoragePath() { return array( array( 'mwstore://', true ), array( 'mwstore://backend', true ), @@ -106,13 +116,14 @@ class FileBackendTest extends MediaWikiTestCase { /** * @dataProvider provider_testSplitStoragePath + * @covers FileBackend::splitStoragePath */ public function testSplitStoragePath( $path, $res ) { $this->assertEquals( $res, FileBackend::splitStoragePath( $path ), "FileBackend::splitStoragePath on path '$path'" ); } - function provider_testSplitStoragePath() { + public static function provider_testSplitStoragePath() { return array( array( 'mwstore://backend/container', array( 'backend', 'container', '' ) ), array( 'mwstore://backend/container/', array( 'backend', 'container', '' ) ), @@ -130,39 +141,41 @@ class FileBackendTest extends MediaWikiTestCase { /** * @dataProvider provider_normalizeStoragePath + * @covers FileBackend::normalizeStoragePath */ public function testNormalizeStoragePath( $path, $res ) { $this->assertEquals( $res, FileBackend::normalizeStoragePath( $path ), "FileBackend::normalizeStoragePath on path '$path'" ); } - function provider_normalizeStoragePath() { + public static function provider_normalizeStoragePath() { return array( array( 'mwstore://backend/container', 'mwstore://backend/container' ), array( 'mwstore://backend/container/', 'mwstore://backend/container' ), array( 'mwstore://backend/container/path', 'mwstore://backend/container/path' ), array( 'mwstore://backend/container//path', 'mwstore://backend/container/path' ), array( 'mwstore://backend/container///path', 'mwstore://backend/container/path' ), - array( 'mwstore://backend/container///path//to///obj', 'mwstore://backend/container/path/to/obj', + array( 'mwstore://backend/container///path//to///obj', 'mwstore://backend/container/path/to/obj' ), array( 'mwstore://', null ), array( 'mwstore://backend', null ), array( 'mwstore://backend//container/path', null ), array( 'mwstore://backend//container//path', null ), array( 'mwstore:///', null ), array( 'mwstore:/', null ), - array( 'mwstore:', null ), ) + array( 'mwstore:', null ), ); } /** * @dataProvider provider_testParentStoragePath + * @covers FileBackend::parentStoragePath */ public function testParentStoragePath( $path, $res ) { $this->assertEquals( $res, FileBackend::parentStoragePath( $path ), "FileBackend::parentStoragePath on path '$path'" ); } - function provider_testParentStoragePath() { + public static function provider_testParentStoragePath() { return array( array( 'mwstore://backend/container/path/to/obj', 'mwstore://backend/container/path/to' ), array( 'mwstore://backend/container/path/to', 'mwstore://backend/container/path' ), @@ -177,13 +190,14 @@ class FileBackendTest extends MediaWikiTestCase { /** * @dataProvider provider_testExtensionFromPath + * @covers FileBackend::extensionFromPath */ public function testExtensionFromPath( $path, $res ) { $this->assertEquals( $res, FileBackend::extensionFromPath( $path ), "FileBackend::extensionFromPath on path '$path'" ); } - function provider_testExtensionFromPath() { + public static function provider_testExtensionFromPath() { return array( array( 'mwstore://backend/container/path.txt', 'txt' ), array( 'mwstore://backend/container/path.svg.png', 'png' ), @@ -210,6 +224,9 @@ class FileBackendTest extends MediaWikiTestCase { $this->tearDownFiles(); } + /** + * @covers FileBackend::doOperation + */ private function doTestStore( $op ) { $backendName = $this->backendClass(); @@ -248,11 +265,11 @@ class FileBackendTest extends MediaWikiTestCase { $this->assertBackendPathsConsistent( array( $dest ) ); } - public function provider_testStore() { + public static function provider_testStore() { $cases = array(); $tmpName = TempFSFile::factory( "unittests_", 'txt' )->getPath(); - $toPath = $this->baseStorePath() . '/unittest-cont1/e/fun/obj1.txt'; + $toPath = self::baseStorePath() . '/unittest-cont1/e/fun/obj1.txt'; $op = array( 'op' => 'store', 'src' => $tmpName, 'dst' => $toPath ); $cases[] = array( $op, // operation @@ -281,6 +298,7 @@ class FileBackendTest extends MediaWikiTestCase { /** * @dataProvider provider_testCopy + * @covers FileBackend::doOperation */ public function testCopy( $op ) { $this->backend = $this->singleBackend; @@ -302,6 +320,20 @@ class FileBackendTest extends MediaWikiTestCase { $this->prepare( array( 'dir' => dirname( $source ) ) ); $this->prepare( array( 'dir' => dirname( $dest ) ) ); + if ( isset( $op['ignoreMissingSource'] ) ) { + $status = $this->backend->doOperation( $op ); + $this->assertGoodStatus( $status, + "Move from $source to $dest succeeded without warnings ($backendName)." ); + $this->assertEquals( array( 0 => true ), $status->success, + "Move from $source to $dest has proper 'success' field in Status ($backendName)." ); + $this->assertEquals( false, $this->backend->fileExists( array( 'src' => $source ) ), + "Source file $source does not exist ($backendName)." ); + $this->assertEquals( false, $this->backend->fileExists( array( 'src' => $dest ) ), + "Destination file $dest does not exist ($backendName)." ); + + return; // done + } + $status = $this->backend->doOperation( array( 'op' => 'create', 'content' => 'blahblah', 'dst' => $source ) ); $this->assertGoodStatus( $status, @@ -337,11 +369,11 @@ class FileBackendTest extends MediaWikiTestCase { $this->assertBackendPathsConsistent( array( $source, $dest ) ); } - public function provider_testCopy() { + public static function provider_testCopy() { $cases = array(); - $source = $this->baseStorePath() . '/unittest-cont1/e/file.txt'; - $dest = $this->baseStorePath() . '/unittest-cont2/a/fileMoved.txt'; + $source = self::baseStorePath() . '/unittest-cont1/e/file.txt'; + $dest = self::baseStorePath() . '/unittest-cont2/a/fileMoved.txt'; $op = array( 'op' => 'copy', 'src' => $source, 'dst' => $dest ); $cases[] = array( @@ -366,11 +398,28 @@ class FileBackendTest extends MediaWikiTestCase { $dest, // dest ); + $op2 = $op; + $op2['ignoreMissingSource'] = true; + $cases[] = array( + $op2, // operation + $source, // source + $dest, // dest + ); + + $op2 = $op; + $op2['ignoreMissingSource'] = true; + $cases[] = array( + $op2, // operation + self::baseStorePath() . '/unittest-cont-bad/e/file.txt', // source + $dest, // dest + ); + return $cases; } /** * @dataProvider provider_testMove + * @covers FileBackend::doOperation */ public function testMove( $op ) { $this->backend = $this->singleBackend; @@ -392,6 +441,20 @@ class FileBackendTest extends MediaWikiTestCase { $this->prepare( array( 'dir' => dirname( $source ) ) ); $this->prepare( array( 'dir' => dirname( $dest ) ) ); + if ( isset( $op['ignoreMissingSource'] ) ) { + $status = $this->backend->doOperation( $op ); + $this->assertGoodStatus( $status, + "Move from $source to $dest succeeded without warnings ($backendName)." ); + $this->assertEquals( array( 0 => true ), $status->success, + "Move from $source to $dest has proper 'success' field in Status ($backendName)." ); + $this->assertEquals( false, $this->backend->fileExists( array( 'src' => $source ) ), + "Source file $source does not exist ($backendName)." ); + $this->assertEquals( false, $this->backend->fileExists( array( 'src' => $dest ) ), + "Destination file $dest does not exist ($backendName)." ); + + return; // done + } + $status = $this->backend->doOperation( array( 'op' => 'create', 'content' => 'blahblah', 'dst' => $source ) ); $this->assertGoodStatus( $status, @@ -428,11 +491,11 @@ class FileBackendTest extends MediaWikiTestCase { $this->assertBackendPathsConsistent( array( $source, $dest ) ); } - public function provider_testMove() { + public static function provider_testMove() { $cases = array(); - $source = $this->baseStorePath() . '/unittest-cont1/e/file.txt'; - $dest = $this->baseStorePath() . '/unittest-cont2/a/fileMoved.txt'; + $source = self::baseStorePath() . '/unittest-cont1/e/file.txt'; + $dest = self::baseStorePath() . '/unittest-cont2/a/fileMoved.txt'; $op = array( 'op' => 'move', 'src' => $source, 'dst' => $dest ); $cases[] = array( @@ -457,11 +520,28 @@ class FileBackendTest extends MediaWikiTestCase { $dest, // dest ); + $op2 = $op; + $op2['ignoreMissingSource'] = true; + $cases[] = array( + $op2, // operation + $source, // source + $dest, // dest + ); + + $op2 = $op; + $op2['ignoreMissingSource'] = true; + $cases[] = array( + $op2, // operation + self::baseStorePath() . '/unittest-cont-bad/e/file.txt', // source + $dest, // dest + ); + return $cases; } /** * @dataProvider provider_testDelete + * @covers FileBackend::doOperation */ public function testDelete( $op, $withSource, $okStatus ) { $this->backend = $this->singleBackend; @@ -515,10 +595,10 @@ class FileBackendTest extends MediaWikiTestCase { $this->assertBackendPathsConsistent( array( $source ) ); } - public function provider_testDelete() { + public static function provider_testDelete() { $cases = array(); - $source = $this->baseStorePath() . '/unittest-cont1/e/myfacefile.txt'; + $source = self::baseStorePath() . '/unittest-cont1/e/myfacefile.txt'; $op = array( 'op' => 'delete', 'src' => $source ); $cases[] = array( @@ -540,11 +620,88 @@ class FileBackendTest extends MediaWikiTestCase { true // succeeds ); + $op['ignoreMissingSource'] = true; + $op['src'] = self::baseStorePath() . '/unittest-cont-bad/e/file.txt'; + $cases[] = array( + $op, // operation + false, // without source + true // succeeds + ); + + return $cases; + } + + /** + * @dataProvider provider_testDescribe + * @covers FileBackend::doOperation + */ + public function testDescribe( $op, $withSource, $okStatus ) { + $this->backend = $this->singleBackend; + $this->tearDownFiles(); + $this->doTestDescribe( $op, $withSource, $okStatus ); + $this->tearDownFiles(); + + $this->backend = $this->multiBackend; + $this->tearDownFiles(); + $this->doTestDescribe( $op, $withSource, $okStatus ); + $this->tearDownFiles(); + } + + private function doTestDescribe( $op, $withSource, $okStatus ) { + $backendName = $this->backendClass(); + + $source = $op['src']; + $this->prepare( array( 'dir' => dirname( $source ) ) ); + + if ( $withSource ) { + $status = $this->backend->doOperation( + array( 'op' => 'create', 'content' => 'blahblah', 'dst' => $source ) ); + $this->assertGoodStatus( $status, + "Creation of file at $source succeeded ($backendName)." ); + } + + $status = $this->backend->doOperation( $op ); + if ( $okStatus ) { + $this->assertGoodStatus( $status, + "Describe of file at $source succeeded without warnings ($backendName)." ); + $this->assertEquals( true, $status->isOK(), + "Describe of file at $source succeeded ($backendName)." ); + $this->assertEquals( array( 0 => true ), $status->success, + "Describe of file at $source has proper 'success' field in Status ($backendName)." ); + } else { + $this->assertEquals( false, $status->isOK(), + "Describe of file at $source failed ($backendName)." ); + } + + $this->assertBackendPathsConsistent( array( $source ) ); + } + + public static function provider_testDescribe() { + $cases = array(); + + $source = self::baseStorePath() . '/unittest-cont1/e/myfacefile.txt'; + + $op = array( 'op' => 'describe', 'src' => $source, + 'headers' => array( 'X-Content-Length' => '91.3', 'Content-Old-Header' => '' ), + 'disposition' => 'inline' ); + $cases[] = array( + $op, // operation + true, // with source + true // succeeds + ); + + $cases[] = array( + $op, // operation + false, // without source + false // fails + ); + return $cases; } /** * @dataProvider provider_testCreate + * @covers FileBackend::doOperation */ public function testCreate( $op, $alreadyExists, $okStatus, $newSize ) { $this->backend = $this->singleBackend; @@ -611,10 +768,10 @@ class FileBackendTest extends MediaWikiTestCase { /** * @dataProvider provider_testCreate */ - public function provider_testCreate() { + public static function provider_testCreate() { $cases = array(); - $dest = $this->baseStorePath() . '/unittest-cont2/a/myspacefile.txt'; + $dest = self::baseStorePath() . '/unittest-cont2/a/myspacefile.txt'; $op = array( 'op' => 'create', 'content' => 'test test testing', 'dst' => $dest ); $cases[] = array( @@ -665,6 +822,9 @@ class FileBackendTest extends MediaWikiTestCase { return $cases; } + /** + * @covers FileBackend::doQuickOperations + */ public function testDoQuickOperations() { $this->backend = $this->singleBackend; $this->doTestDoQuickOperations(); @@ -678,38 +838,72 @@ class FileBackendTest extends MediaWikiTestCase { private function doTestDoQuickOperations() { $backendName = $this->backendClass(); - $base = $this->baseStorePath(); + $base = self::baseStorePath(); $files = array( "$base/unittest-cont1/e/fileA.a", "$base/unittest-cont1/e/fileB.a", "$base/unittest-cont1/e/fileC.a" ); - $ops = array(); + $createOps = array(); $purgeOps = array(); foreach ( $files as $path ) { $status = $this->prepare( array( 'dir' => dirname( $path ) ) ); $this->assertGoodStatus( $status, "Preparing $path succeeded without warnings ($backendName)." ); - $ops[] = array( 'op' => 'create', 'dst' => $path, 'content' => mt_rand(0,50000) ); + $createOps[] = array( 'op' => 'create', 'dst' => $path, 'content' => mt_rand( 0, 50000 ) ); + $copyOps[] = array( 'op' => 'copy', 'src' => $path, 'dst' => "$path-2" ); + $moveOps[] = array( 'op' => 'move', 'src' => "$path-2", 'dst' => "$path-3" ); $purgeOps[] = array( 'op' => 'delete', 'src' => $path ); + $purgeOps[] = array( 'op' => 'delete', 'src' => "$path-3" ); } $purgeOps[] = array( 'op' => 'null' ); - $status = $this->backend->doQuickOperations( $ops ); - $this->assertGoodStatus( $status, - "Creation of source files succeeded ($backendName)." ); + $this->assertGoodStatus( + $this->backend->doQuickOperations( $createOps ), + "Creation of source files succeeded ($backendName)." ); foreach ( $files as $file ) { $this->assertTrue( $this->backend->fileExists( array( 'src' => $file ) ), "File $file exists." ); } - $status = $this->backend->doQuickOperations( $purgeOps ); - $this->assertGoodStatus( $status, - "Quick deletion of source files succeeded ($backendName)." ); + $this->assertGoodStatus( + $this->backend->doQuickOperations( $copyOps ), + "Quick copy of source files succeeded ($backendName)." ); + foreach ( $files as $file ) { + $this->assertTrue( $this->backend->fileExists( array( 'src' => "$file-2" ) ), + "File $file-2 exists." ); + } + $this->assertGoodStatus( + $this->backend->doQuickOperations( $moveOps ), + "Quick move of source files succeeded ($backendName)." ); + foreach ( $files as $file ) { + $this->assertTrue( $this->backend->fileExists( array( 'src' => "$file-3" ) ), + "File $file-3 move in." ); + $this->assertFalse( $this->backend->fileExists( array( 'src' => "$file-2" ) ), + "File $file-2 moved away." ); + } + + $this->assertGoodStatus( + $this->backend->quickCopy( array( 'src' => $files[0], 'dst' => $files[0] ) ), + "Copy of file {$files[0]} over itself succeeded ($backendName)." ); + $this->assertTrue( $this->backend->fileExists( array( 'src' => $files[0] ) ), + "File {$files[0]} still exists." ); + + $this->assertGoodStatus( + $this->backend->quickMove( array( 'src' => $files[0], 'dst' => $files[0] ) ), + "Move of file {$files[0]} over itself succeeded ($backendName)." ); + $this->assertTrue( $this->backend->fileExists( array( 'src' => $files[0] ) ), + "File {$files[0]} still exists." ); + + $this->assertGoodStatus( + $this->backend->doQuickOperations( $purgeOps ), + "Quick deletion of source files succeeded ($backendName)." ); foreach ( $files as $file ) { $this->assertFalse( $this->backend->fileExists( array( 'src' => $file ) ), "File $file purged." ); + $this->assertFalse( $this->backend->fileExists( array( 'src' => "$file-3" ) ), + "File $file-3 purged." ); } } @@ -722,6 +916,7 @@ class FileBackendTest extends MediaWikiTestCase { $this->backend = $this->singleBackend; $this->tearDownFiles(); $this->doTestConcatenate( $op, $srcs, $srcsContent, $alreadyExists, $okStatus ); + $this->filesToPrune[] = $op['dst']; # avoid file leaking $this->tearDownFiles(); $this->backend = $this->multiBackend; @@ -740,8 +935,8 @@ class FileBackendTest extends MediaWikiTestCase { foreach ( $srcs as $i => $source ) { $this->prepare( array( 'dir' => dirname( $source ) ) ); $ops[] = array( - 'op' => 'create', // operation - 'dst' => $source, // source + 'op' => 'create', // operation + 'dst' => $source, // source 'content' => $srcsContent[$i] ); $expContent .= $srcsContent[$i]; @@ -794,22 +989,22 @@ class FileBackendTest extends MediaWikiTestCase { } } - function provider_testConcatenate() { + public static function provider_testConcatenate() { $cases = array(); $rand = mt_rand( 0, 2000000000 ) . time(); $dest = wfTempDir() . "/randomfile!$rand.txt"; $srcs = array( - $this->baseStorePath() . '/unittest-cont1/e/file1.txt', - $this->baseStorePath() . '/unittest-cont1/e/file2.txt', - $this->baseStorePath() . '/unittest-cont1/e/file3.txt', - $this->baseStorePath() . '/unittest-cont1/e/file4.txt', - $this->baseStorePath() . '/unittest-cont1/e/file5.txt', - $this->baseStorePath() . '/unittest-cont1/e/file6.txt', - $this->baseStorePath() . '/unittest-cont1/e/file7.txt', - $this->baseStorePath() . '/unittest-cont1/e/file8.txt', - $this->baseStorePath() . '/unittest-cont1/e/file9.txt', - $this->baseStorePath() . '/unittest-cont1/e/file10.txt' + self::baseStorePath() . '/unittest-cont1/e/file1.txt', + self::baseStorePath() . '/unittest-cont1/e/file2.txt', + self::baseStorePath() . '/unittest-cont1/e/file3.txt', + self::baseStorePath() . '/unittest-cont1/e/file4.txt', + self::baseStorePath() . '/unittest-cont1/e/file5.txt', + self::baseStorePath() . '/unittest-cont1/e/file6.txt', + self::baseStorePath() . '/unittest-cont1/e/file7.txt', + self::baseStorePath() . '/unittest-cont1/e/file8.txt', + self::baseStorePath() . '/unittest-cont1/e/file9.txt', + self::baseStorePath() . '/unittest-cont1/e/file10.txt' ); $content = array( 'egfage', @@ -846,6 +1041,7 @@ class FileBackendTest extends MediaWikiTestCase { /** * @dataProvider provider_testGetFileStat + * @covers FileBackend::getFileStat */ public function testGetFileStat( $path, $content, $alreadyExists ) { $this->backend = $this->singleBackend; @@ -908,10 +1104,10 @@ class FileBackendTest extends MediaWikiTestCase { } } - function provider_testGetFileStat() { + public static function provider_testGetFileStat() { $cases = array(); - $base = $this->baseStorePath(); + $base = self::baseStorePath(); $cases[] = array( "$base/unittest-cont1/e/b/z/some_file.txt", "some file contents", true ); $cases[] = array( "$base/unittest-cont1/e/b/some-other_file.txt", "", true ); $cases[] = array( "$base/unittest-cont1/e/b/some-diff_file.txt", null, false ); @@ -921,6 +1117,8 @@ class FileBackendTest extends MediaWikiTestCase { /** * @dataProvider provider_testGetFileContents + * @covers FileBackend::getFileContents + * @covers FileBackend::getFileContentsMulti */ public function testGetFileContents( $source, $content ) { $this->backend = $this->singleBackend; @@ -937,35 +1135,50 @@ class FileBackendTest extends MediaWikiTestCase { private function doTestGetFileContents( $source, $content ) { $backendName = $this->backendClass(); - $this->prepare( array( 'dir' => dirname( $source ) ) ); - - $status = $this->backend->doOperation( - array( 'op' => 'create', 'content' => $content, 'dst' => $source ) ); - $this->assertGoodStatus( $status, - "Creation of file at $source succeeded ($backendName)." ); - $this->assertEquals( true, $status->isOK(), - "Creation of file at $source succeeded with OK status ($backendName)." ); - - $newContents = $this->backend->getFileContents( array( 'src' => $source, 'latest' => 1 ) ); - $this->assertNotEquals( false, $newContents, - "Read of file at $source succeeded ($backendName)." ); + $srcs = (array)$source; + $content = (array)$content; + foreach ( $srcs as $i => $src ) { + $this->prepare( array( 'dir' => dirname( $src ) ) ); + $status = $this->backend->doOperation( + array( 'op' => 'create', 'content' => $content[$i], 'dst' => $src ) ); + $this->assertGoodStatus( $status, + "Creation of file at $src succeeded ($backendName)." ); + } - $this->assertEquals( $content, $newContents, - "Contents read match data at $source ($backendName)." ); + if ( is_array( $source ) ) { + $contents = $this->backend->getFileContentsMulti( array( 'srcs' => $source ) ); + foreach ( $contents as $path => $data ) { + $this->assertNotEquals( false, $data, "Contents of $path exists ($backendName)." ); + $this->assertEquals( current( $content ), $data, "Contents of $path is correct ($backendName)." ); + next( $content ); + } + $this->assertEquals( $source, array_keys( $contents ), "Contents in right order ($backendName)." ); + $this->assertEquals( count( $source ), count( $contents ), "Contents array size correct ($backendName)." ); + } else { + $data = $this->backend->getFileContents( array( 'src' => $source ) ); + $this->assertNotEquals( false, $data, "Contents of $source exists ($backendName)." ); + $this->assertEquals( $content[0], $data, "Contents of $source is correct ($backendName)." ); + } } - function provider_testGetFileContents() { + public static function provider_testGetFileContents() { $cases = array(); - $base = $this->baseStorePath(); + $base = self::baseStorePath(); $cases[] = array( "$base/unittest-cont1/e/b/z/some_file.txt", "some file contents" ); $cases[] = array( "$base/unittest-cont1/e/b/some-other_file.txt", "more file contents" ); + $cases[] = array( + array( "$base/unittest-cont1/e/a/x.txt", "$base/unittest-cont1/e/a/y.txt", + "$base/unittest-cont1/e/a/z.txt" ), + array( "contents xx", "contents xy", "contents xz" ) + ); return $cases; } /** * @dataProvider provider_testGetLocalCopy + * @covers FileBackend::getLocalCopy */ public function testGetLocalCopy( $source, $content ) { $this->backend = $this->singleBackend; @@ -982,33 +1195,60 @@ class FileBackendTest extends MediaWikiTestCase { private function doTestGetLocalCopy( $source, $content ) { $backendName = $this->backendClass(); - $this->prepare( array( 'dir' => dirname( $source ) ) ); - - $status = $this->backend->doOperation( - array( 'op' => 'create', 'content' => $content, 'dst' => $source ) ); - $this->assertGoodStatus( $status, - "Creation of file at $source succeeded ($backendName)." ); + $srcs = (array)$source; + $content = (array)$content; + foreach ( $srcs as $i => $src ) { + $this->prepare( array( 'dir' => dirname( $src ) ) ); + $status = $this->backend->doOperation( + array( 'op' => 'create', 'content' => $content[$i], 'dst' => $src ) ); + $this->assertGoodStatus( $status, + "Creation of file at $src succeeded ($backendName)." ); + } - $tmpFile = $this->backend->getLocalCopy( array( 'src' => $source ) ); - $this->assertNotNull( $tmpFile, - "Creation of local copy of $source succeeded ($backendName)." ); + if ( is_array( $source ) ) { + $tmpFiles = $this->backend->getLocalCopyMulti( array( 'srcs' => $source ) ); + foreach ( $tmpFiles as $path => $tmpFile ) { + $this->assertNotNull( $tmpFile, + "Creation of local copy of $path succeeded ($backendName)." ); + $contents = file_get_contents( $tmpFile->getPath() ); + $this->assertNotEquals( false, $contents, "Local copy of $path exists ($backendName)." ); + $this->assertEquals( current( $content ), $contents, "Local copy of $path is correct ($backendName)." ); + next( $content ); + } + $this->assertEquals( $source, array_keys( $tmpFiles ), "Local copies in right order ($backendName)." ); + $this->assertEquals( count( $source ), count( $tmpFiles ), "Local copies array size correct ($backendName)." ); + } else { + $tmpFile = $this->backend->getLocalCopy( array( 'src' => $source ) ); + $this->assertNotNull( $tmpFile, + "Creation of local copy of $source succeeded ($backendName)." ); + $contents = file_get_contents( $tmpFile->getPath() ); + $this->assertNotEquals( false, $contents, "Local copy of $source exists ($backendName)." ); + $this->assertEquals( $content[0], $contents, "Local copy of $source is correct ($backendName)." ); + } - $contents = file_get_contents( $tmpFile->getPath() ); - $this->assertNotEquals( false, $contents, "Local copy of $source exists ($backendName)." ); + $obj = new stdClass(); + $tmpFile->bind( $obj ); } - function provider_testGetLocalCopy() { + public static function provider_testGetLocalCopy() { $cases = array(); - $base = $this->baseStorePath(); + $base = self::baseStorePath(); $cases[] = array( "$base/unittest-cont1/e/a/z/some_file.txt", "some file contents" ); $cases[] = array( "$base/unittest-cont1/e/a/some-other_file.txt", "more file contents" ); + $cases[] = array( "$base/unittest-cont1/e/a/\$odd&.txt", "test file contents" ); + $cases[] = array( + array( "$base/unittest-cont1/e/a/x.txt", "$base/unittest-cont1/e/a/y.txt", + "$base/unittest-cont1/e/a/z.txt" ), + array( "contents xx", "contents xy", "contents xz" ) + ); return $cases; } /** * @dataProvider provider_testGetLocalReference + * @covers FileBackend::getLocalReference */ public function testGetLocalReference( $source, $content ) { $this->backend = $this->singleBackend; @@ -1025,32 +1265,133 @@ class FileBackendTest extends MediaWikiTestCase { private function doTestGetLocalReference( $source, $content ) { $backendName = $this->backendClass(); - $this->prepare( array( 'dir' => dirname( $source ) ) ); + $srcs = (array)$source; + $content = (array)$content; + foreach ( $srcs as $i => $src ) { + $this->prepare( array( 'dir' => dirname( $src ) ) ); + $status = $this->backend->doOperation( + array( 'op' => 'create', 'content' => $content[$i], 'dst' => $src ) ); + $this->assertGoodStatus( $status, + "Creation of file at $src succeeded ($backendName)." ); + } + + if ( is_array( $source ) ) { + $tmpFiles = $this->backend->getLocalReferenceMulti( array( 'srcs' => $source ) ); + foreach ( $tmpFiles as $path => $tmpFile ) { + $this->assertNotNull( $tmpFile, + "Creation of local copy of $path succeeded ($backendName)." ); + $contents = file_get_contents( $tmpFile->getPath() ); + $this->assertNotEquals( false, $contents, "Local ref of $path exists ($backendName)." ); + $this->assertEquals( current( $content ), $contents, "Local ref of $path is correct ($backendName)." ); + next( $content ); + } + $this->assertEquals( $source, array_keys( $tmpFiles ), "Local refs in right order ($backendName)." ); + $this->assertEquals( count( $source ), count( $tmpFiles ), "Local refs array size correct ($backendName)." ); + } else { + $tmpFile = $this->backend->getLocalReference( array( 'src' => $source ) ); + $this->assertNotNull( $tmpFile, + "Creation of local copy of $source succeeded ($backendName)." ); + $contents = file_get_contents( $tmpFile->getPath() ); + $this->assertNotEquals( false, $contents, "Local ref of $source exists ($backendName)." ); + $this->assertEquals( $content[0], $contents, "Local ref of $source is correct ($backendName)." ); + } + } + + public static function provider_testGetLocalReference() { + $cases = array(); + + $base = self::baseStorePath(); + $cases[] = array( "$base/unittest-cont1/e/a/z/some_file.txt", "some file contents" ); + $cases[] = array( "$base/unittest-cont1/e/a/some-other_file.txt", "more file contents" ); + $cases[] = array( "$base/unittest-cont1/e/a/\$odd&.txt", "test file contents" ); + $cases[] = array( + array( "$base/unittest-cont1/e/a/x.txt", "$base/unittest-cont1/e/a/y.txt", + "$base/unittest-cont1/e/a/z.txt" ), + array( "contents xx", "contents xy", "contents xz" ) + ); + + return $cases; + } + + /** + * @covers FileBackend::getLocalCopy + * @covers FileBackend::getLocalReference + */ + public function testGetLocalCopyAndReference404() { + $this->backend = $this->singleBackend; + $this->tearDownFiles(); + $this->doTestGetLocalCopyAndReference404(); + $this->tearDownFiles(); - $status = $this->create( array( 'content' => $content, 'dst' => $source ) ); + $this->backend = $this->multiBackend; + $this->tearDownFiles(); + $this->doTestGetLocalCopyAndReference404(); + $this->tearDownFiles(); + } + + public function doTestGetLocalCopyAndReference404() { + $backendName = $this->backendClass(); + + $base = self::baseStorePath(); + + $tmpFile = $this->backend->getLocalCopy( array( + 'src' => "$base/unittest-cont1/not-there" ) ); + $this->assertEquals( null, $tmpFile, "Local copy of not existing file is null ($backendName)." ); + + $tmpFile = $this->backend->getLocalReference( array( + 'src' => "$base/unittest-cont1/not-there" ) ); + $this->assertEquals( null, $tmpFile, "Local ref of not existing file is null ($backendName)." ); + } + + /** + * @dataProvider provider_testGetFileHttpUrl + * @covers FileBackend::getFileHttpUrl + */ + public function testGetFileHttpUrl( $source, $content ) { + $this->backend = $this->singleBackend; + $this->tearDownFiles(); + $this->doTestGetFileHttpUrl( $source, $content ); + $this->tearDownFiles(); + + $this->backend = $this->multiBackend; + $this->tearDownFiles(); + $this->doTestGetFileHttpUrl( $source, $content ); + $this->tearDownFiles(); + } + + private function doTestGetFileHttpUrl( $source, $content ) { + $backendName = $this->backendClass(); + + $this->prepare( array( 'dir' => dirname( $source ) ) ); + $status = $this->backend->doOperation( + array( 'op' => 'create', 'content' => $content, 'dst' => $source ) ); $this->assertGoodStatus( $status, "Creation of file at $source succeeded ($backendName)." ); - $tmpFile = $this->backend->getLocalReference( array( 'src' => $source ) ); - $this->assertNotNull( $tmpFile, - "Creation of local copy of $source succeeded ($backendName)." ); + $url = $this->backend->getFileHttpUrl( array( 'src' => $source ) ); - $contents = file_get_contents( $tmpFile->getPath() ); - $this->assertNotEquals( false, $contents, "Local copy of $source exists ($backendName)." ); + if ( $url !== null ) { // supported + $data = Http::request( "GET", $url ); + $this->assertEquals( $content, $data, + "HTTP GET of URL has right contents ($backendName)." ); + } } - function provider_testGetLocalReference() { + public static function provider_testGetFileHttpUrl() { $cases = array(); - $base = $this->baseStorePath(); + $base = self::baseStorePath(); $cases[] = array( "$base/unittest-cont1/e/a/z/some_file.txt", "some file contents" ); $cases[] = array( "$base/unittest-cont1/e/a/some-other_file.txt", "more file contents" ); + $cases[] = array( "$base/unittest-cont1/e/a/\$odd&.txt", "test file contents" ); return $cases; } /** * @dataProvider provider_testPrepareAndClean + * @covers FileBackend::prepare + * @covers FileBackend::clean */ public function testPrepareAndClean( $path, $isOK ) { $this->backend = $this->singleBackend; @@ -1062,8 +1403,9 @@ class FileBackendTest extends MediaWikiTestCase { $this->tearDownFiles(); } - function provider_testPrepareAndClean() { - $base = $this->baseStorePath(); + public static function provider_testPrepareAndClean() { + $base = self::baseStorePath(); + return array( array( "$base/unittest-cont1/e/a/z/some_file1.txt", true ), array( "$base/unittest-cont2/a/z/some_file2.txt", true ), @@ -1108,11 +1450,16 @@ class FileBackendTest extends MediaWikiTestCase { $this->tearDownFiles(); } + /** + * @covers FileBackend::clean + */ private function doTestRecursiveClean() { $backendName = $this->backendClass(); - $base = $this->baseStorePath(); + $base = self::baseStorePath(); $dirs = array( + "$base/unittest-cont1", + "$base/unittest-cont1/e", "$base/unittest-cont1/e/a", "$base/unittest-cont1/e/a/b", "$base/unittest-cont1/e/a/b/c", @@ -1150,8 +1497,11 @@ class FileBackendTest extends MediaWikiTestCase { } } - // @TODO: testSecure + // @todo testSecure + /** + * @covers FileBackend::doOperations + */ public function testDoOperations() { $this->backend = $this->singleBackend; $this->tearDownFiles(); @@ -1165,7 +1515,7 @@ class FileBackendTest extends MediaWikiTestCase { } private function doTestDoOperations() { - $base = $this->baseStorePath(); + $base = self::baseStorePath(); $fileA = "$base/unittest-cont1/e/a/b/fileA.txt"; $fileAContents = '3tqtmoeatmn4wg4qe-mg3qt3 tq'; @@ -1184,6 +1534,8 @@ class FileBackendTest extends MediaWikiTestCase { $this->prepare( array( 'dir' => dirname( $fileD ) ) ); $status = $this->backend->doOperations( array( + array( 'op' => 'describe', 'src' => $fileA, + 'headers' => array( 'X-Content-Length' => '91.3' ), 'disposition' => 'inline' ), array( 'op' => 'copy', 'src' => $fileA, 'dst' => $fileC, 'overwrite' => 1 ), // Now: A:<A>, B:<B>, C:<A>, D:<empty> (file:<orginal contents>) array( 'op' => 'copy', 'src' => $fileC, 'dst' => $fileA, 'overwriteSame' => 1 ), @@ -1214,7 +1566,7 @@ class FileBackendTest extends MediaWikiTestCase { $this->assertGoodStatus( $status, "Operation batch succeeded" ); $this->assertEquals( true, $status->isOK(), "Operation batch succeeded" ); - $this->assertEquals( 13, count( $status->success ), + $this->assertEquals( 14, count( $status->success ), "Operation batch has correct success array" ); $this->assertEquals( false, $this->backend->fileExists( array( 'src' => $fileA ) ), @@ -1237,6 +1589,9 @@ class FileBackendTest extends MediaWikiTestCase { "Correct file SHA-1 of $fileC" ); } + /** + * @covers FileBackend::doOperations + */ public function testDoOperationsPipeline() { $this->backend = $this->singleBackend; $this->tearDownFiles(); @@ -1251,7 +1606,7 @@ class FileBackendTest extends MediaWikiTestCase { // concurrency orientated private function doTestDoOperationsPipeline() { - $base = $this->baseStorePath(); + $base = self::baseStorePath(); $fileAContents = '3tqtmoeatmn4wg4qe-mg3qt3 tq'; $fileBContents = 'g-jmq3gpqgt3qtg q3GT '; @@ -1336,6 +1691,9 @@ class FileBackendTest extends MediaWikiTestCase { "Correct file SHA-1 of $fileC" ); } + /** + * @covers FileBackend::doOperations + */ public function testDoOperationsFailing() { $this->backend = $this->singleBackend; $this->tearDownFiles(); @@ -1349,7 +1707,7 @@ class FileBackendTest extends MediaWikiTestCase { } private function doTestDoOperationsFailing() { - $base = $this->baseStorePath(); + $base = self::baseStorePath(); $fileA = "$base/unittest-cont2/a/b/fileA.txt"; $fileAContents = '3tqtmoeatmn4wg4qe-mg3qt3 tq'; @@ -1410,6 +1768,9 @@ class FileBackendTest extends MediaWikiTestCase { "Correct file SHA-1 of $fileA" ); } + /** + * @covers FileBackend::getFileList + */ public function testGetFileList() { $this->backend = $this->singleBackend; $this->tearDownFiles(); @@ -1424,7 +1785,7 @@ class FileBackendTest extends MediaWikiTestCase { private function doTestGetFileList() { $backendName = $this->backendClass(); - $base = $this->baseStorePath(); + $base = self::baseStorePath(); // Should have no errors $iter = $this->backend->getFileList( array( 'dir' => "$base/unittest-cont-notexists" ) ); @@ -1458,7 +1819,7 @@ class FileBackendTest extends MediaWikiTestCase { $this->assertEquals( true, $status->isOK(), "Creation of files succeeded with OK status ($backendName)." ); - // Expected listing + // Expected listing at root $expected = array( "e/test1.txt", "e/test2.txt", @@ -1477,27 +1838,28 @@ class FileBackendTest extends MediaWikiTestCase { ); sort( $expected ); - // Actual listing (no trailing slash) - $list = array(); + // Actual listing (no trailing slash) at root $iter = $this->backend->getFileList( array( 'dir' => "$base/unittest-cont1" ) ); - foreach ( $iter as $file ) { - $list[] = $file; - } + $list = $this->listToArray( $iter ); sort( $list ); + $this->assertEquals( $expected, $list, "Correct file listing ($backendName)." ); + // Actual listing (no trailing slash) at root with advise + $iter = $this->backend->getFileList( array( 'dir' => "$base/unittest-cont1", 'adviseStat' => 1 ) ); + $list = $this->listToArray( $iter ); + sort( $list ); $this->assertEquals( $expected, $list, "Correct file listing ($backendName)." ); - // Actual listing (with trailing slash) + // Actual listing (with trailing slash) at root $list = array(); $iter = $this->backend->getFileList( array( 'dir' => "$base/unittest-cont1/" ) ); foreach ( $iter as $file ) { $list[] = $file; } sort( $list ); - $this->assertEquals( $expected, $list, "Correct file listing ($backendName)." ); - // Expected listing + // Expected listing at subdir $expected = array( "test1.txt", "test2.txt", @@ -1509,36 +1871,39 @@ class FileBackendTest extends MediaWikiTestCase { ); sort( $expected ); - // Actual listing (no trailing slash) - $list = array(); + // Actual listing (no trailing slash) at subdir $iter = $this->backend->getFileList( array( 'dir' => "$base/unittest-cont1/e/subdir2/subdir" ) ); - foreach ( $iter as $file ) { - $list[] = $file; - } + $list = $this->listToArray( $iter ); sort( $list ); + $this->assertEquals( $expected, $list, "Correct file listing ($backendName)." ); + // Actual listing (no trailing slash) at subdir with advise + $iter = $this->backend->getFileList( array( 'dir' => "$base/unittest-cont1/e/subdir2/subdir", 'adviseStat' => 1 ) ); + $list = $this->listToArray( $iter ); + sort( $list ); $this->assertEquals( $expected, $list, "Correct file listing ($backendName)." ); - // Actual listing (with trailing slash) + // Actual listing (with trailing slash) at subdir $list = array(); $iter = $this->backend->getFileList( array( 'dir' => "$base/unittest-cont1/e/subdir2/subdir/" ) ); foreach ( $iter as $file ) { $list[] = $file; } sort( $list ); - $this->assertEquals( $expected, $list, "Correct file listing ($backendName)." ); // Actual listing (using iterator second time) - $list = array(); - foreach ( $iter as $file ) { - $list[] = $file; - } + $list = $this->listToArray( $iter ); sort( $list ); - $this->assertEquals( $expected, $list, "Correct file listing ($backendName), second iteration." ); - // Expected listing (top files only) + // Actual listing (top files only) at root + $iter = $this->backend->getTopFileList( array( 'dir' => "$base/unittest-cont1" ) ); + $list = $this->listToArray( $iter ); + sort( $list ); + $this->assertEquals( array(), $list, "Correct top file listing ($backendName)." ); + + // Expected listing (top files only) at subdir $expected = array( "test1.txt", "test2.txt", @@ -1548,14 +1913,16 @@ class FileBackendTest extends MediaWikiTestCase { ); sort( $expected ); - // Actual listing (top files only) - $list = array(); + // Actual listing (top files only) at subdir $iter = $this->backend->getTopFileList( array( 'dir' => "$base/unittest-cont1/e/subdir2/subdir" ) ); - foreach ( $iter as $file ) { - $list[] = $file; - } + $list = $this->listToArray( $iter ); sort( $list ); + $this->assertEquals( $expected, $list, "Correct top file listing ($backendName)." ); + // Actual listing (top files only) at subdir with advise + $iter = $this->backend->getTopFileList( array( 'dir' => "$base/unittest-cont1/e/subdir2/subdir", 'adviseStat' => 1 ) ); + $list = $this->listToArray( $iter ); + sort( $list ); $this->assertEquals( $expected, $list, "Correct top file listing ($backendName)." ); foreach ( $files as $file ) { // clean up @@ -1563,9 +1930,15 @@ class FileBackendTest extends MediaWikiTestCase { } $iter = $this->backend->getFileList( array( 'dir' => "$base/unittest-cont1/not/exists" ) ); - foreach ( $iter as $iter ) {} // no errors + foreach ( $iter as $iter ) { + // no errors + } } + /** + * @covers FileBackend::getTopDirectoryList + * @covers FileBackend::getDirectoryList + */ public function testGetDirectoryList() { $this->backend = $this->singleBackend; $this->tearDownFiles(); @@ -1581,7 +1954,7 @@ class FileBackendTest extends MediaWikiTestCase { private function doTestGetDirectoryList() { $backendName = $this->backendClass(); - $base = $this->baseStorePath(); + $base = self::baseStorePath(); $files = array( "$base/unittest-cont1/e/test1.txt", "$base/unittest-cont1/e/test2.txt", @@ -1751,14 +2124,31 @@ class FileBackendTest extends MediaWikiTestCase { $this->assertEquals( $expected, $list, "Correct dir listing ($backendName)." ); + $iter = $this->backend->getDirectoryList( array( 'dir' => "$base/unittest-cont1/e/subdir1" ) ); + $items = $this->listToArray( $iter ); + $this->assertEquals( array(), $items, "Directory listing is empty." ); + foreach ( $files as $file ) { // clean up $this->backend->doOperation( array( 'op' => 'delete', 'src' => $file ) ); } $iter = $this->backend->getDirectoryList( array( 'dir' => "$base/unittest-cont1/not/exists" ) ); - foreach ( $iter as $iter ) {} // no errors + foreach ( $iter as $file ) { + // no errors + } + + $items = $this->listToArray( $iter ); + $this->assertEquals( array(), $items, "Directory listing is empty." ); + + $iter = $this->backend->getDirectoryList( array( 'dir' => "$base/unittest-cont1/e/not/exists" ) ); + $items = $this->listToArray( $iter ); + $this->assertEquals( array(), $items, "Directory listing is empty." ); } + /** + * @covers FileBackend::lockFiles + * @covers FileBackend::unlockFiles + */ public function testLockCalls() { $this->backend = $this->singleBackend; $this->doTestLockCalls(); @@ -1767,54 +2157,102 @@ class FileBackendTest extends MediaWikiTestCase { private function doTestLockCalls() { $backendName = $this->backendClass(); - for ( $i=0; $i<50; $i++ ) { - $paths = array( - "test1.txt", - "test2.txt", - "test3.txt", - "subdir1", - "subdir1", // duplicate - "subdir1/test1.txt", - "subdir1/test2.txt", - "subdir2", - "subdir2", // duplicate - "subdir2/test3.txt", - "subdir2/test4.txt", - "subdir2/subdir", - "subdir2/subdir/test1.txt", - "subdir2/subdir/test2.txt", - "subdir2/subdir/test3.txt", - "subdir2/subdir/test4.txt", - "subdir2/subdir/test5.txt", - "subdir2/subdir/sub", - "subdir2/subdir/sub/test0.txt", - "subdir2/subdir/sub/120-px-file.txt", - ); + $paths = array( + "test1.txt", + "test2.txt", + "test3.txt", + "subdir1", + "subdir1", // duplicate + "subdir1/test1.txt", + "subdir1/test2.txt", + "subdir2", + "subdir2", // duplicate + "subdir2/test3.txt", + "subdir2/test4.txt", + "subdir2/subdir", + "subdir2/subdir/test1.txt", + "subdir2/subdir/test2.txt", + "subdir2/subdir/test3.txt", + "subdir2/subdir/test4.txt", + "subdir2/subdir/test5.txt", + "subdir2/subdir/sub", + "subdir2/subdir/sub/test0.txt", + "subdir2/subdir/sub/120-px-file.txt", + ); + for ( $i = 0; $i < 25; $i++ ) { $status = $this->backend->lockFiles( $paths, LockManager::LOCK_EX ); - $this->assertEquals( array(), $status->errors, - "Locking of files succeeded ($backendName)." ); + $this->assertEquals( print_r( array(), true ), print_r( $status->errors, true ), + "Locking of files succeeded ($backendName) ($i)." ); $this->assertEquals( true, $status->isOK(), - "Locking of files succeeded with OK status ($backendName)." ); + "Locking of files succeeded with OK status ($backendName) ($i)." ); $status = $this->backend->lockFiles( $paths, LockManager::LOCK_SH ); - $this->assertEquals( array(), $status->errors, - "Locking of files succeeded ($backendName)." ); + $this->assertEquals( print_r( array(), true ), print_r( $status->errors, true ), + "Locking of files succeeded ($backendName) ($i)." ); $this->assertEquals( true, $status->isOK(), - "Locking of files succeeded with OK status ($backendName)." ); + "Locking of files succeeded with OK status ($backendName) ($i)." ); $status = $this->backend->unlockFiles( $paths, LockManager::LOCK_SH ); - $this->assertEquals( array(), $status->errors, - "Locking of files succeeded ($backendName)." ); + $this->assertEquals( print_r( array(), true ), print_r( $status->errors, true ), + "Locking of files succeeded ($backendName) ($i)." ); $this->assertEquals( true, $status->isOK(), - "Locking of files succeeded with OK status ($backendName)." ); + "Locking of files succeeded with OK status ($backendName) ($i)." ); $status = $this->backend->unlockFiles( $paths, LockManager::LOCK_EX ); - $this->assertEquals( array(), $status->errors, - "Locking of files succeeded ($backendName)." ); + $this->assertEquals( print_r( array(), true ), print_r( $status->errors, true ), + "Locking of files succeeded ($backendName). ($i)" ); $this->assertEquals( true, $status->isOK(), - "Locking of files succeeded with OK status ($backendName)." ); + "Locking of files succeeded with OK status ($backendName) ($i)." ); + + ## Flip the acquire/release ordering around ## + + $status = $this->backend->lockFiles( $paths, LockManager::LOCK_SH ); + $this->assertEquals( print_r( array(), true ), print_r( $status->errors, true ), + "Locking of files succeeded ($backendName) ($i)." ); + $this->assertEquals( true, $status->isOK(), + "Locking of files succeeded with OK status ($backendName) ($i)." ); + + $status = $this->backend->lockFiles( $paths, LockManager::LOCK_EX ); + $this->assertEquals( print_r( array(), true ), print_r( $status->errors, true ), + "Locking of files succeeded ($backendName) ($i)." ); + $this->assertEquals( true, $status->isOK(), + "Locking of files succeeded with OK status ($backendName) ($i)." ); + + $status = $this->backend->unlockFiles( $paths, LockManager::LOCK_EX ); + $this->assertEquals( print_r( array(), true ), print_r( $status->errors, true ), + "Locking of files succeeded ($backendName). ($i)" ); + $this->assertEquals( true, $status->isOK(), + "Locking of files succeeded with OK status ($backendName) ($i)." ); + + $status = $this->backend->unlockFiles( $paths, LockManager::LOCK_SH ); + $this->assertEquals( print_r( array(), true ), print_r( $status->errors, true ), + "Locking of files succeeded ($backendName) ($i)." ); + $this->assertEquals( true, $status->isOK(), + "Locking of files succeeded with OK status ($backendName) ($i)." ); } + + $status = Status::newGood(); + $sl = $this->backend->getScopedFileLocks( $paths, LockManager::LOCK_EX, $status ); + $this->assertType( 'ScopedLock', $sl, + "Scoped locking of files succeeded ($backendName)." ); + $this->assertEquals( array(), $status->errors, + "Scoped locking of files succeeded ($backendName)." ); + $this->assertEquals( true, $status->isOK(), + "Scoped locking of files succeeded with OK status ($backendName)." ); + + ScopedLock::release( $sl ); + $this->assertEquals( null, $sl, + "Scoped unlocking of files succeeded ($backendName)." ); + $this->assertEquals( array(), $status->errors, + "Scoped unlocking of files succeeded ($backendName)." ); + $this->assertEquals( true, $status->isOK(), + "Scoped unlocking of files succeeded with OK status ($backendName)." ); + } + + // helper function + private function listToArray( $iter ) { + return is_array( $iter ) ? $iter : iterator_to_array( $iter ); } // test helper wrapper for backend prepare() function @@ -1825,14 +2263,17 @@ class FileBackendTest extends MediaWikiTestCase { // test helper wrapper for backend prepare() function private function create( array $params ) { $params['op'] = 'create'; + return $this->backend->doQuickOperations( array( $params ) ); } function tearDownFiles() { foreach ( $this->filesToPrune as $file ) { - @unlink( $file ); + if ( is_file( $file ) ) { + unlink( $file ); + } } - $containers = array( 'unittest-cont1', 'unittest-cont2', 'unittest-cont3' ); + $containers = array( 'unittest-cont1', 'unittest-cont2' ); foreach ( $containers as $container ) { $this->deleteFiles( $container ); } @@ -1840,13 +2281,14 @@ class FileBackendTest extends MediaWikiTestCase { } private function deleteFiles( $container ) { - $base = $this->baseStorePath(); + $base = self::baseStorePath(); $iter = $this->backend->getFileList( array( 'dir' => "$base/$container" ) ); if ( $iter ) { foreach ( $iter as $file ) { - $this->backend->delete( array( 'src' => "$base/$container/$file" ), - array( 'force' => 1, 'nonLocking' => 1 ) ); + $this->backend->quickDelete( array( 'src' => "$base/$container/$file" ) ); } + // free the directory, to avoid Permission denied under windows on rmdir + unset( $iter ); } $this->backend->clean( array( 'dir' => "$base/$container", 'recursive' => 1 ) ); } @@ -1861,8 +2303,4 @@ class FileBackendTest extends MediaWikiTestCase { function assertGoodStatus( $status, $msg ) { $this->assertEquals( print_r( array(), 1 ), print_r( $status->errors, 1 ), $msg ); } - - function tearDown() { - parent::tearDown(); - } } diff --git a/tests/phpunit/includes/filerepo/FileRepoTest.php b/tests/phpunit/includes/filerepo/FileRepoTest.php index 8f92c123..e3a75567 100644 --- a/tests/phpunit/includes/filerepo/FileRepoTest.php +++ b/tests/phpunit/includes/filerepo/FileRepoTest.php @@ -4,39 +4,49 @@ class FileRepoTest extends MediaWikiTestCase { /** * @expectedException MWException + * @covers FileRepo::__construct */ - function testFileRepoConstructionOptionCanNotBeNull() { - $f = new FileRepo(); + public function testFileRepoConstructionOptionCanNotBeNull() { + new FileRepo(); } + /** * @expectedException MWException + * @covers FileRepo::__construct */ - function testFileRepoConstructionOptionCanNotBeAnEmptyArray() { - $f = new FileRepo( array() ); + public function testFileRepoConstructionOptionCanNotBeAnEmptyArray() { + new FileRepo( array() ); } + /** * @expectedException MWException + * @covers FileRepo::__construct */ - function testFileRepoConstructionOptionNeedNameKey() { - $f = new FileRepo( array( + public function testFileRepoConstructionOptionNeedNameKey() { + new FileRepo( array( 'backend' => 'foobar' ) ); } + /** * @expectedException MWException + * @covers FileRepo::__construct */ - function testFileRepoConstructionOptionNeedBackendKey() { - $f = new FileRepo( array( + public function testFileRepoConstructionOptionNeedBackendKey() { + new FileRepo( array( 'name' => 'foobar' ) ); } - function testFileRepoConstructionWithRequiredOptions() { + /** + * @covers FileRepo::__construct + */ + public function testFileRepoConstructionWithRequiredOptions() { $f = new FileRepo( array( - 'name' => 'FileRepoTestRepository', + 'name' => 'FileRepoTestRepository', 'backend' => new FSFileBackend( array( - 'name' => 'local-testing', - 'lockManager' => 'nullLockManager', + 'name' => 'local-testing', + 'lockManager' => 'nullLockManager', 'containerPaths' => array() ) ) ) ); diff --git a/tests/phpunit/includes/filerepo/StoreBatchTest.php b/tests/phpunit/includes/filerepo/StoreBatchTest.php index 3ab56af8..b33c1bbb 100644 --- a/tests/phpunit/includes/filerepo/StoreBatchTest.php +++ b/tests/phpunit/includes/filerepo/StoreBatchTest.php @@ -1,11 +1,17 @@ <?php + /** * @group FileRepo * @group medium */ class StoreBatchTest extends MediaWikiTestCase { - public function setUp() { + protected $createdFiles; + protected $date; + /** @var FileRepo */ + protected $repo; + + protected function setUp() { global $wgFileBackends; parent::setUp(); @@ -24,18 +30,18 @@ class StoreBatchTest extends MediaWikiTestCase { $backend = new $class( $useConfig ); } else { $backend = new FSFileBackend( array( - 'name' => 'local-testing', + 'name' => 'local-testing', 'lockManager' => 'nullLockManager', 'containerPaths' => array( - 'unittests-public' => "{$tmpPrefix}-public", - 'unittests-thumb' => "{$tmpPrefix}-thumb", - 'unittests-temp' => "{$tmpPrefix}-temp", + 'unittests-public' => "{$tmpPrefix}-public", + 'unittests-thumb' => "{$tmpPrefix}-thumb", + 'unittests-temp' => "{$tmpPrefix}-temp", 'unittests-deleted' => "{$tmpPrefix}-deleted", ) ) ); } $this->repo = new FileRepo( array( - 'name' => 'unittests', + 'name' => 'unittests', 'backend' => $backend ) ); @@ -43,14 +49,26 @@ class StoreBatchTest extends MediaWikiTestCase { $this->createdFiles = array(); } + protected function tearDown() { + $this->repo->cleanupBatch( $this->createdFiles ); // delete files + foreach ( $this->createdFiles as $tmp ) { // delete dirs + $tmp = $this->repo->resolveVirtualUrl( $tmp ); + while ( $tmp = FileBackend::parentStoragePath( $tmp ) ) { + $this->repo->getBackend()->clean( array( 'dir' => $tmp ) ); + } + } + parent::tearDown(); + } + /** * Store a file or virtual URL source into a media file name. * * @param $originalName string The title of the image * @param $srcPath string The filepath or virtual URL * @param $flags integer Flags to pass into repo::store(). + * @return FileRepoStatus */ - private function storeit($originalName, $srcPath, $flags) { + private function storeit( $originalName, $srcPath, $flags ) { $hashPath = $this->repo->getHashPath( $originalName ); $dstRel = "$hashPath{$this->date}!$originalName"; $dstUrlRel = $hashPath . $this->date . '!' . rawurlencode( $originalName ); @@ -58,6 +76,7 @@ class StoreBatchTest extends MediaWikiTestCase { $result = $this->repo->store( $srcPath, 'temp', $dstRel, $flags ); $result->value = $this->repo->getVirtualUrl( 'temp' ) . '/' . $dstUrlRel; $this->createdFiles[] = $result->value; + return $result; } @@ -67,57 +86,49 @@ class StoreBatchTest extends MediaWikiTestCase { * @param $fn string The title of the image * @param $infn string The name of the file (in the filesystem) * @param $otherfn string The name of the different file (in the filesystem) - * @param $fromrepo logical 'true' if we want to copy from a virtual URL out of the Repo. + * @param $fromrepo bool 'true' if we want to copy from a virtual URL out of the Repo. */ - private function storecohort($fn, $infn, $otherfn, $fromrepo) { + private function storecohort( $fn, $infn, $otherfn, $fromrepo ) { $f = $this->storeit( $fn, $infn, 0 ); $this->assertTrue( $f->isOK(), 'failed to store a new file' ); $this->assertEquals( $f->failCount, 0, "counts wrong {$f->successCount} {$f->failCount}" ); - $this->assertEquals( $f->successCount, 1 , "counts wrong {$f->successCount} {$f->failCount}" ); + $this->assertEquals( $f->successCount, 1, "counts wrong {$f->successCount} {$f->failCount}" ); if ( $fromrepo ) { - $f = $this->storeit( "Other-$fn", $infn, FileRepo::OVERWRITE); + $f = $this->storeit( "Other-$fn", $infn, FileRepo::OVERWRITE ); $infn = $f->value; } // This should work because we're allowed to overwrite $f = $this->storeit( $fn, $infn, FileRepo::OVERWRITE ); $this->assertTrue( $f->isOK(), 'We should be allowed to overwrite' ); $this->assertEquals( $f->failCount, 0, "counts wrong {$f->successCount} {$f->failCount}" ); - $this->assertEquals( $f->successCount, 1 , "counts wrong {$f->successCount} {$f->failCount}" ); + $this->assertEquals( $f->successCount, 1, "counts wrong {$f->successCount} {$f->failCount}" ); // This should fail because we're overwriting. $f = $this->storeit( $fn, $infn, 0 ); $this->assertFalse( $f->isOK(), 'We should not be allowed to overwrite' ); $this->assertEquals( $f->failCount, 1, "counts wrong {$f->successCount} {$f->failCount}" ); - $this->assertEquals( $f->successCount, 0 , "counts wrong {$f->successCount} {$f->failCount}" ); + $this->assertEquals( $f->successCount, 0, "counts wrong {$f->successCount} {$f->failCount}" ); // This should succeed because we're overwriting the same content. $f = $this->storeit( $fn, $infn, FileRepo::OVERWRITE_SAME ); $this->assertTrue( $f->isOK(), 'We should be able to overwrite the same content' ); $this->assertEquals( $f->failCount, 0, "counts wrong {$f->successCount} {$f->failCount}" ); - $this->assertEquals( $f->successCount, 1 , "counts wrong {$f->successCount} {$f->failCount}" ); + $this->assertEquals( $f->successCount, 1, "counts wrong {$f->successCount} {$f->failCount}" ); // This should fail because we're overwriting different content. if ( $fromrepo ) { - $f = $this->storeit( "Other-$fn", $otherfn, FileRepo::OVERWRITE); + $f = $this->storeit( "Other-$fn", $otherfn, FileRepo::OVERWRITE ); $otherfn = $f->value; } $f = $this->storeit( $fn, $otherfn, FileRepo::OVERWRITE_SAME ); $this->assertFalse( $f->isOK(), 'We should not be allowed to overwrite different content' ); $this->assertEquals( $f->failCount, 1, "counts wrong {$f->successCount} {$f->failCount}" ); - $this->assertEquals( $f->successCount, 0 , "counts wrong {$f->successCount} {$f->failCount}" ); + $this->assertEquals( $f->successCount, 0, "counts wrong {$f->successCount} {$f->failCount}" ); } + /** + * @covers FileRepo::store + */ public function teststore() { global $IP; $this->storecohort( "Test1.png", "$IP/skins/monobook/wiki.png", "$IP/skins/monobook/video.png", false ); $this->storecohort( "Test2.png", "$IP/skins/monobook/wiki.png", "$IP/skins/monobook/video.png", true ); } - - public function tearDown() { - $this->repo->cleanupBatch( $this->createdFiles ); // delete files - foreach ( $this->createdFiles as $tmp ) { // delete dirs - $tmp = $this->repo->resolveVirtualUrl( $tmp ); - while ( $tmp = FileBackend::parentStoragePath( $tmp ) ) { - $this->repo->getBackend()->clean( array( 'dir' => $tmp ) ); - } - } - parent::tearDown(); - } } diff --git a/tests/phpunit/includes/installer/InstallDocFormatterTest.php b/tests/phpunit/includes/installer/InstallDocFormatterTest.php index 56485d3e..0e5f2671 100644 --- a/tests/phpunit/includes/installer/InstallDocFormatterTest.php +++ b/tests/phpunit/includes/installer/InstallDocFormatterTest.php @@ -1,5 +1,5 @@ <?php -/* +/* * To change this template, choose Tools | Templates * and open the template in the editor. */ @@ -9,7 +9,7 @@ class InstallDocFormatterTest extends MediaWikiTestCase { * @covers InstallDocFormatter::format * @dataProvider provideDocFormattingTests */ - function testFormat( $expected, $unformattedText, $message = '' ) { + public function testFormat( $expected, $unformattedText, $message = '' ) { $this->assertEquals( $expected, InstallDocFormatter::format( $unformattedText ), @@ -20,21 +20,22 @@ class InstallDocFormatterTest extends MediaWikiTestCase { /** * Provider for testFormat() */ - function provideDocFormattingTests() { + public static function provideDocFormattingTests() { # Format: (expected string, unformattedText string, optional message) return array( # Escape some wikitext - array( 'Install <tag>' , 'Install <tag>', 'Escaping <' ), - array( 'Install {{template}}' , 'Install {{template}}', 'Escaping [[' ), - array( 'Install [[page]]' , 'Install [[page]]', 'Escaping {{' ), - array( 'Install ' , "Install \r", 'Removing \r' ), + array( 'Install <tag>', 'Install <tag>', 'Escaping <' ), + array( 'Install {{template}}', 'Install {{template}}', 'Escaping [[' ), + array( 'Install [[page]]', 'Install [[page]]', 'Escaping {{' ), + array( 'Install __TOC__', 'Install __TOC__', 'Escaping __' ), + array( 'Install ', "Install \r", 'Removing \r' ), # Transform \t{1,2} into :{1,2} array( ':One indentation', "\tOne indentation", 'Replacing a single \t' ), array( '::Two indentations', "\t\tTwo indentations", 'Replacing 2 x \t' ), # Transform 'bug 123' links - array( + array( '<span class="config-plainlink">[https://bugzilla.wikimedia.org/123 bug 123]</span>', 'bug 123', 'Testing bug 123 links' ), array( @@ -55,7 +56,7 @@ class InstallDocFormatterTest extends MediaWikiTestCase { array( '<span class="config-plainlink">[http://www.mediawiki.org/wiki/Manual:$wgFoo_Bar $wgFoo_Bar]</span>', '$wgFoo_Bar', 'Testing $wgFoo_Bar (with underscore)' ), - + # Icky variables that shouldn't link array( '$myAwesomeVariable', '$myAwesomeVariable', 'Testing $myAwesomeVariable (not starting with $wg)' ), array( '$()not!a&Var', '$()not!a&Var', 'Testing $()not!a&Var (obviously not a variable)' ), diff --git a/tests/phpunit/includes/installer/OracleInstallerTest.php b/tests/phpunit/includes/installer/OracleInstallerTest.php new file mode 100644 index 00000000..66e65592 --- /dev/null +++ b/tests/phpunit/includes/installer/OracleInstallerTest.php @@ -0,0 +1,48 @@ +<?php + +/** + * Tests for OracleInstaller + * + * @group Database + * @group Installer + */ + +class OracleInstallerTest extends MediaWikiTestCase { + + /** + * @dataProvider provideOracleConnectStrings + * @covers OracleInstaller::checkConnectStringFormat + */ + public function testCheckConnectStringFormat( $expected, $connectString, $msg = '' ) { + $validity = $expected ? 'should be valid' : 'should NOT be valid'; + $msg = "'$connectString' ($msg) $validity."; + $this->assertEquals( $expected, + OracleInstaller::checkConnectStringFormat( $connectString ), + $msg + ); + } + + /** + * Provider to test OracleInstaller::checkConnectStringFormat() + */ + function provideOracleConnectStrings() { + // expected result, connectString[, message] + return array( + array( true, 'simple_01', 'Simple TNS name' ), + array( true, 'simple_01.world', 'TNS name with domain' ), + array( true, 'simple_01.domain.net', 'TNS name with domain' ), + array( true, 'host123', 'Host only' ), + array( true, 'host123.domain.net', 'FQDN only' ), + array( true, '//host123.domain.net', 'FQDN URL only' ), + array( true, '123.223.213.132', 'Host IP only' ), + array( true, 'host:1521', 'Host and port' ), + array( true, 'host:1521/service', 'Host, port and service' ), + array( true, 'host:1521/service:shared', 'Host, port, service and shared server type' ), + array( true, 'host:1521/service:dedicated', 'Host, port, service and dedicated server type' ), + array( true, 'host:1521/service:pooled', 'Host, port, service and pooled server type' ), + array( true, 'host:1521/service:shared/instance1', 'Host, port, service, server type and instance' ), + array( true, 'host:1521//instance1', 'Host, port and instance' ), + ); + } + +} diff --git a/tests/phpunit/includes/jobqueue/JobQueueTest.php b/tests/phpunit/includes/jobqueue/JobQueueTest.php new file mode 100644 index 00000000..4e51c4fc --- /dev/null +++ b/tests/phpunit/includes/jobqueue/JobQueueTest.php @@ -0,0 +1,329 @@ +<?php + +/** + * @group JobQueue + * @group medium + * @group Database + */ +class JobQueueTest extends MediaWikiTestCase { + protected $key; + protected $queueRand, $queueRandTTL, $queueFifo, $queueFifoTTL; + + function __construct( $name = null, array $data = array(), $dataName = '' ) { + parent::__construct( $name, $data, $dataName ); + + $this->tablesUsed[] = 'job'; + } + + protected function setUp() { + global $wgJobTypeConf; + parent::setUp(); + + $this->setMwGlobals( 'wgMemc', new HashBagOStuff() ); + + if ( $this->getCliArg( 'use-jobqueue=' ) ) { + $name = $this->getCliArg( 'use-jobqueue=' ); + if ( !isset( $wgJobTypeConf[$name] ) ) { + throw new MWException( "No \$wgJobTypeConf entry for '$name'." ); + } + $baseConfig = $wgJobTypeConf[$name]; + } else { + $baseConfig = array( 'class' => 'JobQueueDB' ); + } + $baseConfig['type'] = 'null'; + $baseConfig['wiki'] = wfWikiID(); + $variants = array( + 'queueRand' => array( 'order' => 'random', 'claimTTL' => 0 ), + 'queueRandTTL' => array( 'order' => 'random', 'claimTTL' => 10 ), + 'queueTimestamp' => array( 'order' => 'timestamp', 'claimTTL' => 0 ), + 'queueTimestampTTL' => array( 'order' => 'timestamp', 'claimTTL' => 10 ), + 'queueFifo' => array( 'order' => 'fifo', 'claimTTL' => 0 ), + 'queueFifoTTL' => array( 'order' => 'fifo', 'claimTTL' => 10 ), + ); + foreach ( $variants as $q => $settings ) { + try { + $this->$q = JobQueue::factory( $settings + $baseConfig ); + if ( !( $this->$q instanceof JobQueueDB ) ) { + $this->$q->setTestingPrefix( 'unittests-' . wfRandomString( 32 ) ); + } + } catch ( MWException $e ) { + // unsupported? + // @todo What if it was another error? + }; + } + } + + protected function tearDown() { + parent::tearDown(); + foreach ( + array( + 'queueRand', 'queueRandTTL', 'queueTimestamp', 'queueTimestampTTL', + 'queueFifo', 'queueFifoTTL' + ) as $q + ) { + if ( $this->$q ) { + $this->$q->delete(); + } + $this->$q = null; + } + } + + /** + * @dataProvider provider_queueLists + */ + public function testProperties( $queue, $recycles, $desc ) { + $queue = $this->$queue; + if ( !$queue ) { + $this->markTestSkipped( $desc ); + } + + $this->assertEquals( wfWikiID(), $queue->getWiki(), "Proper wiki ID ($desc)" ); + $this->assertEquals( 'null', $queue->getType(), "Proper job type ($desc)" ); + } + + /** + * @dataProvider provider_queueLists + */ + public function testBasicOperations( $queue, $recycles, $desc ) { + $queue = $this->$queue; + if ( !$queue ) { + $this->markTestSkipped( $desc ); + } + + $this->assertTrue( $queue->isEmpty(), "Queue is empty ($desc)" ); + + $queue->flushCaches(); + $this->assertEquals( 0, $queue->getSize(), "Queue is empty ($desc)" ); + $this->assertEquals( 0, $queue->getAcquiredCount(), "Queue is empty ($desc)" ); + + $this->assertTrue( $queue->push( $this->newJob() ), "Push worked ($desc)" ); + $this->assertTrue( $queue->batchPush( array( $this->newJob() ) ), "Push worked ($desc)" ); + + $this->assertFalse( $queue->isEmpty(), "Queue is not empty ($desc)" ); + + $queue->flushCaches(); + $this->assertEquals( 2, $queue->getSize(), "Queue size is correct ($desc)" ); + $this->assertEquals( 0, $queue->getAcquiredCount(), "No jobs active ($desc)" ); + $jobs = iterator_to_array( $queue->getAllQueuedJobs() ); + $this->assertEquals( 2, count( $jobs ), "Queue iterator size is correct ($desc)" ); + + $job1 = $queue->pop(); + $this->assertFalse( $queue->isEmpty(), "Queue is not empty ($desc)" ); + + $queue->flushCaches(); + $this->assertEquals( 1, $queue->getSize(), "Queue size is correct ($desc)" ); + + $queue->flushCaches(); + if ( $recycles ) { + $this->assertEquals( 1, $queue->getAcquiredCount(), "Active job count ($desc)" ); + } else { + $this->assertEquals( 0, $queue->getAcquiredCount(), "Active job count ($desc)" ); + } + + $job2 = $queue->pop(); + $this->assertTrue( $queue->isEmpty(), "Queue is empty ($desc)" ); + $this->assertEquals( 0, $queue->getSize(), "Queue is empty ($desc)" ); + + $queue->flushCaches(); + if ( $recycles ) { + $this->assertEquals( 2, $queue->getAcquiredCount(), "Active job count ($desc)" ); + } else { + $this->assertEquals( 0, $queue->getAcquiredCount(), "Active job count ($desc)" ); + } + + $queue->ack( $job1 ); + + $queue->flushCaches(); + if ( $recycles ) { + $this->assertEquals( 1, $queue->getAcquiredCount(), "Active job count ($desc)" ); + } else { + $this->assertEquals( 0, $queue->getAcquiredCount(), "Active job count ($desc)" ); + } + + $queue->ack( $job2 ); + + $queue->flushCaches(); + $this->assertEquals( 0, $queue->getAcquiredCount(), "Active job count ($desc)" ); + + $this->assertTrue( $queue->batchPush( array( $this->newJob(), $this->newJob() ) ), + "Push worked ($desc)" ); + $this->assertFalse( $queue->isEmpty(), "Queue is not empty ($desc)" ); + + $queue->delete(); + $queue->flushCaches(); + $this->assertTrue( $queue->isEmpty(), "Queue is empty ($desc)" ); + $this->assertEquals( 0, $queue->getSize(), "Queue is empty ($desc)" ); + } + + /** + * @dataProvider provider_queueLists + */ + public function testBasicDeduplication( $queue, $recycles, $desc ) { + $queue = $this->$queue; + if ( !$queue ) { + $this->markTestSkipped( $desc ); + } + + $this->assertTrue( $queue->isEmpty(), "Queue is empty ($desc)" ); + + $queue->flushCaches(); + $this->assertEquals( 0, $queue->getSize(), "Queue is empty ($desc)" ); + $this->assertEquals( 0, $queue->getAcquiredCount(), "Queue is empty ($desc)" ); + + $this->assertTrue( + $queue->batchPush( + array( $this->newDedupedJob(), $this->newDedupedJob(), $this->newDedupedJob() ) + ), + "Push worked ($desc)" ); + + $this->assertFalse( $queue->isEmpty(), "Queue is not empty ($desc)" ); + + $queue->flushCaches(); + $this->assertEquals( 1, $queue->getSize(), "Queue size is correct ($desc)" ); + $this->assertEquals( 0, $queue->getAcquiredCount(), "No jobs active ($desc)" ); + + $this->assertTrue( + $queue->batchPush( + array( $this->newDedupedJob(), $this->newDedupedJob(), $this->newDedupedJob() ) + ), + "Push worked ($desc)" + ); + + $this->assertFalse( $queue->isEmpty(), "Queue is not empty ($desc)" ); + + $queue->flushCaches(); + $this->assertEquals( 1, $queue->getSize(), "Queue size is correct ($desc)" ); + $this->assertEquals( 0, $queue->getAcquiredCount(), "No jobs active ($desc)" ); + + $job1 = $queue->pop(); + $this->assertTrue( $queue->isEmpty(), "Queue is empty ($desc)" ); + + $queue->flushCaches(); + $this->assertEquals( 0, $queue->getSize(), "Queue is empty ($desc)" ); + if ( $recycles ) { + $this->assertEquals( 1, $queue->getAcquiredCount(), "Active job count ($desc)" ); + } else { + $this->assertEquals( 0, $queue->getAcquiredCount(), "Active job count ($desc)" ); + } + + $queue->ack( $job1 ); + + $queue->flushCaches(); + $this->assertEquals( 0, $queue->getAcquiredCount(), "Active job count ($desc)" ); + } + + /** + * @dataProvider provider_queueLists + */ + public function testRootDeduplication( $queue, $recycles, $desc ) { + $queue = $this->$queue; + if ( !$queue ) { + $this->markTestSkipped( $desc ); + } + + $this->assertTrue( $queue->isEmpty(), "Queue is empty ($desc)" ); + + $queue->flushCaches(); + $this->assertEquals( 0, $queue->getSize(), "Queue is empty ($desc)" ); + $this->assertEquals( 0, $queue->getAcquiredCount(), "Queue is empty ($desc)" ); + + $id = wfRandomString( 32 ); + $root1 = Job::newRootJobParams( "nulljobspam:$id" ); // task ID/timestamp + for ( $i = 0; $i < 5; ++$i ) { + $this->assertTrue( $queue->push( $this->newJob( 0, $root1 ) ), "Push worked ($desc)" ); + } + $queue->deduplicateRootJob( $this->newJob( 0, $root1 ) ); + sleep( 1 ); // roo job timestamp will increase + $root2 = Job::newRootJobParams( "nulljobspam:$id" ); // task ID/timestamp + $this->assertNotEquals( $root1['rootJobTimestamp'], $root2['rootJobTimestamp'], + "Root job signatures have different timestamps." ); + for ( $i = 0; $i < 5; ++$i ) { + $this->assertTrue( $queue->push( $this->newJob( 0, $root2 ) ), "Push worked ($desc)" ); + } + $queue->deduplicateRootJob( $this->newJob( 0, $root2 ) ); + + $this->assertFalse( $queue->isEmpty(), "Queue is not empty ($desc)" ); + + $queue->flushCaches(); + $this->assertEquals( 10, $queue->getSize(), "Queue size is correct ($desc)" ); + $this->assertEquals( 0, $queue->getAcquiredCount(), "No jobs active ($desc)" ); + + $dupcount = 0; + $jobs = array(); + do { + $job = $queue->pop(); + if ( $job ) { + $jobs[] = $job; + $queue->ack( $job ); + } + if ( $job instanceof DuplicateJob ) { + ++$dupcount; + } + } while ( $job ); + + $this->assertEquals( 10, count( $jobs ), "Correct number of jobs popped ($desc)" ); + $this->assertEquals( 5, $dupcount, "Correct number of duplicate jobs popped ($desc)" ); + } + + /** + * @dataProvider provider_fifoQueueLists + */ + public function testJobOrder( $queue, $recycles, $desc ) { + $queue = $this->$queue; + if ( !$queue ) { + $this->markTestSkipped( $desc ); + } + + $this->assertTrue( $queue->isEmpty(), "Queue is empty ($desc)" ); + + $queue->flushCaches(); + $this->assertEquals( 0, $queue->getSize(), "Queue is empty ($desc)" ); + $this->assertEquals( 0, $queue->getAcquiredCount(), "Queue is empty ($desc)" ); + + for ( $i = 0; $i < 10; ++$i ) { + $this->assertTrue( $queue->push( $this->newJob( $i ) ), "Push worked ($desc)" ); + } + + for ( $i = 0; $i < 10; ++$i ) { + $job = $queue->pop(); + $this->assertTrue( $job instanceof Job, "Jobs popped from queue ($desc)" ); + $params = $job->getParams(); + $this->assertEquals( $i, $params['i'], "Job popped from queue is FIFO ($desc)" ); + $queue->ack( $job ); + } + + $this->assertFalse( $queue->pop(), "Queue is not empty ($desc)" ); + + $queue->flushCaches(); + $this->assertEquals( 0, $queue->getSize(), "Queue is empty ($desc)" ); + $this->assertEquals( 0, $queue->getAcquiredCount(), "No jobs active ($desc)" ); + } + + public static function provider_queueLists() { + return array( + array( 'queueRand', false, 'Random queue without ack()' ), + array( 'queueRandTTL', true, 'Random queue with ack()' ), + array( 'queueTimestamp', false, 'Time ordered queue without ack()' ), + array( 'queueTimestampTTL', true, 'Time ordered queue with ack()' ), + array( 'queueFifo', false, 'FIFO ordered queue without ack()' ), + array( 'queueFifoTTL', true, 'FIFO ordered queue with ack()' ) + ); + } + + public static function provider_fifoQueueLists() { + return array( + array( 'queueFifo', false, 'Ordered queue without ack()' ), + array( 'queueFifoTTL', true, 'Ordered queue with ack()' ) + ); + } + + function newJob( $i = 0, $rootJob = array() ) { + return new NullJob( Title::newMainPage(), + array( 'lives' => 0, 'usleep' => 0, 'removeDuplicates' => 0, 'i' => $i ) + $rootJob ); + } + + function newDedupedJob( $i = 0, $rootJob = array() ) { + return new NullJob( Title::newMainPage(), + array( 'lives' => 0, 'usleep' => 0, 'removeDuplicates' => 1, 'i' => $i ) + $rootJob ); + } +} diff --git a/tests/phpunit/includes/json/FormatJsonTest.php b/tests/phpunit/includes/json/FormatJsonTest.php new file mode 100644 index 00000000..149be05b --- /dev/null +++ b/tests/phpunit/includes/json/FormatJsonTest.php @@ -0,0 +1,161 @@ +<?php + +class FormatJsonTest extends MediaWikiTestCase { + + public function testEncoderPrettyPrinting() { + $obj = array( + 'emptyObject' => new stdClass, + 'emptyArray' => array(), + 'string' => 'foobar\\', + 'filledArray' => array( + array( + 123, + 456, + ), + // Nested json works without problems + '"7":["8",{"9":"10"}]', + // Whitespace clean up doesn't touch strings that look alike + "{\n\t\"emptyObject\": {\n\t},\n\t\"emptyArray\": [ ]\n}", + ), + ); + + // 4 space indent, no trailing whitespace, no trailing linefeed + $json = '{ + "emptyObject": {}, + "emptyArray": [], + "string": "foobar\\\\", + "filledArray": [ + [ + 123, + 456 + ], + "\"7\":[\"8\",{\"9\":\"10\"}]", + "{\n\t\"emptyObject\": {\n\t},\n\t\"emptyArray\": [ ]\n}" + ] +}'; + + $json = str_replace( "\r", '', $json ); // Windows compat + $this->assertSame( $json, FormatJson::encode( $obj, true ) ); + } + + public static function provideEncodeDefault() { + return self::getEncodeTestCases( array() ); + } + + /** + * @dataProvider provideEncodeDefault + */ + public function testEncodeDefault( $from, $to ) { + $this->assertSame( $to, FormatJson::encode( $from ) ); + } + + public static function provideEncodeUtf8() { + return self::getEncodeTestCases( array( 'unicode' ) ); + } + + /** + * @dataProvider provideEncodeUtf8 + */ + public function testEncodeUtf8( $from, $to ) { + $this->assertSame( $to, FormatJson::encode( $from, false, FormatJson::UTF8_OK ) ); + } + + public static function provideEncodeXmlMeta() { + return self::getEncodeTestCases( array( 'xmlmeta' ) ); + } + + /** + * @dataProvider provideEncodeXmlMeta + */ + public function testEncodeXmlMeta( $from, $to ) { + $this->assertSame( $to, FormatJson::encode( $from, false, FormatJson::XMLMETA_OK ) ); + } + + public static function provideEncodeAllOk() { + return self::getEncodeTestCases( array( 'unicode', 'xmlmeta' ) ); + } + + /** + * @dataProvider provideEncodeAllOk + */ + public function testEncodeAllOk( $from, $to ) { + $this->assertSame( $to, FormatJson::encode( $from, false, FormatJson::ALL_OK ) ); + } + + public function testEncodePhpBug46944() { + $this->assertNotEquals( + '\ud840\udc00', + strtolower( FormatJson::encode( "\xf0\xa0\x80\x80" ) ), + 'Test encoding an broken json_encode character (U+20000)' + ); + } + + public function testDecodeReturnType() { + $this->assertInternalType( + 'object', + FormatJson::decode( '{"Name": "Cheeso", "Rank": 7}' ), + 'Default to object' + ); + + $this->assertInternalType( + 'array', + FormatJson::decode( '{"Name": "Cheeso", "Rank": 7}', true ), + 'Optional array' + ); + } + + /** + * Generate a set of test cases for a particular combination of encoder options. + * + * @param array $unescapedGroups List of character groups to leave unescaped + * @return array: Arrays of unencoded strings and corresponding encoded strings + */ + private static function getEncodeTestCases( array $unescapedGroups ) { + $groups = array( + 'always' => array( + // Forward slash (always unescaped) + '/' => '/', + + // Control characters + "\0" => '\u0000', + "\x08" => '\b', + "\t" => '\t', + "\n" => '\n', + "\r" => '\r', + "\f" => '\f', + "\x1f" => '\u001f', // representative example + + // Double quotes + '"' => '\"', + + // Backslashes + '\\' => '\\\\', + '\\\\' => '\\\\\\\\', + '\\u00e9' => '\\\u00e9', // security check for Unicode unescaping + + // Line terminators + "\xe2\x80\xa8" => '\u2028', + "\xe2\x80\xa9" => '\u2029', + ), + 'unicode' => array( + "\xc3\xa9" => '\u00e9', + "\xf0\x9d\x92\x9e" => '\ud835\udc9e', // U+1D49E, outside the BMP + ), + 'xmlmeta' => array( + '<' => '\u003C', // JSON_HEX_TAG uses uppercase hex digits + '>' => '\u003E', + '&' => '\u0026', + ), + ); + + $cases = array(); + foreach ( $groups as $name => $rules ) { + $leaveUnescaped = in_array( $name, $unescapedGroups ); + foreach ( $rules as $from => $to ) { + $cases[] = array( $from, '"' . ( $leaveUnescaped ? $from : $to ) . '"' ); + } + } + + return $cases; + } +} diff --git a/tests/phpunit/includes/json/ServicesJsonTest.php b/tests/phpunit/includes/json/ServicesJsonTest.php deleted file mode 100644 index 8f2421a2..00000000 --- a/tests/phpunit/includes/json/ServicesJsonTest.php +++ /dev/null @@ -1,93 +0,0 @@ -<?php -/* - * Test cases for our Services_Json library. Requires PHP json support as well, - * so we can compare output - */ -class ServicesJsonTest extends MediaWikiTestCase { - /** - * Test to make sure core json_encode() and our Services_Json()->encode() - * produce the same output - * - * @dataProvider provideValuesToEncode - */ - public function testJsonEncode( $input, $desc ) { - if ( !function_exists( 'json_encode' ) ) { - $this->markTestIncomplete( 'No PHP json support, unable to test' ); - return; - } elseif( strtolower( json_encode( "\xf0\xa0\x80\x80" ) ) != '"\ud840\udc00"' ) { - $this->markTestIncomplete( 'Have buggy PHP json support, unable to test' ); - return; - } else { - $jsonObj = new Services_JSON(); - $this->assertEquals( - $jsonObj->encode( $input ), - json_encode( $input ), - $desc - ); - } - } - - /** - * Test to make sure core json_decode() and our Services_Json()->decode() - * produce the same output - * - * @dataProvider provideValuesToDecode - */ - public function testJsonDecode( $input, $desc ) { - if ( !function_exists( 'json_decode' ) ) { - $this->markTestIncomplete( 'No PHP json support, unable to test' ); - return; - } else { - $jsonObj = new Services_JSON(); - $this->assertEquals( - $jsonObj->decode( $input ), - json_decode( $input ), - $desc - ); - } - } - - function provideValuesToEncode() { - $obj = new stdClass(); - $obj->property = 'value'; - $obj->property2 = null; - $obj->property3 = 1.234; - return array( - array( 1, 'basic integer' ), - array( -1, 'negative integer' ), - array( 1.1, 'basic float' ), - array( true, 'basic bool true' ), - array( false, 'basic bool false' ), - array( 'some string', 'basic string test' ), - array( "some string\nwith newline", 'newline string test' ), - array( '♥ü', 'unicode string test' ), - array( array( 'some', 'string', 'values' ), 'basic array of strings' ), - array( array( 'key1' => 'val1', 'key2' => 'val2' ), 'array with string keys' ), - array( array( 1 => 'val1', 3 => 'val2', '2' => 'val3' ), 'out of order numbered array test' ), - array( array(), 'empty array test' ), - array( $obj, 'basic object test' ), - array( new stdClass, 'empty object test' ), - array( null, 'null test' ), - ); - } - - function provideValuesToDecode() { - return array( - array( '1', 'basic integer' ), - array( '-1', 'negative integer' ), - array( '1.1', 'basic float' ), - array( '1.1e1', 'scientific float' ), - array( 'true', 'basic bool true' ), - array( 'false', 'basic bool false' ), - array( '"some string"', 'basic string test' ), - array( '"some string\nwith newline"', 'newline string test' ), - array( '"♥ü"', 'unicode character string test' ), - array( '"\u2665"', 'unicode \\u string test' ), - array( '["some","string","values"]', 'basic array of strings' ), - array( '[]', 'empty array test' ), - array( '{"key":"value"}', 'Basic key => value test' ), - array( '{}', 'empty object test' ), - array( 'null', 'null test' ), - ); - } -} diff --git a/tests/phpunit/includes/libs/CSSJanusTest.php b/tests/phpunit/includes/libs/CSSJanusTest.php index 54f66077..5a3c1619 100644 --- a/tests/phpunit/includes/libs/CSSJanusTest.php +++ b/tests/phpunit/includes/libs/CSSJanusTest.php @@ -4,12 +4,14 @@ * CSSJanus libary: * http://code.google.com/p/cssjanus/source/browse/trunk/cssjanus_test.py * Ported to PHP for ResourceLoader and has been extended since. + * + * @covers CSSJanus */ class CSSJanusTest extends MediaWikiTestCase { /** * @dataProvider provideTransformCases */ - function testTransform( $cssA, $cssB = null ) { + public function testTransform( $cssA, $cssB = null ) { if ( $cssB ) { $transformedA = CSSJanus::transform( $cssA ); @@ -17,10 +19,9 @@ class CSSJanusTest extends MediaWikiTestCase { $transformedB = CSSJanus::transform( $cssB ); $this->assertEquals( $transformedB, $cssA, 'Test B-A transformation' ); - - // If no B version is provided, it means - // the output should equal the input. } else { + // If no B version is provided, it means + // the output should equal the input. $transformedA = CSSJanus::transform( $cssA ); $this->assertEquals( $transformedA, $cssA, 'Nothing was flipped' ); } @@ -29,22 +30,23 @@ class CSSJanusTest extends MediaWikiTestCase { /** * @dataProvider provideTransformAdvancedCases */ - function testTransformAdvanced( $code, $expectedOutput, $options = array() ) { + public function testTransformAdvanced( $code, $expectedOutput, $options = array() ) { $swapLtrRtlInURL = isset( $options['swapLtrRtlInURL'] ) ? $options['swapLtrRtlInURL'] : false; $swapLeftRightInURL = isset( $options['swapLeftRightInURL'] ) ? $options['swapLeftRightInURL'] : false; $flipped = CSSJanus::transform( $code, $swapLtrRtlInURL, $swapLeftRightInURL ); $this->assertEquals( $expectedOutput, $flipped, - 'Test flipping, options: url-ltr-rtl=' . ($swapLtrRtlInURL ? 'true' : 'false') - . ' url-left-right=' . ($swapLeftRightInURL ? 'true' : 'false') + 'Test flipping, options: url-ltr-rtl=' . ( $swapLtrRtlInURL ? 'true' : 'false' ) + . ' url-left-right=' . ( $swapLeftRightInURL ? 'true' : 'false' ) ); } + /** * @dataProvider provideTransformBrokenCases * @group Broken */ - function testTransformBroken( $code, $expectedOutput ) { + public function testTransformBroken( $code, $expectedOutput ) { $flipped = CSSJanus::transform( $code ); $this->assertEquals( $expectedOutput, $flipped, 'Test flipping' ); @@ -54,7 +56,7 @@ class CSSJanusTest extends MediaWikiTestCase { * These transform cases are tested *in both directions* * No need to declare a principle twice in both directions here. */ - function provideTransformCases() { + public static function provideTransformCases() { return array( // Property keys array( @@ -137,10 +139,15 @@ class CSSJanusTest extends MediaWikiTestCase { '.foo { padding: 1px inherit 3px auto; }', '.foo { padding: 1px auto 3px inherit; }' ), + // border-radius assigns different meanings to the values array( '.foo { border-radius: .25em 15px 0pt 0ex; }', - '.foo { border-radius: .25em 0ex 0pt 15px; }' + '.foo { border-radius: 15px .25em 0ex 0pt; }' + ), + array( + '.foo { border-radius: 0px 0px 5px 5px; }', ), + // Ensure the rule doesn't break other stuff array( '.foo { x-unknown: a b c d; }' ), @@ -151,14 +158,26 @@ class CSSJanusTest extends MediaWikiTestCase { '#settings td p strong' ), array( - # Not sure how 4+ values should behave, - # testing to make sure changes are detected - '.foo { x-unknown: 1 2 3 4 5; }', - '.foo { x-unknown: 1 4 3 2 5; }', + // Color names + '.foo { border-color: red green blue white }', + '.foo { border-color: red white blue green }', + ), + array( + // Color name, hexdecimal, RGB & RGBA + '.foo { border-color: red #f00 rgb(255, 0, 0) rgba(255, 0, 0, 0.5) }', + '.foo { border-color: red rgba(255, 0, 0, 0.5) rgb(255, 0, 0) #f00 }', ), array( - '.foo { x-unknown: 1 2 3 4 5 6; }', - '.foo { x-unknown: 1 4 3 2 5 6; }', + // Color name, hexdecimal, HSL & HSLA + '.foo { border-color: red #f00 hsl(0, 100%, 50%) hsla(0, 100%, 50%, 0.5) }', + '.foo { border-color: red hsla(0, 100%, 50%, 0.5) hsl(0, 100%, 50%) #f00 }', + ), + array( + // Do not mangle 5 or more values + '.foo { -x-unknown: 1 2 3 4 5; }' + ), + array( + '.foo { -x-unknown: 1 2 3 4 5 6; }' ), // Shorthand / Three notation @@ -179,6 +198,28 @@ class CSSJanusTest extends MediaWikiTestCase { '.foo { padding: 1px; }' ), + // text-shadow and box-shadow + array( + '.foo { box-shadow: -6px 3px 8px 5px rgba(0, 0, 0, 0.25); }', + '.foo { box-shadow: 6px 3px 8px 5px rgba(0, 0, 0, 0.25); }', + ), + array( + '.foo { box-shadow: inset -6px 3px 8px 5px rgba(0, 0, 0, 0.25); }', + '.foo { box-shadow: inset 6px 3px 8px 5px rgba(0, 0, 0, 0.25); }', + ), + array( + '.foo { text-shadow: orange 2px 0; }', + '.foo { text-shadow: orange -2px 0; }', + ), + array( + '.foo { text-shadow: 2px 0 orange; }', + '.foo { text-shadow: -2px 0 orange; }', + ), + array( + // Don't mangle zeroes + '.foo { text-shadow: orange 0 2px; }' + ), + // Direction // Note: This differs from the Python implementation, // see also CSSJanus::fixDirection for more info. @@ -377,6 +418,11 @@ class CSSJanusTest extends MediaWikiTestCase { '/* @noflip */ div { float: left; } .foo { float: right; }' ), array( + // support parentheses in selector + '/* @noflip */ .test:not(:first) { margin-right: -0.25em; margin-left: 0.25em; }', + '/* @noflip */ .test:not(:first) { margin-right: -0.25em; margin-left: 0.25em; }' + ), + array( // after multiple rules '.foo { float: left; } /* @noflip */ div { float: left; }', '.foo { float: right; } /* @noflip */ div { float: left; }' @@ -458,6 +504,16 @@ class CSSJanusTest extends MediaWikiTestCase { ".foo\t{\tleft\t:\t0;}", ".foo\t{\tright\t:\t0;}" ), + + // Guard against partial keys + array( + '.foo { leftxx: 0; }', + '.foo { leftxx: 0; }' + ), + array( + '.foo { rightxx: 0; }', + '.foo { rightxx: 0; }' + ), ); } @@ -466,7 +522,7 @@ class CSSJanusTest extends MediaWikiTestCase { * If both ways can be tested, either put both versions in here or move * it to provideTransformCases(). */ - function provideTransformAdvancedCases() { + public static function provideTransformAdvancedCases() { $bgPairs = array( # [ - _ . ] <-> [ left right ltr rtl ] 'foo.jpg' => 'foo.jpg', @@ -532,18 +588,8 @@ class CSSJanusTest extends MediaWikiTestCase { * Cases that are currently failing, but * should be looked at in the future as enhancements and/or bug fix */ - function provideTransformBrokenCases() { + public static function provideTransformBrokenCases() { return array( - // Guard against partial keys - array( - '.foo { leftxx: 0; }', - '.foo { leftxx: 0; }' - ), - array( - '.foo { rightxx: 0; }', - '.foo { rightxx: 0; }' - ), - // Guard against selectors that look flippable array( # <foo-left-x attr="x"> diff --git a/tests/phpunit/includes/libs/CSSMinTest.php b/tests/phpunit/includes/libs/CSSMinTest.php index a3827756..951dd7b9 100644 --- a/tests/phpunit/includes/libs/CSSMinTest.php +++ b/tests/phpunit/includes/libs/CSSMinTest.php @@ -6,37 +6,28 @@ */ class CSSMinTest extends MediaWikiTestCase { - protected $oldServer = null, $oldCanServer = null; - function setUp() { + protected function setUp() { parent::setUp(); - // Fake $wgServer and $wgCanonicalServer - global $wgServer, $wgCanonicalServer; - $this->oldServer = $wgServer; - $this->oldCanServer = $wgCanonicalServer; - $wgServer = $wgCanonicalServer = 'http://wiki.example.org'; - } - - function tearDown() { - // Restore $wgServer and $wgCanonicalServer - global $wgServer, $wgCanonicalServer; - $wgServer = $this->oldServer; - $wgCanonicalServer = $this->oldCanServer; + $server = 'http://doc.example.org'; - parent::tearDown(); + $this->setMwGlobals( array( + 'wgServer' => $server, + 'wgCanonicalServer' => $server, + ) ); } /** * @dataProvider provideMinifyCases */ - function testMinify( $code, $expectedOutput ) { + public function testMinify( $code, $expectedOutput ) { $minified = CSSMin::minify( $code ); $this->assertEquals( $expectedOutput, $minified, 'Minified output should be in the form expected.' ); } - function provideMinifyCases() { + public static function provideMinifyCases() { return array( // Whitespace array( "\r\t\f \v\n\r", "" ), @@ -79,14 +70,14 @@ class CSSMinTest extends MediaWikiTestCase { /** * @dataProvider provideRemapCases */ - function testRemap( $message, $params, $expectedOutput ) { + public function testRemap( $message, $params, $expectedOutput ) { $remapped = call_user_func_array( 'CSSMin::remap', $params ); $messageAdd = " Case: $message"; $this->assertEquals( $expectedOutput, $remapped, 'CSSMin::remap should return the expected url form.' . $messageAdd ); } - function provideRemapCases() { + public static function provideRemapCases() { // Parameter signature: // CSSMin::remap( $code, $local, $remote, $embedData = true ) return array( @@ -113,7 +104,7 @@ class CSSMinTest extends MediaWikiTestCase { array( 'Expand absolute paths', array( 'foo { prop: url(/w/skin/images/bar.png); }', false, 'http://example.org/quux', false ), - 'foo { prop: url(http://wiki.example.org/w/skin/images/bar.png); }', + 'foo { prop: url(http://doc.example.org/w/skin/images/bar.png); }', ), ); } @@ -124,11 +115,11 @@ class CSSMinTest extends MediaWikiTestCase { * @group Broken * @dataProvider provideStringCases */ - function testMinifyWithCSSStringValues( $code, $expectedOutput ) { + public function testMinifyWithCSSStringValues( $code, $expectedOutput ) { $this->testMinifyOutput( $code, $expectedOutput ); } - function provideStringCases() { + public static function provideStringCases() { return array( // String values should be respected // - More than one space in a string value diff --git a/tests/phpunit/includes/libs/GenericArrayObjectTest.php b/tests/phpunit/includes/libs/GenericArrayObjectTest.php index 70fce111..7436c43c 100644 --- a/tests/phpunit/includes/libs/GenericArrayObjectTest.php +++ b/tests/phpunit/includes/libs/GenericArrayObjectTest.php @@ -36,7 +36,7 @@ abstract class GenericArrayObjectTest extends MediaWikiTestCase { * * @return array */ - public abstract function elementInstancesProvider(); + abstract public function elementInstancesProvider(); /** * Returns the name of the concrete class being tested. @@ -45,7 +45,7 @@ abstract class GenericArrayObjectTest extends MediaWikiTestCase { * * @return string */ - public abstract function getInstanceClass(); + abstract public function getInstanceClass(); /** * Provides instances of the concrete class being tested. @@ -58,7 +58,7 @@ abstract class GenericArrayObjectTest extends MediaWikiTestCase { $instances = array(); foreach ( $this->elementInstancesProvider() as $elementInstances ) { - $instances[] = $this->getNew( $elementInstances ); + $instances[] = $this->getNew( $elementInstances[0] ); } return $this->arrayWrap( $instances ); @@ -73,6 +73,7 @@ abstract class GenericArrayObjectTest extends MediaWikiTestCase { */ protected function getNew( array $elements = array() ) { $class = $this->getInstanceClass(); + return new $class( $elements ); } @@ -110,7 +111,9 @@ abstract class GenericArrayObjectTest extends MediaWikiTestCase { * @param GenericArrayObject $list */ public function testUnset( GenericArrayObject $list ) { - if ( !$list->isEmpty() ) { + if ( $list->isEmpty() ) { + $this->assertTrue( true ); // We cannot test unset if there are no elements + } else { $offset = $list->getIterator()->key(); $count = $list->count(); $list->offsetUnset( $offset ); @@ -123,10 +126,6 @@ abstract class GenericArrayObjectTest extends MediaWikiTestCase { unset( $list[$offset] ); $this->assertEquals( $count - 1, $list->count() ); } - - $exception = null; - try { $list->offsetUnset( 'sdfsedtgsrdysftu' ); } catch ( \Exception $exception ){} - $this->assertInstanceOf( '\Exception', $exception ); } /** @@ -155,7 +154,7 @@ abstract class GenericArrayObjectTest extends MediaWikiTestCase { $this->assertEquals( $listSize, $list->count() ); - $this->checkTypeChecks( function( GenericArrayObject $list, $element ) { + $this->checkTypeChecks( function ( GenericArrayObject $list, $element ) { $list->append( $element ); } ); } @@ -171,14 +170,13 @@ abstract class GenericArrayObjectTest extends MediaWikiTestCase { $elementClass = $list->getObjectType(); - foreach ( array( 42, 'foo', array(), new \stdClass(), 4.2 ) as $element ) { + foreach ( array( 42, 'foo', array(), new stdClass(), 4.2 ) as $element ) { $validValid = $element instanceof $elementClass; - try{ + try { call_user_func( $function, $list, $element ); $valid = true; - } - catch ( InvalidArgumentException $exception ) { + } catch ( InvalidArgumentException $exception ) { $valid = false; } @@ -200,6 +198,7 @@ abstract class GenericArrayObjectTest extends MediaWikiTestCase { public function testOffsetSet( array $elements ) { if ( $elements === array() ) { $this->assertTrue( true ); + return; } @@ -237,9 +236,28 @@ abstract class GenericArrayObjectTest extends MediaWikiTestCase { $this->assertEquals( count( $elements ), $list->count() ); - $this->checkTypeChecks( function( GenericArrayObject $list, $element ) { + $this->checkTypeChecks( function ( GenericArrayObject $list, $element ) { $list->offsetSet( mt_rand(), $element ); } ); } + /** + * @dataProvider instanceProvider + * + * @since 1.21 + * + * @param GenericArrayObject $list + */ + public function testSerialization( GenericArrayObject $list ) { + $serialization = serialize( $list ); + $copy = unserialize( $serialization ); + + $this->assertEquals( $serialization, serialize( $copy ) ); + $this->assertEquals( count( $list ), count( $copy ) ); + + $list = $list->getArrayCopy(); + $copy = $copy->getArrayCopy(); + + $this->assertArrayEquals( $list, $copy, true, true ); + } } diff --git a/tests/phpunit/includes/libs/IEUrlExtensionTest.php b/tests/phpunit/includes/libs/IEUrlExtensionTest.php index c6270e90..66fe915a 100644 --- a/tests/phpunit/includes/libs/IEUrlExtensionTest.php +++ b/tests/phpunit/includes/libs/IEUrlExtensionTest.php @@ -4,15 +4,15 @@ * Tests for IEUrlExtension::findIE6Extension */ class IEUrlExtensionTest extends MediaWikiTestCase { - function testSimple() { - $this->assertEquals( + public function testSimple() { + $this->assertEquals( 'y', IEUrlExtension::findIE6Extension( 'x.y' ), 'Simple extension' ); } - function testSimpleNoExt() { + public function testSimpleNoExt() { $this->assertEquals( '', IEUrlExtension::findIE6Extension( 'x' ), @@ -20,7 +20,7 @@ class IEUrlExtensionTest extends MediaWikiTestCase { ); } - function testEmpty() { + public function testEmpty() { $this->assertEquals( '', IEUrlExtension::findIE6Extension( '' ), @@ -28,7 +28,7 @@ class IEUrlExtensionTest extends MediaWikiTestCase { ); } - function testQuestionMark() { + public function testQuestionMark() { $this->assertEquals( '', IEUrlExtension::findIE6Extension( '?' ), @@ -36,7 +36,7 @@ class IEUrlExtensionTest extends MediaWikiTestCase { ); } - function testExtQuestionMark() { + public function testExtQuestionMark() { $this->assertEquals( 'x', IEUrlExtension::findIE6Extension( '.x?' ), @@ -44,7 +44,7 @@ class IEUrlExtensionTest extends MediaWikiTestCase { ); } - function testQuestionMarkExt() { + public function testQuestionMarkExt() { $this->assertEquals( 'x', IEUrlExtension::findIE6Extension( '?.x' ), @@ -52,7 +52,7 @@ class IEUrlExtensionTest extends MediaWikiTestCase { ); } - function testInvalidChar() { + public function testInvalidChar() { $this->assertEquals( '', IEUrlExtension::findIE6Extension( '.x*' ), @@ -60,7 +60,7 @@ class IEUrlExtensionTest extends MediaWikiTestCase { ); } - function testInvalidCharThenExtension() { + public function testInvalidCharThenExtension() { $this->assertEquals( 'x', IEUrlExtension::findIE6Extension( '*.x' ), @@ -68,7 +68,7 @@ class IEUrlExtensionTest extends MediaWikiTestCase { ); } - function testMultipleQuestionMarks() { + public function testMultipleQuestionMarks() { $this->assertEquals( 'c', IEUrlExtension::findIE6Extension( 'a?b?.c?.d?e?f' ), @@ -76,7 +76,7 @@ class IEUrlExtensionTest extends MediaWikiTestCase { ); } - function testExeException() { + public function testExeException() { $this->assertEquals( 'd', IEUrlExtension::findIE6Extension( 'a?b?.exe?.d?.e' ), @@ -84,7 +84,7 @@ class IEUrlExtensionTest extends MediaWikiTestCase { ); } - function testExeException2() { + public function testExeException2() { $this->assertEquals( 'exe', IEUrlExtension::findIE6Extension( 'a?b?.exe' ), @@ -92,7 +92,7 @@ class IEUrlExtensionTest extends MediaWikiTestCase { ); } - function testHash() { + public function testHash() { $this->assertEquals( '', IEUrlExtension::findIE6Extension( 'a#b.c' ), @@ -100,7 +100,7 @@ class IEUrlExtensionTest extends MediaWikiTestCase { ); } - function testHash2() { + public function testHash2() { $this->assertEquals( '', IEUrlExtension::findIE6Extension( 'a?#b.c' ), @@ -108,11 +108,19 @@ class IEUrlExtensionTest extends MediaWikiTestCase { ); } - function testDotAtEnd() { + public function testDotAtEnd() { $this->assertEquals( '', IEUrlExtension::findIE6Extension( '.' ), 'Dot at end of string' ); } + + public function testTwoDots() { + $this->assertEquals( + 'z', + IEUrlExtension::findIE6Extension( 'x.y.z' ), + 'Two dots' + ); + } } diff --git a/tests/phpunit/includes/libs/JavaScriptMinifierTest.php b/tests/phpunit/includes/libs/JavaScriptMinifierTest.php index f121b018..ab72e361 100644 --- a/tests/phpunit/includes/libs/JavaScriptMinifierTest.php +++ b/tests/phpunit/includes/libs/JavaScriptMinifierTest.php @@ -2,7 +2,7 @@ class JavaScriptMinifierTest extends MediaWikiTestCase { - function provideCases() { + public static function provideCases() { return array( // Basic whitespace and comments that should be stripped entirely @@ -14,7 +14,7 @@ class JavaScriptMinifierTest extends MediaWikiTestCase { * At some point there was a bug that caused this comment to be ended at '* /', * causing /M... to be left as the beginning of a regex. */ - array( "/**\n * Foo\n * {\n * 'bar' : {\n * //Multiple rules with configurable operators\n * 'baz' : false\n * }\n */", ""), + array( "/**\n * Foo\n * {\n * 'bar' : {\n * //Multiple rules with configurable operators\n * 'baz' : false\n * }\n */", "" ), /** * ' Foo \' bar \ @@ -80,7 +80,7 @@ class JavaScriptMinifierTest extends MediaWikiTestCase { array( "switch(x){case y?z:{}/ x/g:{}/ x/g;}", "switch(x){case y?z:{}/x/g:{}/ x/g;}" ), array( "function x(){}/ x/g", "function x(){}/ x/g" ), array( "+function x(){}/ x/g", "+function x(){}/x/g" ), - + // Multiline quoted string array( "var foo=\"\\\nblah\\\n\";", "var foo=\"\\\nblah\\\n\";" ), @@ -96,16 +96,16 @@ class JavaScriptMinifierTest extends MediaWikiTestCase { // Division vs. regex nastiness array( "alert( (10+10) / '/'.charCodeAt( 0 ) + '//' );", "alert((10+10)/'/'.charCodeAt(0)+'//');" ), array( "if(1)/a /g.exec('Pa ss');", "if(1)/a /g.exec('Pa ss');" ), - + // newline insertion after 1000 chars: break after the "++", not before array( str_repeat( ';', 996 ) . "if(x++);", str_repeat( ';', 996 ) . "if(x++\n);" ), // Unicode letter characters should pass through ok in identifiers (bug 31187) - array( "var KaŝSkatolVal = {}", 'var KaŝSkatolVal={}'), + array( "var KaŝSkatolVal = {}", 'var KaŝSkatolVal={}' ), // Per spec unicode char escape values should work in identifiers, // as long as it's a valid char. In future it might get normalized. - array( "var Ka\\u015dSkatolVal = {}", 'var Ka\\u015dSkatolVal={}'), + array( "var Ka\\u015dSkatolVal = {}", 'var Ka\\u015dSkatolVal={}' ), // Some structures that might look invalid at first sight array( "var a = 5.;", "var a=5.;" ), @@ -119,7 +119,7 @@ class JavaScriptMinifierTest extends MediaWikiTestCase { /** * @dataProvider provideCases */ - function testJavaScriptMinifierOutput( $code, $expectedOutput ) { + public function testJavaScriptMinifierOutput( $code, $expectedOutput ) { $minified = JavaScriptMinifier::minify( $code ); // JSMin+'s parser will throw an exception if output is not valid JS. @@ -132,12 +132,12 @@ class JavaScriptMinifierTest extends MediaWikiTestCase { $this->assertEquals( $expectedOutput, $minified, "Minified output should be in the form expected." ); } - function provideBug32548() { + public static function provideBug32548() { return array( array( // This one gets interpreted all together by the prior code; // no break at the 'E' happens. - '1.23456789E55', + '1.23456789E55', ), array( // This one breaks under the bad code; splits between 'E' and '+' @@ -153,7 +153,7 @@ class JavaScriptMinifierTest extends MediaWikiTestCase { /** * @dataProvider provideBug32548 */ - function testBug32548Exponent( $num ) { + public function testBug32548Exponent( $num ) { // Long line breaking was being incorrectly done between the base and // exponent part of a number, causing a syntax error. The line should // instead break at the start of the number. @@ -165,6 +165,6 @@ class JavaScriptMinifierTest extends MediaWikiTestCase { $minified = JavaScriptMinifier::minify( $input ); - $this->assertEquals( $expected, $minified, "Line breaks must not occur in middle of exponent"); + $this->assertEquals( $expected, $minified, "Line breaks must not occur in middle of exponent" ); } } diff --git a/tests/phpunit/includes/logging/LogFormatterTest.php b/tests/phpunit/includes/logging/LogFormatterTest.php new file mode 100644 index 00000000..e8ccf433 --- /dev/null +++ b/tests/phpunit/includes/logging/LogFormatterTest.php @@ -0,0 +1,207 @@ +<?php +/** + * @group Database + */ +class LogFormatterTest extends MediaWikiLangTestCase { + + /** + * @var User + */ + protected $user; + + /** + * @var Title + */ + protected $title; + + /** + * @var RequestContext + */ + protected $context; + + protected function setUp() { + parent::setUp(); + + global $wgLang; + + $this->setMwGlobals( array( + 'wgLogTypes' => array( 'phpunit' ), + 'wgLogActionsHandlers' => array( 'phpunit/test' => 'LogFormatter', + 'phpunit/param' => 'LogFormatter' ), + 'wgUser' => User::newFromName( 'Testuser' ), + 'wgExtensionMessagesFiles' => array( 'LogTests' => __DIR__ . '/LogTests.i18n.php' ), + ) ); + + $wgLang->getLocalisationCache()->recache( $wgLang->getCode() ); + + $this->user = User::newFromName( 'Testuser' ); + $this->title = Title::newMainPage(); + + $this->context = new RequestContext(); + $this->context->setUser( $this->user ); + $this->context->setTitle( $this->title ); + $this->context->setLanguage( $wgLang ); + } + + protected function tearDown() { + parent::tearDown(); + + global $wgLang; + $wgLang->getLocalisationCache()->recache( $wgLang->getCode() ); + } + + public function newLogEntry( $action, $params ) { + $logEntry = new ManualLogEntry( 'phpunit', $action ); + $logEntry->setPerformer( $this->user ); + $logEntry->setTarget( $this->title ); + $logEntry->setComment( 'A very good reason' ); + + $logEntry->setParameters( $params ); + + return $logEntry; + } + + public function testNormalLogParams() { + $entry = $this->newLogEntry( 'test', array() ); + $formatter = LogFormatter::newFromEntry( $entry ); + $formatter->setContext( $this->context ); + + $formatter->setShowUserToolLinks( false ); + $paramsWithoutTools = $formatter->getMessageParametersForTesting(); + unset( $formatter->parsedParameters ); + + $formatter->setShowUserToolLinks( true ); + $paramsWithTools = $formatter->getMessageParametersForTesting(); + + $userLink = Linker::userLink( + $this->user->getId(), + $this->user->getName() + ); + + $userTools = Linker::userToolLinksRedContribs( + $this->user->getId(), + $this->user->getName(), + $this->user->getEditCount() + ); + + $titleLink = Linker::link( $this->title, null, array(), array() ); + + // $paramsWithoutTools and $paramsWithTools should be only different + // in index 0 + $this->assertEquals( $paramsWithoutTools[1], $paramsWithTools[1] ); + $this->assertEquals( $paramsWithoutTools[2], $paramsWithTools[2] ); + + $this->assertEquals( $userLink, $paramsWithoutTools[0]['raw'] ); + $this->assertEquals( $userLink . $userTools, $paramsWithTools[0]['raw'] ); + + $this->assertEquals( $this->user->getName(), $paramsWithoutTools[1] ); + + $this->assertEquals( $titleLink, $paramsWithoutTools[2]['raw'] ); + } + + public function testLogParamsTypeRaw() { + $params = array( '4:raw:raw' => Linker::link( $this->title, null, array(), array() ) ); + $expected = Linker::link( $this->title, null, array(), array() ); + + $entry = $this->newLogEntry( 'param', $params ); + $formatter = LogFormatter::newFromEntry( $entry ); + $formatter->setContext( $this->context ); + + $logParam = $formatter->getActionText(); + + $this->assertEquals( $expected, $logParam ); + } + + public function testLogParamsTypeMsg() { + $params = array( '4:msg:msg' => 'log-description-phpunit' ); + $expected = wfMessage( 'log-description-phpunit' )->text(); + + $entry = $this->newLogEntry( 'param', $params ); + $formatter = LogFormatter::newFromEntry( $entry ); + $formatter->setContext( $this->context ); + + $logParam = $formatter->getActionText(); + + $this->assertEquals( $expected, $logParam ); + } + + public function testLogParamsTypeMsgContent() { + $params = array( '4:msg-content:msgContent' => 'log-description-phpunit' ); + $expected = wfMessage( 'log-description-phpunit' )->inContentLanguage()->text(); + + $entry = $this->newLogEntry( 'param', $params ); + $formatter = LogFormatter::newFromEntry( $entry ); + $formatter->setContext( $this->context ); + + $logParam = $formatter->getActionText(); + + $this->assertEquals( $expected, $logParam ); + } + + public function testLogParamsTypeNumber() { + global $wgLang; + + $params = array( '4:number:number' => 123456789 ); + $expected = $wgLang->formatNum( 123456789 ); + + $entry = $this->newLogEntry( 'param', $params ); + $formatter = LogFormatter::newFromEntry( $entry ); + $formatter->setContext( $this->context ); + + $logParam = $formatter->getActionText(); + + $this->assertEquals( $expected, $logParam ); + } + + public function testLogParamsTypeUserLink() { + $params = array( '4:user-link:userLink' => $this->user->getName() ); + $expected = Linker::userLink( + $this->user->getId(), + $this->user->getName() + ); + + $entry = $this->newLogEntry( 'param', $params ); + $formatter = LogFormatter::newFromEntry( $entry ); + $formatter->setContext( $this->context ); + + $logParam = $formatter->getActionText(); + + $this->assertEquals( $expected, $logParam ); + } + + public function testLogParamsTypeTitleLink() { + $params = array( '4:title-link:titleLink' => $this->title->getText() ); + $expected = Linker::link( $this->title, null, array(), array() ); + + $entry = $this->newLogEntry( 'param', $params ); + $formatter = LogFormatter::newFromEntry( $entry ); + $formatter->setContext( $this->context ); + + $logParam = $formatter->getActionText(); + + $this->assertEquals( $expected, $logParam ); + } + + public function testLogParamsTypePlain() { + $params = array( '4:plain:plain' => 'Some plain text' ); + $expected = 'Some plain text'; + + $entry = $this->newLogEntry( 'param', $params ); + $formatter = LogFormatter::newFromEntry( $entry ); + $formatter->setContext( $this->context ); + + $logParam = $formatter->getActionText(); + + $this->assertEquals( $expected, $logParam ); + } + + public function testLogComment() { + $entry = $this->newLogEntry( 'test', array() ); + $formatter = LogFormatter::newFromEntry( $entry ); + $formatter->setContext( $this->context ); + + $comment = ltrim( Linker::commentBlock( $entry->getComment() ) ); + + $this->assertEquals( $comment, $formatter->getComment() ); + } +} diff --git a/tests/phpunit/includes/logging/LogTests.i18n.php b/tests/phpunit/includes/logging/LogTests.i18n.php new file mode 100644 index 00000000..78787ba1 --- /dev/null +++ b/tests/phpunit/includes/logging/LogTests.i18n.php @@ -0,0 +1,15 @@ +<?php +/** + * Internationalisation file for log tests. + * + * @file + */ + +$messages = array(); + +$messages['en'] = array( + 'log-name-phpunit' => 'PHPUnit-log', + 'log-description-phpunit' => 'Log for PHPUnit-tests', + 'logentry-phpunit-test' => '$1 {{GENDER:$2|tests}} with page $3', + 'logentry-phpunit-param' => '$4', +); diff --git a/tests/phpunit/includes/media/BitmapMetadataHandlerTest.php b/tests/phpunit/includes/media/BitmapMetadataHandlerTest.php index 88f87ef9..a0e63a8a 100644 --- a/tests/phpunit/includes/media/BitmapMetadataHandlerTest.php +++ b/tests/phpunit/includes/media/BitmapMetadataHandlerTest.php @@ -1,7 +1,11 @@ <?php class BitmapMetadataHandlerTest extends MediaWikiTestCase { - public function setUp() { + protected function setUp() { + parent::setUp(); + + $this->setMwGlobals( 'wgShowEXIF', false ); + $this->filePath = __DIR__ . '/../../data/media/'; } @@ -12,33 +16,31 @@ class BitmapMetadataHandlerTest extends MediaWikiTestCase { * Basically the file has IPTC and XMP metadata, the * IPTC should override the XMP, except for the multilingual * translation (to en) where XMP should win. + * @covers BitmapMetadataHandler::Jpeg */ public function testMultilingualCascade() { - if ( !wfDl( 'exif' ) ) { + if ( !extension_loaded( 'exif' ) ) { $this->markTestSkipped( "This test needs the exif extension." ); } - if ( !wfDl( 'xml' ) ) { + if ( !extension_loaded( 'xml' ) ) { $this->markTestSkipped( "This test needs the xml extension." ); } - global $wgShowEXIF; - $oldExif = $wgShowEXIF; - $wgShowEXIF = true; + + $this->setMwGlobals( 'wgShowEXIF', true ); $meta = BitmapMetadataHandler::Jpeg( $this->filePath . '/Xmp-exif-multilingual_test.jpg' ); $expected = array( 'x-default' => 'right(iptc)', - 'en' => 'right translation', - '_type' => 'lang' + 'en' => 'right translation', + '_type' => 'lang' ); - + $this->assertArrayHasKey( 'ImageDescription', $meta, 'Did not extract any ImageDescription info?!' ); $this->assertEquals( $expected, $meta['ImageDescription'] ); - - $wgShowEXIF = $oldExif; } /** @@ -47,6 +49,7 @@ class BitmapMetadataHandlerTest extends MediaWikiTestCase { * * There's more extensive tests of comment extraction in * JpegMetadataExtractorTests.php + * @covers BitmapMetadataHandler::Jpeg */ public function testJpegComment() { $meta = BitmapMetadataHandler::Jpeg( $this->filePath . @@ -59,6 +62,7 @@ class BitmapMetadataHandlerTest extends MediaWikiTestCase { /** * Make sure a bad iptc block doesn't stop the other metadata * from being extracted. + * @covers BitmapMetadataHandler::Jpeg */ public function testBadIPTC() { $meta = BitmapMetadataHandler::Jpeg( $this->filePath . @@ -66,6 +70,9 @@ class BitmapMetadataHandlerTest extends MediaWikiTestCase { $this->assertEquals( 'Created with GIMP', $meta['JPEGFileComment'][0] ); } + /** + * @covers BitmapMetadataHandler::Jpeg + */ public function testIPTCDates() { $meta = BitmapMetadataHandler::Jpeg( $this->filePath . 'iptc-timetest.jpg' ); @@ -73,9 +80,11 @@ class BitmapMetadataHandlerTest extends MediaWikiTestCase { $this->assertEquals( '2020:07:14 01:36:05', $meta['DateTimeDigitized'] ); $this->assertEquals( '1997:03:02 00:01:02', $meta['DateTimeOriginal'] ); } + /** * File has an invalid time (+ one valid but really weird time) * that shouldn't be included + * @covers BitmapMetadataHandler::Jpeg */ public function testIPTCDatesInvalid() { $meta = BitmapMetadataHandler::Jpeg( $this->filePath . @@ -89,6 +98,8 @@ class BitmapMetadataHandlerTest extends MediaWikiTestCase { * XMP data should take priority over iptc data * when hash has been updated, but not when * the hash is wrong. + * @covers BitmapMetadataHandler::addMetadata + * @covers BitmapMetadataHandler::getMetadataArray */ public function testMerging() { $merger = new BitmapMetadataHandler(); @@ -112,35 +123,45 @@ class BitmapMetadataHandlerTest extends MediaWikiTestCase { $this->assertEquals( $expected, $actual ); } + /** + * @covers BitmapMetadataHandler::png + */ public function testPNGXMP() { - if ( !wfDl( 'xml' ) ) { + if ( !extension_loaded( 'xml' ) ) { $this->markTestSkipped( "This test needs the xml extension." ); } $handler = new BitmapMetadataHandler(); $result = $handler->png( $this->filePath . 'xmp.png' ); - $expected = array ( + $expected = array( 'frameCount' => 0, 'loopCount' => 1, 'duration' => 0, 'bitDepth' => 1, 'colorType' => 'index-coloured', - 'metadata' => array ( + 'metadata' => array( 'SerialNumber' => '123456789', '_MW_PNG_VERSION' => 1, ), ); - $this->assertEquals( $expected, $result ); + $this->assertEquals( $expected, $result ); } + + /** + * @covers BitmapMetadataHandler::png + */ public function testPNGNative() { $handler = new BitmapMetadataHandler(); $result = $handler->png( $this->filePath . 'Png-native-test.png' ); $expected = 'http://example.com/url'; - $this->assertEquals( $expected, $result['metadata']['Identifier']['x-default'] ); + $this->assertEquals( $expected, $result['metadata']['Identifier']['x-default'] ); } + + /** + * @covers BitmapMetadataHandler::getTiffByteOrder + */ public function testTiffByteOrder() { $handler = new BitmapMetadataHandler(); $res = $handler->getTiffByteOrder( $this->filePath . 'test.tiff' ); $this->assertEquals( 'LE', $res ); } - } diff --git a/tests/phpunit/includes/media/BitmapScalingTest.php b/tests/phpunit/includes/media/BitmapScalingTest.php index 11d9dc47..9395b660 100644 --- a/tests/phpunit/includes/media/BitmapScalingTest.php +++ b/tests/phpunit/includes/media/BitmapScalingTest.php @@ -2,36 +2,34 @@ class BitmapScalingTest extends MediaWikiTestCase { - function setUp() { - global $wgMaxImageArea, $wgCustomConvertCommand; - $this->oldMaxImageArea = $wgMaxImageArea; - $this->oldCustomConvertCommand = $wgCustomConvertCommand; - $wgMaxImageArea = 1.25e7; // 3500x3500 - $wgCustomConvertCommand = 'dummy'; // Set so that we don't get client side rendering - } - function tearDown() { - global $wgMaxImageArea, $wgCustomConvertCommand; - $wgMaxImageArea = $this->oldMaxImageArea; - $wgCustomConvertCommand = $this->oldCustomConvertCommand; + protected function setUp() { + parent::setUp(); + + $this->setMwGlobals( array( + 'wgMaxImageArea' => 1.25e7, // 3500x3500 + 'wgCustomConvertCommand' => 'dummy', // Set so that we don't get client side rendering + ) ); } + /** * @dataProvider provideNormaliseParams + * @covers BitmapHandler::normaliseParams */ - function testNormaliseParams( $fileDimensions, $expectedParams, $params, $msg ) { + public function testNormaliseParams( $fileDimensions, $expectedParams, $params, $msg ) { $file = new FakeDimensionFile( $fileDimensions ); $handler = new BitmapHandler; $valid = $handler->normaliseParams( $file, $params ); $this->assertTrue( $valid ); $this->assertEquals( $expectedParams, $params, $msg ); } - - function provideNormaliseParams() { + + public static function provideNormaliseParams() { return array( - /* Regular resize operations */ + /* Regular resize operations */ array( array( 1024, 768 ), - array( - 'width' => 512, 'height' => 384, + array( + 'width' => 512, 'height' => 384, 'physicalWidth' => 512, 'physicalHeight' => 384, 'page' => 1, ), @@ -40,53 +38,53 @@ class BitmapScalingTest extends MediaWikiTestCase { ), array( array( 1024, 768 ), - array( - 'width' => 512, 'height' => 384, + array( + 'width' => 512, 'height' => 384, 'physicalWidth' => 512, 'physicalHeight' => 384, - 'page' => 1, + 'page' => 1, ), array( 'width' => 512, 'height' => 768 ), 'Resizing with height set too high', ), array( array( 1024, 768 ), - array( - 'width' => 512, 'height' => 384, + array( + 'width' => 512, 'height' => 384, 'physicalWidth' => 512, 'physicalHeight' => 384, - 'page' => 1, + 'page' => 1, ), array( 'width' => 1024, 'height' => 384 ), 'Resizing with height set', ), - + /* Very tall images */ array( array( 1000, 100 ), - array( + array( 'width' => 5, 'height' => 1, 'physicalWidth' => 5, 'physicalHeight' => 1, - 'page' => 1, + 'page' => 1, ), array( 'width' => 5 ), 'Very wide image', ), - + array( array( 100, 1000 ), - array( + array( 'width' => 1, 'height' => 10, 'physicalWidth' => 1, 'physicalHeight' => 10, - 'page' => 1, + 'page' => 1, ), array( 'width' => 1 ), 'Very high image', ), array( array( 100, 1000 ), - array( + array( 'width' => 1, 'height' => 5, 'physicalWidth' => 1, 'physicalHeight' => 10, - 'page' => 1, + 'page' => 1, ), array( 'width' => 10, 'height' => 5 ), 'Very high image with height set', @@ -94,58 +92,46 @@ class BitmapScalingTest extends MediaWikiTestCase { /* Max image area */ array( array( 4000, 4000 ), - array( + array( 'width' => 5000, 'height' => 5000, 'physicalWidth' => 4000, 'physicalHeight' => 4000, - 'page' => 1, + 'page' => 1, ), array( 'width' => 5000 ), 'Bigger than max image size but doesn\'t need scaling', ), ); - } - function testTooBigImage() { + } + + /** + * @covers BitmapHandler::doTransform + */ + public function testTooBigImage() { $file = new FakeDimensionFile( array( 4000, 4000 ) ); $handler = new BitmapHandler; $params = array( 'width' => '3700' ); // Still bigger than max size. - $this->assertEquals( 'TransformParameterError', + $this->assertEquals( 'TransformParameterError', get_class( $handler->doTransform( $file, 'dummy path', '', $params ) ) ); } - function testTooBigMustRenderImage() { + + /** + * @covers BitmapHandler::doTransform + */ + public function testTooBigMustRenderImage() { $file = new FakeDimensionFile( array( 4000, 4000 ) ); $file->mustRender = true; $handler = new BitmapHandler; $params = array( 'width' => '5000' ); // Still bigger than max size. - $this->assertEquals( 'TransformParameterError', + $this->assertEquals( 'TransformParameterError', get_class( $handler->doTransform( $file, 'dummy path', '', $params ) ) ); } - - function testImageArea() { + + /** + * @covers BitmapHandler::getImageArea + */ + public function testImageArea() { $file = new FakeDimensionFile( array( 7, 9 ) ); $handler = new BitmapHandler; $this->assertEquals( 63, $handler->getImageArea( $file ) ); } } - -class FakeDimensionFile extends File { - public $mustRender = false; - - public function __construct( $dimensions ) { - parent::__construct( Title::makeTitle( NS_FILE, 'Test' ), - new NullRepo( null ) ); - - $this->dimensions = $dimensions; - } - public function getWidth( $page = 1 ) { - return $this->dimensions[0]; - } - public function getHeight( $page = 1 ) { - return $this->dimensions[1]; - } - public function mustRender() { - return $this->mustRender; - } - public function getPath() { - return ''; - } -} diff --git a/tests/phpunit/includes/media/ExifBitmapTest.php b/tests/phpunit/includes/media/ExifBitmapTest.php index b2f6b7ba..a2e0eb62 100644 --- a/tests/phpunit/includes/media/ExifBitmapTest.php +++ b/tests/phpunit/includes/media/ExifBitmapTest.php @@ -2,60 +2,91 @@ class ExifBitmapTest extends MediaWikiTestCase { - public function setUp() { - global $wgShowEXIF; - $this->showExif = $wgShowEXIF; - $wgShowEXIF = true; - $this->handler = new ExifBitmapHandler; - if ( !wfDl( 'exif' ) ) { + /** + * @var ExifBitmapHandler + */ + protected $handler; + + protected function setUp() { + parent::setUp(); + if ( !extension_loaded( 'exif' ) ) { $this->markTestSkipped( "This test needs the exif extension." ); } - } - public function tearDown() { - global $wgShowEXIF; - $wgShowEXIF = $this->showExif; + $this->setMwGlobals( 'wgShowEXIF', true ); + + $this->handler = new ExifBitmapHandler; + } + /** + * @covers ExifBitmapHandler::isMetadataValid + */ public function testIsOldBroken() { $res = $this->handler->isMetadataValid( null, ExifBitmapHandler::OLD_BROKEN_FILE ); $this->assertEquals( ExifBitmapHandler::METADATA_COMPATIBLE, $res ); } + + /** + * @covers ExifBitmapHandler::isMetadataValid + */ public function testIsBrokenFile() { $res = $this->handler->isMetadataValid( null, ExifBitmapHandler::BROKEN_FILE ); $this->assertEquals( ExifBitmapHandler::METADATA_GOOD, $res ); } + + /** + * @covers ExifBitmapHandler::isMetadataValid + */ public function testIsInvalid() { $res = $this->handler->isMetadataValid( null, 'Something Invalid Here.' ); $this->assertEquals( ExifBitmapHandler::METADATA_BAD, $res ); } + + /** + * @covers ExifBitmapHandler::isMetadataValid + */ public function testGoodMetadata() { $meta = 'a:16:{s:10:"ImageWidth";i:20;s:11:"ImageLength";i:20;s:13:"BitsPerSample";a:3:{i:0;i:8;i:1;i:8;i:2;i:8;}s:11:"Compression";i:5;s:25:"PhotometricInterpretation";i:2;s:16:"ImageDescription";s:17:"Created with GIMP";s:12:"StripOffsets";i:8;s:11:"Orientation";i:1;s:15:"SamplesPerPixel";i:3;s:12:"RowsPerStrip";i:64;s:15:"StripByteCounts";i:238;s:11:"XResolution";s:19:"1207959552/16777216";s:11:"YResolution";s:19:"1207959552/16777216";s:19:"PlanarConfiguration";i:1;s:14:"ResolutionUnit";i:2;s:22:"MEDIAWIKI_EXIF_VERSION";i:2;}'; $res = $this->handler->isMetadataValid( null, $meta ); $this->assertEquals( ExifBitmapHandler::METADATA_GOOD, $res ); } + + /** + * @covers ExifBitmapHandler::isMetadataValid + */ public function testIsOldGood() { $meta = 'a:16:{s:10:"ImageWidth";i:20;s:11:"ImageLength";i:20;s:13:"BitsPerSample";a:3:{i:0;i:8;i:1;i:8;i:2;i:8;}s:11:"Compression";i:5;s:25:"PhotometricInterpretation";i:2;s:16:"ImageDescription";s:17:"Created with GIMP";s:12:"StripOffsets";i:8;s:11:"Orientation";i:1;s:15:"SamplesPerPixel";i:3;s:12:"RowsPerStrip";i:64;s:15:"StripByteCounts";i:238;s:11:"XResolution";s:19:"1207959552/16777216";s:11:"YResolution";s:19:"1207959552/16777216";s:19:"PlanarConfiguration";i:1;s:14:"ResolutionUnit";i:2;s:22:"MEDIAWIKI_EXIF_VERSION";i:1;}'; $res = $this->handler->isMetadataValid( null, $meta ); $this->assertEquals( ExifBitmapHandler::METADATA_COMPATIBLE, $res ); } - // Handle metadata from paged tiff handler (gotten via instant commons) - // gracefully. + + /** + * Handle metadata from paged tiff handler (gotten via instant commons) gracefully. + * @covers ExifBitmapHandler::isMetadataValid + */ public function testPagedTiffHandledGracefully() { $meta = 'a:6:{s:9:"page_data";a:1:{i:1;a:5:{s:5:"width";i:643;s:6:"height";i:448;s:5:"alpha";s:4:"true";s:4:"page";i:1;s:6:"pixels";i:288064;}}s:10:"page_count";i:1;s:10:"first_page";i:1;s:9:"last_page";i:1;s:4:"exif";a:9:{s:10:"ImageWidth";i:643;s:11:"ImageLength";i:448;s:11:"Compression";i:5;s:25:"PhotometricInterpretation";i:2;s:11:"Orientation";i:1;s:15:"SamplesPerPixel";i:4;s:12:"RowsPerStrip";i:50;s:19:"PlanarConfiguration";i:1;s:22:"MEDIAWIKI_EXIF_VERSION";i:1;}s:21:"TIFF_METADATA_VERSION";s:3:"1.4";}'; $res = $this->handler->isMetadataValid( null, $meta ); $this->assertEquals( ExifBitmapHandler::METADATA_BAD, $res ); } - function testConvertMetadataLatest() { + /** + * @covers ExifBitmapHandler::convertMetadataVersion + */ + public function testConvertMetadataLatest() { $metadata = array( - 'foo' => array( 'First', 'Second', '_type' => 'ol' ), - 'MEDIAWIKI_EXIF_VERSION' => 2 - ); + 'foo' => array( 'First', 'Second', '_type' => 'ol' ), + 'MEDIAWIKI_EXIF_VERSION' => 2 + ); $res = $this->handler->convertMetadataVersion( $metadata, 2 ); $this->assertEquals( $metadata, $res ); } - function testConvertMetadataToOld() { + + /** + * @covers ExifBitmapHandler::convertMetadataVersion + */ + public function testConvertMetadataToOld() { $metadata = array( 'foo' => array( 'First', 'Second', '_type' => 'ol' ), 'bar' => array( 'First', 'Second', '_type' => 'ul' ), @@ -73,9 +104,13 @@ class ExifBitmapTest extends MediaWikiTestCase { $res = $this->handler->convertMetadataVersion( $metadata, 1 ); $this->assertEquals( $expected, $res ); } - function testConvertMetadataSoftware() { + + /** + * @covers ExifBitmapHandler::convertMetadataVersion + */ + public function testConvertMetadataSoftware() { $metadata = array( - 'Software' => array( array('GIMP', '1.1' ) ), + 'Software' => array( array( 'GIMP', '1.1' ) ), 'MEDIAWIKI_EXIF_VERSION' => 2, ); $expected = array( @@ -85,7 +120,11 @@ class ExifBitmapTest extends MediaWikiTestCase { $res = $this->handler->convertMetadataVersion( $metadata, 1 ); $this->assertEquals( $expected, $res ); } - function testConvertMetadataSoftwareNormal() { + + /** + * @covers ExifBitmapHandler::convertMetadataVersion + */ + public function testConvertMetadataSoftwareNormal() { $metadata = array( 'Software' => array( "GIMP 1.2", "vim" ), 'MEDIAWIKI_EXIF_VERSION' => 2, diff --git a/tests/phpunit/includes/media/ExifRotationTest.php b/tests/phpunit/includes/media/ExifRotationTest.php index 6af52dd1..64276d92 100644 --- a/tests/phpunit/includes/media/ExifRotationTest.php +++ b/tests/phpunit/includes/media/ExifRotationTest.php @@ -1,51 +1,44 @@ <?php - /** - * Tests related to auto rotation + * Tests related to auto rotation. + * + * @group medium + * + * @todo covers tags */ class ExifRotationTest extends MediaWikiTestCase { - function setUp() { + protected function setUp() { parent::setUp(); + if ( !extension_loaded( 'exif' ) ) { + $this->markTestSkipped( "This test needs the exif extension." ); + } + $this->handler = new BitmapHandler(); $filePath = __DIR__ . '/../../data/media'; $tmpDir = $this->getNewTempDirectory(); $this->repo = new FSRepo( array( - 'name' => 'temp', - 'url' => 'http://localhost/thumbtest', - 'backend' => new FSFileBackend( array( - 'name' => 'localtesting', - 'lockManager' => 'nullLockManager', + 'name' => 'temp', + 'url' => 'http://localhost/thumbtest', + 'backend' => new FSFileBackend( array( + 'name' => 'localtesting', + 'lockManager' => 'nullLockManager', 'containerPaths' => array( 'temp-thumb' => $tmpDir, 'data' => $filePath ) ) ) ) ); - if ( !wfDl( 'exif' ) ) { - $this->markTestSkipped( "This test needs the exif extension." ); - } - global $wgShowEXIF; - $this->show = $wgShowEXIF; - $wgShowEXIF = true; - - global $wgEnableAutoRotation; - $this->oldAuto = $wgEnableAutoRotation; - $wgEnableAutoRotation = true; - } - - public function tearDown() { - global $wgShowEXIF, $wgEnableAutoRotation; - $wgShowEXIF = $this->show; - $wgEnableAutoRotation = $this->oldAuto; - parent::tearDown(); + $this->setMwGlobals( array( + 'wgShowEXIF' => true, + 'wgEnableAutoRotation' => true, + ) ); } /** - * - * @dataProvider providerFiles + * @dataProvider provideFiles */ - function testMetadata( $name, $type, $info ) { + public function testMetadata( $name, $type, $info ) { if ( !BitmapHandler::canRotate() ) { $this->markTestSkipped( "This test needs a rasterizer that can auto-rotate." ); } @@ -56,14 +49,14 @@ class ExifRotationTest extends MediaWikiTestCase { /** * - * @dataProvider providerFiles + * @dataProvider provideFiles */ - function testRotationRendering( $name, $type, $info, $thumbs ) { + public function testRotationRendering( $name, $type, $info, $thumbs ) { if ( !BitmapHandler::canRotate() ) { $this->markTestSkipped( "This test needs a rasterizer that can auto-rotate." ); } - foreach( $thumbs as $size => $out ) { - if( preg_match('/^(\d+)px$/', $size, $matches ) ) { + foreach ( $thumbs as $size => $out ) { + if ( preg_match( '/^(\d+)px$/', $size, $matches ) ) { $params = array( 'width' => $matches[1], ); @@ -73,7 +66,7 @@ class ExifRotationTest extends MediaWikiTestCase { 'height' => $matches[2] ); } else { - throw new MWException('bogus test data format ' . $size); + throw new MWException( 'bogus test data format ' . $size ); } $file = $this->dataFile( $name, $type ); @@ -83,23 +76,24 @@ class ExifRotationTest extends MediaWikiTestCase { $this->assertEquals( $out[1], $thumb->getHeight(), "$name: thumb reported height check for $size" ); $gis = getimagesize( $thumb->getLocalCopyPath() ); - if ($out[0] > $info['width']) { + if ( $out[0] > $info['width'] ) { // Physical image won't be scaled bigger than the original. - $this->assertEquals( $info['width'], $gis[0], "$name: thumb actual width check for $size"); - $this->assertEquals( $info['height'], $gis[1], "$name: thumb actual height check for $size"); + $this->assertEquals( $info['width'], $gis[0], "$name: thumb actual width check for $size" ); + $this->assertEquals( $info['height'], $gis[1], "$name: thumb actual height check for $size" ); } else { - $this->assertEquals( $out[0], $gis[0], "$name: thumb actual width check for $size"); - $this->assertEquals( $out[1], $gis[1], "$name: thumb actual height check for $size"); + $this->assertEquals( $out[0], $gis[0], "$name: thumb actual width check for $size" ); + $this->assertEquals( $out[1], $gis[1], "$name: thumb actual height check for $size" ); } } } + /* Utility function */ private function dataFile( $name, $type ) { return new UnregisteredLocalFile( false, $this->repo, "mwstore://localtesting/data/$name", $type ); } - function providerFiles() { + public static function provideFiles() { return array( array( 'landscape-plain.jpg', @@ -134,29 +128,25 @@ class ExifRotationTest extends MediaWikiTestCase { /** * Same as before, but with auto-rotation disabled. - * @dataProvider providerFilesNoAutoRotate + * @dataProvider provideFilesNoAutoRotate */ - function testMetadataNoAutoRotate( $name, $type, $info ) { - global $wgEnableAutoRotation; - $wgEnableAutoRotation = false; + public function testMetadataNoAutoRotate( $name, $type, $info ) { + $this->setMwGlobals( 'wgEnableAutoRotation', false ); $file = $this->dataFile( $name, $type ); $this->assertEquals( $info['width'], $file->getWidth(), "$name: width check" ); $this->assertEquals( $info['height'], $file->getHeight(), "$name: height check" ); - - $wgEnableAutoRotation = true; } /** * - * @dataProvider providerFilesNoAutoRotate + * @dataProvider provideFilesNoAutoRotate */ - function testRotationRenderingNoAutoRotate( $name, $type, $info, $thumbs ) { - global $wgEnableAutoRotation; - $wgEnableAutoRotation = false; + public function testRotationRenderingNoAutoRotate( $name, $type, $info, $thumbs ) { + $this->setMwGlobals( 'wgEnableAutoRotation', false ); - foreach( $thumbs as $size => $out ) { - if( preg_match('/^(\d+)px$/', $size, $matches ) ) { + foreach ( $thumbs as $size => $out ) { + if ( preg_match( '/^(\d+)px$/', $size, $matches ) ) { $params = array( 'width' => $matches[1], ); @@ -166,7 +156,7 @@ class ExifRotationTest extends MediaWikiTestCase { 'height' => $matches[2] ); } else { - throw new MWException('bogus test data format ' . $size); + throw new MWException( 'bogus test data format ' . $size ); } $file = $this->dataFile( $name, $type ); @@ -176,19 +166,18 @@ class ExifRotationTest extends MediaWikiTestCase { $this->assertEquals( $out[1], $thumb->getHeight(), "$name: thumb reported height check for $size" ); $gis = getimagesize( $thumb->getLocalCopyPath() ); - if ($out[0] > $info['width']) { + if ( $out[0] > $info['width'] ) { // Physical image won't be scaled bigger than the original. - $this->assertEquals( $info['width'], $gis[0], "$name: thumb actual width check for $size"); - $this->assertEquals( $info['height'], $gis[1], "$name: thumb actual height check for $size"); + $this->assertEquals( $info['width'], $gis[0], "$name: thumb actual width check for $size" ); + $this->assertEquals( $info['height'], $gis[1], "$name: thumb actual height check for $size" ); } else { - $this->assertEquals( $out[0], $gis[0], "$name: thumb actual width check for $size"); - $this->assertEquals( $out[1], $gis[1], "$name: thumb actual height check for $size"); + $this->assertEquals( $out[0], $gis[0], "$name: thumb actual width check for $size" ); + $this->assertEquals( $out[1], $gis[1], "$name: thumb actual height check for $size" ); } } - $wgEnableAutoRotation = true; } - function providerFilesNoAutoRotate() { + public static function provideFilesNoAutoRotate() { return array( array( 'landscape-plain.jpg', @@ -220,41 +209,40 @@ class ExifRotationTest extends MediaWikiTestCase { ) ); } - - + + const TEST_WIDTH = 100; const TEST_HEIGHT = 200; - + /** * @dataProvider provideBitmapExtractPreRotationDimensions */ - function testBitmapExtractPreRotationDimensions( $rotation, $expected ) { + public function testBitmapExtractPreRotationDimensions( $rotation, $expected ) { $result = $this->handler->extractPreRotationDimensions( array( - 'physicalWidth' => self::TEST_WIDTH, - 'physicalHeight' => self::TEST_HEIGHT, - ), $rotation ); + 'physicalWidth' => self::TEST_WIDTH, + 'physicalHeight' => self::TEST_HEIGHT, + ), $rotation ); $this->assertEquals( $expected, $result ); } - - function provideBitmapExtractPreRotationDimensions() { + + public static function provideBitmapExtractPreRotationDimensions() { return array( array( 0, - array( self::TEST_WIDTH, self::TEST_HEIGHT ) + array( self::TEST_WIDTH, self::TEST_HEIGHT ) ), array( 90, - array( self::TEST_HEIGHT, self::TEST_WIDTH ) + array( self::TEST_HEIGHT, self::TEST_WIDTH ) ), array( 180, - array( self::TEST_WIDTH, self::TEST_HEIGHT ) + array( self::TEST_WIDTH, self::TEST_HEIGHT ) ), array( 270, - array( self::TEST_HEIGHT, self::TEST_WIDTH ) + array( self::TEST_HEIGHT, self::TEST_WIDTH ) ), ); } } - diff --git a/tests/phpunit/includes/media/ExifTest.php b/tests/phpunit/includes/media/ExifTest.php index 045777d7..dea36b03 100644 --- a/tests/phpunit/includes/media/ExifTest.php +++ b/tests/phpunit/includes/media/ExifTest.php @@ -1,25 +1,25 @@ <?php class ExifTest extends MediaWikiTestCase { - public function setUp() { - $this->mediaPath = __DIR__ . '/../../data/media/'; + /** @var string */ + protected $mediaPath; - if ( !wfDl( 'exif' ) ) { + protected function setUp() { + parent::setUp(); + if ( !extension_loaded( 'exif' ) ) { $this->markTestSkipped( "This test needs the exif extension." ); } - global $wgShowEXIF; - $this->showExif = $wgShowEXIF; - $wgShowEXIF = true; - } - public function tearDown() { - global $wgShowEXIF; - $wgShowEXIF = $this->showExif; + $this->mediaPath = __DIR__ . '/../../data/media/'; + + + + $this->setMwGlobals( 'wgShowEXIF', true ); } public function testGPSExtraction() { $filename = $this->mediaPath . 'exif-gps.jpg'; - $seg = JpegMetadataExtractor::segmentSplitter( $filename ); + $seg = JpegMetadataExtractor::segmentSplitter( $filename ); $exif = new Exif( $filename, $seg['byteOrder'] ); $data = $exif->getFilteredData(); $expected = array( @@ -34,7 +34,7 @@ class ExifTest extends MediaWikiTestCase { public function testUnicodeUserComment() { $filename = $this->mediaPath . 'exif-user-comment.jpg'; - $seg = JpegMetadataExtractor::segmentSplitter( $filename ); + $seg = JpegMetadataExtractor::segmentSplitter( $filename ); $exif = new Exif( $filename, $seg['byteOrder'] ); $data = $exif->getFilteredData(); @@ -43,6 +43,4 @@ class ExifTest extends MediaWikiTestCase { ); $this->assertEquals( $expected, $data ); } - - } diff --git a/tests/phpunit/includes/media/FakeDimensionFile.php b/tests/phpunit/includes/media/FakeDimensionFile.php new file mode 100644 index 00000000..7926000b --- /dev/null +++ b/tests/phpunit/includes/media/FakeDimensionFile.php @@ -0,0 +1,28 @@ +<?php + +class FakeDimensionFile extends File { + public $mustRender = false; + + public function __construct( $dimensions ) { + parent::__construct( Title::makeTitle( NS_FILE, 'Test' ), + new NullRepo( null ) ); + + $this->dimensions = $dimensions; + } + + public function getWidth( $page = 1 ) { + return $this->dimensions[0]; + } + + public function getHeight( $page = 1 ) { + return $this->dimensions[1]; + } + + public function mustRender() { + return $this->mustRender; + } + + public function getPath() { + return ''; + } +}
\ No newline at end of file diff --git a/tests/phpunit/includes/media/FormatMetadataTest.php b/tests/phpunit/includes/media/FormatMetadataTest.php index 6ade6702..a073e4ca 100644 --- a/tests/phpunit/includes/media/FormatMetadataTest.php +++ b/tests/phpunit/includes/media/FormatMetadataTest.php @@ -1,36 +1,43 @@ <?php + +/** + * @todo covers tags + */ class FormatMetadataTest extends MediaWikiTestCase { - public function setUp() { - if ( !wfDl( 'exif' ) ) { + + /** @var FSFileBackend */ + protected $backend; + /** @var FSRepo */ + protected $repo; + + protected function setUp() { + parent::setUp(); + + if ( !extension_loaded( 'exif' ) ) { $this->markTestSkipped( "This test needs the exif extension." ); } - $filePath = __DIR__ . '/../../data/media'; + $filePath = __DIR__ . '/../../data/media'; $this->backend = new FSFileBackend( array( - 'name' => 'localtesting', - 'lockManager' => 'nullLockManager', + 'name' => 'localtesting', + 'lockManager' => 'nullLockManager', 'containerPaths' => array( 'data' => $filePath ) ) ); $this->repo = new FSRepo( array( - 'name' => 'temp', - 'url' => 'http://localhost/thumbtest', + 'name' => 'temp', + 'url' => 'http://localhost/thumbtest', 'backend' => $this->backend ) ); - global $wgShowEXIF; - $this->show = $wgShowEXIF; - $wgShowEXIF = true; - } - public function tearDown() { - global $wgShowEXIF; - $wgShowEXIF = $this->show; + + $this->setMwGlobals( 'wgShowEXIF', true ); } public function testInvalidDate() { $file = $this->dataFile( 'broken_exif_date.jpg', 'image/jpeg' ); - + // Throws an error if bug hit $meta = $file->formatMetadata(); $this->assertNotEquals( false, $meta, 'Valid metadata extracted' ); - + // Find date exif entry $this->assertArrayHasKey( 'visible', $meta ); $dateIndex = null; @@ -40,7 +47,7 @@ class FormatMetadataTest extends MediaWikiTestCase { } } $this->assertNotNull( $dateIndex, 'Date entry exists in metadata' ); - $this->assertEquals( '0000:01:00 00:02:27', + $this->assertEquals( '0000:01:00 00:02:27', $meta['visible'][$dateIndex]['value'], 'File with invalid date metadata (bug 29471)' ); } diff --git a/tests/phpunit/includes/media/GIFMetadataExtractorTest.php b/tests/phpunit/includes/media/GIFMetadataExtractorTest.php index 650fdd5c..9e3f9244 100644 --- a/tests/phpunit/includes/media/GIFMetadataExtractorTest.php +++ b/tests/phpunit/includes/media/GIFMetadataExtractorTest.php @@ -1,20 +1,25 @@ <?php class GIFMetadataExtractorTest extends MediaWikiTestCase { - public function setUp() { + protected function setUp() { + parent::setUp(); + $this->mediaPath = __DIR__ . '/../../data/media/'; } + /** * Put in a file, and see if the metadata coming out is as expected. * @param $filename String * @param $expected Array The extracted metadata. - * @dataProvider dataGetMetadata + * @dataProvider provideGetMetadata + * @covers GIFMetadataExtractor::getMetadata */ public function testGetMetadata( $filename, $expected ) { $actual = GIFMetadataExtractor::getMetadata( $this->mediaPath . $filename ); $this->assertEquals( $expected, $actual ); } - public function dataGetMetadata() { + + public static function provideGetMetadata() { $xmpNugget = <<<EOF <?xpacket begin='' id='W5M0MpCehiHzreSzNTczkc9d'?> @@ -66,29 +71,35 @@ EOF; $xmpNugget = str_replace( "\r", '', $xmpNugget ); // Windows compat return array( - array( 'nonanimated.gif', array( - 'comment' => array( 'GIF test file ⁕ Created with GIMP' ), - 'duration' => 0.1, - 'frameCount' => 1, - 'looped' => false, - 'xmp' => '', + array( + 'nonanimated.gif', + array( + 'comment' => array( 'GIF test file ⁕ Created with GIMP' ), + 'duration' => 0.1, + 'frameCount' => 1, + 'looped' => false, + 'xmp' => '', ) ), - array( 'animated.gif', array( - 'comment' => array( 'GIF test file . Created with GIMP' ), - 'duration' => 2.4, - 'frameCount' => 4, - 'looped' => true, - 'xmp' => '', + array( + 'animated.gif', + array( + 'comment' => array( 'GIF test file . Created with GIMP' ), + 'duration' => 2.4, + 'frameCount' => 4, + 'looped' => true, + 'xmp' => '', ) ), - array( 'animated-xmp.gif', array( - 'xmp' => $xmpNugget, - 'duration' => 2.4, - 'frameCount' => 4, - 'looped' => true, - 'comment' => array( 'GIƒ·test·file' ), + array( + 'animated-xmp.gif', + array( + 'xmp' => $xmpNugget, + 'duration' => 2.4, + 'frameCount' => 4, + 'looped' => true, + 'comment' => array( 'GIƒ·test·file' ), ) ), ); diff --git a/tests/phpunit/includes/media/GIFTest.php b/tests/phpunit/includes/media/GIFTest.php index 5dcbeee0..c8e729c8 100644 --- a/tests/phpunit/includes/media/GIFTest.php +++ b/tests/phpunit/includes/media/GIFTest.php @@ -1,36 +1,53 @@ <?php class GIFHandlerTest extends MediaWikiTestCase { - public function setUp() { - $this->filePath = __DIR__ . '/../../data/media'; + /** @var FSFileBackend */ + protected $backend; + /** @var GIFHandler */ + protected $handler; + /** @var FSRepo */ + protected $repo; + /** @var string */ + protected $filePath; + + protected function setUp() { + parent::setUp(); + + $this->filePath = __DIR__ . '/../../data/media'; $this->backend = new FSFileBackend( array( - 'name' => 'localtesting', - 'lockManager' => 'nullLockManager', + 'name' => 'localtesting', + 'lockManager' => 'nullLockManager', 'containerPaths' => array( 'data' => $this->filePath ) ) ); $this->repo = new FSRepo( array( - 'name' => 'temp', - 'url' => 'http://localhost/thumbtest', + 'name' => 'temp', + 'url' => 'http://localhost/thumbtest', 'backend' => $this->backend ) ); $this->handler = new GIFHandler(); } + /** + * @covers GIFHandler::getMetadata + */ public function testInvalidFile() { $res = $this->handler->getMetadata( null, $this->filePath . '/README' ); $this->assertEquals( GIFHandler::BROKEN_FILE, $res ); } + /** * @param $filename String basename of the file to check * @param $expected boolean Expected result. - * @dataProvider dataIsAnimated + * @dataProvider provideIsAnimated + * @covers GIFHandler::isAnimatedImage */ public function testIsAnimanted( $filename, $expected ) { $file = $this->dataFile( $filename, 'image/gif' ); $actual = $this->handler->isAnimatedImage( $file ); $this->assertEquals( $expected, $actual ); } - public function dataIsAnimated() { + + public static function provideIsAnimated() { return array( array( 'animated.gif', true ), array( 'nonanimated.gif', false ), @@ -40,14 +57,16 @@ class GIFHandlerTest extends MediaWikiTestCase { /** * @param $filename String * @param $expected Integer Total image area - * @dataProvider dataGetImageArea + * @dataProvider provideGetImageArea + * @covers GIFHandler::getImageArea */ public function testGetImageArea( $filename, $expected ) { $file = $this->dataFile( $filename, 'image/gif' ); $actual = $this->handler->getImageArea( $file, $file->getWidth(), $file->getHeight() ); $this->assertEquals( $expected, $actual ); } - public function dataGetImageArea() { + + public static function provideGetImageArea() { return array( array( 'animated.gif', 5400 ), array( 'nonanimated.gif', 1350 ), @@ -57,13 +76,15 @@ class GIFHandlerTest extends MediaWikiTestCase { /** * @param $metadata String Serialized metadata * @param $expected Integer One of the class constants of GIFHandler - * @dataProvider dataIsMetadataValid + * @dataProvider provideIsMetadataValid + * @covers GIFHandler::isMetadataValid */ public function testIsMetadataValid( $metadata, $expected ) { $actual = $this->handler->isMetadataValid( null, $metadata ); $this->assertEquals( $expected, $actual ); } - public function dataIsMetadataValid() { + + public static function provideIsMetadataValid() { return array( array( GIFHandler::BROKEN_FILE, GIFHandler::METADATA_GOOD ), array( '', GIFHandler::METADATA_BAD ), @@ -76,7 +97,8 @@ class GIFHandlerTest extends MediaWikiTestCase { /** * @param $filename String * @param $expected String Serialized array - * @dataProvider dataGetMetadata + * @dataProvider provideGetMetadata + * @covers GIFHandler::getMetadata */ public function testGetMetadata( $filename, $expected ) { $file = $this->dataFile( $filename, 'image/gif' ); @@ -84,7 +106,7 @@ class GIFHandlerTest extends MediaWikiTestCase { $this->assertEquals( unserialize( $expected ), unserialize( $actual ) ); } - public function dataGetMetadata() { + public static function provideGetMetadata() { return array( array( 'nonanimated.gif', 'a:4:{s:10:"frameCount";i:1;s:6:"looped";b:0;s:8:"duration";d:0.1000000000000000055511151231257827021181583404541015625;s:8:"metadata";a:2:{s:14:"GIFFileComment";a:1:{i:0;s:35:"GIF test file ⁕ Created with GIMP";}s:15:"_MW_GIF_VERSION";i:1;}}' ), array( 'animated-xmp.gif', 'a:4:{s:10:"frameCount";i:4;s:6:"looped";b:1;s:8:"duration";d:2.399999999999999911182158029987476766109466552734375;s:8:"metadata";a:5:{s:6:"Artist";s:7:"Bawolff";s:16:"ImageDescription";a:2:{s:9:"x-default";s:18:"A file to test GIF";s:5:"_type";s:4:"lang";}s:15:"SublocationDest";s:13:"The interwebs";s:14:"GIFFileComment";a:1:{i:0;s:16:"GIƒ·test·file";}s:15:"_MW_GIF_VERSION";i:1;}}' ), diff --git a/tests/phpunit/includes/media/IPTCTest.php b/tests/phpunit/includes/media/IPTCTest.php index ec6deeb8..81c1d287 100644 --- a/tests/phpunit/includes/media/IPTCTest.php +++ b/tests/phpunit/includes/media/IPTCTest.php @@ -1,11 +1,19 @@ <?php + class IPTCTest extends MediaWikiTestCase { + + /** + * @covers IPTC::getCharset + */ public function testRecognizeUtf8() { // utf-8 is the only one used in practise. $res = IPTC::getCharset( "\x1b%G" ); $this->assertEquals( 'UTF-8', $res ); } + /** + * @covers IPTC::Parse + */ public function testIPTCParseNoCharset88591() { // basically IPTC for keyword with value of 0xBC which is 1/4 in iso-8859-1 // This data doesn't specify a charset. We're supposed to guess @@ -14,16 +22,23 @@ class IPTCTest extends MediaWikiTestCase { $res = IPTC::Parse( $iptcData ); $this->assertEquals( array( '¼' ), $res['Keywords'] ); } - /* This one contains a sequence that's valid iso 8859-1 but not valid utf8 */ - /* \xC3 = Ã, \xB8 = ¸ */ + + /** + * @covers IPTC::Parse + */ public function testIPTCParseNoCharset88591b() { + /* This one contains a sequence that's valid iso 8859-1 but not valid utf8 */ + /* \xC3 = Ã, \xB8 = ¸ */ $iptcData = "Photoshop 3.0\08BIM\4\4\0\0\0\0\0\x09\x1c\x02\x19\x00\x04\xC3\xC3\xC3\xB8"; $res = IPTC::Parse( $iptcData ); $this->assertEquals( array( 'ÃÃø' ), $res['Keywords'] ); } - /* Same as testIPTCParseNoCharset88591b, but forcing the charset to utf-8. + + /** + * Same as testIPTCParseNoCharset88591b, but forcing the charset to utf-8. * What should happen is the first "\xC3\xC3" should be dropped as invalid, * leaving \xC3\xB8, which is ø + * @covers IPTC::Parse */ public function testIPTCParseForcedUTFButInvalid() { $iptcData = "Photoshop 3.0\08BIM\4\4\0\0\0\0\0\x11\x1c\x02\x19\x00\x04\xC3\xC3\xC3\xB8" @@ -31,12 +46,20 @@ class IPTCTest extends MediaWikiTestCase { $res = IPTC::Parse( $iptcData ); $this->assertEquals( array( 'ø' ), $res['Keywords'] ); } + + /** + * @covers IPTC::Parse + */ public function testIPTCParseNoCharsetUTF8() { $iptcData = "Photoshop 3.0\08BIM\4\4\0\0\0\0\0\x07\x1c\x02\x19\x00\x02¼"; $res = IPTC::Parse( $iptcData ); $this->assertEquals( array( '¼' ), $res['Keywords'] ); } - // Testing something that has 2 values for keyword + + /** + * Testing something that has 2 values for keyword + * @covers IPTC::Parse + */ public function testIPTCParseMulti() { $iptcData = /* identifier */ "Photoshop 3.0\08BIM\4\4" /* length */ . "\0\0\0\0\0\x0D" @@ -45,11 +68,14 @@ class IPTCTest extends MediaWikiTestCase { $res = IPTC::Parse( $iptcData ); $this->assertEquals( array( '¼', '¼½' ), $res['Keywords'] ); } + + /** + * @covers IPTC::Parse + */ public function testIPTCParseUTF8() { // This has the magic "\x1c\x01\x5A\x00\x03\x1B\x25\x47" which marks content as UTF8. $iptcData = "Photoshop 3.0\08BIM\4\4\0\0\0\0\0\x0F\x1c\x02\x19\x00\x02¼\x1c\x01\x5A\x00\x03\x1B\x25\x47"; $res = IPTC::Parse( $iptcData ); $this->assertEquals( array( '¼' ), $res['Keywords'] ); } - } diff --git a/tests/phpunit/includes/media/JpegMetadataExtractorTest.php b/tests/phpunit/includes/media/JpegMetadataExtractorTest.php index 41d81190..eafc8a2e 100644 --- a/tests/phpunit/includes/media/JpegMetadataExtractorTest.php +++ b/tests/phpunit/includes/media/JpegMetadataExtractorTest.php @@ -5,10 +5,15 @@ * serve as a very good "test". (Adobe photoshop probably creates such files * but it costs money). The implementation of it currently in MediaWiki is based * solely on reading the standard, without any real world test files. + * @todo covers tags */ class JpegMetadataExtractorTest extends MediaWikiTestCase { - public function setUp() { + protected $filePath; + + protected function setUp() { + parent::setUp(); + $this->filePath = __DIR__ . '/../../data/media/'; } @@ -16,26 +21,29 @@ class JpegMetadataExtractorTest extends MediaWikiTestCase { * We also use this test to test padding bytes don't * screw stuff up * - * @param $file filename + * @param string $file filename * - * @dataProvider dataUtf8Comment + * @dataProvider provideUtf8Comment */ public function testUtf8Comment( $file ) { $res = JpegMetadataExtractor::segmentSplitter( $this->filePath . $file ); $this->assertEquals( array( 'UTF-8 JPEG Comment — ¼' ), $res['COM'] ); } - public function dataUtf8Comment() { + + public static function provideUtf8Comment() { return array( array( 'jpeg-comment-utf.jpg' ), array( 'jpeg-padding-even.jpg' ), array( 'jpeg-padding-odd.jpg' ), ); } + /** The file is iso-8859-1, but it should get auto converted */ public function testIso88591Comment() { $res = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-comment-iso8859-1.jpg' ); $this->assertEquals( array( 'ISO-8859-1 JPEG Comment - ¼' ), $res['COM'] ); } + /** Comment values that are non-textual (random binary junk) should not be shown. * The example test file has a comment with a 0x5 byte in it which is a control character * and considered binary junk for our purposes. @@ -44,6 +52,7 @@ class JpegMetadataExtractorTest extends MediaWikiTestCase { $res = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-comment-binary.jpg' ); $this->assertEmpty( $res['COM'] ); } + /* Very rarely a file can have multiple comments. * Order of comments is based on order inside the file. */ @@ -51,16 +60,19 @@ class JpegMetadataExtractorTest extends MediaWikiTestCase { $res = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-comment-multiple.jpg' ); $this->assertEquals( array( 'foo', 'bar' ), $res['COM'] ); } + public function testXMPExtraction() { $res = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-xmp-psir.jpg' ); $expected = file_get_contents( $this->filePath . 'jpeg-xmp-psir.xmp' ); $this->assertEquals( $expected, $res['XMP'] ); } + public function testPSIRExtraction() { $res = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-xmp-psir.jpg' ); $expected = '50686f746f73686f7020332e30003842494d04040000000000181c02190004746573741c02190003666f6f1c020000020004'; $this->assertEquals( $expected, bin2hex( $res['PSIR'][0] ) ); } + public function testXMPExtractionAltAppId() { $res = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-xmp-alt.jpg' ); $expected = file_get_contents( $this->filePath . 'jpeg-xmp-psir.xmp' ); @@ -74,18 +86,21 @@ class JpegMetadataExtractorTest extends MediaWikiTestCase { $this->assertEquals( 'iptc-no-hash', $res ); } + public function testIPTCHashComparisionBadHash() { $segments = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-iptc-bad-hash.jpg' ); $res = JpegMetadataExtractor::doPSIR( $segments['PSIR'][0] ); $this->assertEquals( 'iptc-bad-hash', $res ); } + public function testIPTCHashComparisionGoodHash() { $segments = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-iptc-good-hash.jpg' ); $res = JpegMetadataExtractor::doPSIR( $segments['PSIR'][0] ); $this->assertEquals( 'iptc-good-hash', $res ); } + public function testExifByteOrder() { $res = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'exif-user-comment.jpg' ); $expected = 'BE'; diff --git a/tests/phpunit/includes/media/JpegTest.php b/tests/phpunit/includes/media/JpegTest.php index ea007f90..9af4f1e1 100644 --- a/tests/phpunit/includes/media/JpegTest.php +++ b/tests/phpunit/includes/media/JpegTest.php @@ -1,18 +1,19 @@ <?php +/** + * @todo covers tags + */ class JpegTest extends MediaWikiTestCase { - public function setUp() { - $this->filePath = __DIR__ . '/../../data/media/'; - if ( !wfDl( 'exif' ) ) { + protected function setUp() { + parent::setUp(); + if ( !extension_loaded( 'exif' ) ) { $this->markTestSkipped( "This test needs the exif extension." ); } - global $wgShowEXIF; - $this->show = $wgShowEXIF; - $wgShowEXIF = true; - } - public function tearDown() { - global $wgShowEXIF; - $wgShowEXIF = $this->show; + + $this->filePath = __DIR__ . '/../../data/media/'; + + + $this->setMwGlobals( 'wgShowEXIF', true ); } public function testInvalidFile() { @@ -20,6 +21,7 @@ class JpegTest extends MediaWikiTestCase { $res = $jpeg->getMetadata( null, $this->filePath . 'README' ); $this->assertEquals( ExifBitmapHandler::BROKEN_FILE, $res ); } + public function testJpegMetadataExtraction() { $h = new JpegHandler; $res = $h->getMetadata( null, $this->filePath . 'test.jpg' ); diff --git a/tests/phpunit/includes/media/MediaHandlerTest.php b/tests/phpunit/includes/media/MediaHandlerTest.php index 99df4f80..c28898bb 100644 --- a/tests/phpunit/includes/media/MediaHandlerTest.php +++ b/tests/phpunit/includes/media/MediaHandlerTest.php @@ -1,7 +1,12 @@ <?php class MediaHandlerTest extends MediaWikiTestCase { - function testFitBoxWidth() { + + /** + * @covers MediaHandler::fitBoxWidth + * @todo split into a dataprovider and test method + */ + public function testFitBoxWidth() { $vals = array( array( 'width' => 50, @@ -46,5 +51,3 @@ class MediaHandlerTest extends MediaWikiTestCase { } } } - - diff --git a/tests/phpunit/includes/media/PNGMetadataExtractorTest.php b/tests/phpunit/includes/media/PNGMetadataExtractorTest.php index 1b1b2ec3..939f2cfc 100644 --- a/tests/phpunit/includes/media/PNGMetadataExtractorTest.php +++ b/tests/phpunit/includes/media/PNGMetadataExtractorTest.php @@ -1,13 +1,21 @@ <?php + +/** + * @todo covers tags + */ class PNGMetadataExtractorTest extends MediaWikiTestCase { - function setUp() { + protected function setUp() { + parent::setUp(); $this->filePath = __DIR__ . '/../../data/media/'; } + /** - * Tests zTXt tag (compressed textual metadata) + * Tests zTXt tag (compressed textual metadata) */ - function testPngNativetZtxt() { + public function testPngNativetZtxt() { + $this->checkPHPExtension( 'zlib' ); + $meta = PNGMetadataExtractor::getMetadata( $this->filePath . 'Png-native-test.png' ); $expected = "foo bar baz foo foo foo foof foo foo foo foo"; @@ -22,7 +30,7 @@ class PNGMetadataExtractorTest extends MediaWikiTestCase { /** * Test tEXt tag (Uncompressed textual metadata) */ - function testPngNativeText() { + public function testPngNativeText() { $meta = PNGMetadataExtractor::getMetadata( $this->filePath . 'Png-native-test.png' ); $expected = "Some long image desc"; @@ -39,7 +47,7 @@ class PNGMetadataExtractorTest extends MediaWikiTestCase { * tEXt tags must be encoded iso-8859-1 (vs iTXt which are utf-8) * Make sure non-ascii characters get converted properly */ - function testPngNativeTextNonAscii() { + public function testPngNativeTextNonAscii() { $meta = PNGMetadataExtractor::getMetadata( $this->filePath . 'Png-native-test.png' ); @@ -48,7 +56,6 @@ class PNGMetadataExtractorTest extends MediaWikiTestCase { // encoded as just \xA9. $expected = "© 2010 Bawolff"; - $this->assertArrayHasKey( 'text', $meta ); $meta = $meta['text']; $this->assertArrayHasKey( 'Copyright', $meta ); @@ -60,7 +67,9 @@ class PNGMetadataExtractorTest extends MediaWikiTestCase { /** * Test extraction of pHYs tags, which can tell what the * actual resolution of the image is (aka in dots per meter). - function testPngPhysTag () { + */ + /* + public function testPngPhysTag() { $meta = PNGMetadataExtractor::getMetadata( $this->filePath . 'Png-native-test.png' ); @@ -71,11 +80,12 @@ class PNGMetadataExtractorTest extends MediaWikiTestCase { $this->assertEquals( '2835/100', $meta['YResolution'] ); $this->assertEquals( 3, $meta['ResolutionUnit'] ); // 3 = cm } + */ /** * Given a normal static PNG, check the animation metadata returned. */ - function testStaticPngAnimationMetadata() { + public function testStaticPngAnimationMetadata() { $meta = PNGMetadataExtractor::getMetadata( $this->filePath . 'Png-native-test.png' ); @@ -88,7 +98,7 @@ class PNGMetadataExtractorTest extends MediaWikiTestCase { * Given an animated APNG image file * check it gets animated metadata right. */ - function testApngAnimationMetadata() { + public function testApngAnimationMetadata() { $meta = PNGMetadataExtractor::getMetadata( $this->filePath . 'Animated_PNG_example_bouncing_beach_ball.png' ); @@ -98,44 +108,48 @@ class PNGMetadataExtractorTest extends MediaWikiTestCase { $this->assertEquals( 1.5, $meta['duration'], '', 0.00001 ); } - function testPngBitDepth8() { + public function testPngBitDepth8() { $meta = PNGMetadataExtractor::getMetadata( $this->filePath . 'Png-native-test.png' ); $this->assertEquals( 8, $meta['bitDepth'] ); } - function testPngBitDepth1() { + + public function testPngBitDepth1() { $meta = PNGMetadataExtractor::getMetadata( $this->filePath . '1bit-png.png' ); $this->assertEquals( 1, $meta['bitDepth'] ); } - function testPngIndexColour() { + public function testPngIndexColour() { $meta = PNGMetadataExtractor::getMetadata( $this->filePath . 'Png-native-test.png' ); $this->assertEquals( 'index-coloured', $meta['colorType'] ); } - function testPngRgbColour() { + + public function testPngRgbColour() { $meta = PNGMetadataExtractor::getMetadata( $this->filePath . 'rgb-png.png' ); $this->assertEquals( 'truecolour-alpha', $meta['colorType'] ); } - function testPngRgbNoAlphaColour() { + + public function testPngRgbNoAlphaColour() { $meta = PNGMetadataExtractor::getMetadata( $this->filePath . 'rgb-na-png.png' ); $this->assertEquals( 'truecolour', $meta['colorType'] ); } - function testPngGreyscaleColour() { + + public function testPngGreyscaleColour() { $meta = PNGMetadataExtractor::getMetadata( $this->filePath . 'greyscale-png.png' ); $this->assertEquals( 'greyscale-alpha', $meta['colorType'] ); } - function testPngGreyscaleNoAlphaColour() { + + public function testPngGreyscaleNoAlphaColour() { $meta = PNGMetadataExtractor::getMetadata( $this->filePath . 'greyscale-na-png.png' ); $this->assertEquals( 'greyscale', $meta['colorType'] ); } - } diff --git a/tests/phpunit/includes/media/PNGTest.php b/tests/phpunit/includes/media/PNGTest.php index fe73c9c7..ad4c2493 100644 --- a/tests/phpunit/includes/media/PNGTest.php +++ b/tests/phpunit/includes/media/PNGTest.php @@ -1,36 +1,53 @@ <?php class PNGHandlerTest extends MediaWikiTestCase { - public function setUp() { - $this->filePath = __DIR__ . '/../../data/media'; + /** @var PNGHandler */ + protected $handler; + /** @var FSRepo */ + protected $repo; + /** @var FSFileBackend */ + protected $backend; + /** @var string */ + protected $filePath; + + protected function setUp() { + parent::setUp(); + + $this->filePath = __DIR__ . '/../../data/media'; $this->backend = new FSFileBackend( array( - 'name' => 'localtesting', - 'lockManager' => 'nullLockManager', + 'name' => 'localtesting', + 'lockManager' => 'nullLockManager', 'containerPaths' => array( 'data' => $this->filePath ) ) ); $this->repo = new FSRepo( array( - 'name' => 'temp', - 'url' => 'http://localhost/thumbtest', + 'name' => 'temp', + 'url' => 'http://localhost/thumbtest', 'backend' => $this->backend ) ); $this->handler = new PNGHandler(); } + /** + * @covers PNGHandler::getMetadata + */ public function testInvalidFile() { $res = $this->handler->getMetadata( null, $this->filePath . '/README' ); $this->assertEquals( PNGHandler::BROKEN_FILE, $res ); } + /** * @param $filename String basename of the file to check * @param $expected boolean Expected result. - * @dataProvider dataIsAnimated + * @dataProvider provideIsAnimated + * @covers PNGHandler::isAnimatedImage */ public function testIsAnimanted( $filename, $expected ) { $file = $this->dataFile( $filename, 'image/png' ); $actual = $this->handler->isAnimatedImage( $file ); $this->assertEquals( $expected, $actual ); } - public function dataIsAnimated() { + + public static function provideIsAnimated() { return array( array( 'Animated_PNG_example_bouncing_beach_ball.png', true ), array( '1bit-png.png', false ), @@ -40,14 +57,16 @@ class PNGHandlerTest extends MediaWikiTestCase { /** * @param $filename String * @param $expected Integer Total image area - * @dataProvider dataGetImageArea + * @dataProvider provideGetImageArea + * @covers PNGHandler::getImageArea */ public function testGetImageArea( $filename, $expected ) { - $file = $this->dataFile($filename, 'image/png' ); + $file = $this->dataFile( $filename, 'image/png' ); $actual = $this->handler->getImageArea( $file, $file->getWidth(), $file->getHeight() ); $this->assertEquals( $expected, $actual ); } - public function dataGetImageArea() { + + public static function provideGetImageArea() { return array( array( '1bit-png.png', 2500 ), array( 'greyscale-png.png', 2500 ), @@ -59,13 +78,15 @@ class PNGHandlerTest extends MediaWikiTestCase { /** * @param $metadata String Serialized metadata * @param $expected Integer One of the class constants of PNGHandler - * @dataProvider dataIsMetadataValid + * @dataProvider provideIsMetadataValid + * @covers PNGHandler::isMetadataValid */ public function testIsMetadataValid( $metadata, $expected ) { $actual = $this->handler->isMetadataValid( null, $metadata ); $this->assertEquals( $expected, $actual ); } - public function dataIsMetadataValid() { + + public static function provideIsMetadataValid() { return array( array( PNGHandler::BROKEN_FILE, PNGHandler::METADATA_GOOD ), array( '', PNGHandler::METADATA_BAD ), @@ -78,7 +99,8 @@ class PNGHandlerTest extends MediaWikiTestCase { /** * @param $filename String * @param $expected String Serialized array - * @dataProvider dataGetMetadata + * @dataProvider provideGetMetadata + * @covers PNGHandler::getMetadata */ public function testGetMetadata( $filename, $expected ) { $file = $this->dataFile( $filename, 'image/png' ); @@ -86,10 +108,11 @@ class PNGHandlerTest extends MediaWikiTestCase { // $this->assertEquals( unserialize( $expected ), unserialize( $actual ) ); $this->assertEquals( ( $expected ), ( $actual ) ); } - public function dataGetMetadata() { + + public static function provideGetMetadata() { return array( array( 'rgb-na-png.png', 'a:6:{s:10:"frameCount";i:0;s:9:"loopCount";i:1;s:8:"duration";d:0;s:8:"bitDepth";i:8;s:9:"colorType";s:10:"truecolour";s:8:"metadata";a:1:{s:15:"_MW_PNG_VERSION";i:1;}}' ), - array( 'xmp.png', 'a:6:{s:10:"frameCount";i:0;s:9:"loopCount";i:1;s:8:"duration";d:0;s:8:"bitDepth";i:1;s:9:"colorType";s:14:"index-coloured";s:8:"metadata";a:2:{s:12:"SerialNumber";s:9:"123456789";s:15:"_MW_PNG_VERSION";i:1;}}' ), + array( 'xmp.png', 'a:6:{s:10:"frameCount";i:0;s:9:"loopCount";i:1;s:8:"duration";d:0;s:8:"bitDepth";i:1;s:9:"colorType";s:14:"index-coloured";s:8:"metadata";a:2:{s:12:"SerialNumber";s:9:"123456789";s:15:"_MW_PNG_VERSION";i:1;}}' ), ); } diff --git a/tests/phpunit/includes/media/SVGMetadataExtractorTest.php b/tests/phpunit/includes/media/SVGMetadataExtractorTest.php index 2116554e..257009b0 100644 --- a/tests/phpunit/includes/media/SVGMetadataExtractorTest.php +++ b/tests/phpunit/includes/media/SVGMetadataExtractorTest.php @@ -1,25 +1,30 @@ <?php +/** + * @todo covers tags + */ class SVGMetadataExtractorTest extends MediaWikiTestCase { - function setUp() { + protected function setUp() { + parent::setUp(); AutoLoader::loadClass( 'SVGMetadataExtractorTest' ); } /** - * @dataProvider providerSvgFiles + * @dataProvider provideSvgFiles */ - function testGetMetadata( $infile, $expected ) { + public function testGetMetadata( $infile, $expected ) { $this->assertMetadata( $infile, $expected ); } - + /** - * @dataProvider providerSvgFilesWithXMLMetadata + * @dataProvider provideSvgFilesWithXMLMetadata */ - function testGetXMLMetadata( $infile, $expected ) { + public function testGetXMLMetadata( $infile, $expected ) { $r = new XMLReader(); - if( !method_exists( $r, 'readInnerXML' ) ) { + if ( !method_exists( $r, 'readInnerXML' ) ) { $this->markTestSkipped( 'XMLReader::readInnerXML() does not exist (libxml >2.6.20 needed).' ); + return; } $this->assertMetadata( $infile, $expected ); @@ -38,8 +43,9 @@ class SVGMetadataExtractorTest extends MediaWikiTestCase { } } - function providerSvgFiles() { + public static function provideSvgFiles() { $base = __DIR__ . '/../../data/media'; + return array( array( "$base/Wikimedia-logo.svg", @@ -81,10 +87,9 @@ class SVGMetadataExtractorTest extends MediaWikiTestCase { ); } - function providerSvgFilesWithXMLMetadata() { + public static function provideSvgFilesWithXMLMetadata() { $base = __DIR__ . '/../../data/media'; - $metadata = - '<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"> + $metadata = '<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"> <ns4:Work xmlns:ns4="http://creativecommons.org/ns#" rdf:about=""> <ns5:format xmlns:ns5="http://purl.org/dc/elements/1.1/">image/svg+xml</ns5:format> <ns5:type xmlns:ns5="http://purl.org/dc/elements/1.1/" rdf:resource="http://purl.org/dc/dcmitype/StillImage"/> @@ -105,4 +110,3 @@ class SVGMetadataExtractorTest extends MediaWikiTestCase { ); } } - diff --git a/tests/phpunit/includes/media/TiffTest.php b/tests/phpunit/includes/media/TiffTest.php index 4c79f66c..8d74b98d 100644 --- a/tests/phpunit/includes/media/TiffTest.php +++ b/tests/phpunit/includes/media/TiffTest.php @@ -1,31 +1,35 @@ <?php class TiffTest extends MediaWikiTestCase { - public function setUp() { - global $wgShowEXIF; - $this->showExif = $wgShowEXIF; - $wgShowEXIF = true; + /** @var TiffHandler */ + protected $handler; + /** @var string */ + protected $filePath; + + protected function setUp() { + parent::setUp(); + if ( !extension_loaded( 'exif' ) ) { + $this->markTestSkipped( "This test needs the exif extension." ); + } + + $this->setMwGlobals( 'wgShowEXIF', true ); + $this->filePath = __DIR__ . '/../../data/media/'; $this->handler = new TiffHandler; } - public function tearDown() { - global $wgShowEXIF; - $wgShowEXIF = $this->showExif; - } - + /** + * @covers TiffHandler::getMetadata + */ public function testInvalidFile() { - if ( !wfDl( 'exif' ) ) { - $this->markTestIncomplete( "This test needs the exif extension." ); - } $res = $this->handler->getMetadata( null, $this->filePath . 'README' ); $this->assertEquals( ExifBitmapHandler::BROKEN_FILE, $res ); } + /** + * @covers TiffHandler::getMetadata + */ public function testTiffMetadataExtraction() { - if ( !wfDl( 'exif' ) ) { - $this->markTestIncomplete( "This test needs the exif extension." ); - } $res = $this->handler->getMetadata( null, $this->filePath . 'test.tiff' ); $expected = 'a:16:{s:10:"ImageWidth";i:20;s:11:"ImageLength";i:20;s:13:"BitsPerSample";a:3:{i:0;i:8;i:1;i:8;i:2;i:8;}s:11:"Compression";i:5;s:25:"PhotometricInterpretation";i:2;s:16:"ImageDescription";s:17:"Created with GIMP";s:12:"StripOffsets";i:8;s:11:"Orientation";i:1;s:15:"SamplesPerPixel";i:3;s:12:"RowsPerStrip";i:64;s:15:"StripByteCounts";i:238;s:11:"XResolution";s:19:"1207959552/16777216";s:11:"YResolution";s:19:"1207959552/16777216";s:19:"PlanarConfiguration";i:1;s:14:"ResolutionUnit";i:2;s:22:"MEDIAWIKI_EXIF_VERSION";i:2;}'; // Re-unserialize in case there are subtle differences between how versions diff --git a/tests/phpunit/includes/media/XMPTest.php b/tests/phpunit/includes/media/XMPTest.php index 8198d3b0..d12e9b00 100644 --- a/tests/phpunit/includes/media/XMPTest.php +++ b/tests/phpunit/includes/media/XMPTest.php @@ -1,8 +1,13 @@ <?php + +/** + * @todo covers tags + */ class XMPTest extends MediaWikiTestCase { - function setUp() { - if ( !wfDl( 'xml' ) ) { + protected function setUp() { + parent::setUp(); + if ( !extension_loaded( 'xml' ) ) { $this->markTestSkipped( 'Requires libxml to do XMP parsing' ); } } @@ -14,7 +19,8 @@ class XMPTest extends MediaWikiTestCase { * @param $expected Array expected result of parsing the xmp. * @param $info String Short sentence on what's being tested. * - * @dataProvider dataXMPParse + * @throws Exception + * @dataProvider provideXMPParse */ public function testXMPParse( $xmp, $expected, $info ) { if ( !is_string( $xmp ) || !is_array( $expected ) ) { @@ -25,8 +31,8 @@ class XMPTest extends MediaWikiTestCase { $this->assertEquals( $expected, $reader->getResults(), $info, 0.0000000001 ); } - public function dataXMPParse() { - $xmpPath = __DIR__ . '/../../data/xmp/' ; + public static function provideXMPParse() { + $xmpPath = __DIR__ . '/../../data/xmp/'; $data = array(); // $xmpFiles format: array of arrays with first arg file base name, @@ -53,16 +59,18 @@ class XMPTest extends MediaWikiTestCase { array( 'utf32LE', 'UTF-32LE encoding' ), array( 'xmpExt', 'Extended XMP missing second part' ), array( 'gps', 'Handling of exif GPS parameters in XMP' ), - ); - foreach( $xmpFiles as $file ) { + ); + + foreach ( $xmpFiles as $file ) { $xmp = file_get_contents( $xmpPath . $file[0] . '.xmp' ); // I'm not sure if this is the best way to handle getting the // result array, but it seems kind of big to put directly in the test // file. $result = null; - include( $xmpPath . $file[0] . '.result.php' ); + include $xmpPath . $file[0] . '.result.php'; $data[] = array( $xmp, $result, '[' . $file[0] . '.xmp] ' . $file[1] ); } + return $data; } @@ -72,7 +80,7 @@ class XMPTest extends MediaWikiTestCase { * @todo This is based on what the standard says. Need to find a real * world example file to double check the support for this is right. */ - function testExtendedXMP() { + public function testExtendedXMP() { $xmpPath = __DIR__ . '/../../data/xmp/'; $standardXMP = file_get_contents( $xmpPath . 'xmpExt.xmp' ); $extendedXMP = file_get_contents( $xmpPath . 'xmpExt2.xmp' ); @@ -87,8 +95,8 @@ class XMPTest extends MediaWikiTestCase { $reader->parseExtended( $extendedPacket ); $actual = $reader->getResults(); - $expected = array( 'xmp-exif' => - array( + $expected = array( + 'xmp-exif' => array( 'DigitalZoomRatio' => '0/10', 'Flash' => 9, 'FNumber' => '2/10', @@ -102,7 +110,7 @@ class XMPTest extends MediaWikiTestCase { * This test has an extended XMP block with a wrong guid (md5sum) * and thus should only return the StandardXMP, not the ExtendedXMP. */ - function testExtendedXMPWithWrongGUID() { + public function testExtendedXMPWithWrongGUID() { $xmpPath = __DIR__ . '/../../data/xmp/'; $standardXMP = file_get_contents( $xmpPath . 'xmpExt.xmp' ); $extendedXMP = file_get_contents( $xmpPath . 'xmpExt2.xmp' ); @@ -117,8 +125,8 @@ class XMPTest extends MediaWikiTestCase { $reader->parseExtended( $extendedPacket ); $actual = $reader->getResults(); - $expected = array( 'xmp-exif' => - array( + $expected = array( + 'xmp-exif' => array( 'DigitalZoomRatio' => '0/10', 'Flash' => 9, ) @@ -126,11 +134,12 @@ class XMPTest extends MediaWikiTestCase { $this->assertEquals( $expected, $actual ); } + /** * Have a high offset to simulate a missing packet, * which should cause it to ignore the ExtendedXMP packet. */ - function testExtendedXMPMissingPacket() { + public function testExtendedXMPMissingPacket() { $xmpPath = __DIR__ . '/../../data/xmp/'; $standardXMP = file_get_contents( $xmpPath . 'xmpExt.xmp' ); $extendedXMP = file_get_contents( $xmpPath . 'xmpExt2.xmp' ); @@ -145,8 +154,8 @@ class XMPTest extends MediaWikiTestCase { $reader->parseExtended( $extendedPacket ); $actual = $reader->getResults(); - $expected = array( 'xmp-exif' => - array( + $expected = array( + 'xmp-exif' => array( 'DigitalZoomRatio' => '0/10', 'Flash' => 9, ) @@ -154,5 +163,4 @@ class XMPTest extends MediaWikiTestCase { $this->assertEquals( $expected, $actual ); } - } diff --git a/tests/phpunit/includes/media/XMPValidateTest.php b/tests/phpunit/includes/media/XMPValidateTest.php index e2bb8d8d..96bf5e47 100644 --- a/tests/phpunit/includes/media/XMPValidateTest.php +++ b/tests/phpunit/includes/media/XMPValidateTest.php @@ -2,15 +2,16 @@ class XMPValidateTest extends MediaWikiTestCase { /** - * @dataProvider providerDate + * @dataProvider provideDates + * @covers XMPValidate::validateDate */ - function testValidateDate( $value, $expected ) { + public function testValidateDate( $value, $expected ) { // The method should modify $value. XMPValidate::validateDate( array(), $value, true ); $this->assertEquals( $expected, $value ); } - function providerDate() { + public static function provideDates() { /* For reference valid date formats are: * YYYY * YYYY-MM @@ -41,7 +42,5 @@ class XMPValidateTest extends MediaWikiTestCase { array( '2001-05-12T15', null ), array( '2001-12T15:13', null ), ); - } - } diff --git a/tests/phpunit/includes/mobile/DeviceDetectionTest.php b/tests/phpunit/includes/mobile/DeviceDetectionTest.php deleted file mode 100644 index 0e156532..00000000 --- a/tests/phpunit/includes/mobile/DeviceDetectionTest.php +++ /dev/null @@ -1,40 +0,0 @@ -<?php - -/** - * @group Mobile - */ - class DeviceDetectionTest extends MediaWikiTestCase { - - /** - * @dataProvider provideTestFormatName - */ - public function testFormatName( $format, $userAgent ) { - $detector = new DeviceDetection(); - $this->assertEquals( $format, $detector->detectFormatName( $userAgent ) ); - } - - public function provideTestFormatName() { - return array( - array( 'android', 'Mozilla/5.0 (Linux; U; Android 2.1; en-us; Nexus One Build/ERD62) AppleWebKit/530.17 (KHTML, like Gecko) Version/4.0 Mobile Safari/530.17' ), - array( 'iphone2', 'Mozilla/5.0 (ipod: U;CPU iPhone OS 2_2 like Mac OS X: es_es) AppleWebKit/525.18.1 (KHTML, like Gecko) Version/3.0 Mobile/3B48b Safari/419.3' ), - array( 'iphone', 'Mozilla/5.0 (iPhone; U; CPU like Mac OS X; en) AppleWebKit/420.1 (KHTML, like Gecko) Version/3.0 Mobile/3B48b Safari/419.3' ), - array( 'nokia', 'Mozilla/5.0 (SymbianOS/9.1; U; [en]; SymbianOS/91 Series60/3.0) AppleWebKit/413 (KHTML, like Gecko) Safari/413' ), - array( 'palm_pre', 'Mozilla/5.0 (webOS/1.0; U; en-US) AppleWebKit/525.27.1 (KHTML, like Gecko) Version/1.0 Safari/525.27.1 Pre/1.0' ), - array( 'wii', 'Opera/9.00 (Nintendo Wii; U; ; 1309-9; en)' ), - array( 'operamini', 'Opera/9.50 (J2ME/MIDP; Opera Mini/4.0.10031/298; U; en)' ), - array( 'operamobile', 'Opera/9.51 Beta (Microsoft Windows; PPC; Opera Mobi/1718; U; en)' ), - array( 'kindle', 'Mozilla/4.0 (compatible; Linux 2.6.10) NetFront/3.3 Kindle/1.0 (screen 600x800)' ), - array( 'kindle2', 'Mozilla/4.0 (compatible; Linux 2.6.22) NetFront/3.4 Kindle/2.0 (screen 824x1200; rotate)' ), - array( 'capable', 'Mozilla/5.0 (X11; Linux i686; rv:2.0.1) Gecko/20100101 Firefox/4.0.1' ), - array( 'netfront', 'Mozilla/4.08 (Windows; Mobile Content Viewer/1.0) NetFront/3.2' ), - array( 'wap2', 'SonyEricssonK608i/R2L/SN356841000828910 Browser/SEMC-Browser/4.2 Profile/MIDP-2.0 Configuration/CLDC-1.1' ), - array( 'wap2', 'NokiaN73-2/3.0-630.0.2 Series60/3.0 Profile/MIDP-2.0 Configuration/CLDC-1.1' ), - array( 'psp', 'Mozilla/4.0 (PSP (PlayStation Portable); 2.00)' ), - array( 'ps3', 'Mozilla/5.0 (PLAYSTATION 3; 1.00)' ), - array( 'ie', 'Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0)' ), - array( 'ie', 'Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.0)' ), - array( 'blackberry', 'BlackBerry9300/5.0.0.716 Profile/MIDP-2.1 Configuration/CLDC-1.1 VendorID/133' ), - array( 'blackberry-lt5', 'BlackBerry7250/4.0.0 Profile/MIDP-2.0 Configuration/CLDC-1.1' ), - ); - } -} diff --git a/tests/phpunit/includes/normal/CleanUpTest.php b/tests/phpunit/includes/normal/CleanUpTest.php index d5ad18d8..52dd2ef5 100644 --- a/tests/phpunit/includes/normal/CleanUpTest.php +++ b/tests/phpunit/includes/normal/CleanUpTest.php @@ -29,16 +29,21 @@ * Requires PHPUnit. * * @ingroup UtfNormal + * @group Large + * + * We ignore code coverage for this test suite until they are rewritten + * to use data providers (bug 46561). + * @codeCoverageIgnore */ class CleanUpTest extends MediaWikiTestCase { /** @todo document */ - function testAscii() { + public function testAscii() { $text = 'This is plain ASCII text.'; $this->assertEquals( $text, UtfNormal::cleanUp( $text ) ); } /** @todo document */ - function testNull() { + public function testNull() { $text = "a \x00 null"; $expect = "a \xef\xbf\xbd null"; $this->assertEquals( @@ -47,13 +52,13 @@ class CleanUpTest extends MediaWikiTestCase { } /** @todo document */ - function testLatin() { + public function testLatin() { $text = "L'\xc3\xa9cole"; $this->assertEquals( $text, UtfNormal::cleanUp( $text ) ); } /** @todo document */ - function testLatinNormal() { + public function testLatinNormal() { $text = "L'e\xcc\x81cole"; $expect = "L'\xc3\xa9cole"; $this->assertEquals( $expect, UtfNormal::cleanUp( $text ) ); @@ -65,19 +70,24 @@ class CleanUpTest extends MediaWikiTestCase { */ function XtestAllChars() { $rep = UTF8_REPLACEMENT; - for( $i = 0x0; $i < UNICODE_MAX; $i++ ) { + for ( $i = 0x0; $i < UNICODE_MAX; $i++ ) { $char = codepointToUtf8( $i ); $clean = UtfNormal::cleanUp( $char ); $x = sprintf( "%04X", $i ); - if( $i % 0x1000 == 0 ) echo "U+$x\n"; - if( $i == 0x0009 || - $i == 0x000a || - $i == 0x000d || - ($i > 0x001f && $i < UNICODE_SURROGATE_FIRST) || - ($i > UNICODE_SURROGATE_LAST && $i < 0xfffe ) || - ($i > 0xffff && $i <= UNICODE_MAX ) ) { - if( isset( UtfNormal::$utfCanonicalComp[$char] ) || isset( UtfNormal::$utfCanonicalDecomp[$char] ) ) { - $comp = UtfNormal::NFC( $char ); + + if ( $i % 0x1000 == 0 ) { + echo "U+$x\n"; + } + + if ( $i == 0x0009 || + $i == 0x000a || + $i == 0x000d || + ( $i > 0x001f && $i < UNICODE_SURROGATE_FIRST ) || + ( $i > UNICODE_SURROGATE_LAST && $i < 0xfffe ) || + ( $i > 0xffff && $i <= UNICODE_MAX ) + ) { + if ( isset( UtfNormal::$utfCanonicalComp[$char] ) || isset( UtfNormal::$utfCanonicalDecomp[$char] ) ) { + $comp = UtfNormal::NFC( $char ); $this->assertEquals( bin2hex( $comp ), bin2hex( $clean ), @@ -95,7 +105,7 @@ class CleanUpTest extends MediaWikiTestCase { } /** @todo document */ - function testAllBytes() { + public function testAllBytes() { $this->doTestBytes( '', '' ); $this->doTestBytes( 'x', '' ); $this->doTestBytes( '', 'x' ); @@ -104,32 +114,38 @@ class CleanUpTest extends MediaWikiTestCase { /** @todo document */ function doTestBytes( $head, $tail ) { - for( $i = 0x0; $i < 256; $i++ ) { + for ( $i = 0x0; $i < 256; $i++ ) { $char = $head . chr( $i ) . $tail; $clean = UtfNormal::cleanUp( $char ); $x = sprintf( "%02X", $i ); - if( $i == 0x0009 || - $i == 0x000a || - $i == 0x000d || - ($i > 0x001f && $i < 0x80) ) { + + if ( $i == 0x0009 || + $i == 0x000a || + $i == 0x000d || + ( $i > 0x001f && $i < 0x80 ) + ) { $this->assertEquals( bin2hex( $char ), bin2hex( $clean ), "ASCII byte $x should be intact" ); - if( $char != $clean ) return; + if ( $char != $clean ) { + return; + } } else { $norm = $head . UTF8_REPLACEMENT . $tail; $this->assertEquals( bin2hex( $norm ), bin2hex( $clean ), "Forbidden byte $x should be rejected" ); - if( $norm != $clean ) return; + if ( $norm != $clean ) { + return; + } } } } /** @todo document */ - function testDoubleBytes() { + public function testDoubleBytes() { $this->doTestDoubleBytes( '', '' ); $this->doTestDoubleBytes( 'x', '' ); $this->doTestDoubleBytes( '', 'x' ); @@ -140,42 +156,49 @@ class CleanUpTest extends MediaWikiTestCase { * @todo document */ function doTestDoubleBytes( $head, $tail ) { - for( $first = 0xc0; $first < 0x100; $first+=2 ) { - for( $second = 0x80; $second < 0x100; $second+=2 ) { + for ( $first = 0xc0; $first < 0x100; $first += 2 ) { + for ( $second = 0x80; $second < 0x100; $second += 2 ) { $char = $head . chr( $first ) . chr( $second ) . $tail; $clean = UtfNormal::cleanUp( $char ); $x = sprintf( "%02X,%02X", $first, $second ); - if( $first > 0xc1 && - $first < 0xe0 && - $second < 0xc0 ) { - $norm = UtfNormal::NFC( $char ); + if ( $first > 0xc1 && + $first < 0xe0 && + $second < 0xc0 + ) { + $norm = UtfNormal::NFC( $char ); $this->assertEquals( bin2hex( $norm ), bin2hex( $clean ), "Pair $x should be intact" ); - if( $norm != $clean ) return; - } elseif( $first > 0xfd || $second > 0xbf ) { + if ( $norm != $clean ) { + return; + } + } elseif ( $first > 0xfd || $second > 0xbf ) { # fe and ff are not legal head bytes -- expect two replacement chars $norm = $head . UTF8_REPLACEMENT . UTF8_REPLACEMENT . $tail; $this->assertEquals( bin2hex( $norm ), bin2hex( $clean ), "Forbidden pair $x should be rejected" ); - if( $norm != $clean ) return; + if ( $norm != $clean ) { + return; + } } else { $norm = $head . UTF8_REPLACEMENT . $tail; $this->assertEquals( bin2hex( $norm ), bin2hex( $clean ), "Forbidden pair $x should be rejected" ); - if( $norm != $clean ) return; + if ( $norm != $clean ) { + return; + } } } } } /** @todo document */ - function testTripleBytes() { + public function testTripleBytes() { $this->doTestTripleBytes( '', '' ); $this->doTestTripleBytes( 'x', '' ); $this->doTestTripleBytes( '', 'x' ); @@ -184,24 +207,27 @@ class CleanUpTest extends MediaWikiTestCase { /** @todo document */ function doTestTripleBytes( $head, $tail ) { - for( $first = 0xc0; $first < 0x100; $first+=2 ) { - for( $second = 0x80; $second < 0x100; $second+=2 ) { + for ( $first = 0xc0; $first < 0x100; $first += 2 ) { + for ( $second = 0x80; $second < 0x100; $second += 2 ) { #for( $third = 0x80; $third < 0x100; $third++ ) { - for( $third = 0x80; $third < 0x81; $third++ ) { + for ( $third = 0x80; $third < 0x81; $third++ ) { $char = $head . chr( $first ) . chr( $second ) . chr( $third ) . $tail; $clean = UtfNormal::cleanUp( $char ); $x = sprintf( "%02X,%02X,%02X", $first, $second, $third ); - if( $first >= 0xe0 && + + if ( $first >= 0xe0 && $first < 0xf0 && $second < 0xc0 && - $third < 0xc0 ) { - if( $first == 0xe0 && $second < 0xa0 ) { + $third < 0xc0 + ) { + if ( $first == 0xe0 && $second < 0xa0 ) { $this->assertEquals( bin2hex( $head . UTF8_REPLACEMENT . $tail ), bin2hex( $clean ), "Overlong triplet $x should be rejected" ); - } elseif( $first == 0xed && - ( chr( $first ) . chr( $second ) . chr( $third )) >= UTF8_SURROGATE_FIRST ) { + } elseif ( $first == 0xed && + ( chr( $first ) . chr( $second ) . chr( $third ) ) >= UTF8_SURROGATE_FIRST + ) { $this->assertEquals( bin2hex( $head . UTF8_REPLACEMENT . $tail ), bin2hex( $clean ), @@ -212,27 +238,28 @@ class CleanUpTest extends MediaWikiTestCase { bin2hex( $clean ), "Triplet $x should be intact" ); } - } elseif( $first > 0xc1 && $first < 0xe0 && $second < 0xc0 ) { + } elseif ( $first > 0xc1 && $first < 0xe0 && $second < 0xc0 ) { $this->assertEquals( bin2hex( UtfNormal::NFC( $head . chr( $first ) . chr( $second ) ) . UTF8_REPLACEMENT . $tail ), bin2hex( $clean ), "Valid 2-byte $x + broken tail" ); - } elseif( $second > 0xc1 && $second < 0xe0 && $third < 0xc0 ) { + } elseif ( $second > 0xc1 && $second < 0xe0 && $third < 0xc0 ) { $this->assertEquals( bin2hex( $head . UTF8_REPLACEMENT . UtfNormal::NFC( chr( $second ) . chr( $third ) . $tail ) ), bin2hex( $clean ), "Broken head + valid 2-byte $x" ); - } elseif( ( $first > 0xfd || $second > 0xfd ) && - ( ( $second > 0xbf && $third > 0xbf ) || - ( $second < 0xc0 && $third < 0xc0 ) || - ( $second > 0xfd ) || - ( $third > 0xfd ) ) ) { + } elseif ( ( $first > 0xfd || $second > 0xfd ) && + ( ( $second > 0xbf && $third > 0xbf ) || + ( $second < 0xc0 && $third < 0xc0 ) || + ( $second > 0xfd ) || + ( $third > 0xfd ) ) + ) { # fe and ff are not legal head bytes -- expect three replacement chars $this->assertEquals( bin2hex( $head . UTF8_REPLACEMENT . UTF8_REPLACEMENT . UTF8_REPLACEMENT . $tail ), bin2hex( $clean ), "Forbidden triplet $x should be rejected" ); - } elseif( $first > 0xc2 && $second < 0xc0 && $third < 0xc0 ) { + } elseif ( $first > 0xc2 && $second < 0xc0 && $third < 0xc0 ) { $this->assertEquals( bin2hex( $head . UTF8_REPLACEMENT . $tail ), bin2hex( $clean ), @@ -249,22 +276,22 @@ class CleanUpTest extends MediaWikiTestCase { } /** @todo document */ - function testChunkRegression() { + public function testChunkRegression() { # Check for regression against a chunking bug - $text = "\x46\x55\xb8" . - "\xdc\x96" . - "\xee" . - "\xe7" . - "\x44" . - "\xaa" . - "\x2f\x25"; + $text = "\x46\x55\xb8" . + "\xdc\x96" . + "\xee" . + "\xe7" . + "\x44" . + "\xaa" . + "\x2f\x25"; $expect = "\x46\x55\xef\xbf\xbd" . - "\xdc\x96" . - "\xef\xbf\xbd" . - "\xef\xbf\xbd" . - "\x44" . - "\xef\xbf\xbd" . - "\x2f\x25"; + "\xdc\x96" . + "\xef\xbf\xbd" . + "\xef\xbf\xbd" . + "\x44" . + "\xef\xbf\xbd" . + "\x2f\x25"; $this->assertEquals( bin2hex( $expect ), @@ -272,34 +299,34 @@ class CleanUpTest extends MediaWikiTestCase { } /** @todo document */ - function testInterposeRegression() { - $text = "\x4e\x30" . - "\xb1" . # bad tail - "\x3a" . - "\x92" . # bad tail - "\x62\x3a" . - "\x84" . # bad tail - "\x43" . - "\xc6" . # bad head - "\x3f" . - "\x92" . # bad tail - "\xad" . # bad tail - "\x7d" . - "\xd9\x95"; + public function testInterposeRegression() { + $text = "\x4e\x30" . + "\xb1" . # bad tail + "\x3a" . + "\x92" . # bad tail + "\x62\x3a" . + "\x84" . # bad tail + "\x43" . + "\xc6" . # bad head + "\x3f" . + "\x92" . # bad tail + "\xad" . # bad tail + "\x7d" . + "\xd9\x95"; $expect = "\x4e\x30" . - "\xef\xbf\xbd" . - "\x3a" . - "\xef\xbf\xbd" . - "\x62\x3a" . - "\xef\xbf\xbd" . - "\x43" . - "\xef\xbf\xbd" . - "\x3f" . - "\xef\xbf\xbd" . - "\xef\xbf\xbd" . - "\x7d" . - "\xd9\x95"; + "\xef\xbf\xbd" . + "\x3a" . + "\xef\xbf\xbd" . + "\x62\x3a" . + "\xef\xbf\xbd" . + "\x43" . + "\xef\xbf\xbd" . + "\x3f" . + "\xef\xbf\xbd" . + "\xef\xbf\xbd" . + "\x7d" . + "\xd9\x95"; $this->assertEquals( bin2hex( $expect ), @@ -307,63 +334,63 @@ class CleanUpTest extends MediaWikiTestCase { } /** @todo document */ - function testOverlongRegression() { - $text = "\x67" . - "\x1a" . # forbidden ascii - "\xea" . # bad head - "\xc1\xa6" . # overlong sequence - "\xad" . # bad tail - "\x1c" . # forbidden ascii - "\xb0" . # bad tail - "\x3c" . - "\x9e"; # bad tail + public function testOverlongRegression() { + $text = "\x67" . + "\x1a" . # forbidden ascii + "\xea" . # bad head + "\xc1\xa6" . # overlong sequence + "\xad" . # bad tail + "\x1c" . # forbidden ascii + "\xb0" . # bad tail + "\x3c" . + "\x9e"; # bad tail $expect = "\x67" . - "\xef\xbf\xbd" . - "\xef\xbf\xbd" . - "\xef\xbf\xbd" . - "\xef\xbf\xbd" . - "\xef\xbf\xbd" . - "\xef\xbf\xbd" . - "\x3c" . - "\xef\xbf\xbd"; + "\xef\xbf\xbd" . + "\xef\xbf\xbd" . + "\xef\xbf\xbd" . + "\xef\xbf\xbd" . + "\xef\xbf\xbd" . + "\xef\xbf\xbd" . + "\x3c" . + "\xef\xbf\xbd"; $this->assertEquals( bin2hex( $expect ), bin2hex( UtfNormal::cleanUp( $text ) ) ); } /** @todo document */ - function testSurrogateRegression() { - $text = "\xed\xb4\x96" . # surrogate 0xDD16 - "\x83" . # bad tail - "\xb4" . # bad tail - "\xac"; # bad head + public function testSurrogateRegression() { + $text = "\xed\xb4\x96" . # surrogate 0xDD16 + "\x83" . # bad tail + "\xb4" . # bad tail + "\xac"; # bad head $expect = "\xef\xbf\xbd" . - "\xef\xbf\xbd" . - "\xef\xbf\xbd" . - "\xef\xbf\xbd"; + "\xef\xbf\xbd" . + "\xef\xbf\xbd" . + "\xef\xbf\xbd"; $this->assertEquals( bin2hex( $expect ), bin2hex( UtfNormal::cleanUp( $text ) ) ); } /** @todo document */ - function testBomRegression() { - $text = "\xef\xbf\xbe" . # U+FFFE, illegal char - "\xb2" . # bad tail - "\xef" . # bad head - "\x59"; + public function testBomRegression() { + $text = "\xef\xbf\xbe" . # U+FFFE, illegal char + "\xb2" . # bad tail + "\xef" . # bad head + "\x59"; $expect = "\xef\xbf\xbd" . - "\xef\xbf\xbd" . - "\xef\xbf\xbd" . - "\x59"; + "\xef\xbf\xbd" . + "\xef\xbf\xbd" . + "\x59"; $this->assertEquals( bin2hex( $expect ), bin2hex( UtfNormal::cleanUp( $text ) ) ); } /** @todo document */ - function testForbiddenRegression() { - $text = "\xef\xbf\xbf"; # U+FFFF, illegal char + public function testForbiddenRegression() { + $text = "\xef\xbf\xbf"; # U+FFFF, illegal char $expect = "\xef\xbf\xbd"; $this->assertEquals( bin2hex( $expect ), @@ -371,10 +398,10 @@ class CleanUpTest extends MediaWikiTestCase { } /** @todo document */ - function testHangulRegression() { + public function testHangulRegression() { $text = "\xed\x9c\xaf" . # Hangul char - "\xe1\x87\x81"; # followed by another final jamo - $expect = $text; # Should *not* change. + "\xe1\x87\x81"; # followed by another final jamo + $expect = $text; # Should *not* change. $this->assertEquals( bin2hex( $expect ), bin2hex( UtfNormal::cleanUp( $text ) ) ); diff --git a/tests/phpunit/includes/objectcache/BagOStuffTest.php b/tests/phpunit/includes/objectcache/BagOStuffTest.php new file mode 100644 index 00000000..aa783943 --- /dev/null +++ b/tests/phpunit/includes/objectcache/BagOStuffTest.php @@ -0,0 +1,149 @@ +<?php +/** + * This class will test BagOStuff. + * + * @author Matthias Mullie <mmullie@wikimedia.org> + */ +class BagOStuffTest extends MediaWikiTestCase { + private $cache; + + protected function setUp() { + parent::setUp(); + + // type defined through parameter + if ( $this->getCliArg( 'use-bagostuff=' ) ) { + $name = $this->getCliArg( 'use-bagostuff=' ); + + $this->cache = ObjectCache::newFromId( $name ); + } else { + // no type defined - use simple hash + $this->cache = new HashBagOStuff; + } + + $this->cache->delete( wfMemcKey( 'test' ) ); + } + + protected function tearDown() { + } + + public function testMerge() { + $key = wfMemcKey( 'test' ); + + $usleep = 0; + + /** + * Callback method: append "merged" to whatever is in cache. + * + * @param BagOStuff $cache + * @param string $key + * @param int $existingValue + * @use int $usleep + * @return int + */ + $callback = function ( BagOStuff $cache, $key, $existingValue ) use ( &$usleep ) { + // let's pretend this is an expensive callback to test concurrent merge attempts + usleep( $usleep ); + + if ( $existingValue === false ) { + return 'merged'; + } + + return $existingValue . 'merged'; + }; + + // merge on non-existing value + $merged = $this->cache->merge( $key, $callback, 0 ); + $this->assertTrue( $merged ); + $this->assertEquals( $this->cache->get( $key ), 'merged' ); + + // merge on existing value + $merged = $this->cache->merge( $key, $callback, 0 ); + $this->assertTrue( $merged ); + $this->assertEquals( $this->cache->get( $key ), 'mergedmerged' ); + + /* + * Test concurrent merges by forking this process, if: + * - not manually called with --use-bagostuff + * - pcntl_fork is supported by the system + * - cache type will correctly support calls over forks + */ + $fork = (bool)$this->getCliArg( 'use-bagostuff=' ); + $fork &= function_exists( 'pcntl_fork' ); + $fork &= !$this->cache instanceof HashBagOStuff; + $fork &= !$this->cache instanceof EmptyBagOStuff; + $fork &= !$this->cache instanceof MultiWriteBagOStuff; + if ( $fork ) { + // callback should take awhile now so that we can test concurrent merge attempts + $usleep = 5000; + + $pid = pcntl_fork(); + if ( $pid == -1 ) { + // can't fork, ignore this test... + } elseif ( $pid ) { + // wait a little, making sure that the child process is calling merge + usleep( 3000 ); + + // attempt a merge - this should fail + $merged = $this->cache->merge( $key, $callback, 0, 1 ); + + // merge has failed because child process was merging (and we only attempted once) + $this->assertFalse( $merged ); + + // make sure the child's merge is completed and verify + usleep( 3000 ); + $this->assertEquals( $this->cache->get( $key ), 'mergedmergedmerged' ); + } else { + $this->cache->merge( $key, $callback, 0, 1 ); + + // Note: I'm not even going to check if the merge worked, I'll + // compare values in the parent process to test if this merge worked. + // I'm just going to exit this child process, since I don't want the + // child to output any test results (would be rather confusing to + // have test output twice) + exit; + } + } + } + + public function testAdd() { + $key = wfMemcKey( 'test' ); + $this->assertTrue( $this->cache->add( $key, 'test' ) ); + } + + public function testGet() { + $value = array( 'this' => 'is', 'a' => 'test' ); + + $key = wfMemcKey( 'test' ); + $this->cache->add( $key, $value ); + $this->assertEquals( $this->cache->get( $key ), $value ); + } + + /** + * @covers BagOStuff::incr + */ + public function testIncr() { + $key = wfMemcKey( 'test' ); + $this->cache->add( $key, 0 ); + $this->cache->incr( $key ); + $expectedValue = 1; + $actualValue = $this->cache->get( $key ); + $this->assertEquals( $expectedValue, $actualValue, 'Value should be 1 after incrementing' ); + } + + public function testGetMulti() { + $value1 = array( 'this' => 'is', 'a' => 'test' ); + $value2 = array( 'this' => 'is', 'another' => 'test' ); + + $key1 = wfMemcKey( 'test1' ); + $key2 = wfMemcKey( 'test2' ); + + $this->cache->add( $key1, $value1 ); + $this->cache->add( $key2, $value2 ); + + $this->assertEquals( $this->cache->getMulti( array( $key1, $key2 ) ), array( $key1 => $value1, $key2 => $value2 ) ); + + // cleanup + $this->cache->delete( $key1 ); + $this->cache->delete( $key2 ); + } +} diff --git a/tests/phpunit/includes/parser/MagicVariableTest.php b/tests/phpunit/includes/parser/MagicVariableTest.php index 31645313..c2c97c01 100644 --- a/tests/phpunit/includes/parser/MagicVariableTest.php +++ b/tests/phpunit/includes/parser/MagicVariableTest.php @@ -9,11 +9,13 @@ * @author Antoine Musso * @copyright Copyright © 2011, Antoine Musso * @file + * @todo covers tags */ -/** */ class MagicVariableTest extends MediaWikiTestCase { - /** Will contains a parser object*/ + /** + * @var Parser + */ private $testParser = null; /** @@ -29,12 +31,17 @@ class MagicVariableTest extends MediaWikiTestCase { ); /** setup a basic parser object */ - function setUp() { - global $wgContLang; - $wgContLang = Language::factory( 'en' ); + protected function setUp() { + parent::setUp(); + + $contLang = Language::factory( 'en' ); + $this->setMwGlobals( array( + 'wgLanguageCode' => 'en', + 'wgContLang' => $contLang, + ) ); $this->testParser = new Parser(); - $this->testParser->Options( new ParserOptions() ); + $this->testParser->Options( ParserOptions::newFromUserAndLang( new User, $contLang ) ); # initialize parser output $this->testParser->clearState(); @@ -46,9 +53,31 @@ class MagicVariableTest extends MediaWikiTestCase { $this->testParser->setTitle( $title ); } - /** destroy parser (TODO: is it really neded?)*/ - function tearDown() { - unset( $this->testParser ); + /** + * @param int $num upper limit for numbers + * @return array of numbers from 1 up to $num + */ + private static function createProviderUpTo( $num ) { + $ret = array(); + for ( $i = 1; $i <= $num; $i++ ) { + $ret[] = array( $i ); + } + + return $ret; + } + + /** + * @return array of months numbers (as an integer) + */ + public static function provideMonths() { + return self::createProviderUpTo( 12 ); + } + + /** + * @return array of days numbers (as an integer) + */ + public static function provideDays() { + return self::createProviderUpTo( 31 ); } ############### TESTS ############################################# @@ -58,117 +87,129 @@ class MagicVariableTest extends MediaWikiTestCase { # day - /** @dataProvider MediaWikiProvide::Days */ - function testCurrentdayIsUnPadded( $day ) { + /** @dataProvider provideDays */ + public function testCurrentdayIsUnPadded( $day ) { $this->assertUnPadded( 'currentday', $day ); } - /** @dataProvider MediaWikiProvide::Days */ - function testCurrentdaytwoIsZeroPadded( $day ) { + + /** @dataProvider provideDays */ + public function testCurrentdaytwoIsZeroPadded( $day ) { $this->assertZeroPadded( 'currentday2', $day ); } - /** @dataProvider MediaWikiProvide::Days */ - function testLocaldayIsUnPadded( $day ) { + + /** @dataProvider provideDays */ + public function testLocaldayIsUnPadded( $day ) { $this->assertUnPadded( 'localday', $day ); } - /** @dataProvider MediaWikiProvide::Days */ - function testLocaldaytwoIsZeroPadded( $day ) { + + /** @dataProvider provideDays */ + public function testLocaldaytwoIsZeroPadded( $day ) { $this->assertZeroPadded( 'localday2', $day ); } - + # month - /** @dataProvider MediaWikiProvide::Months */ - function testCurrentmonthIsZeroPadded( $month ) { + /** @dataProvider provideMonths */ + public function testCurrentmonthIsZeroPadded( $month ) { $this->assertZeroPadded( 'currentmonth', $month ); } - /** @dataProvider MediaWikiProvide::Months */ - function testCurrentmonthoneIsUnPadded( $month ) { + + /** @dataProvider provideMonths */ + public function testCurrentmonthoneIsUnPadded( $month ) { $this->assertUnPadded( 'currentmonth1', $month ); } - /** @dataProvider MediaWikiProvide::Months */ - function testLocalmonthIsZeroPadded( $month ) { + + /** @dataProvider provideMonths */ + public function testLocalmonthIsZeroPadded( $month ) { $this->assertZeroPadded( 'localmonth', $month ); } - /** @dataProvider MediaWikiProvide::Months */ - function testLocalmonthoneIsUnPadded( $month ) { + + /** @dataProvider provideMonths */ + public function testLocalmonthoneIsUnPadded( $month ) { $this->assertUnPadded( 'localmonth1', $month ); } - # revision day - /** @dataProvider MediaWikiProvide::Days */ - function testRevisiondayIsUnPadded( $day ) { + /** @dataProvider provideDays */ + public function testRevisiondayIsUnPadded( $day ) { $this->assertUnPadded( 'revisionday', $day ); } - /** @dataProvider MediaWikiProvide::Days */ - function testRevisiondaytwoIsZeroPadded( $day ) { + + /** @dataProvider provideDays */ + public function testRevisiondaytwoIsZeroPadded( $day ) { $this->assertZeroPadded( 'revisionday2', $day ); } - + # revision month - /** @dataProvider MediaWikiProvide::Months */ - function testRevisionmonthIsZeroPadded( $month ) { + /** @dataProvider provideMonths */ + public function testRevisionmonthIsZeroPadded( $month ) { $this->assertZeroPadded( 'revisionmonth', $month ); } - /** @dataProvider MediaWikiProvide::Months */ - function testRevisionmonthoneIsUnPadded( $month ) { + + /** @dataProvider provideMonths */ + public function testRevisionmonthoneIsUnPadded( $month ) { $this->assertUnPadded( 'revisionmonth1', $month ); } /** * Rough tests for {{SERVERNAME}} magic word * Bug 31176 + * @group Database + * @dataProvider provideDataServernameFromDifferentProtocols */ - function testServernameFromDifferentProtocols() { - global $wgServer; - $saved_wgServer= $wgServer; + public function testServernameFromDifferentProtocols( $server ) { + $this->setMwGlobals( 'wgServer', $server ); - $wgServer = 'http://localhost/'; - $this->assertMagic( 'localhost', 'servername' ); - $wgServer = 'https://localhost/'; - $this->assertMagic( 'localhost', 'servername' ); - $wgServer = '//localhost/'; # bug 31176 $this->assertMagic( 'localhost', 'servername' ); + } - $wgServer = $saved_wgServer; + public static function provideDataServernameFromDifferentProtocols() { + return array( + array( 'http://localhost/' ), + array( 'https://localhost/' ), + array( '//localhost/' ), # bug 31176 + ); } ############### HELPERS ############################################ /** assertion helper expecting a magic output which is zero padded */ - PUBLIC function assertZeroPadded( $magic, $value ) { + public function assertZeroPadded( $magic, $value ) { $this->assertMagicPadding( $magic, $value, '%02d' ); } /** assertion helper expecting a magic output which is unpadded */ - PUBLIC function assertUnPadded( $magic, $value ) { + public function assertUnPadded( $magic, $value ) { $this->assertMagicPadding( $magic, $value, '%d' ); } /** * Main assertion helper for magic variables padding - * @param $magic string Magic variable name + * @param $magic string Magic variable name * @param $value mixed Month or day - * @param $format string sprintf format for $value + * @param $format string sprintf format for $value */ private function assertMagicPadding( $magic, $value, $format ) { # Initialize parser timestamp as year 2010 at 12h34 56s. # month and day are given by the caller ($value). Month < 12! - if( $value > 12 ) { $month = $value % 12; } - else { $month = $value; } - + if ( $value > 12 ) { + $month = $value % 12; + } else { + $month = $value; + } + $this->setParserTS( sprintf( '2010%02d%02d123456', $month, $value ) ); - # please keep the following commented line of code. It helps debugging. + # please keep the following commented line of code. It helps debugging. //print "\nDEBUG (value $value):" . sprintf( '2010%02d%02d123456', $value, $value ) . "\n"; # format expectation and test it $expected = sprintf( $format, $value ); - $this->assertMagic( $expected, $magic ); + $this->assertMagic( $expected, $magic ); } /** helper to set the parser timestamp and revision timestamp */ @@ -181,8 +222,8 @@ class MagicVariableTest extends MediaWikiTestCase { * Assertion helper to test a magic variable output */ private function assertMagic( $expected, $magic ) { - if( in_array( $magic, $this->expectedAsInteger ) ) { - $expected = (int) $expected; + if ( in_array( $magic, $this->expectedAsInteger ) ) { + $expected = (int)$expected; } # Generate a message for the assertion diff --git a/tests/phpunit/includes/parser/MediaWikiParserTest.php b/tests/phpunit/includes/parser/MediaWikiParserTest.php index 6a6fded1..c120ca34 100644 --- a/tests/phpunit/includes/parser/MediaWikiParserTest.php +++ b/tests/phpunit/includes/parser/MediaWikiParserTest.php @@ -1,36 +1,120 @@ <?php -require_once( __DIR__ . '/NewParserTest.php' ); +require_once __DIR__ . '/NewParserTest.php'; /** * The UnitTest must be either a class that inherits from MediaWikiTestCase - * or a class that provides a public static suite() method which returns + * or a class that provides a public static suite() method which returns * an PHPUnit_Framework_Test object - * + * * @group Parser * @group Database */ class MediaWikiParserTest { - public static function suite() { - global $wgParserTestFiles; + /** + * @defgroup filtering_constants Filtering constants + * + * Limit inclusion of parser tests files coming from MediaWiki core + * @{ + */ - $suite = new PHPUnit_Framework_TestSuite; + /** Include files shipped with MediaWiki core */ + const CORE_ONLY = 1; + /** Include non core files as set in $wgParserTestFiles */ + const NO_CORE = 2; + /** Include anything set via $wgParserTestFiles */ + const WITH_ALL = 3; # CORE_ONLY | NO_CORE + + /** @} */ + + /** + * Get a PHPUnit test suite of parser tests. Optionally filtered with + * $flags. + * + * @par Examples: + * Get a suite of parser tests shipped by MediaWiki core: + * @code + * MediaWikiParserTest::suite( MediaWikiParserTest::CORE_ONLY ); + * @endcode + * Get a suite of various parser tests, like extensions: + * @code + * MediaWikiParserTest::suite( MediaWikiParserTest::NO_CORE ); + * @endcode + * Get any test defined via $wgParserTestFiles: + * @code + * MediaWikiParserTest::suite( MediaWikiParserTest::WITH_ALL ); + * @endcode + * + * @param $flags bitwise flag to filter out the $wgParserTestFiles that + * will be included. Default: MediaWikiParserTest::CORE_ONLY + * + * @return PHPUnit_Framework_TestSuite + */ + public static function suite( $flags = self::CORE_ONLY ) { + if ( is_string( $flags ) ) { + $flags = self::CORE_ONLY; + } + global $wgParserTestFiles, $IP; + + $mwTestDir = $IP . '/tests/'; + + # Human friendly helpers + $wantsCore = ( $flags & self::CORE_ONLY ); + $wantsRest = ( $flags & self::NO_CORE ); + + # Will hold the .txt parser test files we will include + $filesToTest = array(); + + # Filter out .txt files + foreach ( $wgParserTestFiles as $parserTestFile ) { + $isCore = ( 0 === strpos( $parserTestFile, $mwTestDir ) ); + + if ( $isCore && $wantsCore ) { + self::debug( "included core parser tests: $parserTestFile" ); + $filesToTest[] = $parserTestFile; + } elseif ( !$isCore && $wantsRest ) { + self::debug( "included non core parser tests: $parserTestFile" ); + $filesToTest[] = $parserTestFile; + } else { + self::debug( "skipped parser tests: $parserTestFile" ); + } + } + self::debug( 'parser tests files: ' + . implode( ' ', $filesToTest ) ); - foreach ( $wgParserTestFiles as $filename ) { - $testsName = basename( $filename, '.txt' ); + $suite = new PHPUnit_Framework_TestSuite; + foreach ( $filesToTest as $fileName ) { + $testsName = basename( $fileName, '.txt' ); + $escapedFileName = strtr( $fileName, array( "'" => "\\'", '\\' => '\\\\' ) ); /* This used to be ucfirst( basename( dirname( $filename ) ) ) * and then was ucfirst( basename( $filename, '.txt' ) * but that didn't work with names like foo.tests.txt */ - $className = str_replace( '.', '_', ucfirst( basename( $filename, '.txt' ) ) ); - - eval( "/** @group Database\n@group Parser\n*/ class $className extends NewParserTest { protected \$file = '" . strtr( $filename, array( "'" => "\\'", '\\' => '\\\\' ) ) . "'; } " ); + $parserTestClassName = str_replace( '.', '_', ucfirst( $testsName ) ); + $parserTestClassDefinition = <<<EOT +/** + * @group Database + * @group Parser + * @group ParserTests + * @group ParserTests_$parserTestClassName + */ +class $parserTestClassName extends NewParserTest { + protected \$file = '$escapedFileName'; +} +EOT; - $parserTester = new $className( $testsName ); - $suite->addTestSuite( new ReflectionClass ( $parserTester ) ); + eval( $parserTestClassDefinition ); + self::debug( "Adding test class $parserTestClassName" ); + $suite->addTestSuite( $parserTestClassName ); } - - return $suite; } + + /** + * Write $msg under log group 'tests-parser' + * @param string $msg Message to log + */ + protected static function debug( $msg ) { + return wfDebugLog( 'tests-parser', wfGetCaller() . ' ' . $msg ); + } } diff --git a/tests/phpunit/includes/parser/NewParserTest.php b/tests/phpunit/includes/parser/NewParserTest.php index 69a96e66..eac4de5c 100644 --- a/tests/phpunit/includes/parser/NewParserTest.php +++ b/tests/phpunit/includes/parser/NewParserTest.php @@ -6,19 +6,21 @@ * @group Database * @group Parser * @group Stub + * + * @todo covers tags */ class NewParserTest extends MediaWikiTestCase { - static protected $articles = array(); // Array of test articles defined by the tests - /* The dataProvider is run on a different instance than the test, so it must be static + static protected $articles = array(); // Array of test articles defined by the tests + /* The data provider is run on a different instance than the test, so it must be static * When running tests from several files, all tests will see all articles. */ static protected $backendToUse; public $keepUploads = false; public $runDisabled = false; + public $runParsoid = false; public $regex = ''; public $showProgress = true; - public $savedInitialGlobals = array(); public $savedWeirdGlobals = array(); public $savedGlobals = array(); public $hooks = array(); @@ -31,10 +33,16 @@ class NewParserTest extends MediaWikiTestCase { protected $file = false; - function setUp() { - global $wgContLang, $wgNamespaceProtection, $wgNamespaceAliases; + public static function setUpBeforeClass() { + // Inject ParserTest well-known interwikis + ParserTest::setupInterwikis(); + } + + protected function setUp() { + global $wgNamespaceAliases, $wgContLang; global $wgHooks, $IP; - $wgContLang = Language::factory( 'en' ); + + parent::setUp(); //Setup CLI arguments if ( $this->getCliArg( 'regex=' ) ) { @@ -48,133 +56,138 @@ class NewParserTest extends MediaWikiTestCase { $tmpGlobals = array(); + $tmpGlobals['wgLanguageCode'] = 'en'; + $tmpGlobals['wgContLang'] = Language::factory( 'en' ); + $tmpGlobals['wgSitename'] = 'MediaWiki'; + $tmpGlobals['wgServer'] = 'http://example.org'; $tmpGlobals['wgScript'] = '/index.php'; $tmpGlobals['wgScriptPath'] = '/'; $tmpGlobals['wgArticlePath'] = '/wiki/$1'; - $tmpGlobals['wgStyleSheetPath'] = '/skins'; + $tmpGlobals['wgActionPaths'] = array(); + $tmpGlobals['wgVariantArticlePath'] = false; + $tmpGlobals['wgExtensionAssetsPath'] = '/extensions'; $tmpGlobals['wgStylePath'] = '/skins'; + $tmpGlobals['wgEnableUploads'] = true; $tmpGlobals['wgThumbnailScriptPath'] = false; $tmpGlobals['wgLocalFileRepo'] = array( - 'class' => 'LocalRepo', - 'name' => 'local', - 'url' => 'http://example.com/images', - 'hashLevels' => 2, + 'class' => 'LocalRepo', + 'name' => 'local', + 'url' => 'http://example.com/images', + 'hashLevels' => 2, 'transformVia404' => false, - 'backend' => 'local-backend' + 'backend' => 'local-backend' ); $tmpGlobals['wgForeignFileRepos'] = array(); + $tmpGlobals['wgDefaultExternalStore'] = array(); $tmpGlobals['wgEnableParserCache'] = false; - $tmpGlobals['wgHooks'] = $wgHooks; + $tmpGlobals['wgCapitalLinks'] = true; + $tmpGlobals['wgNoFollowLinks'] = true; + $tmpGlobals['wgNoFollowDomainExceptions'] = array(); + $tmpGlobals['wgExternalLinkTarget'] = false; + $tmpGlobals['wgThumbnailScriptPath'] = false; + $tmpGlobals['wgUseImageResize'] = true; + $tmpGlobals['wgAllowExternalImages'] = true; + $tmpGlobals['wgRawHtml'] = false; + $tmpGlobals['wgUseTidy'] = false; + $tmpGlobals['wgAlwaysUseTidy'] = false; + $tmpGlobals['wgWellFormedXml'] = true; + $tmpGlobals['wgAllowMicrodataAttributes'] = true; + $tmpGlobals['wgExperimentalHtmlIds'] = false; + $tmpGlobals['wgAdaptiveMessageCache'] = true; + $tmpGlobals['wgUseDatabaseMessages'] = true; + $tmpGlobals['wgLocaltimezone'] = 'UTC'; $tmpGlobals['wgDeferredUpdateList'] = array(); - $tmpGlobals['wgMemc'] = wfGetMainCache(); - $tmpGlobals['messageMemc'] = wfGetMessageCacheStorage(); - $tmpGlobals['parserMemc'] = wfGetParserCacheStorage(); + $tmpGlobals['wgGroupPermissions'] = array( + '*' => array( + 'createaccount' => true, + 'read' => true, + 'edit' => true, + 'createpage' => true, + 'createtalk' => true, + ) ); + $tmpGlobals['wgNamespaceProtection'] = array( NS_MEDIAWIKI => 'editinterface' ); - // $tmpGlobals['wgContLang'] = new StubContLang; - $tmpGlobals['wgUser'] = new User; - $context = new RequestContext(); - $tmpGlobals['wgLang'] = $context->getLanguage(); - $tmpGlobals['wgOut'] = $context->getOutput(); - $tmpGlobals['wgParser'] = new StubObject( 'wgParser', $GLOBALS['wgParserConf']['class'], array( $GLOBALS['wgParserConf'] ) ); - $tmpGlobals['wgRequest'] = $context->getRequest(); + $tmpGlobals['wgParser'] = new StubObject( + 'wgParser', $GLOBALS['wgParserConf']['class'], + array( $GLOBALS['wgParserConf'] ) ); + + $tmpGlobals['wgFileExtensions'][] = 'svg'; + $tmpGlobals['wgSVGConverter'] = 'rsvg'; + $tmpGlobals['wgSVGConverters']['rsvg'] = + '$path/rsvg-convert -w $width -h $height $input -o $output'; if ( $GLOBALS['wgStyleDirectory'] === false ) { $tmpGlobals['wgStyleDirectory'] = "$IP/skins"; } + # Replace all media handlers with a mock. We do not need to generate + # actual thumbnails to do parser testing, we only care about receiving + # a ThumbnailImage properly initialized. + global $wgMediaHandlers; + foreach ( $wgMediaHandlers as $type => $handler ) { + $tmpGlobals['wgMediaHandlers'][$type] = 'MockBitmapHandler'; + } + // Vector images have to be handled slightly differently + $tmpGlobals['wgMediaHandlers']['image/svg+xml'] = 'MockSvgHandler'; - foreach ( $tmpGlobals as $var => $val ) { - if ( array_key_exists( $var, $GLOBALS ) ) { - $this->savedInitialGlobals[$var] = $GLOBALS[$var]; - } + $tmpHooks = $wgHooks; + $tmpHooks['ParserTestParser'][] = 'ParserTestParserHook::setup'; + $tmpHooks['ParserGetVariableValueTs'][] = 'ParserTest::getFakeTimestamp'; + $tmpGlobals['wgHooks'] = $tmpHooks; + # add a namespace shadowing a interwiki link, to test + # proper precedence when resolving links. (bug 51680) + $tmpGlobals['wgExtraNamespaces'] = array( 100 => 'MemoryAlpha' ); - $GLOBALS[$var] = $val; - } + $this->setMwGlobals( $tmpGlobals ); - $this->savedWeirdGlobals['mw_namespace_protection'] = $wgNamespaceProtection[NS_MEDIAWIKI]; $this->savedWeirdGlobals['image_alias'] = $wgNamespaceAliases['Image']; $this->savedWeirdGlobals['image_talk_alias'] = $wgNamespaceAliases['Image_talk']; - $wgNamespaceProtection[NS_MEDIAWIKI] = 'editinterface'; $wgNamespaceAliases['Image'] = NS_FILE; $wgNamespaceAliases['Image_talk'] = NS_FILE_TALK; - } - public function tearDown() { - foreach ( $this->savedInitialGlobals as $var => $val ) { - $GLOBALS[$var] = $val; - } + MWNamespace::getCanonicalNamespaces( true ); # reset namespace cache + $wgContLang->resetNamespaces(); # reset namespace cache + } - global $wgNamespaceProtection, $wgNamespaceAliases; + protected function tearDown() { + global $wgNamespaceAliases, $wgContLang; - $wgNamespaceProtection[NS_MEDIAWIKI] = $this->savedWeirdGlobals['mw_namespace_protection']; $wgNamespaceAliases['Image'] = $this->savedWeirdGlobals['image_alias']; $wgNamespaceAliases['Image_talk'] = $this->savedWeirdGlobals['image_talk_alias']; // Restore backends RepoGroup::destroySingleton(); FileBackendGroup::destroySingleton(); + + // Remove temporary pages from the link cache + LinkCache::singleton()->clear(); + + // Restore message cache (temporary pages and $wgUseDatabaseMessages) + MessageCache::destroyInstance(); + + parent::tearDown(); + + MWNamespace::getCanonicalNamespaces( true ); # reset namespace cache + $wgContLang->resetNamespaces(); # reset namespace cache + } + + public static function tearDownAfterClass() { + ParserTest::tearDownInterwikis(); + parent::tearDownAfterClass(); } function addDBData() { $this->tablesUsed[] = 'site_stats'; - $this->tablesUsed[] = 'interwiki'; # disabled for performance #$this->tablesUsed[] = 'image'; - # Hack: insert a few Wikipedia in-project interwiki prefixes, - # for testing inter-language links - $this->db->insert( 'interwiki', array( - array( 'iw_prefix' => 'wikipedia', - 'iw_url' => 'http://en.wikipedia.org/wiki/$1', - 'iw_api' => '', - 'iw_wikiid' => '', - 'iw_local' => 0 ), - array( 'iw_prefix' => 'meatball', - 'iw_url' => 'http://www.usemod.com/cgi-bin/mb.pl?$1', - 'iw_api' => '', - 'iw_wikiid' => '', - 'iw_local' => 0 ), - array( 'iw_prefix' => 'zh', - 'iw_url' => 'http://zh.wikipedia.org/wiki/$1', - 'iw_api' => '', - 'iw_wikiid' => '', - 'iw_local' => 1 ), - array( 'iw_prefix' => 'es', - 'iw_url' => 'http://es.wikipedia.org/wiki/$1', - 'iw_api' => '', - 'iw_wikiid' => '', - 'iw_local' => 1 ), - array( 'iw_prefix' => 'fr', - 'iw_url' => 'http://fr.wikipedia.org/wiki/$1', - 'iw_api' => '', - 'iw_wikiid' => '', - 'iw_local' => 1 ), - array( 'iw_prefix' => 'ru', - 'iw_url' => 'http://ru.wikipedia.org/wiki/$1', - 'iw_api' => '', - 'iw_wikiid' => '', - 'iw_local' => 1 ), - /** - * @todo Fixme! Why are we inserting duplicate data here? Shouldn't - * need this IGNORE or shouldn't need the insert at all. - */ - ), __METHOD__, array( 'IGNORE' ) - ); - - # Update certain things in site_stats $this->db->insert( 'site_stats', array( 'ss_row_id' => 1, 'ss_images' => 2, 'ss_good_articles' => 1 ), __METHOD__ ); - # Reinitialise the LocalisationCache to match the database state - Language::getLocalisationCache()->unloadAll(); - - # Clear the message cache - MessageCache::singleton()->clear(); - $user = User::newFromId( 0 ); LinkCache::singleton()->clear(); # Avoids the odd failure at creating the nullRevision @@ -182,6 +195,10 @@ class NewParserTest extends MediaWikiTestCase { # We will upload the actual files later. Note that if anything causes LocalFile::load() # to be triggered before then, it will break via maybeUpgrade() setting the fileExists # member to false and storing it in cache. + # note that the size/width/height/bits/etc of the file + # are actually set by inspecting the file itself; the arguments + # to recordUpload2 have no effect. That said, we try to make things + # match up so it is less confusing to readers of the code & tests. $image = wfLocalFile( Title::makeTitle( NS_FILE, 'Foobar.jpg' ) ); if ( !$this->db->selectField( 'image', '1', array( 'img_name' => $image->getName() ) ) ) { $image->recordUpload2( @@ -189,19 +206,39 @@ class NewParserTest extends MediaWikiTestCase { 'Upload of some lame file', 'Some lame file', array( - 'size' => 12345, - 'width' => 1941, - 'height' => 220, - 'bits' => 24, - 'media_type' => MEDIATYPE_BITMAP, - 'mime' => 'image/jpeg', - 'metadata' => serialize( array() ), - 'sha1' => wfBaseConvert( '', 16, 36, 31 ), - 'fileExists' => true ), + 'size' => 7881, + 'width' => 1941, + 'height' => 220, + 'bits' => 8, + 'media_type' => MEDIATYPE_BITMAP, + 'mime' => 'image/jpeg', + 'metadata' => serialize( array() ), + 'sha1' => wfBaseConvert( '1', 16, 36, 31 ), + 'fileExists' => true ), $this->db->timestamp( '20010115123500' ), $user ); } + $image = wfLocalFile( Title::makeTitle( NS_FILE, 'Thumb.png' ) ); + if ( !$this->db->selectField( 'image', '1', array( 'img_name' => $image->getName() ) ) ) { + $image->recordUpload2( + '', // archive name + 'Upload of some lame thumbnail', + 'Some lame thumbnail', + array( + 'size' => 22589, + 'width' => 135, + 'height' => 135, + 'bits' => 8, + 'media_type' => MEDIATYPE_BITMAP, + 'mime' => 'image/png', + 'metadata' => serialize( array() ), + 'sha1' => wfBaseConvert( '2', 16, 36, 31 ), + 'fileExists' => true ), + $this->db->timestamp( '20130225203040' ), $user + ); + } + # This image will be blacklisted in [[MediaWiki:Bad image list]] $image = wfLocalFile( Title::makeTitle( NS_FILE, 'Bad.jpg' ) ); if ( !$this->db->selectField( 'image', '1', array( 'img_name' => $image->getName() ) ) ) { @@ -210,30 +247,41 @@ class NewParserTest extends MediaWikiTestCase { 'zomgnotcensored', 'Borderline image', array( + 'size' => 12345, + 'width' => 320, + 'height' => 240, + 'bits' => 24, + 'media_type' => MEDIATYPE_BITMAP, + 'mime' => 'image/jpeg', + 'metadata' => serialize( array() ), + 'sha1' => wfBaseConvert( '3', 16, 36, 31 ), + 'fileExists' => true ), + $this->db->timestamp( '20010115123500' ), $user + ); + } + $image = wfLocalFile( Title::makeTitle( NS_FILE, 'Foobar.svg' ) ); + if ( !$this->db->selectField( 'image', '1', array( 'img_name' => $image->getName() ) ) ) { + $image->recordUpload2( '', 'Upload of some lame SVG', 'Some lame SVG', array( 'size' => 12345, - 'width' => 320, - 'height' => 240, + 'width' => 200, + 'height' => 200, 'bits' => 24, - 'media_type' => MEDIATYPE_BITMAP, - 'mime' => 'image/jpeg', + 'media_type' => MEDIATYPE_DRAWING, + 'mime' => 'image/svg+xml', 'metadata' => serialize( array() ), 'sha1' => wfBaseConvert( '', 16, 36, 31 ), - 'fileExists' => true ), - $this->db->timestamp( '20010115123500' ), $user - ); + 'fileExists' => true + ), $this->db->timestamp( '20010115123500' ), $user ); } } - - - //ParserTest setup/teardown functions /** * Set up the global variables for a consistent environment for each test. * Ideally this should replace the global configuration entirely. */ - protected function setupGlobals( $opts = '', $config = '' ) { + protected function setupGlobals( $opts = array(), $config = '' ) { global $wgFileBackends; # Find out values for some special options. $lang = @@ -263,73 +311,39 @@ class NewParserTest extends MediaWikiTestCase { $backend = self::$backendToUse; } } else { - $backend = new FSFileBackend( array( - 'name' => 'local-backend', + # Replace with a mock. We do not care about generating real + # files on the filesystem, just need to expose the file + # informations. + $backend = new MockFileBackend( array( + 'name' => 'local-backend', 'lockManager' => 'nullLockManager', 'containerPaths' => array( 'local-public' => "$uploadDir", - 'local-thumb' => "$uploadDir/thumb", + 'local-thumb' => "$uploadDir/thumb", ) ) ); } $settings = array( - 'wgServer' => 'http://Britney-Spears', - 'wgScript' => '/index.php', - 'wgScriptPath' => '/', - 'wgArticlePath' => '/wiki/$1', - 'wgExtensionAssetsPath' => '/extensions', - 'wgActionPaths' => array(), 'wgLocalFileRepo' => array( - 'class' => 'LocalRepo', - 'name' => 'local', - 'url' => 'http://example.com/images', - 'hashLevels' => 2, + 'class' => 'LocalRepo', + 'name' => 'local', + 'url' => 'http://example.com/images', + 'hashLevels' => 2, 'transformVia404' => false, - 'backend' => $backend + 'backend' => $backend ), 'wgEnableUploads' => self::getOptionValue( 'wgEnableUploads', $opts, true ), - 'wgStylePath' => '/skins', - 'wgStyleSheetPath' => '/skins', - 'wgSitename' => 'MediaWiki', 'wgLanguageCode' => $lang, 'wgDBprefix' => $this->db->getType() != 'oracle' ? 'unittest_' : 'ut_', - 'wgRawHtml' => isset( $opts['rawhtml'] ), - 'wgLang' => null, - 'wgContLang' => null, - 'wgNamespacesWithSubpages' => array( 0 => isset( $opts['subpage'] ) ), + 'wgRawHtml' => self::getOptionValue( 'wgRawHtml', $opts, false ), + 'wgNamespacesWithSubpages' => array( NS_MAIN => isset( $opts['subpage'] ) ), + 'wgAllowExternalImages' => self::getOptionValue( 'wgAllowExternalImages', $opts, true ), 'wgMaxTocLevel' => $maxtoclevel, - 'wgCapitalLinks' => true, - 'wgNoFollowLinks' => true, - 'wgNoFollowDomainExceptions' => array(), - 'wgThumbnailScriptPath' => false, - 'wgUseImageResize' => false, - 'wgUseTeX' => isset( $opts['math'] ), + 'wgUseTeX' => isset( $opts['math'] ) || isset( $opts['texvc'] ), 'wgMathDirectory' => $uploadDir . '/math', - 'wgLocaltimezone' => 'UTC', - 'wgAllowExternalImages' => true, - 'wgUseTidy' => false, 'wgDefaultLanguageVariant' => $variant, - 'wgVariantArticlePath' => false, - 'wgGroupPermissions' => array( '*' => array( - 'createaccount' => true, - 'read' => true, - 'edit' => true, - 'createpage' => true, - 'createtalk' => true, - ) ), - 'wgNamespaceProtection' => array( NS_MEDIAWIKI => 'editinterface' ), - 'wgDefaultExternalStore' => array(), - 'wgForeignFileRepos' => array(), 'wgLinkHolderBatchSize' => $linkHolderBatchSize, - 'wgExperimentalHtmlIds' => false, - 'wgExternalLinkTarget' => false, - 'wgAlwaysUseTidy' => false, - 'wgHtml5' => true, - 'wgWellFormedXml' => true, - 'wgAllowMicrodataAttributes' => true, - 'wgAdaptiveMessageCache' => true, - 'wgUseDatabaseMessages' => true, ); if ( $config ) { @@ -347,6 +361,15 @@ class NewParserTest extends MediaWikiTestCase { /** @since 1.20 */ wfRunHooks( 'ParserTestGlobals', array( &$settings ) ); + $langObj = Language::factory( $lang ); + $settings['wgContLang'] = $langObj; + $settings['wgLang'] = $langObj; + + $context = new RequestContext(); + $settings['wgOut'] = $context->getOutput(); + $settings['wgUser'] = $context->getUser(); + $settings['wgRequest'] = $context->getRequest(); + foreach ( $settings as $var => $val ) { if ( array_key_exists( $var, $GLOBALS ) ) { $this->savedGlobals[$var] = $GLOBALS[$var]; @@ -355,21 +378,9 @@ class NewParserTest extends MediaWikiTestCase { $GLOBALS[$var] = $val; } - $langObj = Language::factory( $lang ); - $GLOBALS['wgContLang'] = $langObj; - $context = new RequestContext(); - $GLOBALS['wgLang'] = $context->getLanguage(); - - $GLOBALS['wgMemc'] = new EmptyBagOStuff; - $GLOBALS['wgOut'] = $context->getOutput(); - $GLOBALS['wgUser'] = $context->getUser(); - - global $wgHooks; - - $wgHooks['ParserTestParser'][] = 'ParserTestParserHook::setup'; - $wgHooks['ParserGetVariableValueTs'][] = 'ParserTest::getFakeTimestamp'; - MagicWord::clearCache(); + + # The entries saved into RepoGroup cache with previous globals will be wrong. RepoGroup::destroySingleton(); FileBackendGroup::destroySingleton(); @@ -379,9 +390,6 @@ class NewParserTest extends MediaWikiTestCase { # Publish the articles after we have the final language set $this->publishTestArticles(); - # The entries saved into RepoGroup cache with previous globals will be wrong. - RepoGroup::destroySingleton(); - FileBackendGroup::destroySingleton(); MessageCache::destroyInstance(); return $context; @@ -406,6 +414,7 @@ class NewParserTest extends MediaWikiTestCase { // wfDebug( "Creating upload directory $dir\n" ); if ( file_exists( $dir ) ) { wfDebug( "Already exists!\n" ); + return $dir; } @@ -427,10 +436,27 @@ class NewParserTest extends MediaWikiTestCase { $backend->store( array( 'src' => "$IP/skins/monobook/headbg.jpg", 'dst' => "$base/local-public/3/3a/Foobar.jpg" ) ); + $backend->prepare( array( 'dir' => "$base/local-public/e/ea" ) ); + $backend->store( array( + 'src' => "$IP/skins/monobook/wiki.png", 'dst' => "$base/local-public/e/ea/Thumb.png" + ) ); $backend->prepare( array( 'dir' => "$base/local-public/0/09" ) ); $backend->store( array( 'src' => "$IP/skins/monobook/headbg.jpg", 'dst' => "$base/local-public/0/09/Bad.jpg" ) ); + + // No helpful SVG file to copy, so make one ourselves + $tmpDir = wfTempDir(); + $tempFsFile = new TempFSFile( "$tmpDir/Foobar.svg" ); + $tempFsFile->autocollect(); // destroy file when $tempFsFile leaves scope + file_put_contents( "$tmpDir/Foobar.svg", + '<?xml version="1.0" encoding="utf-8"?>' . + '<svg xmlns="http://www.w3.org/2000/svg" width="200" height="200"><text>Foo</text></svg>' ); + + $backend->prepare( array( 'dir' => "$base/local-public/f/ff" ) ); + $backend->quickStore( array( + 'src' => "$tmpDir/Foobar.svg", 'dst' => "$base/local-public/f/ff/Foobar.svg" + ) ); } /** @@ -443,9 +469,6 @@ class NewParserTest extends MediaWikiTestCase { foreach ( $this->savedGlobals as $var => $val ) { $GLOBALS[$var] = $val; } - - RepoGroup::destroySingleton(); - LinkCache::singleton()->clear(); } /** @@ -456,18 +479,43 @@ class NewParserTest extends MediaWikiTestCase { return; } + $backend = RepoGroup::singleton()->getLocalRepo()->getBackend(); + if ( $backend instanceof MockFileBackend ) { + # In memory backend, so dont bother cleaning them up. + return; + } + $base = $this->getBaseDir(); // delete the files first, then the dirs. self::deleteFiles( - array ( + array( "$base/local-public/3/3a/Foobar.jpg", "$base/local-thumb/3/3a/Foobar.jpg/180px-Foobar.jpg", "$base/local-thumb/3/3a/Foobar.jpg/200px-Foobar.jpg", "$base/local-thumb/3/3a/Foobar.jpg/640px-Foobar.jpg", "$base/local-thumb/3/3a/Foobar.jpg/120px-Foobar.jpg", + "$base/local-thumb/3/3a/Foobar.jpg/1280px-Foobar.jpg", + "$base/local-thumb/3/3a/Foobar.jpg/20px-Foobar.jpg", + "$base/local-thumb/3/3a/Foobar.jpg/270px-Foobar.jpg", + "$base/local-thumb/3/3a/Foobar.jpg/300px-Foobar.jpg", + "$base/local-thumb/3/3a/Foobar.jpg/30px-Foobar.jpg", + "$base/local-thumb/3/3a/Foobar.jpg/360px-Foobar.jpg", + "$base/local-thumb/3/3a/Foobar.jpg/400px-Foobar.jpg", + "$base/local-thumb/3/3a/Foobar.jpg/40px-Foobar.jpg", + "$base/local-thumb/3/3a/Foobar.jpg/70px-Foobar.jpg", + "$base/local-thumb/3/3a/Foobar.jpg/960px-Foobar.jpg", + + "$base/local-public/e/ea/Thumb.png", "$base/local-public/0/09/Bad.jpg", - "$base/local-thumb/0/09/Bad.jpg", + + "$base/local-public/f/ff/Foobar.svg", + "$base/local-thumb/f/ff/Foobar.svg/180px-Foobar.svg.jpg", + "$base/local-thumb/f/ff/Foobar.svg/270px-Foobar.svg.jpg", + "$base/local-thumb/f/ff/Foobar.svg/360px-Foobar.svg.jpg", + "$base/local-thumb/f/ff/Foobar.svg/langde-180px-Foobar.svg.jpg", + "$base/local-thumb/f/ff/Foobar.svg/langde-270px-Foobar.svg.jpg", + "$base/local-thumb/f/ff/Foobar.svg/langde-360px-Foobar.svg.jpg", "$base/local-public/math/f/a/5/fa50b8b616463173474302ca3e63586b.png", ) @@ -502,6 +550,7 @@ class NewParserTest extends MediaWikiTestCase { global $wgParserTestFiles; $this->file = $wgParserTestFiles[0]; } + return new TestFileIterator( $this->file, $this ); } @@ -523,6 +572,14 @@ class NewParserTest extends MediaWikiTestCase { return; } + if ( !$this->isWikitextNS( NS_MAIN ) ) { + // parser tests frequently assume that the main namespace contains wikitext. + // @todo When setting up pages, force the content model. Only skip if + // $wgtContentModelUseDB is false. + $this->markTestSkipped( "Main namespace does not support wikitext," + . "skipping parser test: $desc" ); + } + wfDebug( "Running parser test: $desc\n" ); $opts = $this->parseOptions( $opts ); @@ -533,8 +590,7 @@ class NewParserTest extends MediaWikiTestCase { if ( isset( $opts['title'] ) ) { $titleText = $opts['title']; - } - else { + } else { $titleText = 'Parser test'; } @@ -544,6 +600,20 @@ class NewParserTest extends MediaWikiTestCase { $title = Title::newFromText( $titleText ); + # Parser test requiring math. Make sure texvc is executable + # or just skip such tests. + if ( isset( $opts['math'] ) || isset( $opts['texvc'] ) ) { + global $wgTexvc; + + if ( !isset( $wgTexvc ) ) { + $this->markTestSkipped( "SKIPPED: \$wgTexvc is not set" ); + } elseif ( !is_executable( $wgTexvc ) ) { + $this->markTestSkipped( "SKIPPED: texvc binary does not exist" + . " or is not executable.\n" + . "Current configuration is:\n\$wgTexvc = '$wgTexvc'" ); + } + } + if ( isset( $opts['pst'] ) ) { $out = $parser->preSaveTransform( $input, $title, $user, $options ); } elseif ( isset( $opts['msg'] ) ) { @@ -558,9 +628,10 @@ class NewParserTest extends MediaWikiTestCase { } elseif ( isset( $opts['comment'] ) ) { $out = Linker::formatComment( $input, $title, $local ); } elseif ( isset( $opts['preload'] ) ) { - $out = $parser->getpreloadText( $input, $title, $options ); + $out = $parser->getPreloadText( $input, $title, $options ); } else { $output = $parser->parse( $input, $title, $options, true, true, 1337 ); + $output->setTOCEnabled( !isset( $opts['notoc'] ) ); $out = $output->getText(); if ( isset( $opts['showtitle'] ) ) { @@ -602,12 +673,12 @@ class NewParserTest extends MediaWikiTestCase { * * @group ParserFuzz */ - function testFuzzTests() { + public function testFuzzTests() { global $wgParserTestFiles; $files = $wgParserTestFiles; - if( $this->getCliArg( 'file=' ) ) { + if ( $this->getCliArg( 'file=' ) ) { $files = array( $this->getCliArg( 'file=' ) ); } @@ -647,7 +718,9 @@ class NewParserTest extends MediaWikiTestCase { } catch ( Exception $exception ) { $input_dump = sprintf( "string(%d) \"%s\"\n", strlen( $input ), $input ); - $this->assertTrue( false, "Test $id, fuzz seed {$this->fuzzSeed}. \n\nInput: $input_dump\n\nError: {$exception->getMessage()}\n\nBacktrace: {$exception->getTraceAsString()}" ); + $this->assertTrue( false, "Test $id, fuzz seed {$this->fuzzSeed}. \n\n" . + "Input: $input_dump\n\nError: {$exception->getMessage()}\n\n" . + "Backtrace: {$exception->getTraceAsString()}" ); } $this->teardownGlobals(); @@ -669,7 +742,6 @@ class NewParserTest extends MediaWikiTestCase { } $id++; - } } @@ -769,15 +841,16 @@ class NewParserTest extends MediaWikiTestCase { */ public function requireHook( $name ) { global $wgParser; - $wgParser->firstCallInit( ); // make sure hooks are loaded. + $wgParser->firstCallInit(); // make sure hooks are loaded. return isset( $wgParser->mTagHooks[$name] ); } public function requireFunctionHook( $name ) { global $wgParser; - $wgParser->firstCallInit( ); // make sure hooks are loaded. + $wgParser->firstCallInit(); // make sure hooks are loaded. return isset( $wgParser->mFunctionHooks[$name] ); } + //Various "cleanup" functions /** @@ -803,8 +876,7 @@ class NewParserTest extends MediaWikiTestCase { public function removeEndingNewline( $s ) { if ( substr( $s, -1 ) === "\n" ) { return substr( $s, 0, -1 ); - } - else { + } else { return $s; } } @@ -863,6 +935,7 @@ class NewParserTest extends MediaWikiTestCase { } } } + return $opts; } @@ -874,6 +947,7 @@ class NewParserTest extends MediaWikiTestCase { if ( substr( $opt, 0, 2 ) == '[[' ) { return substr( $opt, 2, -2 ); } + return $opt; } diff --git a/tests/phpunit/includes/parser/ParserMethodsTest.php b/tests/phpunit/includes/parser/ParserMethodsTest.php index dea406c3..e5c5cb21 100644 --- a/tests/phpunit/includes/parser/ParserMethodsTest.php +++ b/tests/phpunit/includes/parser/ParserMethodsTest.php @@ -2,19 +2,20 @@ class ParserMethodsTest extends MediaWikiLangTestCase { - public function dataPreSaveTransform() { + public static function providePreSaveTransform() { return array( array( 'hello this is ~~~', - "hello this is [[Special:Contributions/127.0.0.1|127.0.0.1]]", + "hello this is [[Special:Contributions/127.0.0.1|127.0.0.1]]", ), array( 'hello \'\'this\'\' is <nowiki>~~~</nowiki>', - 'hello \'\'this\'\' is <nowiki>~~~</nowiki>', + 'hello \'\'this\'\' is <nowiki>~~~</nowiki>', ), ); } /** - * @dataProvider dataPreSaveTransform + * @dataProvider providePreSaveTransform + * @covers Parser::preSaveTransform */ public function testPreSaveTransform( $text, $expected ) { global $wgParser; @@ -28,6 +29,67 @@ class ParserMethodsTest extends MediaWikiLangTestCase { $this->assertEquals( $expected, $text ); } - // TODO: Add tests for cleanSig() / cleanSigInSig(), getSection(), replaceSection(), getPreloadText() -} + /** + * @covers Parser::callParserFunction + */ + public function testCallParserFunction() { + global $wgParser; + + // Normal parses test passing PPNodes. Test passing an array. + $title = Title::newFromText( str_replace( '::', '__', __METHOD__ ) ); + $wgParser->startExternalParse( $title, new ParserOptions(), Parser::OT_HTML ); + $frame = $wgParser->getPreprocessor()->newFrame(); + $ret = $wgParser->callParserFunction( $frame, '#tag', + array( 'pre', 'foo', 'style' => 'margin-left: 1.6em' ) + ); + $ret['text'] = $wgParser->mStripState->unstripBoth( $ret['text'] ); + $this->assertSame( array( + 'found' => true, + 'text' => '<pre style="margin-left: 1.6em">foo</pre>', + ), $ret, 'callParserFunction works for {{#tag:pre|foo|style=margin-left: 1.6em}}' ); + } + + /** + * @covers Parser::parse + * @covers ParserOutput::getSections + */ + public function testGetSections() { + global $wgParser; + $title = Title::newFromText( str_replace( '::', '__', __METHOD__ ) ); + $out = $wgParser->parse( "==foo==\n<h2>bar</h2>\n==baz==\n", $title, new ParserOptions() ); + $this->assertSame( array( + array( + 'toclevel' => 1, + 'level' => '2', + 'line' => 'foo', + 'number' => '1', + 'index' => '1', + 'fromtitle' => $title->getPrefixedDBkey(), + 'byteoffset' => 0, + 'anchor' => 'foo', + ), + array( + 'toclevel' => 1, + 'level' => '2', + 'line' => 'bar', + 'number' => '2', + 'index' => '', + 'fromtitle' => false, + 'byteoffset' => null, + 'anchor' => 'bar', + ), + array( + 'toclevel' => 1, + 'level' => '2', + 'line' => 'baz', + 'number' => '3', + 'index' => '2', + 'fromtitle' => $title->getPrefixedDBkey(), + 'byteoffset' => 21, + 'anchor' => 'baz', + ), + ), $out->getSections(), 'getSections() with proper value when <h2> is used' ); + } + //@Todo Add tests for cleanSig() / cleanSigInSig(), getSection(), replaceSection(), getPreloadText() +} diff --git a/tests/phpunit/includes/parser/ParserOutputTest.php b/tests/phpunit/includes/parser/ParserOutputTest.php new file mode 100644 index 00000000..c73666da --- /dev/null +++ b/tests/phpunit/includes/parser/ParserOutputTest.php @@ -0,0 +1,59 @@ +<?php + +class ParserOutputTest extends MediaWikiTestCase { + + public static function provideIsLinkInternal() { + return array( + // Different domains + array( false, 'http://example.org', 'http://mediawiki.org' ), + // Same domains + array( true, 'http://example.org', 'http://example.org' ), + array( true, 'https://example.org', 'https://example.org' ), + array( true, '//example.org', '//example.org' ), + // Same domain different cases + array( true, 'http://example.org', 'http://EXAMPLE.ORG' ), + // Paths, queries, and fragments are not relevant + array( true, 'http://example.org', 'http://example.org/wiki/Main_Page' ), + array( true, 'http://example.org', 'http://example.org?my=query' ), + array( true, 'http://example.org', 'http://example.org#its-a-fragment' ), + // Different protocols + array( false, 'http://example.org', 'https://example.org' ), + array( false, 'https://example.org', 'http://example.org' ), + // Protocol relative servers always match http and https links + array( true, '//example.org', 'http://example.org' ), + array( true, '//example.org', 'https://example.org' ), + // But they don't match strange things like this + array( false, '//example.org', 'irc://example.org' ), + ); + } + + /** + * Test to make sure ParserOutput::isLinkInternal behaves properly + * @dataProvider provideIsLinkInternal + * @covers ParserOutput::isLinkInternal + */ + public function testIsLinkInternal( $shouldMatch, $server, $url ) { + $this->assertEquals( $shouldMatch, ParserOutput::isLinkInternal( $server, $url ) ); + } + + /** + * @covers ParserOutput::setExtensionData + * @covers ParserOutput::getExtensionData + */ + public function testExtensionData() { + $po = new ParserOutput(); + + $po->setExtensionData( "one", "Foo" ); + + $this->assertEquals( "Foo", $po->getExtensionData( "one" ) ); + $this->assertNull( $po->getExtensionData( "spam" ) ); + + $po->setExtensionData( "two", "Bar" ); + $this->assertEquals( "Foo", $po->getExtensionData( "one" ) ); + $this->assertEquals( "Bar", $po->getExtensionData( "two" ) ); + + $po->setExtensionData( "one", null ); + $this->assertNull( $po->getExtensionData( "one" ) ); + $this->assertEquals( "Bar", $po->getExtensionData( "two" ) ); + } +} diff --git a/tests/phpunit/includes/parser/ParserPreloadTest.php b/tests/phpunit/includes/parser/ParserPreloadTest.php index 0e8ef530..d12fee36 100644 --- a/tests/phpunit/includes/parser/ParserPreloadTest.php +++ b/tests/phpunit/includes/parser/ParserPreloadTest.php @@ -4,12 +4,24 @@ * @author Antoine Musso */ class ParserPreloadTest extends MediaWikiTestCase { + /** + * @var Parser + */ private $testParser; + /** + * @var ParserOptions + */ private $testParserOptions; + /** + * @var Title + */ private $title; - function setUp() { - $this->testParserOptions = new ParserOptions(); + protected function setUp() { + global $wgContLang; + + parent::setUp(); + $this->testParserOptions = ParserOptions::newFromUserAndLang( new User, $wgContLang ); $this->testParser = new Parser(); $this->testParser->Options( $this->testParserOptions ); @@ -18,7 +30,9 @@ class ParserPreloadTest extends MediaWikiTestCase { $this->title = Title::newFromText( 'Preload Test' ); } - function tearDown() { + protected function tearDown() { + parent::tearDown(); + unset( $this->testParser ); unset( $this->title ); } @@ -26,14 +40,14 @@ class ParserPreloadTest extends MediaWikiTestCase { /** * @covers Parser::getPreloadText */ - function testPreloadSimpleText() { + public function testPreloadSimpleText() { $this->assertPreloaded( 'simple', 'simple' ); } /** * @covers Parser::getPreloadText */ - function testPreloadedPreIsUnstripped() { + public function testPreloadedPreIsUnstripped() { $this->assertPreloaded( '<pre>monospaced</pre>', '<pre>monospaced</pre>', @@ -44,7 +58,7 @@ class ParserPreloadTest extends MediaWikiTestCase { /** * @covers Parser::getPreloadText */ - function testPreloadedNowikiIsUnstripped() { + public function testPreloadedNowikiIsUnstripped() { $this->assertPreloaded( '<nowiki>[[Dummy title]]</nowiki>', '<nowiki>[[Dummy title]]</nowiki>', @@ -52,7 +66,7 @@ class ParserPreloadTest extends MediaWikiTestCase { ); } - function assertPreloaded( $expected, $text, $msg='') { + protected function assertPreloaded( $expected, $text, $msg = '' ) { $this->assertEquals( $expected, $this->testParser->getPreloadText( @@ -63,5 +77,4 @@ class ParserPreloadTest extends MediaWikiTestCase { $msg ); } - } diff --git a/tests/phpunit/includes/parser/PreprocessorTest.php b/tests/phpunit/includes/parser/PreprocessorTest.php index fee56748..8aee937c 100644 --- a/tests/phpunit/includes/parser/PreprocessorTest.php +++ b/tests/phpunit/includes/parser/PreprocessorTest.php @@ -1,13 +1,21 @@ <?php class PreprocessorTest extends MediaWikiTestCase { - var $mTitle = 'Page title'; - var $mPPNodeCount = 0; - var $mOptions; + protected $mTitle = 'Page title'; + protected $mPPNodeCount = 0; + /** + * @var ParserOptions + */ + protected $mOptions; + /** + * @var Preprocessor + */ + protected $mPreprocessor; - function setUp() { - global $wgParserConf; - $this->mOptions = new ParserOptions(); + protected function setUp() { + global $wgParserConf, $wgContLang; + parent::setUp(); + $this->mOptions = ParserOptions::newFromUserAndLang( new User, $wgContLang ); $name = isset( $wgParserConf['preprocessorClass'] ) ? $wgParserConf['preprocessorClass'] : 'Preprocessor_DOM'; $this->mPreprocessor = new $name( $this ); @@ -17,7 +25,7 @@ class PreprocessorTest extends MediaWikiTestCase { return array( 'gallery', 'display map' /* Used by Maps, see r80025 CR */, '/foo' ); } - function provideCases() { + public static function provideCases() { return array( array( "Foo", "<root>Foo</root>" ), array( "<!-- Foo -->", "<root><comment><!-- Foo --></comment></root>" ), @@ -51,16 +59,16 @@ class PreprocessorTest extends MediaWikiTestCase { array( "Foo\n=\n==\n=\n", "<root>Foo\n=\n==\n=\n</root>" ), array( "{{Foo}}", "<root><template><title>Foo</title></template></root>" ), array( "\n{{Foo}}", "<root>\n<template lineStart=\"1\"><title>Foo</title></template></root>" ), - array( "{{Foo|bar}}", "<root><template><title>Foo</title><part><name index=\"1\" /><value>bar</value></part></template></root>" ), - array( "{{Foo|bar}}a", "<root><template><title>Foo</title><part><name index=\"1\" /><value>bar</value></part></template>a</root>" ), - array( "{{Foo|bar|baz}}", "<root><template><title>Foo</title><part><name index=\"1\" /><value>bar</value></part><part><name index=\"2\" /><value>baz</value></part></template></root>" ), + array( "{{Foo|bar}}", "<root><template><title>Foo</title><part><name index=\"1\" /><value>bar</value></part></template></root>" ), + array( "{{Foo|bar}}a", "<root><template><title>Foo</title><part><name index=\"1\" /><value>bar</value></part></template>a</root>" ), + array( "{{Foo|bar|baz}}", "<root><template><title>Foo</title><part><name index=\"1\" /><value>bar</value></part><part><name index=\"2\" /><value>baz</value></part></template></root>" ), array( "{{Foo|1=bar}}", "<root><template><title>Foo</title><part><name>1</name>=<value>bar</value></part></template></root>" ), array( "{{Foo|=bar}}", "<root><template><title>Foo</title><part><name></name>=<value>bar</value></part></template></root>" ), - array( "{{Foo|bar=baz}}", "<root><template><title>Foo</title><part><name>bar</name>=<value>baz</value></part></template></root>" ), + array( "{{Foo|bar=baz}}", "<root><template><title>Foo</title><part><name>bar</name>=<value>baz</value></part></template></root>" ), array( "{{Foo|{{bar}}=baz}}", "<root><template><title>Foo</title><part><name><template><title>bar</title></template></name>=<value>baz</value></part></template></root>" ), - array( "{{Foo|1=bar|baz}}", "<root><template><title>Foo</title><part><name>1</name>=<value>bar</value></part><part><name index=\"1\" /><value>baz</value></part></template></root>" ), + array( "{{Foo|1=bar|baz}}", "<root><template><title>Foo</title><part><name>1</name>=<value>bar</value></part><part><name index=\"1\" /><value>baz</value></part></template></root>" ), array( "{{Foo|1=bar|2=baz}}", "<root><template><title>Foo</title><part><name>1</name>=<value>bar</value></part><part><name>2</name>=<value>baz</value></part></template></root>" ), - array( "{{Foo|bar|foo=baz}}", "<root><template><title>Foo</title><part><name index=\"1\" /><value>bar</value></part><part><name>foo</name>=<value>baz</value></part></template></root>" ), + array( "{{Foo|bar|foo=baz}}", "<root><template><title>Foo</title><part><name index=\"1\" /><value>bar</value></part><part><name>foo</name>=<value>baz</value></part></template></root>" ), array( "{{{1}}}", "<root><tplarg><title>1</title></tplarg></root>" ), array( "{{{1|}}}", "<root><tplarg><title>1</title><part><name index=\"1\" /><value></value></part></tplarg></root>" ), array( "{{{Foo}}}", "<root><tplarg><title>Foo</title></tplarg></root>" ), @@ -83,26 +91,26 @@ class PreprocessorTest extends MediaWikiTestCase { array( "Foo <gallery bar=\"baz\" />", "<root>Foo <ext><name>gallery</name><attr> bar="baz" </attr></ext></root>" ), array( "Foo <gallery bar=\"1\" baz=2 />", "<root>Foo <ext><name>gallery</name><attr> bar="1" baz=2 </attr></ext></root>" ), array( "</foo>Foo<//foo>", "<root><ext><name>/foo</name><attr></attr><inner>Foo</inner><close><//foo></close></ext></root>" ), # Worth blacklisting IMHO - array( "{{#ifexpr: ({{{1|1}}} = 2) | Foo | Bar }}", "<root><template><title>#ifexpr: (<tplarg><title>1</title><part><name index=\"1\" /><value>1</value></part></tplarg> = 2) </title><part><name index=\"1\" /><value> Foo </value></part><part><name index=\"2\" /><value> Bar </value></part></template></root>"), - array( "{{#if: {{{1|}}} | Foo | {{Bar}} }}", "<root><template><title>#if: <tplarg><title>1</title><part><name index=\"1\" /><value></value></part></tplarg> </title><part><name index=\"1\" /><value> Foo </value></part><part><name index=\"2\" /><value> <template><title>Bar</title></template> </value></part></template></root>"), - array( "{{#if: {{{1|}}} | Foo | [[Bar]] }}", "<root><template><title>#if: <tplarg><title>1</title><part><name index=\"1\" /><value></value></part></tplarg> </title><part><name index=\"1\" /><value> Foo </value></part><part><name index=\"2\" /><value> [[Bar]] </value></part></template></root>"), - array( "{{#if: {{{1|}}} | [[Foo]] | Bar }}", "<root><template><title>#if: <tplarg><title>1</title><part><name index=\"1\" /><value></value></part></tplarg> </title><part><name index=\"1\" /><value> [[Foo]] </value></part><part><name index=\"2\" /><value> Bar </value></part></template></root>"), - array( "{{#if: {{{1|}}} | 1 | {{#if: {{{1|}}} | 2 | 3 }} }}", "<root><template><title>#if: <tplarg><title>1</title><part><name index=\"1\" /><value></value></part></tplarg> </title><part><name index=\"1\" /><value> 1 </value></part><part><name index=\"2\" /><value> <template><title>#if: <tplarg><title>1</title><part><name index=\"1\" /><value></value></part></tplarg> </title><part><name index=\"1\" /><value> 2 </value></part><part><name index=\"2\" /><value> 3 </value></part></template> </value></part></template></root>"), - array( "{{ {{Foo}}", "<root>{{ <template><title>Foo</title></template></root>"), - array( "{{Foobar {{Foo}} {{Bar}} {{Baz}} ", "<root>{{Foobar <template><title>Foo</title></template> <template><title>Bar</title></template> <template><title>Baz</title></template> </root>"), - array( "[[Foo]] |", "<root>[[Foo]] |</root>"), - array( "{{Foo|Bar|", "<root>{{Foo|Bar|</root>"), - array( "[[Foo]", "<root>[[Foo]</root>"), - array( "[[Foo|Bar]", "<root>[[Foo|Bar]</root>"), - array( "{{Foo| [[Bar] }}", "<root>{{Foo| [[Bar] }}</root>"), - array( "{{Foo| [[Bar|Baz] }}", "<root>{{Foo| [[Bar|Baz] }}</root>"), - array( "{{Foo|bar=[[baz]}}", "<root>{{Foo|bar=[[baz]}}</root>"), - array( "{{foo|", "<root>{{foo|</root>"), - array( "{{foo|}", "<root>{{foo|}</root>"), - array( "{{foo|} }}", "<root><template><title>foo</title><part><name index=\"1\" /><value>} </value></part></template></root>"), - array( "{{foo|bar=|}", "<root>{{foo|bar=|}</root>"), - array( "{{Foo|} Bar=", "<root>{{Foo|} Bar=</root>"), - array( "{{Foo|} Bar=}}", "<root><template><title>Foo</title><part><name>} Bar</name>=<value></value></part></template></root>"), + array( "{{#ifexpr: ({{{1|1}}} = 2) | Foo | Bar }}", "<root><template><title>#ifexpr: (<tplarg><title>1</title><part><name index=\"1\" /><value>1</value></part></tplarg> = 2) </title><part><name index=\"1\" /><value> Foo </value></part><part><name index=\"2\" /><value> Bar </value></part></template></root>" ), + array( "{{#if: {{{1|}}} | Foo | {{Bar}} }}", "<root><template><title>#if: <tplarg><title>1</title><part><name index=\"1\" /><value></value></part></tplarg> </title><part><name index=\"1\" /><value> Foo </value></part><part><name index=\"2\" /><value> <template><title>Bar</title></template> </value></part></template></root>" ), + array( "{{#if: {{{1|}}} | Foo | [[Bar]] }}", "<root><template><title>#if: <tplarg><title>1</title><part><name index=\"1\" /><value></value></part></tplarg> </title><part><name index=\"1\" /><value> Foo </value></part><part><name index=\"2\" /><value> [[Bar]] </value></part></template></root>" ), + array( "{{#if: {{{1|}}} | [[Foo]] | Bar }}", "<root><template><title>#if: <tplarg><title>1</title><part><name index=\"1\" /><value></value></part></tplarg> </title><part><name index=\"1\" /><value> [[Foo]] </value></part><part><name index=\"2\" /><value> Bar </value></part></template></root>" ), + array( "{{#if: {{{1|}}} | 1 | {{#if: {{{1|}}} | 2 | 3 }} }}", "<root><template><title>#if: <tplarg><title>1</title><part><name index=\"1\" /><value></value></part></tplarg> </title><part><name index=\"1\" /><value> 1 </value></part><part><name index=\"2\" /><value> <template><title>#if: <tplarg><title>1</title><part><name index=\"1\" /><value></value></part></tplarg> </title><part><name index=\"1\" /><value> 2 </value></part><part><name index=\"2\" /><value> 3 </value></part></template> </value></part></template></root>" ), + array( "{{ {{Foo}}", "<root>{{ <template><title>Foo</title></template></root>" ), + array( "{{Foobar {{Foo}} {{Bar}} {{Baz}} ", "<root>{{Foobar <template><title>Foo</title></template> <template><title>Bar</title></template> <template><title>Baz</title></template> </root>" ), + array( "[[Foo]] |", "<root>[[Foo]] |</root>" ), + array( "{{Foo|Bar|", "<root>{{Foo|Bar|</root>" ), + array( "[[Foo]", "<root>[[Foo]</root>" ), + array( "[[Foo|Bar]", "<root>[[Foo|Bar]</root>" ), + array( "{{Foo| [[Bar] }}", "<root>{{Foo| [[Bar] }}</root>" ), + array( "{{Foo| [[Bar|Baz] }}", "<root>{{Foo| [[Bar|Baz] }}</root>" ), + array( "{{Foo|bar=[[baz]}}", "<root>{{Foo|bar=[[baz]}}</root>" ), + array( "{{foo|", "<root>{{foo|</root>" ), + array( "{{foo|}", "<root>{{foo|}</root>" ), + array( "{{foo|} }}", "<root><template><title>foo</title><part><name index=\"1\" /><value>} </value></part></template></root>" ), + array( "{{foo|bar=|}", "<root>{{foo|bar=|}</root>" ), + array( "{{Foo|} Bar=", "<root>{{Foo|} Bar=</root>" ), + array( "{{Foo|} Bar=}}", "<root><template><title>Foo</title><part><name>} Bar</name>=<value></value></part></template></root>" ), /* array( file_get_contents( __DIR__ . '/QuoteQuran.txt' ), file_get_contents( __DIR__ . '/QuoteQuranExpanded.txt' ) ), */ ); } @@ -114,11 +122,11 @@ class PreprocessorTest extends MediaWikiTestCase { * @param string $wikiText * @return string */ - function preprocessToXml( $wikiText ) { + protected function preprocessToXml( $wikiText ) { if ( method_exists( $this->mPreprocessor, 'preprocessToXml' ) ) { return $this->normalizeXml( $this->mPreprocessor->preprocessToXml( $wikiText ) ); } - + $dom = $this->mPreprocessor->preprocessToObj( $wikiText ); if ( is_callable( array( $dom, 'saveXML' ) ) ) { return $dom->saveXML(); @@ -133,38 +141,36 @@ class PreprocessorTest extends MediaWikiTestCase { * @param string $xml * @return string */ - function normalizeXml( $xml ) { + protected function normalizeXml( $xml ) { return preg_replace( '!<([a-z]+)/>!', '<$1></$1>', str_replace( ' />', '/>', $xml ) ); - - $dom = new DOMDocument(); - // 1 << 19 == XML_PARSE_HUGE, needed so newer versions of libxml2 don't barf when the XML is >256 levels deep - $dom->loadXML( $xml, 1 << 19 ); - return $dom->saveXML(); } /** * @dataProvider provideCases + * @covers Preprocessor_DOM::preprocessToXml */ - function testPreprocessorOutput( $wikiText, $expectedXml ) { + public function testPreprocessorOutput( $wikiText, $expectedXml ) { $this->assertEquals( $this->normalizeXml( $expectedXml ), $this->preprocessToXml( $wikiText ) ); } /** * These are more complex test cases taken out of wiki articles. */ - function provideFiles() { + public static function provideFiles() { return array( array( "QuoteQuran" ), # http://en.wikipedia.org/w/index.php?title=Template:QuoteQuran/sandbox&oldid=237348988 GFDL + CC-BY-SA by Striver array( "Factorial" ), # http://en.wikipedia.org/w/index.php?title=Template:Factorial&oldid=98548758 GFDL + CC-BY-SA by Polonium array( "All_system_messages" ), # http://tl.wiktionary.org/w/index.php?title=Suleras:All_system_messages&oldid=2765 GPL text generated by MediaWiki array( "Fundraising" ), # http://tl.wiktionary.org/w/index.php?title=MediaWiki:Sitenotice&oldid=5716 GFDL + CC-BY-SA, copied there by Sky Harbor. + array( "NestedTemplates" ), # bug 27936 ); } /** * @dataProvider provideFiles + * @covers Preprocessor_DOM::preprocessToXml */ - function testPreprocessorOutputFiles( $filename ) { + public function testPreprocessorOutputFiles( $filename ) { $folder = __DIR__ . "/../../../parser/preprocess"; $wikiText = file_get_contents( "$folder/$filename.txt" ); $output = $this->preprocessToXml( $wikiText ); @@ -183,8 +189,8 @@ class PreprocessorTest extends MediaWikiTestCase { /** * Tests from Bug 28642 · https://bugzilla.wikimedia.org/28642 */ - function provideHeadings() { - return array( /* These should become headings: */ + public static function provideHeadings() { + return array( /* These should become headings: */ array( "== h ==<!--c1-->", "<root><h level=\"2\" i=\"1\">== h ==<comment><!--c1--></comment></h></root>" ), array( "== h == <!--c1-->", "<root><h level=\"2\" i=\"1\">== h == <comment><!--c1--></comment></h></root>" ), array( "== h ==<!--c1--> ", "<root><h level=\"2\" i=\"1\">== h ==<comment><!--c1--></comment> </h></root>" ), @@ -212,11 +218,11 @@ class PreprocessorTest extends MediaWikiTestCase { array( "== h == <!--c1--> <!--c2--><!--c3--> ", "<root><h level=\"2\" i=\"1\">== h == <comment><!--c1--></comment> <comment><!--c2--></comment><comment><!--c3--></comment> </h></root>" ), array( "== h == <!--c1--><!--c2--> <!--c3--> ", "<root><h level=\"2\" i=\"1\">== h == <comment><!--c1--></comment><comment><!--c2--></comment> <comment><!--c3--></comment> </h></root>" ), array( "== h == <!--c1--> <!--c2--> <!--c3--> ", "<root><h level=\"2\" i=\"1\">== h == <comment><!--c1--></comment> <comment><!--c2--></comment> <comment><!--c3--></comment> </h></root>" ), + array( "== h ==<!--c1--> <!--c2-->", "<root><h level=\"2\" i=\"1\">== h ==<comment><!--c1--></comment> <comment><!--c2--></comment></h></root>" ), + array( "== h == <!--c1--> <!--c2-->", "<root><h level=\"2\" i=\"1\">== h == <comment><!--c1--></comment> <comment><!--c2--></comment></h></root>" ), + array( "== h ==<!--c1--> <!--c2--> ", "<root><h level=\"2\" i=\"1\">== h ==<comment><!--c1--></comment> <comment><!--c2--></comment> </h></root>" ), /* These are not working: */ - array( "== h ==<!--c1--> <!--c2-->", "<root>== h ==<comment><!--c1--></comment> <comment><!--c2--></comment></root>" ), - array( "== h == <!--c1--> <!--c2-->", "<root>== h == <comment><!--c1--></comment> <comment><!--c2--></comment></root>" ), - array( "== h ==<!--c1--> <!--c2--> ", "<root>== h ==<comment><!--c1--></comment> <comment><!--c2--></comment> </root>" ), array( "== h == x <!--c1--><!--c2--><!--c3--> ", "<root>== h == x <comment><!--c1--></comment><comment><!--c2--></comment><comment><!--c3--></comment> </root>" ), array( "== h ==<!--c1--> x <!--c2--><!--c3--> ", "<root>== h ==<comment><!--c1--></comment> x <comment><!--c2--></comment><comment><!--c3--></comment> </root>" ), array( "== h ==<!--c1--><!--c2--><!--c3--> x ", "<root>== h ==<comment><!--c1--></comment><comment><!--c2--></comment><comment><!--c3--></comment> x </root>" ), @@ -225,9 +231,9 @@ class PreprocessorTest extends MediaWikiTestCase { /** * @dataProvider provideHeadings + * @covers Preprocessor_DOM::preprocessToXml */ - function testHeadings( $wikiText, $expectedXml ) { + public function testHeadings( $wikiText, $expectedXml ) { $this->assertEquals( $this->normalizeXml( $expectedXml ), $this->preprocessToXml( $wikiText ) ); } } - diff --git a/tests/phpunit/includes/parser/TagHooksTest.php b/tests/phpunit/includes/parser/TagHooksTest.php index 713ce846..61cbe45d 100644 --- a/tests/phpunit/includes/parser/TagHooksTest.php +++ b/tests/phpunit/includes/parser/TagHooksTest.php @@ -4,73 +4,78 @@ * @group Parser */ class TagHookTest extends MediaWikiTestCase { - public static function provideValidNames() { return array( array( 'foo' ), array( 'foo-bar' ), array( 'foo_bar' ), array( 'FOO-BAR' ), array( 'foo bar' ) ); } public static function provideBadNames() { - return array( array( "foo<bar" ), array( "foo>bar" ), array( "foo\nbar" ), array( "foo\rbar" ) ); + return array( array( "foo<bar" ), array( "foo>bar" ), array( "foo\nbar" ), array( "foo\rbar" ) ); } - + + protected function setUp() { + parent::setUp(); + + $this->setMwGlobals( 'wgAlwaysUseTidy', false ); + } + /** * @dataProvider provideValidNames */ - function testTagHooks( $tag ) { - global $wgParserConf; + public function testTagHooks( $tag ) { + global $wgParserConf, $wgContLang; $parser = new Parser( $wgParserConf ); - + $parser->setHook( $tag, array( $this, 'tagCallback' ) ); - $parserOutput = $parser->parse( "Foo<$tag>Bar</$tag>Baz", Title::newFromText( 'Test' ), new ParserOptions ); + $parserOutput = $parser->parse( "Foo<$tag>Bar</$tag>Baz", Title::newFromText( 'Test' ), ParserOptions::newFromUserAndLang( new User, $wgContLang ) ); $this->assertEquals( "<p>FooOneBaz\n</p>", $parserOutput->getText() ); - + $parser->mPreprocessor = null; # Break the Parser <-> Preprocessor cycle } - + /** * @dataProvider provideBadNames * @expectedException MWException */ - function testBadTagHooks( $tag ) { - global $wgParserConf; + public function testBadTagHooks( $tag ) { + global $wgParserConf, $wgContLang; $parser = new Parser( $wgParserConf ); - + $parser->setHook( $tag, array( $this, 'tagCallback' ) ); - $parser->parse( "Foo<$tag>Bar</$tag>Baz", Title::newFromText( 'Test' ), new ParserOptions ); - $this->fail('Exception not thrown.'); + $parser->parse( "Foo<$tag>Bar</$tag>Baz", Title::newFromText( 'Test' ), ParserOptions::newFromUserAndLang( new User, $wgContLang ) ); + $this->fail( 'Exception not thrown.' ); } - + /** * @dataProvider provideValidNames */ - function testFunctionTagHooks( $tag ) { - global $wgParserConf; + public function testFunctionTagHooks( $tag ) { + global $wgParserConf, $wgContLang; $parser = new Parser( $wgParserConf ); - + $parser->setFunctionTagHook( $tag, array( $this, 'functionTagCallback' ), 0 ); - $parserOutput = $parser->parse( "Foo<$tag>Bar</$tag>Baz", Title::newFromText( 'Test' ), new ParserOptions ); + $parserOutput = $parser->parse( "Foo<$tag>Bar</$tag>Baz", Title::newFromText( 'Test' ), ParserOptions::newFromUserAndLang( new User, $wgContLang ) ); $this->assertEquals( "<p>FooOneBaz\n</p>", $parserOutput->getText() ); - + $parser->mPreprocessor = null; # Break the Parser <-> Preprocessor cycle } - + /** * @dataProvider provideBadNames * @expectedException MWException */ - function testBadFunctionTagHooks( $tag ) { - global $wgParserConf; + public function testBadFunctionTagHooks( $tag ) { + global $wgParserConf, $wgContLang; $parser = new Parser( $wgParserConf ); - + $parser->setFunctionTagHook( $tag, array( $this, 'functionTagCallback' ), SFH_OBJECT_ARGS ); - $parser->parse( "Foo<$tag>Bar</$tag>Baz", Title::newFromText( 'Test' ), new ParserOptions ); - $this->fail('Exception not thrown.'); + $parser->parse( "Foo<$tag>Bar</$tag>Baz", Title::newFromText( 'Test' ), ParserOptions::newFromUserAndLang( new User, $wgContLang ) ); + $this->fail( 'Exception not thrown.' ); } - + function tagCallback( $text, $params, $parser ) { return str_rot13( $text ); } - + function functionTagCallback( &$parser, $frame, $code, $attribs ) { return str_rot13( $code ); } diff --git a/tests/phpunit/includes/parser/TidyTest.php b/tests/phpunit/includes/parser/TidyTest.php new file mode 100644 index 00000000..57a88b9d --- /dev/null +++ b/tests/phpunit/includes/parser/TidyTest.php @@ -0,0 +1,44 @@ +<?php + +/** + * @group Parser + */ +class TidyTest extends MediaWikiTestCase { + public function setUp() { + parent::setUp(); + $check = MWTidy::tidy( '' ); + if ( strpos( $check, '<!--' ) !== false ) { + $this->markTestSkipped( 'Tidy not found' ); + } + } + + /** + * @dataProvider provideTestWrapping + */ + public function testTidyWrapping( $expected, $text, $msg = '' ) { + $text = MWTidy::tidy( $text ); + // We don't care about where Tidy wants to stick is <p>s + $text = trim( preg_replace( '#</?p>#', '', $text ) ); + // Windows, we love you! + $text = str_replace( "\r", '', $text ); + $this->assertEquals( $expected, $text, $msg ); + } + + public function provideTestWrapping() { + return array( + array( + '<mw:editsection page="foo" section="bar">foo</mw:editsection>', + '<mw:editsection page="foo" section="bar">foo</mw:editsection>', + '<mw:editsection> should survive tidy' + ), + array( + '<editsection page="foo" section="bar">foo</editsection>', + '<editsection page="foo" section="bar">foo</editsection>', + '<editsection> should survive tidy' + ), + array( '<mw:toc>foo</mw:toc>', '<mw:toc>foo</mw:toc>', '<mw:toc> should survive tidy' ), + array( "<link foo=\"bar\" />\nfoo", '<link foo="bar"/>foo', '<link> should survive tidy' ), + array( "<meta foo=\"bar\" />\nfoo", '<meta foo="bar"/>foo', '<meta> should survive tidy' ), + ); + } +}
\ No newline at end of file diff --git a/tests/phpunit/includes/search/SearchEngineTest.php b/tests/phpunit/includes/search/SearchEngineTest.php index 957907c7..87067038 100644 --- a/tests/phpunit/includes/search/SearchEngineTest.php +++ b/tests/phpunit/includes/search/SearchEngineTest.php @@ -1,31 +1,27 @@ <?php /** - * This class is not directly tested. Instead it is extended by SearchDbTest. * @group Search * @group Database */ -class SearchEngineTest extends MediaWikiTestCase { +class SearchEngineTest extends MediaWikiLangTestCase { protected $search, $pageList; - function tearDown() { - unset( $this->search ); - } - /** * Checks for database type & version. * Will skip current test if DB does not support search. */ - function setUp() { + protected function setUp() { parent::setUp(); + // Search tests require MySQL or SQLite with FTS # Get database type and version $dbType = $this->db->getType(); $dbSupported = - ($dbType === 'mysql') - || ( $dbType === 'sqlite' && $this->db->getFulltextSearchModule() == 'FTS3' ); + ( $dbType === 'mysql' ) + || ( $dbType === 'sqlite' && $this->db->getFulltextSearchModule() == 'FTS3' ); - if( !$dbSupported ) { + if ( !$dbSupported ) { $this->markTestSkipped( "MySQL or SQLite with FTS3 only" ); } @@ -33,6 +29,12 @@ class SearchEngineTest extends MediaWikiTestCase { $this->search = new $searchType( $this->db ); } + protected function tearDown() { + unset( $this->search ); + + parent::tearDown(); + } + function pageExists( $title ) { return false; } @@ -41,26 +43,37 @@ class SearchEngineTest extends MediaWikiTestCase { if ( $this->pageExists( 'Not_Main_Page' ) ) { return; } - $this->insertPage( "Not_Main_Page", "This is not a main page", 0 ); - $this->insertPage( 'Talk:Not_Main_Page', 'This is not a talk page to the main page, see [[smithee]]', 1 ); - $this->insertPage( 'Smithee', 'A smithee is one who smiths. See also [[Alan Smithee]]', 0 ); - $this->insertPage( 'Talk:Smithee', 'This article sucks.', 1 ); - $this->insertPage( 'Unrelated_page', 'Nothing in this page is about the S word.', 0 ); - $this->insertPage( 'Another_page', 'This page also is unrelated.', 0 ); - $this->insertPage( 'Help:Help', 'Help me!', 4 ); - $this->insertPage( 'Thppt', 'Blah blah', 0 ); - $this->insertPage( 'Alan_Smithee', 'yum', 0 ); - $this->insertPage( 'Pages', 'are\'food', 0 ); - $this->insertPage( 'HalfOneUp', 'AZ', 0 ); - $this->insertPage( 'FullOneUp', 'AZ', 0 ); - $this->insertPage( 'HalfTwoLow', 'az', 0 ); - $this->insertPage( 'FullTwoLow', 'az', 0 ); - $this->insertPage( 'HalfNumbers', '1234567890', 0 ); - $this->insertPage( 'FullNumbers', '1234567890', 0 ); - $this->insertPage( 'DomainName', 'example.com', 0 ); + + if ( !$this->isWikitextNS( NS_MAIN ) ) { + // @todo cover the case of non-wikitext content in the main namespace + return; + } + + $this->insertPage( "Not_Main_Page", "This is not a main page", 0 ); + $this->insertPage( 'Talk:Not_Main_Page', 'This is not a talk page to the main page, see [[smithee]]', 1 ); + $this->insertPage( 'Smithee', 'A smithee is one who smiths. See also [[Alan Smithee]]', 0 ); + $this->insertPage( 'Talk:Smithee', 'This article sucks.', 1 ); + $this->insertPage( 'Unrelated_page', 'Nothing in this page is about the S word.', 0 ); + $this->insertPage( 'Another_page', 'This page also is unrelated.', 0 ); + $this->insertPage( 'Help:Help', 'Help me!', 4 ); + $this->insertPage( 'Thppt', 'Blah blah', 0 ); + $this->insertPage( 'Alan_Smithee', 'yum', 0 ); + $this->insertPage( 'Pages', 'are\'food', 0 ); + $this->insertPage( 'HalfOneUp', 'AZ', 0 ); + $this->insertPage( 'FullOneUp', 'AZ', 0 ); + $this->insertPage( 'HalfTwoLow', 'az', 0 ); + $this->insertPage( 'FullTwoLow', 'az', 0 ); + $this->insertPage( 'HalfNumbers', '1234567890', 0 ); + $this->insertPage( 'FullNumbers', '1234567890', 0 ); + $this->insertPage( 'DomainName', 'example.com', 0 ); } function fetchIds( $results ) { + if ( !$this->isWikitextNS( NS_MAIN ) ) { + $this->markTestIncomplete( __CLASS__ . " does no yet support non-wikitext content " + . "in the main namespace" ); + } + $this->assertTrue( is_object( $results ) ); $matches = array(); @@ -74,6 +87,7 @@ class SearchEngineTest extends MediaWikiTestCase { # sort them numerically so we will compare simply that we received # the expected matches. sort( $matches ); + return $matches; } @@ -85,7 +99,7 @@ class SearchEngineTest extends MediaWikiTestCase { * @param $n Integer: unused */ function insertPage( $pageName, $text, $ns ) { - $title = Title::newFromText( $pageName ); + $title = Title::newFromText( $pageName, $ns ); $user = User::newFromName( 'WikiSysop' ); $comment = 'Search Test'; @@ -94,14 +108,14 @@ class SearchEngineTest extends MediaWikiTestCase { LinkCache::singleton()->clear(); $page = WikiPage::factory( $title ); - $page->doEdit( $text, $comment, 0, false, $user ); + $page->doEditContent( ContentHandler::makeContent( $text, $title ), $comment, 0, false, $user ); $this->pageList[] = array( $title, $page->getId() ); return true; } - function testFullWidth() { + public function testFullWidth() { $this->assertEquals( array( 'FullOneUp', 'FullTwoLow', 'HalfOneUp', 'HalfTwoLow' ), $this->fetchIds( $this->search->searchText( 'AZ' ) ), @@ -120,14 +134,14 @@ class SearchEngineTest extends MediaWikiTestCase { "Search for normalized from Full-width Lower" ); } - function testTextSearch() { + public function testTextSearch() { $this->assertEquals( - array( 'Smithee' ), - $this->fetchIds( $this->search->searchText( 'smithee' ) ), - "Plain search failed" ); + array( 'Smithee' ), + $this->fetchIds( $this->search->searchText( 'smithee' ) ), + "Plain search failed" ); } - function testTextPowerSearch() { + public function testTextPowerSearch() { $this->search->setNamespaces( array( 0, 1, 4 ) ); $this->assertEquals( array( @@ -138,7 +152,7 @@ class SearchEngineTest extends MediaWikiTestCase { "Power search failed" ); } - function testTitleSearch() { + public function testTitleSearch() { $this->assertEquals( array( 'Alan Smithee', @@ -148,7 +162,7 @@ class SearchEngineTest extends MediaWikiTestCase { "Title search failed" ); } - function testTextTitlePowerSearch() { + public function testTextTitlePowerSearch() { $this->search->setNamespaces( array( 0, 1, 4 ) ); $this->assertEquals( array( @@ -159,5 +173,4 @@ class SearchEngineTest extends MediaWikiTestCase { $this->fetchIds( $this->search->searchTitle( 'smithee' ) ), "Title power search failed" ); } - } diff --git a/tests/phpunit/includes/search/SearchUpdateTest.php b/tests/phpunit/includes/search/SearchUpdateTest.php index 6e49a9a1..2f4fd501 100644 --- a/tests/phpunit/includes/search/SearchUpdateTest.php +++ b/tests/phpunit/includes/search/SearchUpdateTest.php @@ -17,36 +17,20 @@ class MockSearch extends SearchEngine { /** * @group Search + * @group Database */ class SearchUpdateTest extends MediaWikiTestCase { - static $searchType; - function update( $text, $title = 'Test', $id = 1 ) { - $u = new SearchUpdate( $id, $title, $text ); - $u->doUpdate(); - return array( MockSearch::$title, MockSearch::$text ); + protected function setUp() { + parent::setUp(); + $this->setMwGlobals( 'wgSearchType', 'MockSearch' ); } function updateText( $text ) { - list( , $resultText ) = $this->update( $text ); - $resultText = trim( $resultText ); // abstract from some implementation details - return $resultText; + return trim( SearchUpdate::updateText( $text ) ); } - function setUp() { - global $wgSearchType; - - self::$searchType = $wgSearchType; - $wgSearchType = 'MockSearch'; - } - - function tearDown() { - global $wgSearchType; - - $wgSearchType = self::$searchType; - } - - function testUpdateText() { + public function testUpdateText() { $this->assertEquals( 'test', $this->updateText( '<div>TeSt</div>' ), @@ -78,7 +62,7 @@ EOT ); } - function testBug32712() { + public function testBug32712() { $text = "text „http://example.com“ text"; $result = $this->updateText( $text ); $processed = preg_replace( '/Q/u', 'Q', $result ); diff --git a/tests/phpunit/includes/site/MediaWikiSiteTest.php b/tests/phpunit/includes/site/MediaWikiSiteTest.php new file mode 100644 index 00000000..c5d52d33 --- /dev/null +++ b/tests/phpunit/includes/site/MediaWikiSiteTest.php @@ -0,0 +1,90 @@ +<?php + +/** + * Tests for the MediaWikiSite class. + * + * 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 + * + * @file + * @since 1.21 + * + * @ingroup Site + * @ingroup Test + * + * @group Site + * + * @licence GNU GPL v2+ + * @author Jeroen De Dauw < jeroendedauw@gmail.com > + */ +class MediaWikiSiteTest extends SiteTest { + + public function testNormalizePageTitle() { + $site = new MediaWikiSite(); + $site->setGlobalId( 'enwiki' ); + + //NOTE: this does not actually call out to the enwiki site to perform the normalization, + // but uses a local Title object to do so. This is hardcoded on SiteLink::normalizePageTitle + // for the case that MW_PHPUNIT_TEST is set. + $this->assertEquals( 'Foo', $site->normalizePageName( ' foo ' ) ); + } + + public function fileUrlProvider() { + return array( + // url, filepath, path arg, expected + array( 'https://en.wikipedia.org', '/w/$1', 'api.php', 'https://en.wikipedia.org/w/api.php' ), + array( 'https://en.wikipedia.org', '/w/', 'api.php', 'https://en.wikipedia.org/w/' ), + array( 'https://en.wikipedia.org', '/foo/page.php?name=$1', 'api.php', 'https://en.wikipedia.org/foo/page.php?name=api.php' ), + array( 'https://en.wikipedia.org', '/w/$1', '', 'https://en.wikipedia.org/w/' ), + array( 'https://en.wikipedia.org', '/w/$1', 'foo/bar/api.php', 'https://en.wikipedia.org/w/foo/bar/api.php' ), + ); + } + + /** + * @dataProvider fileUrlProvider + * @covers MediaWikiSite::getFileUrl + */ + public function testGetFileUrl( $url, $filePath, $pathArgument, $expected ) { + $site = new MediaWikiSite(); + $site->setFilePath( $url . $filePath ); + + $this->assertEquals( $expected, $site->getFileUrl( $pathArgument ) ); + } + + public static function provideGetPageUrl() { + return array( + // path, page, expected substring + array( 'http://acme.test/wiki/$1', 'Berlin', '/wiki/Berlin' ), + array( 'http://acme.test/wiki/', 'Berlin', '/wiki/' ), + array( 'http://acme.test/w/index.php?title=$1', 'Berlin', '/w/index.php?title=Berlin' ), + array( 'http://acme.test/wiki/$1', '', '/wiki/' ), + array( 'http://acme.test/wiki/$1', 'Berlin/sub page', '/wiki/Berlin/sub_page' ), + array( 'http://acme.test/wiki/$1', 'Cork (city) ', '/Cork_(city)' ), + array( 'http://acme.test/wiki/$1', 'M&M', '/wiki/M%26M' ), + ); + } + + /** + * @dataProvider provideGetPageUrl + * @covers MediaWikiSite::getPageUrl + */ + public function testGetPageUrl( $path, $page, $expected ) { + $site = new MediaWikiSite(); + $site->setLinkPath( $path ); + + $this->assertContains( $path, $site->getPageUrl() ); + $this->assertContains( $expected, $site->getPageUrl( $page ) ); + } +} diff --git a/tests/phpunit/includes/site/SiteListTest.php b/tests/phpunit/includes/site/SiteListTest.php new file mode 100644 index 00000000..8af2fc1b --- /dev/null +++ b/tests/phpunit/includes/site/SiteListTest.php @@ -0,0 +1,197 @@ +<?php + +/** + * Tests for the SiteList class. + * + * 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 + * + * @file + * @since 1.21 + * + * @ingroup Site + * @ingroup Test + * + * @group Site + * + * @licence GNU GPL v2+ + * @author Jeroen De Dauw < jeroendedauw@gmail.com > + */ +class SiteListTest extends MediaWikiTestCase { + + /** + * Returns instances of SiteList implementing objects. + * @return array + */ + public function siteListProvider() { + $sitesArrays = $this->siteArrayProvider(); + + $listInstances = array(); + + foreach ( $sitesArrays as $sitesArray ) { + $listInstances[] = new SiteList( $sitesArray[0] ); + } + + return $this->arrayWrap( $listInstances ); + } + + /** + * Returns arrays with instances of Site implementing objects. + * @return array + */ + public function siteArrayProvider() { + $sites = TestSites::getSites(); + + $siteArrays = array(); + + $siteArrays[] = $sites; + + $siteArrays[] = array( array_shift( $sites ) ); + + $siteArrays[] = array( array_shift( $sites ), array_shift( $sites ) ); + + return $this->arrayWrap( $siteArrays ); + } + + /** + * @dataProvider siteListProvider + * @param SiteList $sites + * @covers SiteList::isEmpty + */ + public function testIsEmpty( SiteList $sites ) { + $this->assertEquals( count( $sites ) === 0, $sites->isEmpty() ); + } + + /** + * @dataProvider siteListProvider + * @param SiteList $sites + * @covers SiteList::getSite + */ + public function testGetSiteByGlobalId( SiteList $sites ) { + if ( $sites->isEmpty() ) { + $this->assertTrue( true ); + } else { + /** + * @var Site $site + */ + foreach ( $sites as $site ) { + $this->assertEquals( $site, $sites->getSite( $site->getGlobalId() ) ); + } + } + } + + /** + * @dataProvider siteListProvider + * @param SiteList $sites + * @covers SiteList::getSiteByInternalId + */ + public function testGetSiteByInternalId( $sites ) { + /** + * @var Site $site + */ + foreach ( $sites as $site ) { + if ( is_integer( $site->getInternalId() ) ) { + $this->assertEquals( $site, $sites->getSiteByInternalId( $site->getInternalId() ) ); + } + } + + $this->assertTrue( true ); + } + + /** + * @dataProvider siteListProvider + * @param SiteList $sites + * @covers SiteList::hasSite + */ + public function testHasGlobalId( $sites ) { + $this->assertFalse( $sites->hasSite( 'non-existing-global-id' ) ); + $this->assertFalse( $sites->hasInternalId( 720101010 ) ); + + if ( !$sites->isEmpty() ) { + /** + * @var Site $site + */ + foreach ( $sites as $site ) { + $this->assertTrue( $sites->hasSite( $site->getGlobalId() ) ); + } + } + } + + /** + * @dataProvider siteListProvider + * @param SiteList $sites + * @covers SiteList::hasInternalId + */ + public function testHasInternallId( $sites ) { + /** + * @var Site $site + */ + foreach ( $sites as $site ) { + if ( is_integer( $site->getInternalId() ) ) { + $this->assertTrue( $site, $sites->hasInternalId( $site->getInternalId() ) ); + } + } + + $this->assertFalse( $sites->hasInternalId( -1 ) ); + } + + /** + * @dataProvider siteListProvider + * @param SiteList $sites + * @covers SiteList::getGlobalIdentifiers + */ + public function testGetGlobalIdentifiers( SiteList $sites ) { + $identifiers = $sites->getGlobalIdentifiers(); + + $this->assertTrue( is_array( $identifiers ) ); + + $expected = array(); + + /** + * @var Site $site + */ + foreach ( $sites as $site ) { + $expected[] = $site->getGlobalId(); + } + + $this->assertArrayEquals( $expected, $identifiers ); + } + + /** + * @dataProvider siteListProvider + * + * @since 1.21 + * + * @param SiteList $list + * @covers SiteList::getSerializationData + * @covers SiteList::unserialize + */ + public function testSerialization( SiteList $list ) { + $serialization = serialize( $list ); + /** + * @var SiteArray $copy + */ + $copy = unserialize( $serialization ); + + $this->assertArrayEquals( $list->getGlobalIdentifiers(), $copy->getGlobalIdentifiers() ); + + /** + * @var Site $site + */ + foreach ( $list as $site ) { + $this->assertTrue( $copy->hasInternalId( $site->getInternalId() ) ); + } + } +} diff --git a/tests/phpunit/includes/site/SiteSQLStoreTest.php b/tests/phpunit/includes/site/SiteSQLStoreTest.php new file mode 100644 index 00000000..6002c1a1 --- /dev/null +++ b/tests/phpunit/includes/site/SiteSQLStoreTest.php @@ -0,0 +1,134 @@ +<?php + +/** + * Tests for the SiteSQLStore class. + * + * 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 + * + * @file + * @since 1.21 + * + * @ingroup Site + * @ingroup Test + * + * @group Site + * @group Database + * + * @licence GNU GPL v2+ + * @author Jeroen De Dauw < jeroendedauw@gmail.com > + */ +class SiteSQLStoreTest extends MediaWikiTestCase { + + /** + * @covers SiteSQLStore::getSites + */ + public function testGetSites() { + $expectedSites = TestSites::getSites(); + TestSites::insertIntoDb(); + + $store = SiteSQLStore::newInstance(); + + $sites = $store->getSites(); + + $this->assertInstanceOf( 'SiteList', $sites ); + + /** + * @var Site $site + */ + foreach ( $sites as $site ) { + $this->assertInstanceOf( 'Site', $site ); + } + + foreach ( $expectedSites as $site ) { + if ( $site->getGlobalId() !== null ) { + $this->assertTrue( $sites->hasSite( $site->getGlobalId() ) ); + } + } + } + + /** + * @covers SiteSQLStore::saveSites + */ + public function testSaveSites() { + $store = SiteSQLStore::newInstance(); + + $sites = array(); + + $site = new Site(); + $site->setGlobalId( 'ertrywuutr' ); + $site->setLanguageCode( 'en' ); + $sites[] = $site; + + $site = new MediaWikiSite(); + $site->setGlobalId( 'sdfhxujgkfpth' ); + $site->setLanguageCode( 'nl' ); + $sites[] = $site; + + $this->assertTrue( $store->saveSites( $sites ) ); + + $site = $store->getSite( 'ertrywuutr' ); + $this->assertInstanceOf( 'Site', $site ); + $this->assertEquals( 'en', $site->getLanguageCode() ); + $this->assertTrue( is_integer( $site->getInternalId() ) ); + $this->assertTrue( $site->getInternalId() >= 0 ); + + $site = $store->getSite( 'sdfhxujgkfpth' ); + $this->assertInstanceOf( 'Site', $site ); + $this->assertEquals( 'nl', $site->getLanguageCode() ); + $this->assertTrue( is_integer( $site->getInternalId() ) ); + $this->assertTrue( $site->getInternalId() >= 0 ); + } + + /** + * @covers SiteSQLStore::reset + */ + public function testReset() { + $store1 = SiteSQLStore::newInstance(); + $store2 = SiteSQLStore::newInstance(); + + // initialize internal cache + $this->assertGreaterThan( 0, $store1->getSites()->count() ); + $this->assertGreaterThan( 0, $store2->getSites()->count() ); + + // Clear actual data. Will purge the external cache and reset the internal + // cache in $store1, but not the internal cache in store2. + $this->assertTrue( $store1->clear() ); + + // sanity check: $store2 should have a stale cache now + $this->assertNotNull( $store2->getSite( 'enwiki' ) ); + + // purge cache + $store2->reset(); + + // ...now the internal cache of $store2 should be updated and thus empty. + $site = $store2->getSite( 'enwiki' ); + $this->assertNull( $site ); + } + + /** + * @covers SiteSQLStore::clear + */ + public function testClear() { + $store = SiteSQLStore::newInstance(); + $this->assertTrue( $store->clear() ); + + $site = $store->getSite( 'enwiki' ); + $this->assertNull( $site ); + + $sites = $store->getSites(); + $this->assertEquals( 0, $sites->count() ); + } +} diff --git a/tests/phpunit/includes/site/SiteTest.php b/tests/phpunit/includes/site/SiteTest.php new file mode 100644 index 00000000..29c1ff33 --- /dev/null +++ b/tests/phpunit/includes/site/SiteTest.php @@ -0,0 +1,296 @@ +<?php + +/** + * Tests for the Site class. + * + * 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 + * + * @file + * @since 1.21 + * + * @ingroup Site + * @ingroup Test + * + * @group Site + * + * @licence GNU GPL v2+ + * @author Jeroen De Dauw < jeroendedauw@gmail.com > + */ +class SiteTest extends MediaWikiTestCase { + + public function instanceProvider() { + return $this->arrayWrap( TestSites::getSites() ); + } + + /** + * @dataProvider instanceProvider + * @param Site $site + * @covers Site::getInterwikiIds + */ + public function testGetInterwikiIds( Site $site ) { + $this->assertInternalType( 'array', $site->getInterwikiIds() ); + } + + /** + * @dataProvider instanceProvider + * @param Site $site + * @covers Site::getNavigationIds + */ + public function testGetNavigationIds( Site $site ) { + $this->assertInternalType( 'array', $site->getNavigationIds() ); + } + + /** + * @dataProvider instanceProvider + * @param Site $site + * @covers Site::addNavigationId + */ + public function testAddNavigationId( Site $site ) { + $site->addNavigationId( 'foobar' ); + $this->assertTrue( in_array( 'foobar', $site->getNavigationIds(), true ) ); + } + + /** + * @dataProvider instanceProvider + * @param Site $site + * @covers Site::addInterwikiId + */ + public function testAddInterwikiId( Site $site ) { + $site->addInterwikiId( 'foobar' ); + $this->assertTrue( in_array( 'foobar', $site->getInterwikiIds(), true ) ); + } + + /** + * @dataProvider instanceProvider + * @param Site $site + * @covers Site::getLanguageCode + */ + public function testGetLanguageCode( Site $site ) { + $this->assertTypeOrValue( 'string', $site->getLanguageCode(), null ); + } + + /** + * @dataProvider instanceProvider + * @param Site $site + * @covers Site::setLanguageCode + */ + public function testSetLanguageCode( Site $site ) { + $site->setLanguageCode( 'en' ); + $this->assertEquals( 'en', $site->getLanguageCode() ); + } + + /** + * @dataProvider instanceProvider + * @param Site $site + * @covers Site::normalizePageName + */ + public function testNormalizePageName( Site $site ) { + $this->assertInternalType( 'string', $site->normalizePageName( 'Foobar' ) ); + } + + /** + * @dataProvider instanceProvider + * @param Site $site + * @covers Site::getGlobalId + */ + public function testGetGlobalId( Site $site ) { + $this->assertTypeOrValue( 'string', $site->getGlobalId(), null ); + } + + /** + * @dataProvider instanceProvider + * @param Site $site + * @covers Site::setGlobalId + */ + public function testSetGlobalId( Site $site ) { + $site->setGlobalId( 'foobar' ); + $this->assertEquals( 'foobar', $site->getGlobalId() ); + } + + /** + * @dataProvider instanceProvider + * @param Site $site + * @covers Site::getType + */ + public function testGetType( Site $site ) { + $this->assertInternalType( 'string', $site->getType() ); + } + + /** + * @dataProvider instanceProvider + * @param Site $site + * @covers Site::getPath + */ + public function testGetPath( Site $site ) { + $this->assertTypeOrValue( 'string', $site->getPath( 'page_path' ), null ); + $this->assertTypeOrValue( 'string', $site->getPath( 'file_path' ), null ); + $this->assertTypeOrValue( 'string', $site->getPath( 'foobar' ), null ); + } + + /** + * @dataProvider instanceProvider + * @param Site $site + * @covers Site::getAllPaths + */ + public function testGetAllPaths( Site $site ) { + $this->assertInternalType( 'array', $site->getAllPaths() ); + } + + /** + * @dataProvider instanceProvider + * @param Site $site + * @covers Site::setPath + * @covers Site::removePath + */ + public function testSetAndRemovePath( Site $site ) { + $count = count( $site->getAllPaths() ); + + $site->setPath( 'spam', 'http://www.wikidata.org/$1' ); + $site->setPath( 'spam', 'http://www.wikidata.org/foo/$1' ); + $site->setPath( 'foobar', 'http://www.wikidata.org/bar/$1' ); + + $this->assertEquals( $count + 2, count( $site->getAllPaths() ) ); + + $this->assertInternalType( 'string', $site->getPath( 'foobar' ) ); + $this->assertEquals( 'http://www.wikidata.org/foo/$1', $site->getPath( 'spam' ) ); + + $site->removePath( 'spam' ); + $site->removePath( 'foobar' ); + + $this->assertEquals( $count, count( $site->getAllPaths() ) ); + + $this->assertNull( $site->getPath( 'foobar' ) ); + $this->assertNull( $site->getPath( 'spam' ) ); + } + + /** + * @covers Site::setLinkPath + */ + public function testSetLinkPath() { + $site = new Site(); + $path = "TestPath/$1"; + + $site->setLinkPath( $path ); + $this->assertEquals( $path, $site->getLinkPath() ); + } + + /** + * @covers Site::getLinkPathType + */ + public function testGetLinkPathType() { + $site = new Site(); + + $path = 'TestPath/$1'; + $site->setLinkPath( $path ); + $this->assertEquals( $path, $site->getPath( $site->getLinkPathType() ) ); + + $path = 'AnotherPath/$1'; + $site->setPath( $site->getLinkPathType(), $path ); + $this->assertEquals( $path, $site->getLinkPath() ); + } + + /** + * @covers Site::setPath + */ + public function testSetPath() { + $site = new Site(); + + $path = 'TestPath/$1'; + $site->setPath( 'foo', $path ); + + $this->assertEquals( $path, $site->getPath( 'foo' ) ); + } + + /** + * @covers Site::setPath + * @covers Site::getProtocol + */ + public function testProtocolRelativePath() { + $site = new Site(); + + $type = $site->getLinkPathType(); + $path = '//acme.com/'; // protocol-relative URL + $site->setPath( $type, $path ); + + $this->assertEquals( '', $site->getProtocol() ); + } + + public static function provideGetPageUrl() { + //NOTE: the assumption that the URL is built by replacing $1 + // with the urlencoded version of $page + // is true for Site but not guaranteed for subclasses. + // Subclasses need to override this provider appropriately. + + return array( + array( #0 + 'http://acme.test/TestPath/$1', + 'Foo', + '/TestPath/Foo', + ), + array( #1 + 'http://acme.test/TestScript?x=$1&y=bla', + 'Foo', + 'TestScript?x=Foo&y=bla', + ), + array( #2 + 'http://acme.test/TestPath/$1', + 'foo & bar/xyzzy (quux-shmoox?)', + '/TestPath/foo%20%26%20bar%2Fxyzzy%20%28quux-shmoox%3F%29', + ), + ); + } + + /** + * @dataProvider provideGetPageUrl + * @covers Site::getPageUrl + */ + public function testGetPageUrl( $path, $page, $expected ) { + $site = new Site(); + + //NOTE: the assumption that getPageUrl is based on getLinkPath + // is true for Site but not guaranteed for subclasses. + // Subclasses need to override this test case appropriately. + $site->setLinkPath( $path ); + $this->assertContains( $path, $site->getPageUrl() ); + + $this->assertContains( $expected, $site->getPageUrl( $page ) ); + } + + protected function assertTypeOrFalse( $type, $value ) { + if ( $value === false ) { + $this->assertTrue( true ); + } else { + $this->assertInternalType( $type, $value ); + } + } + + /** + * @dataProvider instanceProvider + * @param Site $site + * @covers Site::serialize + * @covers Site::unserialize + */ + public function testSerialization( Site $site ) { + $this->assertInstanceOf( 'Serializable', $site ); + + $serialization = serialize( $site ); + $newInstance = unserialize( $serialization ); + + $this->assertInstanceOf( 'Site', $newInstance ); + + $this->assertEquals( $serialization, serialize( $newInstance ) ); + } +} diff --git a/tests/phpunit/includes/site/TestSites.php b/tests/phpunit/includes/site/TestSites.php new file mode 100644 index 00000000..f224b7d7 --- /dev/null +++ b/tests/phpunit/includes/site/TestSites.php @@ -0,0 +1,100 @@ +<?php + +/** + * Holds sites for testing purposes. + * + * 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 + * + * @file + * @since 1.21 + * + * @ingroup Site + * @ingroup Test + * + * @group Site + * + * @licence GNU GPL v2+ + * @author Jeroen De Dauw < jeroendedauw@gmail.com > + */ +class TestSites { + + /** + * @since 1.21 + * + * @return array + */ + public static function getSites() { + $sites = array(); + + $site = new Site(); + $site->setGlobalId( 'foobar' ); + $sites[] = $site; + + $site = new MediaWikiSite(); + $site->setGlobalId( 'enwiktionary' ); + $site->setGroup( 'wiktionary' ); + $site->setLanguageCode( 'en' ); + $site->addNavigationId( 'enwiktionary' ); + $site->setPath( MediaWikiSite::PATH_PAGE, "https://en.wiktionary.org/wiki/$1" ); + $site->setPath( MediaWikiSite::PATH_FILE, "https://en.wiktionary.org/w/$1" ); + $sites[] = $site; + + $site = new MediaWikiSite(); + $site->setGlobalId( 'dewiktionary' ); + $site->setGroup( 'wiktionary' ); + $site->setLanguageCode( 'de' ); + $site->addInterwikiId( 'dewiktionary' ); + $site->addInterwikiId( 'wiktionaryde' ); + $site->setPath( MediaWikiSite::PATH_PAGE, "https://de.wiktionary.org/wiki/$1" ); + $site->setPath( MediaWikiSite::PATH_FILE, "https://de.wiktionary.org/w/$1" ); + $sites[] = $site; + + $site = new Site(); + $site->setGlobalId( 'spam' ); + $site->setGroup( 'spam' ); + $site->setLanguageCode( 'en' ); + $site->addNavigationId( 'spam' ); + $site->addNavigationId( 'spamz' ); + $site->addInterwikiId( 'spamzz' ); + $site->setLinkPath( "http://spamzz.test/testing/" ); + $sites[] = $site; + + foreach ( array( 'en', 'de', 'nl', 'sv', 'sr', 'no', 'nn' ) as $langCode ) { + $site = new MediaWikiSite(); + $site->setGlobalId( $langCode . 'wiki' ); + $site->setGroup( 'wikipedia' ); + $site->setLanguageCode( $langCode ); + $site->addInterwikiId( $langCode ); + $site->addNavigationId( $langCode ); + $site->setPath( MediaWikiSite::PATH_PAGE, "https://$langCode.wikipedia.org/wiki/$1" ); + $site->setPath( MediaWikiSite::PATH_FILE, "https://$langCode.wikipedia.org/w/$1" ); + $sites[] = $site; + } + + return $sites; + } + + /** + * Inserts sites into the database for the unit tests that need them. + * + * @since 0.1 + */ + public static function insertIntoDb() { + $sitesTable = SiteSQLStore::newInstance(); + $sitesTable->clear(); + $sitesTable->saveSites( TestSites::getSites() ); + } +} diff --git a/tests/phpunit/includes/specials/QueryAllSpecialPagesTest.php b/tests/phpunit/includes/specials/QueryAllSpecialPagesTest.php index a33c7b68..a806b4ac 100644 --- a/tests/phpunit/includes/specials/QueryAllSpecialPagesTest.php +++ b/tests/phpunit/includes/specials/QueryAllSpecialPagesTest.php @@ -39,9 +39,9 @@ class QueryAllSpecialPagesTest extends MediaWikiTestCase { parent::__construct(); global $wgQueryPages; - foreach( $wgQueryPages as $page ) { + foreach ( $wgQueryPages as $page ) { $class = $page[0]; - if( ! in_array( $class, $this->manualTest ) ) { + if ( !in_array( $class, $this->manualTest ) ) { $this->queryPages[$class] = new $class; } } @@ -51,25 +51,25 @@ class QueryAllSpecialPagesTest extends MediaWikiTestCase { * Test SQL for each of our QueryPages objects * @group Database */ - function testQuerypageSqlQuery() { + public function testQuerypageSqlQuery() { global $wgDBtype; - foreach( $this->queryPages as $page ) { + foreach ( $this->queryPages as $page ) { // With MySQL, skips special pages reopening a temporary table // See http://bugs.mysql.com/bug.php?id=10327 - if( + 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; - } + $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" ; + $msg = "SQL query for page {$page->getName()} should give a result wrapper object"; $result = $page->reallyDoQuery( 50 ); - if( $result instanceof ResultWrapper ) { + if ( $result instanceof ResultWrapper ) { $this->assertTrue( true, $msg ); } else { $this->assertFalse( false, $msg ); diff --git a/tests/phpunit/includes/specials/SpecialPreferencesTest.php b/tests/phpunit/includes/specials/SpecialPreferencesTest.php new file mode 100644 index 00000000..6c637c65 --- /dev/null +++ b/tests/phpunit/includes/specials/SpecialPreferencesTest.php @@ -0,0 +1,60 @@ +<?php +/** + * Test class for SpecialPreferences class. + * + * Copyright © 2013, Antoine Musso + * Copyright © 2013, Wikimedia Foundation Inc. + * + */ + +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. + */ + public function testBug41337() { + + // Set a low limit + $this->setMwGlobals( 'wgMaxSigChars', 2 ); + + $user = $this->getMock( 'User' ); + $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( array() ) ); + + # The mocked user has a long nickname + $user->expects( $this->any() ) + ->method( 'getOption' ) + ->will( $this->returnValueMap( array( + array( 'nickname', null, false, 'superlongnickname' ), + ) + ) ); + + # Validate the mock (FIXME should probably be removed) + $this->assertFalse( $user->isAnon() ); + $this->assertEquals( array(), + $user->getEffectiveGroups() ); + $this->assertEquals( 'superlongnickname', + $user->getOption( 'nickname' ) ); + + # 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 ); + $special->execute( array() ); + } + +} diff --git a/tests/phpunit/includes/specials/SpecialRecentchangesTest.php b/tests/phpunit/includes/specials/SpecialRecentchangesTest.php index 2e4f4b09..436eb2e2 100644 --- a/tests/phpunit/includes/specials/SpecialRecentchangesTest.php +++ b/tests/phpunit/includes/specials/SpecialRecentchangesTest.php @@ -14,9 +14,6 @@ class SpecialRecentchangesTest extends MediaWikiTestCase { */ protected $rc; - function setUp() { - } - /** helper to test SpecialRecentchanges::buildMainQueryConds() */ private function assertConditions( $expected, $requestOptions = null, $message = '' ) { $context = new RequestContext; @@ -44,8 +41,7 @@ class SpecialRecentchangesTest extends MediaWikiTestCase { /** return false if condition begin with 'rc_timestamp ' */ private static function filterOutRcTimestampCondition( $var ) { - return (false === strpos( $var, 'rc_timestamp ' )); - + return ( false === strpos( $var, 'rc_timestamp ' ) ); } public function testRcNsFilter() { @@ -73,7 +69,7 @@ class SpecialRecentchangesTest extends MediaWikiTestCase { 'namespace' => NS_MAIN, 'invert' => 1, ), - "rc conditions with namespace inverted" + "rc conditions with namespace inverted" ); } @@ -92,7 +88,7 @@ class SpecialRecentchangesTest extends MediaWikiTestCase { 'namespace' => $ns1, 'associated' => 1, ), - "rc conditions with namespace inverted" + "rc conditions with namespace inverted" ); } @@ -108,11 +104,11 @@ class SpecialRecentchangesTest extends MediaWikiTestCase { 1 => sprintf( "(rc_namespace != '%s' AND rc_namespace != '%s')", $ns1, $ns2 ), ), array( - 'namespace' => $ns1, + 'namespace' => $ns1, 'associated' => 1, - 'invert' => 1, + 'invert' => 1, ), - "rc conditions with namespace inverted" + "rc conditions with namespace inverted" ); } @@ -120,13 +116,10 @@ class SpecialRecentchangesTest extends MediaWikiTestCase { * Provides associated namespaces to test recent changes * namespaces association filtering. */ - public function provideNamespacesAssociations() { + public static function provideNamespacesAssociations() { return array( # (NS => Associated_NS) - array( NS_MAIN, NS_TALK), - array( NS_TALK, NS_MAIN), + array( NS_MAIN, NS_TALK ), + array( NS_TALK, NS_MAIN ), ); } - } - - diff --git a/tests/phpunit/includes/specials/SpecialSearchTest.php b/tests/phpunit/includes/specials/SpecialSearchTest.php index 20e42a68..17e883fd 100644 --- a/tests/phpunit/includes/specials/SpecialSearchTest.php +++ b/tests/phpunit/includes/specials/SpecialSearchTest.php @@ -10,9 +10,6 @@ class SpecialSearchTest extends MediaWikiTestCase { private $search; - function setUp() { } - function tearDown() { } - /** * @covers SpecialSearch::load * @dataProvider provideSearchOptionsTests @@ -21,7 +18,7 @@ class SpecialSearchTest extends MediaWikiTestCase { * @param $expectedProfile An expected search profile name * @param $expectedNs Array Expected namespaces */ - function testProfileAndNamespaceLoading( + public function testProfileAndNamespaceLoading( $requested, $userOptions, $expectedProfile, $expectedNS, $message = 'Profile name and namespaces mismatches!' ) { @@ -35,7 +32,7 @@ class SpecialSearchTest extends MediaWikiTestCase { 'ns6'=>true, ) )); */ - $context->setRequest( new FauxRequest( $requested )); + $context->setRequest( new FauxRequest( $requested ) ); $search = new SpecialSearch(); $search->setContext( $context ); $search->load(); @@ -48,28 +45,27 @@ class SpecialSearchTest extends MediaWikiTestCase { $this->assertEquals( array( /** Expected: */ 'ProfileName' => $expectedProfile, - 'Namespaces' => $expectedNS, + 'Namespaces' => $expectedNS, ) , array( /** Actual: */ 'ProfileName' => $search->getProfile(), - 'Namespaces' => $search->getNamespaces(), + 'Namespaces' => $search->getNamespaces(), ) , $message ); - } - function provideSearchOptionsTests() { + public static function provideSearchOptionsTests() { $defaultNS = SearchEngine::defaultNamespaces(); $EMPTY_REQUEST = array(); - $NO_USER_PREF = null; + $NO_USER_PREF = null; return array( /** * Parameters: - * <Web Request>, <User options> + * <Web Request>, <User options> * Followed by expected values: - * <ProfileName>, <NSList> + * <ProfileName>, <NSList> * Then an optional message. */ array( @@ -79,21 +75,19 @@ class SpecialSearchTest extends MediaWikiTestCase { ), array( array( 'ns5' => 1 ), $NO_USER_PREF, - 'advanced', array( 5), + 'advanced', array( 5 ), 'Web request with specific NS should override user preference' ), array( - $EMPTY_REQUEST, array( 'searchNs2' => 1, 'searchNs14' => 1 ), - 'advanced', array( 2, 14 ), - 'Bug 33583: search with no option should honor User search preferences' - ), - array( - $EMPTY_REQUEST, array_fill_keys( array_map( function( $ns ) { - return "searchNs$ns"; - }, $defaultNS ), 0 ) + array( 'searchNs2' => 1, 'searchNs14' => 1 ), + $EMPTY_REQUEST, array( + 'searchNs2' => 1, + 'searchNs14' => 1, + ) + array_fill_keys( array_map( function ( $ns ) { + return "searchNs$ns"; + }, $defaultNS ), 0 ), 'advanced', array( 2, 14 ), 'Bug 33583: search with no option should honor User search preferences' - . 'and have all other namespace disabled' + . ' and have all other namespace disabled' ), ); } @@ -103,14 +97,43 @@ class SpecialSearchTest extends MediaWikiTestCase { * User remains anonymous though */ function newUserWithSearchNS( $opt = null ) { - $u = User::newFromId(0); - if( $opt === null ) { + $u = User::newFromId( 0 ); + if ( $opt === null ) { return $u; } - foreach($opt as $name => $value) { + 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() { + + # Initialize [[Special::Search]] + $search = new SpecialSearch(); + $search->getContext()->setTitle( Title::newFromText( 'Special:Search' ) ); + $search->load(); + + # Simulate a user searching for a given term + $term = '{{SITENAME}}'; + $search->showResults( $term ); + + # 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>" + ); + } +} diff --git a/tests/phpunit/includes/upload/UploadTest.php b/tests/phpunit/includes/upload/UploadBaseTest.php index 6948f5b1..982b46b2 100644 --- a/tests/phpunit/includes/upload/UploadTest.php +++ b/tests/phpunit/includes/upload/UploadBaseTest.php @@ -1,33 +1,38 @@ <?php + /** * @group Upload */ -class UploadTest extends MediaWikiTestCase { - protected $upload; +class UploadBaseTest extends MediaWikiTestCase { + /** @var UploadTestHandler */ + protected $upload; - function setUp() { + protected function setUp() { global $wgHooks; parent::setUp(); $this->upload = new UploadTestHandler; $this->hooks = $wgHooks; - $wgHooks['InterwikiLoadPrefix'][] = function( $prefix, &$data ) { + $wgHooks['InterwikiLoadPrefix'][] = function ( $prefix, &$data ) { return false; }; } - function tearDown() { + protected function tearDown() { global $wgHooks; $wgHooks = $this->hooks; + + parent::tearDown(); } /** * First checks the return code * of UploadBase::getTitle() and then the actual returned title - * - * @dataProvider dataTestTitleValidation + * + * @dataProvider provideTestTitleValidation + * @covers UploadBase::getTitle */ public function testTitleValidation( $srcFilename, $dstFilename, $code, $msg ) { /* Check the result code */ @@ -42,44 +47,45 @@ class UploadTest extends MediaWikiTestCase { "$msg text" ); } } - + /** * Test various forms of valid and invalid titles that can be supplied. */ - public function dataTestTitleValidation() { + public static function provideTestTitleValidation() { return array( /* Test a valid title */ - array( 'ValidTitle.jpg', 'ValidTitle.jpg', UploadBase::OK, + array( 'ValidTitle.jpg', 'ValidTitle.jpg', UploadBase::OK, 'upload valid title' ), /* A title with a slash */ - array( 'A/B.jpg', 'B.jpg', UploadBase::OK, + array( 'A/B.jpg', 'B.jpg', UploadBase::OK, 'upload title with slash' ), /* A title with illegal char */ - array( 'A:B.jpg', 'A-B.jpg', UploadBase::OK, + array( 'A:B.jpg', 'A-B.jpg', UploadBase::OK, 'upload title with colon' ), /* Stripping leading File: prefix */ - array( 'File:C.jpg', 'C.jpg', UploadBase::OK, + array( 'File:C.jpg', 'C.jpg', UploadBase::OK, 'upload title with File prefix' ), /* Test illegal suggested title (r94601) */ - array( '%281%29.JPG', null, UploadBase::ILLEGAL_FILENAME, + array( '%281%29.JPG', null, UploadBase::ILLEGAL_FILENAME, 'illegal title for upload' ), /* A title without extension */ - array( 'A', null, UploadBase::FILETYPE_MISSING, + array( 'A', null, UploadBase::FILETYPE_MISSING, 'upload title without extension' ), /* A title with no basename */ - array( '.jpg', null, UploadBase::MIN_LENGTH_PARTNAME, + array( '.jpg', null, UploadBase::MIN_LENGTH_PARTNAME, 'upload title without basename' ), /* A title that is longer than 255 bytes */ - array( str_repeat( 'a', 255 ) . '.jpg', null, UploadBase::FILENAME_TOO_LONG, + array( str_repeat( 'a', 255 ) . '.jpg', null, UploadBase::FILENAME_TOO_LONG, 'upload title longer than 255 bytes' ), /* A title that is longer than 240 bytes */ - array( str_repeat( 'a', 240 ) . '.jpg', null, UploadBase::FILENAME_TOO_LONG, + array( str_repeat( 'a', 240 ) . '.jpg', null, UploadBase::FILENAME_TOO_LONG, 'upload title longer than 240 bytes' ), ); } /** * Test the upload verification functions + * @covers UploadBase::verifyUpload */ public function testVerifyUpload() { /* Setup with zero file size */ @@ -106,7 +112,6 @@ class UploadTest extends MediaWikiTestCase { * * This method should be abstracted so we can test different settings. */ - public function testMaxUploadSize() { global $wgMaxUploadSize; $savedGlobal = $wgMaxUploadSize; // save global @@ -116,26 +121,27 @@ class UploadTest extends MediaWikiTestCase { $wgMaxUploadSize = 100; $filename = $this->createFileOfSize( $wgMaxUploadSize ); - $this->upload->initializePathInfo( basename($filename) . '.txt', $filename, 100 ); + $this->upload->initializePathInfo( basename( $filename ) . '.txt', $filename, 100 ); $result = $this->upload->verifyUpload(); unlink( $filename ); $this->assertEquals( array( 'status' => UploadBase::OK ), $result ); - $wgMaxUploadSize = $savedGlobal; // restore global + $wgMaxUploadSize = $savedGlobal; // restore global } } class UploadTestHandler extends UploadBase { - public function initializeFromRequest( &$request ) { } - public function testTitleValidation( $name ) { - $this->mTitle = false; - $this->mDesiredDestName = $name; - $this->mTitleError = UploadBase::OK; - $this->getTitle(); - return $this->mTitleError; - } + public function initializeFromRequest( &$request ) { + } + public function testTitleValidation( $name ) { + $this->mTitle = false; + $this->mDesiredDestName = $name; + $this->mTitleError = UploadBase::OK; + $this->getTitle(); + return $this->mTitleError; + } } diff --git a/tests/phpunit/includes/upload/UploadFromUrlTest.php b/tests/phpunit/includes/upload/UploadFromUrlTest.php index f66c387b..a75fba69 100644 --- a/tests/phpunit/includes/upload/UploadFromUrlTest.php +++ b/tests/phpunit/includes/upload/UploadFromUrlTest.php @@ -3,16 +3,17 @@ /** * @group Broken * @group Upload + * @group Database */ class UploadFromUrlTest extends ApiTestCase { - - public function setUp() { - global $wgEnableUploads, $wgAllowCopyUploads, $wgAllowAsyncCopyUploads; + protected function setUp() { parent::setUp(); - $wgEnableUploads = true; - $wgAllowCopyUploads = true; - $wgAllowAsyncCopyUploads = true; + $this->setMwGlobals( array( + 'wgEnableUploads' => true, + 'wgAllowCopyUploads' => true, + 'wgAllowAsyncCopyUploads' => true, + ) ); wfSetupSession(); if ( wfLocalFile( 'UploadFromUrlTest.png' )->exists() ) { @@ -20,7 +21,7 @@ class UploadFromUrlTest extends ApiTestCase { } } - protected function doApiRequest( Array $params, Array $unused = null, $appendModule = false, User $user = null ) { + protected function doApiRequest( array $params, array $unused = null, $appendModule = false, User $user = null ) { $sessionId = session_id(); session_write_close(); @@ -29,6 +30,7 @@ class UploadFromUrlTest extends ApiTestCase { $module->execute(); wfSetupSession( $sessionId ); + return array( $module->getResultData(), $req ); } @@ -36,9 +38,9 @@ class UploadFromUrlTest extends ApiTestCase { * Ensure that the job queue is empty before continuing */ public function testClearQueue() { - $job = Job::pop(); + $job = JobQueueGroup::singleton()->pop(); while ( $job ) { - $job = Job::pop(); + $job = JobQueueGroup::singleton()->pop(); } $this->assertFalse( $job ); } @@ -141,7 +143,7 @@ class UploadFromUrlTest extends ApiTestCase { $this->assertEquals( $data[0]['upload']['result'], 'Queued', 'Queued upload' ); - $job = Job::pop(); + $job = JobQueueGroup::singleton()->pop(); $this->assertThat( $job, $this->isInstanceOf( 'UploadFromUrlJob' ), 'Queued upload inserted' ); } @@ -173,7 +175,6 @@ class UploadFromUrlTest extends ApiTestCase { $this->user->addGroup( 'users' ); - $data = $this->doAsyncUpload( $token ); $this->assertEquals( $data[0]['upload']['result'], 'Warning' ); @@ -202,7 +203,7 @@ class UploadFromUrlTest extends ApiTestCase { public function testSyncDownload( $data ) { $token = $this->user->getEditToken(); - $job = Job::pop(); + $job = JobQueueGroup::singleton()->pop(); $this->assertFalse( $job, 'Starting with an empty jobqueue' ); $this->user->addGroup( 'users' ); @@ -214,7 +215,7 @@ class UploadFromUrlTest extends ApiTestCase { 'token' => $token, ), $data ); - $job = Job::pop(); + $job = JobQueueGroup::singleton()->pop(); $this->assertFalse( $job ); $this->assertEquals( 'Success', $data[0]['upload']['result'] ); @@ -234,7 +235,7 @@ class UploadFromUrlTest extends ApiTestCase { $this->assertFalse( (bool)$talk->getArticleID( Title::GAID_FOR_UPDATE ), 'User talk does not exist' ); - $data = $this->doApiRequest( array( + $this->doApiRequest( array( 'action' => 'upload', 'filename' => 'UploadFromUrlTest.png', 'url' => 'http://bits.wikimedia.org/skins-1.5/common/images/poweredby_mediawiki_88x31.png', @@ -244,7 +245,7 @@ class UploadFromUrlTest extends ApiTestCase { 'ignorewarnings' => 1, ) ); - $job = Job::pop(); + $job = JobQueueGroup::singleton()->pop(); $this->assertEquals( 'UploadFromUrlJob', get_class( $job ) ); $job->run(); @@ -258,7 +259,7 @@ class UploadFromUrlTest extends ApiTestCase { $exception = false; try { - $data = $this->doApiRequest( array( + $this->doApiRequest( array( 'action' => 'upload', 'filename' => 'UploadFromUrlTest.png', 'url' => 'http://bits.wikimedia.org/skins-1.5/common/images/poweredby_mediawiki_88x31.png', @@ -272,11 +273,10 @@ class UploadFromUrlTest extends ApiTestCase { } $this->assertTrue( $exception ); - $job = Job::pop(); + $job = JobQueueGroup::singleton()->pop(); $this->assertFalse( $job ); return; - /* // Broken until using leavemessage with ignorewarnings is supported $job->run(); @@ -314,7 +314,7 @@ class UploadFromUrlTest extends ApiTestCase { $this->assertTrue( isset( $data[0]['upload']['statuskey'] ) ); $statusKey = $data[0]['upload']['statuskey']; - $job = Job::pop(); + $job = JobQueueGroup::singleton()->pop(); $this->assertEquals( 'UploadFromUrlJob', get_class( $job ) ); $status = $job->run(); @@ -329,13 +329,12 @@ class UploadFromUrlTest extends ApiTestCase { return $data; } - /** * */ protected function deleteFile( $name ) { $t = Title::newFromText( $name, NS_FILE ); - $this->assertTrue($t->exists(), "File '$name' exists"); + $this->assertTrue( $t->exists(), "File '$name' exists" ); if ( $t->exists() ) { $file = wfFindFile( $name, array( 'ignoreRedirect' => true ) ); @@ -346,6 +345,6 @@ class UploadFromUrlTest extends ApiTestCase { } $t = Title::newFromText( $name, NS_FILE ); - $this->assertFalse($t->exists(), "File '$name' was deleted"); + $this->assertFalse( $t->exists(), "File '$name' was deleted" ); } - } +} diff --git a/tests/phpunit/includes/upload/UploadStashTest.php b/tests/phpunit/includes/upload/UploadStashTest.php index 66fafaaf..7a0fea48 100644 --- a/tests/phpunit/includes/upload/UploadStashTest.php +++ b/tests/phpunit/includes/upload/UploadStashTest.php @@ -8,7 +8,7 @@ class UploadStashTest extends MediaWikiTestCase { */ public static $users; - public function setUp() { + protected function setUp() { parent::setUp(); // Setup a file for bug 29408 @@ -31,9 +31,20 @@ class UploadStashTest extends MediaWikiTestCase { ); } + protected function tearDown() { + if ( file_exists( $this->bug29408File . "." ) ) { + unlink( $this->bug29408File . "." ); + } + + if ( file_exists( $this->bug29408File ) ) { + unlink( $this->bug29408File ); + } + + parent::tearDown(); + } + public function testBug29408() { - global $wgUser; - $wgUser = self::$users['uploader']->user; + $this->setMwGlobals( 'wgUser', self::$users['uploader']->user ); $repo = RepoGroup::singleton()->getLocalRepo(); $stash = new UploadStash( $repo ); @@ -47,31 +58,19 @@ class UploadStashTest extends MediaWikiTestCase { } public function testValidRequest() { - $request = new FauxRequest( array( 'wpFileKey' => 'foo') ); - $this->assertFalse( UploadFromStash::isValidRequest($request), 'Check failure on bad wpFileKey' ); + $request = new FauxRequest( array( 'wpFileKey' => 'foo' ) ); + $this->assertFalse( UploadFromStash::isValidRequest( $request ), 'Check failure on bad wpFileKey' ); - $request = new FauxRequest( array( 'wpSessionKey' => 'foo') ); - $this->assertFalse( UploadFromStash::isValidRequest($request), 'Check failure on bad wpSessionKey' ); + $request = new FauxRequest( array( 'wpSessionKey' => 'foo' ) ); + $this->assertFalse( UploadFromStash::isValidRequest( $request ), 'Check failure on bad wpSessionKey' ); - $request = new FauxRequest( array( 'wpFileKey' => 'testkey-test.test') ); - $this->assertTrue( UploadFromStash::isValidRequest($request), 'Check good wpFileKey' ); + $request = new FauxRequest( array( 'wpFileKey' => 'testkey-test.test' ) ); + $this->assertTrue( UploadFromStash::isValidRequest( $request ), 'Check good wpFileKey' ); - $request = new FauxRequest( array( 'wpFileKey' => 'testkey-test.test') ); - $this->assertTrue( UploadFromStash::isValidRequest($request), 'Check good wpSessionKey' ); + $request = new FauxRequest( array( 'wpFileKey' => 'testkey-test.test' ) ); + $this->assertTrue( UploadFromStash::isValidRequest( $request ), 'Check good wpSessionKey' ); - $request = new FauxRequest( array( 'wpFileKey' => 'testkey-test.test', 'wpSessionKey' => 'foo') ); - $this->assertTrue( UploadFromStash::isValidRequest($request), 'Check key precedence' ); - } - - public function tearDown() { - parent::tearDown(); - - if( file_exists( $this->bug29408File . "." ) ) { - unlink( $this->bug29408File . "." ); - } - - if( file_exists( $this->bug29408File ) ) { - unlink( $this->bug29408File ); - } + $request = new FauxRequest( array( 'wpFileKey' => 'testkey-test.test', 'wpSessionKey' => 'foo' ) ); + $this->assertTrue( UploadFromStash::isValidRequest( $request ), 'Check key precedence' ); } } diff --git a/tests/phpunit/install-phpunit.sh b/tests/phpunit/install-phpunit.sh index 2d2b53ab..1f602935 100644 --- a/tests/phpunit/install-phpunit.sh +++ b/tests/phpunit/install-phpunit.sh @@ -8,7 +8,7 @@ has_binary () { } if [ `id -u` -ne 0 ]; then - echo '*** ERROR' Must be root to run + echo '*** ERROR: Must be root to run' exit 1 fi @@ -18,8 +18,9 @@ else if ( has_binary pear ); then echo Installing phpunit with pear pear channel-discover pear.phpunit.de pear channel-discover components.ez.no - pear channel-discover pear.symfony-project.com - pear install phpunit/PHPUnit + pear channel-discover pear.symfony.com + pear update-channels + pear install --alldeps phpunit/PHPUnit else if ( has_binary apt-get ); then echo Installing phpunit with apt-get apt-get install phpunit diff --git a/tests/phpunit/languages/LanguageAmTest.php b/tests/phpunit/languages/LanguageAmTest.php index 3a648ded..a644f5e0 100644 --- a/tests/phpunit/languages/LanguageAmTest.php +++ b/tests/phpunit/languages/LanguageAmTest.php @@ -6,24 +6,26 @@ */ /** Tests for MediaWiki languages/LanguageAm.php */ -class LanguageAmTest extends MediaWikiTestCase { - private $lang; - - function setUp() { - $this->lang = Language::factory( 'Am' ); - } - function tearDown() { - unset( $this->lang ); +class LanguageAmTest extends LanguageClassesTestCase { + /** + * @dataProvider providePlural + * @covers Language::convertPlural + */ + public function testPlural( $result, $value ) { + $forms = array( 'one', 'other' ); + $this->assertEquals( $result, $this->getLang()->convertPlural( $value, $forms ) ); } - /** @dataProvider providePlural */ - function testPlural( $result, $value ) { - $forms = array( 'one', 'other' ); - $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) ); + /** + * @dataProvider providePlural + * @covers Language::getPluralRuleType + */ + public function testGetPluralRuleType( $result, $value ) { + $this->assertEquals( $result, $this->getLang()->getPluralRuleType( $value ) ); } - function providePlural() { - return array ( + public static function providePlural() { + return array( array( 'one', 0 ), array( 'one', 1 ), array( 'other', 2 ), diff --git a/tests/phpunit/languages/LanguageArTest.php b/tests/phpunit/languages/LanguageArTest.php index b23e0534..7b48f236 100644 --- a/tests/phpunit/languages/LanguageArTest.php +++ b/tests/phpunit/languages/LanguageArTest.php @@ -5,30 +5,26 @@ */ /** Tests for MediaWiki languages/LanguageAr.php */ -class LanguageArTest extends MediaWikiTestCase { - private $lang; - - function setUp() { - $this->lang = Language::factory( 'Ar' ); - } - function tearDown() { - unset( $this->lang ); - } - - function testFormatNum() { - $this->assertEquals( '١٬٢٣٤٬٥٦٧', $this->lang->formatNum( '1234567' ) ); - $this->assertEquals( '-١٢٫٨٩', $this->lang->formatNum( -12.89 ) ); +class LanguageArTest extends LanguageClassesTestCase { + /** + * @covers Language::formatNum + * @todo split into a test and a dataprovider + */ + public function testFormatNum() { + $this->assertEquals( '١٬٢٣٤٬٥٦٧', $this->getLang()->formatNum( '1234567' ) ); + $this->assertEquals( '-١٢٫٨٩', $this->getLang()->formatNum( -12.89 ) ); } /** * Mostly to test the raw ascii feature. * @dataProvider providerSprintfDate + * @covers Language::sprintfDate */ - function testSprintfDate( $format, $date, $expected ) { - $this->assertEquals( $expected, $this->lang->sprintfDate( $format, $date ) ); + public function testSprintfDate( $format, $date, $expected ) { + $this->assertEquals( $expected, $this->getLang()->sprintfDate( $format, $date ) ); } - function providerSprintfDate() { + public static function providerSprintfDate() { return array( array( 'xg "vs" g', @@ -52,13 +48,26 @@ class LanguageArTest extends MediaWikiTestCase { ), ); } - /** @dataProvider providePlural */ - function testPlural( $result, $value ) { - $forms = array( 'zero', 'one', 'two', 'few', 'many', 'other' ); - $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) ); + + /** + * @dataProvider providePlural + * @covers Language::convertPlural + */ + public function testPlural( $result, $value ) { + $forms = array( 'zero', 'one', 'two', 'few', 'many', 'other' ); + $this->assertEquals( $result, $this->getLang()->convertPlural( $value, $forms ) ); } - function providePlural() { - return array ( + + /** + * @dataProvider providePlural + * @covers Language::getPluralRuleType + */ + public function testGetPluralRuleType( $result, $value ) { + $this->assertEquals( $result, $this->getLang()->getPluralRuleType( $value ) ); + } + + public static function providePlural() { + return array( array( 'zero', 0 ), array( 'one', 1 ), array( 'two', 2 ), diff --git a/tests/phpunit/languages/LanguageBeTest.php b/tests/phpunit/languages/LanguageBeTest.php index 735ccc63..7bd586af 100644 --- a/tests/phpunit/languages/LanguageBeTest.php +++ b/tests/phpunit/languages/LanguageBeTest.php @@ -6,24 +6,26 @@ */ /** Tests for MediaWiki languages/LanguageBe.php */ -class LanguageBeTest extends MediaWikiTestCase { - private $lang; - - function setUp() { - $this->lang = Language::factory( 'Be' ); - } - function tearDown() { - unset( $this->lang ); +class LanguageBeTest extends LanguageClassesTestCase { + /** + * @dataProvider providePlural + * @covers Language::convertPlural + */ + public function testPlural( $result, $value ) { + $forms = array( 'one', 'few', 'many', 'other' ); + $this->assertEquals( $result, $this->getLang()->convertPlural( $value, $forms ) ); } - /** @dataProvider providePlural */ - function testPlural( $result, $value ) { - $forms = array( 'one', 'few', 'many', 'other' ); - $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) ); + /** + * @dataProvider providePlural + * @covers Language::getPluralRuleType + */ + public function testGetPluralRuleType( $result, $value ) { + $this->assertEquals( $result, $this->getLang()->getPluralRuleType( $value ) ); } - function providePlural() { - return array ( + public static function providePlural() { + return array( array( 'one', 1 ), array( 'many', 11 ), array( 'one', 91 ), diff --git a/tests/phpunit/languages/LanguageBe_taraskTest.php b/tests/phpunit/languages/LanguageBe_taraskTest.php index 765cdb8f..dbdb5889 100644 --- a/tests/phpunit/languages/LanguageBe_taraskTest.php +++ b/tests/phpunit/languages/LanguageBe_taraskTest.php @@ -1,40 +1,65 @@ <?php -class LanguageBeTaraskTest extends MediaWikiTestCase { - private $lang; - - function setUp() { - $this->lang = Language::factory( 'Be-tarask' ); - } - function tearDown() { - unset( $this->lang ); +class LanguageBe_taraskTest extends LanguageClassesTestCase { + /** + * Make sure the language code we are given is indeed + * be-tarask. This is to ensure LanguageClassesTestCase + * does not give us the wrong language. + */ + public function testBeTaraskTestsUsesBeTaraskCode() { + $this->assertEquals( 'be-tarask', + $this->getLang()->getCode() + ); } - /** see bug 23156 & r64981 */ - function testSearchRightSingleQuotationMarkAsApostroph() { + /** + * @see bug 23156 & r64981 + * @covers Language::commafy + */ + public function testSearchRightSingleQuotationMarkAsApostroph() { $this->assertEquals( "'", - $this->lang->normalizeForSearch( '’' ), + $this->getLang()->normalizeForSearch( '’' ), 'bug 23156: U+2019 conversion to U+0027' ); } - /** see bug 23156 & r64981 */ - function testCommafy() { - $this->assertEquals( '1,234,567', $this->lang->commafy( '1234567' ) ); - $this->assertEquals( '12,345', $this->lang->commafy( '12345' ) ); + + /** + * @see bug 23156 & r64981 + * @covers Language::commafy + */ + public function testCommafy() { + $this->assertEquals( '1,234,567', $this->getLang()->commafy( '1234567' ) ); + $this->assertEquals( '12,345', $this->getLang()->commafy( '12345' ) ); } - /** see bug 23156 & r64981 */ - function testDoesNotCommafyFourDigitsNumber() { - $this->assertEquals( '1234', $this->lang->commafy( '1234' ) ); + + /** + * @see bug 23156 & r64981 + * @covers Language::commafy + */ + public function testDoesNotCommafyFourDigitsNumber() { + $this->assertEquals( '1234', $this->getLang()->commafy( '1234' ) ); } - /** @dataProvider providePluralFourForms */ - function testPluralFourForms( $result, $value ) { - $forms = array( 'one', 'few', 'many', 'other' ); - $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) ); + + /** + * @dataProvider providePlural + * @covers Language::convertPlural + */ + public function testPlural( $result, $value ) { + $forms = array( 'one', 'few', 'many', 'other' ); + $this->assertEquals( $result, $this->getLang()->convertPlural( $value, $forms ) ); } - function providePluralFourForms() { - return array ( + /** + * @dataProvider providePlural + * @covers Language::getPluralRuleType + */ + public function testGetPluralRuleType( $result, $value ) { + $this->assertEquals( $result, $this->getLang()->getPluralRuleType( $value ) ); + } + + public static function providePlural() { + return array( array( 'one', 1 ), array( 'many', 11 ), array( 'one', 91 ), @@ -48,18 +73,23 @@ class LanguageBeTaraskTest extends MediaWikiTestCase { array( 'many', 120 ), ); } - /** @dataProvider providePluralTwoForms */ - function testPluralTwoForms( $result, $value ) { - $forms = array( 'one', 'several' ); - $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) ); + + /** + * @dataProvider providePluralTwoForms + * @covers Language::convertPlural + */ + public function testPluralTwoForms( $result, $value ) { + $forms = array( '1=one', 'other' ); + $this->assertEquals( $result, $this->getLang()->convertPlural( $value, $forms ) ); } - function providePluralTwoForms() { - return array ( + + public static function providePluralTwoForms() { + return array( + array( 'other', 0 ), array( 'one', 1 ), - array( 'several', 11 ), - array( 'several', 91 ), - array( 'several', 121 ), + array( 'other', 11 ), + array( 'other', 91 ), + array( 'other', 121 ), ); } - } diff --git a/tests/phpunit/languages/LanguageBhTest.php b/tests/phpunit/languages/LanguageBhTest.php deleted file mode 100644 index e1e2a13e..00000000 --- a/tests/phpunit/languages/LanguageBhTest.php +++ /dev/null @@ -1,34 +0,0 @@ -<?php -/** - * @author Santhosh Thottingal - * @copyright Copyright © 2012, Santhosh Thottingal - * @file - */ - -/** Tests for MediaWiki languages/LanguageBh.php */ -class LanguageBhTest extends MediaWikiTestCase { - private $lang; - - function setUp() { - $this->lang = Language::factory( 'Bh' ); - } - function tearDown() { - unset( $this->lang ); - } - - /** @dataProvider providePlural */ - function testPlural( $result, $value ) { - $forms = array( 'one', 'other' ); - $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) ); - } - - function providePlural() { - return array ( - array( 'one', 0 ), - array( 'one', 1 ), - array( 'other', 2 ), - array( 'other', 200 ), - ); - } - -} diff --git a/tests/phpunit/languages/LanguageBhoTest.php b/tests/phpunit/languages/LanguageBhoTest.php new file mode 100644 index 00000000..187bfbbc --- /dev/null +++ b/tests/phpunit/languages/LanguageBhoTest.php @@ -0,0 +1,35 @@ +<?php +/** + * @author Santhosh Thottingal + * @copyright Copyright © 2012, Santhosh Thottingal + * @file + */ + +/** Tests for MediaWiki languages/LanguageBho.php */ +class LanguageBhoTest extends LanguageClassesTestCase { + /** + * @dataProvider providePlural + * @covers Language::convertPlural + */ + public function testPlural( $result, $value ) { + $forms = array( 'one', 'other' ); + $this->assertEquals( $result, $this->getLang()->convertPlural( $value, $forms ) ); + } + + /** + * @dataProvider providePlural + * @covers Language::getPluralRuleType + */ + public function testGetPluralRuleType( $result, $value ) { + $this->assertEquals( $result, $this->getLang()->getPluralRuleType( $value ) ); + } + + public static function providePlural() { + return array( + array( 'one', 0 ), + array( 'one', 1 ), + array( 'other', 2 ), + array( 'other', 200 ), + ); + } +} diff --git a/tests/phpunit/languages/LanguageBsTest.php b/tests/phpunit/languages/LanguageBsTest.php index b6631c03..7aca2ab1 100644 --- a/tests/phpunit/languages/LanguageBsTest.php +++ b/tests/phpunit/languages/LanguageBsTest.php @@ -5,37 +5,38 @@ * @file */ -/** Tests for MediaWiki languages/LanguageBs.php */ -class LanguageBsTest extends MediaWikiTestCase { - private $lang; - - function setUp() { - $this->lang = Language::factory( 'Bs' ); - } - function tearDown() { - unset( $this->lang ); +/** Tests for Croatian (hrvatski) */ +class LanguageBsTest extends LanguageClassesTestCase { + /** + * @dataProvider providePlural + * @covers Language::convertPlural + */ + public function testPlural( $result, $value ) { + $forms = array( 'one', 'few', 'other' ); + $this->assertEquals( $result, $this->getLang()->convertPlural( $value, $forms ) ); } - /** @dataProvider providePlural */ - function testPlural( $result, $value ) { - $forms = array( 'one', 'few', 'many', 'other' ); - $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) ); + /** + * @dataProvider providePlural + * @covers Language::getPluralRuleType + */ + public function testGetPluralRuleType( $result, $value ) { + $this->assertEquals( $result, $this->getLang()->getPluralRuleType( $value ) ); } - function providePlural() { - return array ( - array( 'many', 0 ), + public static function providePlural() { + return array( + array( 'other', 0 ), array( 'one', 1 ), array( 'few', 2 ), array( 'few', 4 ), - array( 'many', 5 ), - array( 'many', 11 ), - array( 'many', 20 ), + array( 'other', 5 ), + array( 'other', 11 ), + array( 'other', 20 ), array( 'one', 21 ), array( 'few', 24 ), - array( 'many', 25 ), - array( 'many', 200 ), + array( 'other', 25 ), + array( 'other', 200 ), ); } - } diff --git a/tests/phpunit/languages/LanguageClassesTestCase.php b/tests/phpunit/languages/LanguageClassesTestCase.php new file mode 100644 index 00000000..632e037f --- /dev/null +++ b/tests/phpunit/languages/LanguageClassesTestCase.php @@ -0,0 +1,74 @@ +<?php +/** + * Helping class to run tests using a clean language instance. + * + * This is intended for the MediaWiki language class tests under + * tests/phpunit/languages. + * + * Before each tests, a new language object is build which you + * can retrieve in your test using the $this->getLang() method: + * + * @par Using the crafted language object: + * @code + * function testHasLanguageObject() { + * $langObject = $this->getLang(); + * $this->assertInstanceOf( 'LanguageFoo', + * $langObject + * ); + * } + * @endcode + */ +abstract class LanguageClassesTestCase extends MediaWikiTestCase { + /** + * Internal language object + * + * A new object is created before each tests thanks to PHPUnit + * setUp() method, it is deleted after each test too. To get + * this object you simply use the getLang method. + * + * You must have setup a language code first. See $LanguageClassCode + * @code + * function testWeAreTheChampions() { + * $this->getLang(); # language object + * } + * @endcode + */ + private $languageObject; + + /** + * @return Language + */ + protected function getLang() { + return $this->languageObject; + } + + /** + * Create a new language object before each test. + */ + protected function setUp() { + parent::setUp(); + $found = preg_match( '/Language(.+)Test/', get_called_class(), $m ); + if ( $found ) { + # Normalize language code since classes uses underscores + $m[1] = str_replace( '_', '-', $m[1] ); + } else { + # Fallback to english language + $m[1] = 'en'; + wfDebug( + __METHOD__ . " could not extract a language name " + . "out of " . get_called_class() . " failling back to 'en'\n" + ); + } + // @todo validate $m[1] which should be a valid language code + $this->languageObject = Language::factory( $m[1] ); + } + + /** + * Delete the internal language object so each test start + * out with a fresh language instance. + */ + protected function tearDown() { + unset( $this->languageObject ); + parent::tearDown(); + } +} diff --git a/tests/phpunit/languages/LanguageCsTest.php b/tests/phpunit/languages/LanguageCsTest.php index dda29f9a..da9e6b88 100644 --- a/tests/phpunit/languages/LanguageCsTest.php +++ b/tests/phpunit/languages/LanguageCsTest.php @@ -6,24 +6,26 @@ */ /** Tests for MediaWiki languages/classes/Languagecs.php */ -class LanguageCsTest extends MediaWikiTestCase { - private $lang; - - function setUp() { - $this->lang = Language::factory( 'cs' ); - } - function tearDown() { - unset( $this->lang ); +class LanguageCsTest extends LanguageClassesTestCase { + /** + * @dataProvider providePlural + * @covers Language::convertPlural + */ + public function testPlural( $result, $value ) { + $forms = array( 'one', 'few', 'other' ); + $this->assertEquals( $result, $this->getLang()->convertPlural( $value, $forms ) ); } - /** @dataProvider providerPlural */ - function testPlural( $result, $value ) { - $forms = array( 'one', 'few', 'other' ); - $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) ); + /** + * @dataProvider providePlural + * @covers Language::getPluralRuleType + */ + public function testGetPluralRuleType( $result, $value ) { + $this->assertEquals( $result, $this->getLang()->getPluralRuleType( $value ) ); } - function providerPlural() { - return array ( + public static function providePlural() { + return array( array( 'other', 0 ), array( 'one', 1 ), array( 'few', 2 ), @@ -36,5 +38,4 @@ class LanguageCsTest extends MediaWikiTestCase { array( 'other', 200 ), ); } - } diff --git a/tests/phpunit/languages/LanguageCuTest.php b/tests/phpunit/languages/LanguageCuTest.php index f8186d7b..07193172 100644 --- a/tests/phpunit/languages/LanguageCuTest.php +++ b/tests/phpunit/languages/LanguageCuTest.php @@ -6,36 +6,37 @@ */ /** Tests for MediaWiki languages/LanguageCu.php */ -class LanguageCuTest extends MediaWikiTestCase { - private $lang; - - function setUp() { - $this->lang = Language::factory( 'cu' ); - } - function tearDown() { - unset( $this->lang ); +class LanguageCuTest extends LanguageClassesTestCase { + /** + * @dataProvider providePlural + * @covers Language::convertPlural + */ + public function testPlural( $result, $value ) { + $forms = array( 'one', 'two', 'few', 'other' ); + $this->assertEquals( $result, $this->getLang()->convertPlural( $value, $forms ) ); } - /** @dataProvider providerPlural */ - function testPlural( $result, $value ) { - $forms = array( 'one', 'few', 'many', 'other' ); - $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) ); + /** + * @dataProvider providePlural + * @covers Language::getPluralRuleType + */ + public function testGetPluralRuleType( $result, $value ) { + $this->assertEquals( $result, $this->getLang()->getPluralRuleType( $value ) ); } - function providerPlural() { - return array ( + public static function providePlural() { + return array( array( 'other', 0 ), array( 'one', 1 ), - array( 'few', 2 ), - array( 'many', 3 ), - array( 'many', 4 ), + array( 'two', 2 ), + array( 'few', 3 ), + array( 'few', 4 ), array( 'other', 5 ), array( 'one', 11 ), array( 'other', 20 ), - array( 'few', 22 ), - array( 'many', 223 ), + array( 'two', 22 ), + array( 'few', 223 ), array( 'other', 200 ), ); } - } diff --git a/tests/phpunit/languages/LanguageCyTest.php b/tests/phpunit/languages/LanguageCyTest.php index e9f9e410..eaf663a8 100644 --- a/tests/phpunit/languages/LanguageCyTest.php +++ b/tests/phpunit/languages/LanguageCyTest.php @@ -6,24 +6,26 @@ */ /** Tests for MediaWiki languages/classes/LanguageCy.php */ -class LanguageCyTest extends MediaWikiTestCase { - private $lang; - - function setUp() { - $this->lang = Language::factory( 'cy' ); - } - function tearDown() { - unset( $this->lang ); +class LanguageCyTest extends LanguageClassesTestCase { + /** + * @dataProvider providePlural + * @covers Language::convertPlural + */ + public function testPlural( $result, $value ) { + $forms = array( 'zero', 'one', 'two', 'few', 'many', 'other' ); + $this->assertEquals( $result, $this->getLang()->convertPlural( $value, $forms ) ); } - /** @dataProvider providerPlural */ - function testPlural( $result, $value ) { - $forms = array( 'zero', 'one', 'two', 'few', 'many', 'other' ); - $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) ); + /** + * @dataProvider providePlural + * @covers Language::getPluralRuleType + */ + public function testGetPluralRuleType( $result, $value ) { + $this->assertEquals( $result, $this->getLang()->getPluralRuleType( $value ) ); } - function providerPlural() { - return array ( + public static function providePlural() { + return array( array( 'zero', 0 ), array( 'one', 1 ), array( 'two', 2 ), @@ -38,5 +40,4 @@ class LanguageCyTest extends MediaWikiTestCase { array( 'other', 200.00 ), ); } - } diff --git a/tests/phpunit/languages/LanguageDsbTest.php b/tests/phpunit/languages/LanguageDsbTest.php index ab7f9313..94c11bcc 100644 --- a/tests/phpunit/languages/LanguageDsbTest.php +++ b/tests/phpunit/languages/LanguageDsbTest.php @@ -6,24 +6,26 @@ */ /** Tests for MediaWiki languages/classes/LanguageDsb.php */ -class LanguageDsbTest extends MediaWikiTestCase { - private $lang; - - function setUp() { - $this->lang = Language::factory( 'dsb' ); - } - function tearDown() { - unset( $this->lang ); +class LanguageDsbTest extends LanguageClassesTestCase { + /** + * @dataProvider providePlural + * @covers Language::convertPlural + */ + public function testPlural( $result, $value ) { + $forms = array( 'one', 'two', 'few', 'other' ); + $this->assertEquals( $result, $this->getLang()->convertPlural( $value, $forms ) ); } - /** @dataProvider providePlural */ - function testPlural( $result, $value ) { - $forms = array( 'one', 'two', 'few', 'other' ); - $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) ); + /** + * @dataProvider providePlural + * @covers Language::getPluralRuleType + */ + public function testGetPluralRuleType( $result, $value ) { + $this->assertEquals( $result, $this->getLang()->getPluralRuleType( $value ) ); } - function providePlural() { - return array ( + public static function providePlural() { + return array( array( 'other', 0 ), array( 'one', 1 ), array( 'one', 101 ), @@ -36,5 +38,4 @@ class LanguageDsbTest extends MediaWikiTestCase { array( 'other', 555 ), ); } - } diff --git a/tests/phpunit/languages/LanguageFrTest.php b/tests/phpunit/languages/LanguageFrTest.php index 8538744e..46b65011 100644 --- a/tests/phpunit/languages/LanguageFrTest.php +++ b/tests/phpunit/languages/LanguageFrTest.php @@ -6,29 +6,30 @@ */ /** Tests for MediaWiki languages/classes/LanguageFr.php */ -class LanguageFrTest extends MediaWikiTestCase { - private $lang; - - function setUp() { - $this->lang = Language::factory( 'fr' ); - } - function tearDown() { - unset( $this->lang ); +class LanguageFrTest extends LanguageClassesTestCase { + /** + * @dataProvider providePlural + * @covers Language::convertPlural + */ + public function testPlural( $result, $value ) { + $forms = array( 'one', 'other' ); + $this->assertEquals( $result, $this->getLang()->convertPlural( $value, $forms ) ); } - /** @dataProvider providePlural */ - function testPlural( $result, $value ) { - $forms = array( 'one', 'other' ); - $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) ); + /** + * @dataProvider providePlural + * @covers Language::getPluralRuleType + */ + public function testGetPluralRuleType( $result, $value ) { + $this->assertEquals( $result, $this->getLang()->getPluralRuleType( $value ) ); } - function providePlural() { - return array ( + public static function providePlural() { + return array( array( 'one', 0 ), array( 'one', 1 ), array( 'other', 2 ), array( 'other', 200 ), ); } - } diff --git a/tests/phpunit/languages/LanguageGaTest.php b/tests/phpunit/languages/LanguageGaTest.php index fbd9f11d..c009f56b 100644 --- a/tests/phpunit/languages/LanguageGaTest.php +++ b/tests/phpunit/languages/LanguageGaTest.php @@ -6,29 +6,30 @@ */ /** Tests for MediaWiki languages/classes/LanguageGa.php */ -class LanguageGaTest extends MediaWikiTestCase { - private $lang; - - function setUp() { - $this->lang = Language::factory( 'ga' ); - } - function tearDown() { - unset( $this->lang ); +class LanguageGaTest extends LanguageClassesTestCase { + /** + * @dataProvider providePlural + * @covers Language::convertPlural + */ + public function testPlural( $result, $value ) { + $forms = array( 'one', 'two', 'other' ); + $this->assertEquals( $result, $this->getLang()->convertPlural( $value, $forms ) ); } - /** @dataProvider providerPlural */ - function testPlural( $result, $value ) { - $forms = array( 'one', 'two', 'other' ); - $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) ); + /** + * @dataProvider providePlural + * @covers Language::getPluralRuleType + */ + public function testGetPluralRuleType( $result, $value ) { + $this->assertEquals( $result, $this->getLang()->getPluralRuleType( $value ) ); } - function providerPlural() { - return array ( + public static function providePlural() { + return array( array( 'other', 0 ), array( 'one', 1 ), array( 'two', 2 ), array( 'other', 200 ), ); } - } diff --git a/tests/phpunit/languages/LanguageGdTest.php b/tests/phpunit/languages/LanguageGdTest.php index 24574bda..0b2612b2 100644 --- a/tests/phpunit/languages/LanguageGdTest.php +++ b/tests/phpunit/languages/LanguageGdTest.php @@ -1,38 +1,53 @@ <?php /** * @author Santhosh Thottingal - * @copyright Copyright © 2012, Santhosh Thottingal + * @copyright Copyright © 2012-2013, Santhosh Thottingal * @file */ /** Tests for MediaWiki languages/classes/LanguageGd.php */ -class LanguageGdTest extends MediaWikiTestCase { - private $lang; - - function setUp() { - $this->lang = Language::factory( 'gd' ); +class LanguageGdTest extends LanguageClassesTestCase { + /** + * @dataProvider providerPlural + * @covers Language::convertPlural + */ + public function testPlural( $result, $value ) { + $forms = array( 'one', 'two', 'few', 'other' ); + $this->assertEquals( $result, $this->getLang()->convertPlural( $value, $forms ) ); } - function tearDown() { - unset( $this->lang ); + + public static function providerPlural() { + return array( + array( 'other', 0 ), + array( 'one', 1 ), + array( 'two', 2 ), + array( 'one', 11 ), + array( 'two', 12 ), + array( 'few', 3 ), + array( 'few', 19 ), + array( 'other', 200 ), + ); } - /** @dataProvider providerPlural */ - function testPlural( $result, $value ) { - // The CLDR ticket for this plural forms is not same as mw plural forms. See http://unicode.org/cldr/trac/ticket/2883 - $forms = array( 'Form 1', 'Form 2', 'Form 3', 'Form 4', 'Form 5', 'Form 6' ); - $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) ); + /** + * @dataProvider providerPluralExplicit + * @covers Language::convertPlural + */ + public function testExplicitPlural( $result, $value ) { + $forms = array( 'one', 'two', 'few', 'other', '11=Form11', '12=Form12' ); + $this->assertEquals( $result, $this->getLang()->convertPlural( $value, $forms ) ); } - function providerPlural() { - return array ( - array( 'Form 6', 0 ), - array( 'Form 1', 1 ), - array( 'Form 2', 2 ), - array( 'Form 3', 11 ), - array( 'Form 4', 12 ), - array( 'Form 5', 3 ), - array( 'Form 5', 19 ), - array( 'Form 6', 200 ), + + public static function providerPluralExplicit() { + return array( + array( 'other', 0 ), + array( 'one', 1 ), + array( 'two', 2 ), + array( 'Form11', 11 ), + array( 'Form12', 12 ), + array( 'few', 3 ), + array( 'few', 19 ), + array( 'other', 200 ), ); } - } diff --git a/tests/phpunit/languages/LanguageGvTest.php b/tests/phpunit/languages/LanguageGvTest.php index 3d298b9b..fc58022a 100644 --- a/tests/phpunit/languages/LanguageGvTest.php +++ b/tests/phpunit/languages/LanguageGvTest.php @@ -1,39 +1,44 @@ <?php /** + * Test for Manx (Gaelg) language + * * @author Santhosh Thottingal - * @copyright Copyright © 2012, Santhosh Thottingal + * @copyright Copyright © 2013, Santhosh Thottingal * @file */ -/** Tests for MediaWiki languages/classes/LanguageGv.php */ -class LanguageGvTest extends MediaWikiTestCase { - private $lang; - - function setUp() { - $this->lang = Language::factory( 'gv' ); - } - function tearDown() { - unset( $this->lang ); +class LanguageGvTest extends LanguageClassesTestCase { + /** + * @dataProvider providePlural + * @covers Language::convertPlural + */ + public function testPlural( $result, $value ) { + $forms = array( 'one', 'two', 'few', 'other' ); + $this->assertEquals( $result, $this->getLang()->convertPlural( $value, $forms ) ); } - /** @dataProvider providerPlural */ - function testPlural( $result, $value ) { - // This is not compatible with CLDR plural rules http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html#gv - $forms = array( 'Form 1', 'Form 2', 'Form 3', 'Form 4' ); - $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) ); + /** + * @dataProvider providePlural + * @covers Language::getPluralRuleType + */ + public function testGetPluralRuleType( $result, $value ) { + $this->assertEquals( $result, $this->getLang()->getPluralRuleType( $value ) ); } - function providerPlural() { - return array ( - array( 'Form 4', 0 ), - array( 'Form 2', 1 ), - array( 'Form 3', 2 ), - array( 'Form 4', 3 ), - array( 'Form 1', 20 ), - array( 'Form 2', 21 ), - array( 'Form 3', 22 ), - array( 'Form 4', 23 ), - array( 'Form 4', 50 ), + + public static function providePlural() { + return array( + array( 'few', 0 ), + array( 'one', 1 ), + array( 'two', 2 ), + array( 'other', 3 ), + array( 'few', 20 ), + array( 'one', 21 ), + array( 'two', 22 ), + array( 'other', 23 ), + array( 'other', 50 ), + array( 'few', 60 ), + array( 'other', 80 ), + array( 'few', 100 ) ); } - } diff --git a/tests/phpunit/languages/LanguageHeTest.php b/tests/phpunit/languages/LanguageHeTest.php index 7833da71..8edc6ddf 100644 --- a/tests/phpunit/languages/LanguageHeTest.php +++ b/tests/phpunit/languages/LanguageHeTest.php @@ -6,43 +6,127 @@ */ /** Tests for MediaWiki languages/classes/LanguageHe.php */ -class LanguageHeTest extends MediaWikiTestCase { - private $lang; +class LanguageHeTest extends LanguageClassesTestCase { + /** + * The most common usage for the plural forms is two forms, + * for singular and plural. In this case, the second form + * is technically dual, but in practice it's used as plural. + * In some cases, usually with expressions of time, three forms + * are needed - singular, dual and plural. + * CLDR also specifies a fourth form for multiples of 10, + * which is very rare. It also has a mistake, because + * the number 10 itself is supposed to be just plural, + * so currently it's overridden in MediaWiki. + */ - function setUp() { - $this->lang = Language::factory( 'he' ); - } - function tearDown() { - unset( $this->lang ); + // @todo the below test*PluralForms test methods can be refactored + // to use a single test method and data provider.. + + /** + * @dataProvider provideTwoPluralForms + * @covers Language::convertPlural + */ + public function testTwoPluralForms( $result, $value ) { + $forms = array( 'one', 'other' ); + $this->assertEquals( $result, $this->getLang()->convertPlural( $value, $forms ) ); } - /** @dataProvider providerPluralDual */ - function testPluralDual( $result, $value ) { + /** + * @dataProvider provideThreePluralForms + * @covers Language::convertPlural + */ + public function testThreePluralForms( $result, $value ) { $forms = array( 'one', 'two', 'other' ); - $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) ); + $this->assertEquals( $result, $this->getLang()->convertPlural( $value, $forms ) ); + } + + /** + * @dataProvider provideFourPluralForms + * @covers Language::convertPlural + */ + public function testFourPluralForms( $result, $value ) { + $forms = array( 'one', 'two', 'many', 'other' ); + $this->assertEquals( $result, $this->getLang()->convertPlural( $value, $forms ) ); } - function providerPluralDual() { - return array ( - array( 'other', 0 ), // Zero -> plural + /** + * @dataProvider provideFourPluralForms + * @covers Language::convertPlural + */ + public function testGetPluralRuleType( $result, $value ) { + $this->assertEquals( $result, $this->getLang()->getPluralRuleType( $value ) ); + } + + public static function provideTwoPluralForms() { + return array( + array( 'other', 0 ), // Zero - plural array( 'one', 1 ), // Singular - array( 'two', 2 ), // Dual - array( 'other', 3 ), // Plural + array( 'other', 2 ), // No third form provided, use it as plural + array( 'other', 3 ), // Plural - other + array( 'other', 10 ), // No fourth form provided, use it as plural + array( 'other', 20 ), // No fourth form provided, use it as plural ); } - /** @dataProvider providerPlural */ - function testPlural( $result, $value ) { - $forms = array( 'one', 'other' ); - $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) ); + public static function provideThreePluralForms() { + return array( + array( 'other', 0 ), // Zero - plural + array( 'one', 1 ), // Singular + array( 'two', 2 ), // Dual + array( 'other', 3 ), // Plural - other + array( 'other', 10 ), // No fourth form provided, use it as plural + array( 'other', 20 ), // No fourth form provided, use it as plural + ); } - function providerPlural() { - return array ( - array( 'other', 0 ), // Zero -> plural + public static function provideFourPluralForms() { + return array( + array( 'other', 0 ), // Zero - plural array( 'one', 1 ), // Singular - array( 'other', 2 ), // Plural, no dual provided - array( 'other', 3 ), // Plural + array( 'two', 2 ), // Dual + array( 'other', 3 ), // Plural - other + array( 'other', 10 ), // 10 is supposed to be plural (other), not "many" + array( 'many', 20 ), // Fourth form provided - rare, but supported by CLDR + ); + } + + /** + * @dataProvider provideGrammar + * @covers Language::convertGrammar + */ + public function testGrammar( $result, $word, $case ) { + $this->assertEquals( $result, $this->getLang()->convertGrammar( $word, $case ) ); + } + + // The comments in the beginning of the line help avoid RTL problems + // with text editors. + public static function provideGrammar() { + return array( + array( + /* result */'וויקיפדיה', + /* word */'ויקיפדיה', + /* case */'תחילית', + ), + array( + /* result */'וולפגנג', + /* word */'וולפגנג', + /* case */'prefixed', + ), + array( + /* result */'קובץ', + /* word */'הקובץ', + /* case */'תחילית', + ), + array( + /* result */'־Wikipedia', + /* word */'Wikipedia', + /* case */'תחילית', + ), + array( + /* result */'־1995', + /* word */'1995', + /* case */'תחילית', + ), ); } } diff --git a/tests/phpunit/languages/LanguageHiTest.php b/tests/phpunit/languages/LanguageHiTest.php index ead9e020..f6d2c9e9 100644 --- a/tests/phpunit/languages/LanguageHiTest.php +++ b/tests/phpunit/languages/LanguageHiTest.php @@ -6,29 +6,30 @@ */ /** Tests for MediaWiki languages/LanguageHi.php */ -class LanguageHiTest extends MediaWikiTestCase { - private $lang; - - function setUp() { - $this->lang = Language::factory( 'Hi' ); - } - function tearDown() { - unset( $this->lang ); +class LanguageHiTest extends LanguageClassesTestCase { + /** + * @dataProvider providePlural + * @covers Language::convertPlural + */ + public function testPlural( $result, $value ) { + $forms = array( 'one', 'other' ); + $this->assertEquals( $result, $this->getLang()->convertPlural( $value, $forms ) ); } - /** @dataProvider providePlural */ - function testPlural( $result, $value ) { - $forms = array( 'one', 'other' ); - $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) ); + /** + * @dataProvider providePlural + * @covers Language::getPluralRuleType + */ + public function testGetPluralRuleType( $result, $value ) { + $this->assertEquals( $result, $this->getLang()->getPluralRuleType( $value ) ); } - function providePlural() { - return array ( + public static function providePlural() { + return array( array( 'one', 0 ), array( 'one', 1 ), array( 'other', 2 ), array( 'other', 200 ), ); } - } diff --git a/tests/phpunit/languages/LanguageHrTest.php b/tests/phpunit/languages/LanguageHrTest.php index 4f1c66bf..644c5255 100644 --- a/tests/phpunit/languages/LanguageHrTest.php +++ b/tests/phpunit/languages/LanguageHrTest.php @@ -6,36 +6,37 @@ */ /** Tests for MediaWiki languages/classes/LanguageHr.php */ -class LanguageHrTest extends MediaWikiTestCase { - private $lang; - - function setUp() { - $this->lang = Language::factory( 'hr' ); - } - function tearDown() { - unset( $this->lang ); +class LanguageHrTest extends LanguageClassesTestCase { + /** + * @dataProvider providePlural + * @covers Language::convertPlural + */ + public function testPlural( $result, $value ) { + $forms = array( 'one', 'few', 'other' ); + $this->assertEquals( $result, $this->getLang()->convertPlural( $value, $forms ) ); } - /** @dataProvider providerPlural */ - function testPlural( $result, $value ) { - $forms = array( 'one', 'few', 'many', 'other' ); - $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) ); + /** + * @dataProvider providePlural + * @covers Language::getPluralRuleType + */ + public function testGetPluralRuleType( $result, $value ) { + $this->assertEquals( $result, $this->getLang()->getPluralRuleType( $value ) ); } - function providerPlural() { - return array ( - array( 'many', 0 ), + public static function providePlural() { + return array( + array( 'other', 0 ), array( 'one', 1 ), array( 'few', 2 ), array( 'few', 4 ), - array( 'many', 5 ), - array( 'many', 11 ), - array( 'many', 20 ), + array( 'other', 5 ), + array( 'other', 11 ), + array( 'other', 20 ), array( 'one', 21 ), array( 'few', 24 ), - array( 'many', 25 ), - array( 'many', 200 ), + array( 'other', 25 ), + array( 'other', 200 ), ); } - } diff --git a/tests/phpunit/languages/LanguageHsbTest.php b/tests/phpunit/languages/LanguageHsbTest.php index 803c7721..f95a43bf 100644 --- a/tests/phpunit/languages/LanguageHsbTest.php +++ b/tests/phpunit/languages/LanguageHsbTest.php @@ -6,24 +6,26 @@ */ /** Tests for MediaWiki languages/classes/LanguageHsb.php */ -class LanguageHsbTest extends MediaWikiTestCase { - private $lang; - - function setUp() { - $this->lang = Language::factory( 'hsb' ); - } - function tearDown() { - unset( $this->lang ); +class LanguageHsbTest extends LanguageClassesTestCase { + /** + * @dataProvider providePlural + * @covers Language::convertPlural + */ + public function testPlural( $result, $value ) { + $forms = array( 'one', 'two', 'few', 'other' ); + $this->assertEquals( $result, $this->getLang()->convertPlural( $value, $forms ) ); } - /** @dataProvider providePlural */ - function testPlural( $result, $value ) { - $forms = array( 'one', 'two', 'few', 'other' ); - $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) ); + /** + * @dataProvider providePlural + * @covers Language::getPluralRuleType + */ + public function testGetPluralRuleType( $result, $value ) { + $this->assertEquals( $result, $this->getLang()->getPluralRuleType( $value ) ); } - function providePlural() { - return array ( + public static function providePlural() { + return array( array( 'other', 0 ), array( 'one', 1 ), array( 'one', 101 ), @@ -36,5 +38,4 @@ class LanguageHsbTest extends MediaWikiTestCase { array( 'other', 555 ), ); } - } diff --git a/tests/phpunit/languages/LanguageHuTest.php b/tests/phpunit/languages/LanguageHuTest.php index adbd37ec..ee9197d7 100644 --- a/tests/phpunit/languages/LanguageHuTest.php +++ b/tests/phpunit/languages/LanguageHuTest.php @@ -6,29 +6,30 @@ */ /** Tests for MediaWiki languages/LanguageHu.php */ -class LanguageHuTest extends MediaWikiTestCase { - private $lang; - - function setUp() { - $this->lang = Language::factory( 'Hu' ); - } - function tearDown() { - unset( $this->lang ); +class LanguageHuTest extends LanguageClassesTestCase { + /** + * @dataProvider providePlural + * @covers Language::convertPlural + */ + public function testPlural( $result, $value ) { + $forms = array( 'one', 'other' ); + $this->assertEquals( $result, $this->getLang()->convertPlural( $value, $forms ) ); } - /** @dataProvider providePlural */ - function testPlural( $result, $value ) { - $forms = array( 'one', 'other' ); - $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) ); + /** + * @dataProvider providePlural + * @covers Language::getPluralRuleType + */ + public function testGetPluralRuleType( $result, $value ) { + $this->assertEquals( $result, $this->getLang()->getPluralRuleType( $value ) ); } - function providePlural() { - return array ( + public static function providePlural() { + return array( array( 'other', 0 ), array( 'one', 1 ), array( 'other', 2 ), array( 'other', 200 ), ); } - } diff --git a/tests/phpunit/languages/LanguageHyTest.php b/tests/phpunit/languages/LanguageHyTest.php index 7990bdfc..92e0ef94 100644 --- a/tests/phpunit/languages/LanguageHyTest.php +++ b/tests/phpunit/languages/LanguageHyTest.php @@ -5,30 +5,31 @@ * @file */ -/** Tests for MediaWiki languages/LanguageHy.php */ -class LanguageHyTest extends MediaWikiTestCase { - private $lang; - - function setUp() { - $this->lang = Language::factory( 'hy' ); - } - function tearDown() { - unset( $this->lang ); +/** Tests for Armenian (Հայերեն) */ +class LanguageHyTest extends LanguageClassesTestCase { + /** + * @dataProvider providePlural + * @covers Language::convertPlural + */ + public function testPlural( $result, $value ) { + $forms = array( 'one', 'other' ); + $this->assertEquals( $result, $this->getLang()->convertPlural( $value, $forms ) ); } - /** @dataProvider providerPlural */ - function testPlural( $result, $value ) { - $forms = array( 'one', 'other' ); - $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) ); + /** + * @dataProvider providePlural + * @covers Language::getPluralRuleType + */ + public function testGetPluralRuleType( $result, $value ) { + $this->assertEquals( $result, $this->getLang()->getPluralRuleType( $value ) ); } - function providerPlural() { - return array ( + public static function providePlural() { + return array( array( 'one', 0 ), array( 'one', 1 ), array( 'other', 2 ), array( 'other', 200 ), ); } - } diff --git a/tests/phpunit/languages/LanguageKshTest.php b/tests/phpunit/languages/LanguageKshTest.php index ab889464..568a3780 100644 --- a/tests/phpunit/languages/LanguageKshTest.php +++ b/tests/phpunit/languages/LanguageKshTest.php @@ -6,29 +6,30 @@ */ /** Tests for MediaWiki languages/classes/LanguageKsh.php */ -class LanguageKshTest extends MediaWikiTestCase { - private $lang; - - function setUp() { - $this->lang = Language::factory( 'ksh' ); - } - function tearDown() { - unset( $this->lang ); +class LanguageKshTest extends LanguageClassesTestCase { + /** + * @dataProvider providePlural + * @covers Language::convertPlural + */ + public function testPlural( $result, $value ) { + $forms = array( 'one', 'other', 'zero' ); + $this->assertEquals( $result, $this->getLang()->convertPlural( $value, $forms ) ); } - /** @dataProvider providerPlural */ - function testPlural( $result, $value ) { - $forms = array( 'one', 'other', 'zero' ); - $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) ); + /** + * @dataProvider providePlural + * @covers Language::getPluralRuleType + */ + public function testGetPluralRuleType( $result, $value ) { + $this->assertEquals( $result, $this->getLang()->getPluralRuleType( $value ) ); } - function providerPlural() { - return array ( + public static function providePlural() { + return array( array( 'zero', 0 ), array( 'one', 1 ), array( 'other', 2 ), array( 'other', 200 ), ); } - } diff --git a/tests/phpunit/languages/LanguageLnTest.php b/tests/phpunit/languages/LanguageLnTest.php index 0fd9167e..10b3234f 100644 --- a/tests/phpunit/languages/LanguageLnTest.php +++ b/tests/phpunit/languages/LanguageLnTest.php @@ -6,29 +6,30 @@ */ /** Tests for MediaWiki languages/classes/LanguageLn.php */ -class LanguageLnTest extends MediaWikiTestCase { - private $lang; - - function setUp() { - $this->lang = Language::factory( 'ln' ); - } - function tearDown() { - unset( $this->lang ); +class LanguageLnTest extends LanguageClassesTestCase { + /** + * @dataProvider providePlural + * @covers Language::convertPlural + */ + public function testPlural( $result, $value ) { + $forms = array( 'one', 'other' ); + $this->assertEquals( $result, $this->getLang()->convertPlural( $value, $forms ) ); } - /** @dataProvider providePlural */ - function testPlural( $result, $value ) { - $forms = array( 'one', 'other' ); - $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) ); + /** + * @dataProvider providePlural + * @covers Language::getPluralRuleType + */ + public function testGetPluralRuleType( $result, $value ) { + $this->assertEquals( $result, $this->getLang()->getPluralRuleType( $value ) ); } - function providePlural() { - return array ( + public static function providePlural() { + return array( array( 'one', 0 ), array( 'one', 1 ), array( 'other', 2 ), array( 'other', 200 ), ); } - } diff --git a/tests/phpunit/languages/LanguageLtTest.php b/tests/phpunit/languages/LanguageLtTest.php index 0d7c7d3e..30642f62 100644 --- a/tests/phpunit/languages/LanguageLtTest.php +++ b/tests/phpunit/languages/LanguageLtTest.php @@ -6,30 +6,26 @@ */ /** Tests for MediaWiki languages/LanguageLt.php */ -class LanguageLtTest extends MediaWikiTestCase { - private $lang; - - function setUp() { - $this->lang = Language::factory( 'Lt' ); - } - function tearDown() { - unset( $this->lang ); +class LanguageLtTest extends LanguageClassesTestCase { + /** + * @dataProvider providePlural + * @covers Language::convertPlural + */ + public function testPlural( $result, $value ) { + $forms = array( 'one', 'few', 'other' ); + $this->assertEquals( $result, $this->getLang()->convertPlural( $value, $forms ) ); } - /** @dataProvider provideOneFewOtherCases */ - function testOneFewOtherPlural( $result, $value ) { - $forms = array( 'one', 'few', 'other' ); - $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) ); - } - - /** @dataProvider provideOneFewCases */ - function testOneFewPlural( $result, $value ) { - $forms = array( 'one', 'few' ); - $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) ); + /** + * @dataProvider providePlural + * @covers Language::getPluralRuleType + */ + public function testGetPluralRuleType( $result, $value ) { + $this->assertEquals( $result, $this->getLang()->getPluralRuleType( $value ) ); } - function provideOneFewOtherCases() { - return array ( + public static function providePlural() { + return array( array( 'other', 0 ), array( 'one', 1 ), array( 'few', 2 ), @@ -43,11 +39,25 @@ class LanguageLtTest extends MediaWikiTestCase { array( 'one', 40001 ), ); } - - function provideOneFewCases() { - return array ( + + /** + * @dataProvider providePluralTwoForms + * @covers Language::convertPlural + */ + public function testOneFewPlural( $result, $value ) { + $forms = array( 'one', 'other' ); + // This fails for 21, but not sure why. + $this->assertEquals( $result, $this->getLang()->convertPlural( $value, $forms ) ); + } + + public static function providePluralTwoForms() { + return array( array( 'one', 1 ), - array( 'few', 15 ), + array( 'other', 2 ), + array( 'other', 15 ), + array( 'other', 20 ), + array( 'one', 21 ), + array( 'other', 22 ), ); } } diff --git a/tests/phpunit/languages/LanguageLvTest.php b/tests/phpunit/languages/LanguageLvTest.php index 0636da5f..7120cfe3 100644 --- a/tests/phpunit/languages/LanguageLvTest.php +++ b/tests/phpunit/languages/LanguageLvTest.php @@ -5,35 +5,40 @@ * @file */ -/** Tests for MediaWiki languages/classes/LanguageLv.php */ -class LanguageLvTest extends MediaWikiTestCase { - private $lang; - - function setUp() { - $this->lang = Language::factory( 'lv' ); - } - function tearDown() { - unset( $this->lang ); +/** Tests for Latvian */ +class LanguageLvTest extends LanguageClassesTestCase { + /** + * @dataProvider providePlural + * @covers Language::convertPlural + */ + public function testPlural( $result, $value ) { + $forms = array( 'zero', 'one', 'other' ); + $this->assertEquals( $result, $this->getLang()->convertPlural( $value, $forms ) ); } - /** @dataProvider providerPlural */ - function testPlural( $result, $value ) { - $forms = array( 'one', 'other' ); - $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) ); + /** + * @dataProvider providePlural + * @covers Language::getPluralRuleType + */ + public function testGetPluralRuleType( $result, $value ) { + $this->assertEquals( $result, $this->getLang()->getPluralRuleType( $value ) ); } - function providerPlural() { - return array ( - array( 'other', 0 ), #this must be zero form as per CLDR + public static function providePlural() { + return array( + array( 'zero', 0 ), array( 'one', 1 ), - array( 'other', 11 ), + array( 'zero', 11 ), array( 'one', 21 ), - array( 'other', 411 ), + array( 'zero', 411 ), + array( 'other', 2 ), + array( 'other', 9 ), + array( 'zero', 12 ), array( 'other', 12.345 ), - array( 'other', 20 ), + array( 'zero', 20 ), + array( 'other', 22 ), array( 'one', 31 ), - array( 'other', 200 ), + array( 'zero', 200 ), ); } - } diff --git a/tests/phpunit/languages/LanguageMgTest.php b/tests/phpunit/languages/LanguageMgTest.php index 06b56547..65e8fd7b 100644 --- a/tests/phpunit/languages/LanguageMgTest.php +++ b/tests/phpunit/languages/LanguageMgTest.php @@ -6,24 +6,26 @@ */ /** Tests for MediaWiki languages/classes/LanguageMg.php */ -class LanguageMgTest extends MediaWikiTestCase { - private $lang; - - function setUp() { - $this->lang = Language::factory( 'mg' ); - } - function tearDown() { - unset( $this->lang ); +class LanguageMgTest extends LanguageClassesTestCase { + /** + * @dataProvider providePlural + * @covers Language::convertPlural + */ + public function testPlural( $result, $value ) { + $forms = array( 'one', 'other' ); + $this->assertEquals( $result, $this->getLang()->convertPlural( $value, $forms ) ); } - /** @dataProvider providePlural */ - function testPlural( $result, $value ) { - $forms = array( 'one', 'other' ); - $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) ); + /** + * @dataProvider providePlural + * @covers Language::getPluralRuleType + */ + public function testGetPluralRuleType( $result, $value ) { + $this->assertEquals( $result, $this->getLang()->getPluralRuleType( $value ) ); } - function providePlural() { - return array ( + public static function providePlural() { + return array( array( 'one', 0 ), array( 'one', 1 ), array( 'other', 2 ), @@ -31,5 +33,4 @@ class LanguageMgTest extends MediaWikiTestCase { array( 'other', 123.3434 ), ); } - } diff --git a/tests/phpunit/languages/LanguageMkTest.php b/tests/phpunit/languages/LanguageMkTest.php index cf5ec3d9..ed155263 100644 --- a/tests/phpunit/languages/LanguageMkTest.php +++ b/tests/phpunit/languages/LanguageMkTest.php @@ -5,37 +5,36 @@ * @file */ -/** Tests for MediaWiki languages/classes/LanguageMk.php */ -class LanguageMkTest extends MediaWikiTestCase { - private $lang; - - function setUp() { - $this->lang = Language::factory( 'mk' ); - } - function tearDown() { - unset( $this->lang ); +/** Tests for македонски/Macedonian */ +class LanguageMkTest extends LanguageClassesTestCase { + /** + * @dataProvider providePlural + * @covers Language::convertPlural + */ + public function testPlural( $result, $value ) { + $forms = array( 'one', 'other' ); + $this->assertEquals( $result, $this->getLang()->convertPlural( $value, $forms ) ); } - /** @dataProvider providerPlural */ - function testPlural( $result, $value ) { - $forms = array( 'one', 'other' ); - $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) ); + /** + * @dataProvider providePlural + * @covers Language::getPluralRuleType + */ + public function testGetPluralRuleType( $result, $value ) { + $this->assertEquals( $result, $this->getLang()->getPluralRuleType( $value ) ); } - - function providerPlural() { - return array ( + public static function providePlural() { + return array( array( 'other', 0 ), array( 'one', 1 ), - array( 'other', 11 ), + array( 'one', 11 ), array( 'one', 21 ), - array( 'other', 411 ), + array( 'one', 411 ), array( 'other', 12.345 ), array( 'other', 20 ), array( 'one', 31 ), array( 'other', 200 ), ); } - - } diff --git a/tests/phpunit/languages/LanguageMlTest.php b/tests/phpunit/languages/LanguageMlTest.php index 8c4b0b2f..4fa45ce3 100644 --- a/tests/phpunit/languages/LanguageMlTest.php +++ b/tests/phpunit/languages/LanguageMlTest.php @@ -6,28 +6,23 @@ */ /** Tests for MediaWiki languages/LanguageMl.php */ -class LanguageMlTest extends MediaWikiTestCase { - private $lang; +class LanguageMlTest extends LanguageClassesTestCase { - function setUp() { - $this->lang = Language::factory( 'Ml' ); - } - function tearDown() { - unset( $this->lang ); - } - - /** see bug 29495 */ - /** @dataProvider providerFormatNum*/ - function testFormatNum( $result, $value ) { - $this->assertEquals( $result, $this->lang->formatNum( $value ) ); + /** + * @dataProvider providerFormatNum + * @see bug 29495 + * @covers Language::formatNum + */ + public function testFormatNum( $result, $value ) { + $this->assertEquals( $result, $this->getLang()->formatNum( $value ) ); } - function providerFormatNum() { + public static function providerFormatNum() { return array( - array( '12,34,567', '1234567' ), + array( '12,34,567', '1234567' ), array( '12,345', '12345' ), array( '1', '1' ), - array( '123', '123' ) , + array( '123', '123' ), array( '1,234', '1234' ), array( '12,345.56', '12345.56' ), array( '12,34,56,79,81,23,45,678', '12345679812345678' ), @@ -35,7 +30,7 @@ class LanguageMlTest extends MediaWikiTestCase { array( '-12,00,000', '-1200000' ), array( '-98', '-98' ), array( '-98', -98 ), - array( '-1,23,45,678', -12345678 ), + array( '-1,23,45,678', -12345678 ), array( '', '' ), array( '', null ), ); diff --git a/tests/phpunit/languages/LanguageMoTest.php b/tests/phpunit/languages/LanguageMoTest.php index 533e590f..e0e54ca8 100644 --- a/tests/phpunit/languages/LanguageMoTest.php +++ b/tests/phpunit/languages/LanguageMoTest.php @@ -6,37 +6,39 @@ */ /** Tests for MediaWiki languages/classes/LanguageMo.php */ -class LanguageMoTest extends MediaWikiTestCase { - private $lang; - - function setUp() { - $this->lang = Language::factory( 'mo' ); - } - function tearDown() { - unset( $this->lang ); +class LanguageMoTest extends LanguageClassesTestCase { + /** + * @dataProvider providePlural + * @covers Language::convertPlural + */ + public function testPlural( $result, $value ) { + $forms = array( 'one', 'few', 'other' ); + $this->assertEquals( $result, $this->getLang()->convertPlural( $value, $forms ) ); } - /** @dataProvider providerPlural */ - function testPlural( $result, $value ) { - $forms = array( 'one', 'few', 'other' ); - $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) ); + /** + * @dataProvider providePlural + * @covers Language::getPluralRuleType + */ + public function testGetPluralRuleType( $result, $value ) { + $this->assertEquals( $result, $this->getLang()->getPluralRuleType( $value ) ); } - function providerPlural() { - return array ( - array( 'few', 0 ), - array( 'one', 1 ), - array( 'few', 2 ), - array( 'few', 19 ), + public static function providePlural() { + return array( + array( 'few', 0 ), + array( 'one', 1 ), + array( 'few', 2 ), + array( 'few', 19 ), array( 'other', 20 ), array( 'other', 99 ), array( 'other', 100 ), - array( 'few', 101 ), - array( 'few', 119 ), + array( 'few', 101 ), + array( 'few', 119 ), array( 'other', 120 ), array( 'other', 200 ), - array( 'few', 201 ), - array( 'few', 219 ), + array( 'few', 201 ), + array( 'few', 219 ), array( 'other', 220 ), ); } diff --git a/tests/phpunit/languages/LanguageMtTest.php b/tests/phpunit/languages/LanguageMtTest.php index 421bb388..96d2bc92 100644 --- a/tests/phpunit/languages/LanguageMtTest.php +++ b/tests/phpunit/languages/LanguageMtTest.php @@ -6,67 +6,72 @@ */ /** Tests for MediaWiki languages/classes/LanguageMt.php */ -class LanguageMtTest extends MediaWikiTestCase { - private $lang; - - function setUp() { - $this->lang = Language::factory( 'mt' ); - } - function tearDown() { - unset( $this->lang ); +class LanguageMtTest extends LanguageClassesTestCase { + /** + * @dataProvider providePlural + * @covers Language::convertPlural + */ + public function testPlural( $result, $value ) { + $forms = array( 'one', 'few', 'many', 'other' ); + $this->assertEquals( $result, $this->getLang()->convertPlural( $value, $forms ) ); } - /** @dataProvider providerPluralAllForms */ - function testPluralAllForms( $result, $value ) { - $forms = array( 'one', 'few', 'many', 'other' ); - $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) ); + /** + * @dataProvider providePlural + * @covers Language::getPluralRuleType + */ + public function testGetPluralRuleType( $result, $value ) { + $this->assertEquals( $result, $this->getLang()->getPluralRuleType( $value ) ); } - function providerPluralAllForms() { - return array ( - array( 'few', 0 ), - array( 'one', 1 ), - array( 'few', 2 ), - array( 'few', 10 ), - array( 'many', 11 ), - array( 'many', 19 ), + public static function providePlural() { + return array( + array( 'few', 0 ), + array( 'one', 1 ), + array( 'few', 2 ), + array( 'few', 10 ), + array( 'many', 11 ), + array( 'many', 19 ), array( 'other', 20 ), array( 'other', 99 ), array( 'other', 100 ), array( 'other', 101 ), - array( 'few', 102 ), - array( 'few', 110 ), - array( 'many', 111 ), - array( 'many', 119 ), + array( 'few', 102 ), + array( 'few', 110 ), + array( 'many', 111 ), + array( 'many', 119 ), array( 'other', 120 ), array( 'other', 201 ), ); } - /** @dataProvider providerPluralTwoForms */ - function testPluralTwoForms( $result, $value ) { - $forms = array( 'one', 'many' ); - $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) ); + /** + * @dataProvider providePluralTwoForms + * @covers Language::convertPlural + */ + public function testPluralTwoForms( $result, $value ) { + $forms = array( 'one', 'other' ); + $this->assertEquals( $result, $this->getLang()->convertPlural( $value, $forms ) ); } - function providerPluralTwoForms() { - return array ( - array( 'many', 0 ), - array( 'one', 1 ), - array( 'many', 2 ), - array( 'many', 10 ), - array( 'many', 11 ), - array( 'many', 19 ), - array( 'many', 20 ), - array( 'many', 99 ), - array( 'many', 100 ), - array( 'many', 101 ), - array( 'many', 102 ), - array( 'many', 110 ), - array( 'many', 111 ), - array( 'many', 119 ), - array( 'many', 120 ), - array( 'many', 201 ), + public static function providePluralTwoForms() { + return array( + array( 'other', 0 ), + array( 'one', 1 ), + array( 'other', 2 ), + array( 'other', 10 ), + array( 'other', 11 ), + array( 'other', 19 ), + array( 'other', 20 ), + array( 'other', 99 ), + array( 'other', 100 ), + array( 'other', 101 ), + array( 'other', 102 ), + array( 'other', 110 ), + array( 'other', 111 ), + array( 'other', 119 ), + array( 'other', 120 ), + array( 'other', 201 ), ); } } diff --git a/tests/phpunit/languages/LanguageNlTest.php b/tests/phpunit/languages/LanguageNlTest.php index cf979cd2..26bd691a 100644 --- a/tests/phpunit/languages/LanguageNlTest.php +++ b/tests/phpunit/languages/LanguageNlTest.php @@ -6,23 +6,19 @@ */ /** Tests for MediaWiki languages/LanguageNl.php */ -class LanguageNlTest extends MediaWikiTestCase { - private $lang; +class LanguageNlTest extends LanguageClassesTestCase { - function setUp() { - $this->lang = Language::factory( 'Nl' ); - } - function tearDown() { - unset( $this->lang ); - } - - function testFormatNum() { - $this->assertEquals( '1.234.567', $this->lang->formatNum( '1234567' ) ); - $this->assertEquals( '12.345', $this->lang->formatNum( '12345' ) ); - $this->assertEquals( '1', $this->lang->formatNum( '1' ) ); - $this->assertEquals( '123', $this->lang->formatNum( '123' ) ); - $this->assertEquals( '1.234', $this->lang->formatNum( '1234' ) ); - $this->assertEquals( '12.345,56', $this->lang->formatNum( '12345.56' ) ); - $this->assertEquals( ',1234556', $this->lang->formatNum( '.1234556' ) ); + /** + * @covers Language::formatNum + * @todo split into a test and a dataprovider + */ + public function testFormatNum() { + $this->assertEquals( '1.234.567', $this->getLang()->formatNum( '1234567' ) ); + $this->assertEquals( '12.345', $this->getLang()->formatNum( '12345' ) ); + $this->assertEquals( '1', $this->getLang()->formatNum( '1' ) ); + $this->assertEquals( '123', $this->getLang()->formatNum( '123' ) ); + $this->assertEquals( '1.234', $this->getLang()->formatNum( '1234' ) ); + $this->assertEquals( '12.345,56', $this->getLang()->formatNum( '12345.56' ) ); + $this->assertEquals( ',1234556', $this->getLang()->formatNum( '.1234556' ) ); } } diff --git a/tests/phpunit/languages/LanguageNsoTest.php b/tests/phpunit/languages/LanguageNsoTest.php index ea393628..18efd736 100644 --- a/tests/phpunit/languages/LanguageNsoTest.php +++ b/tests/phpunit/languages/LanguageNsoTest.php @@ -6,27 +6,29 @@ */ /** Tests for MediaWiki languages/classes/LanguageNso.php */ -class LanguageNsoTest extends MediaWikiTestCase { - private $lang; - - function setUp() { - $this->lang = Language::factory( 'nso' ); - } - function tearDown() { - unset( $this->lang ); +class LanguageNsoTest extends LanguageClassesTestCase { + /** + * @dataProvider providePlural + * @covers Language::convertPlural + */ + public function testPlural( $result, $value ) { + $forms = array( 'one', 'other' ); + $this->assertEquals( $result, $this->getLang()->convertPlural( $value, $forms ) ); } - /** @dataProvider providerPlural */ - function testPlural( $result, $value ) { - $forms = array( 'one', 'many' ); - $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) ); + /** + * @dataProvider providePlural + * @covers Language::getPluralRuleType + */ + public function testGetPluralRuleType( $result, $value ) { + $this->assertEquals( $result, $this->getLang()->getPluralRuleType( $value ) ); } - function providerPlural() { - return array ( - array( 'one', 0 ), - array( 'one', 1 ), - array( 'many', 2 ), + public static function providePlural() { + return array( + array( 'one', 0 ), + array( 'one', 1 ), + array( 'other', 2 ), ); } } diff --git a/tests/phpunit/languages/LanguagePlTest.php b/tests/phpunit/languages/LanguagePlTest.php index e56d4b77..d180037b 100644 --- a/tests/phpunit/languages/LanguagePlTest.php +++ b/tests/phpunit/languages/LanguagePlTest.php @@ -6,67 +6,72 @@ */ /** Tests for MediaWiki languages/classes/LanguagePl.php */ -class LanguagePlTest extends MediaWikiTestCase { - private $lang; - - function setUp() { - $this->lang = Language::factory( 'pl' ); - } - function tearDown() { - unset( $this->lang ); +class LanguagePlTest extends LanguageClassesTestCase { + /** + * @dataProvider providePlural + * @covers Language::convertPlural + */ + public function testPlural( $result, $value ) { + $forms = array( 'one', 'few', 'many' ); + $this->assertEquals( $result, $this->getLang()->convertPlural( $value, $forms ) ); } - /** @dataProvider providerPluralFourForms */ - function testPluralFourForms( $result, $value ) { - $forms = array( 'one', 'few', 'many' ); - $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) ); + /** + * @dataProvider providePlural + * @covers Language::getPluralRuleType + */ + public function testGetPluralRuleType( $result, $value ) { + $this->assertEquals( $result, $this->getLang()->getPluralRuleType( $value ) ); } - function providerPluralFourForms() { - return array ( - array( 'many', 0 ), - array( 'one', 1 ), - array( 'few', 2 ), - array( 'few', 3 ), - array( 'few', 4 ), - array( 'many', 5 ), - array( 'many', 9 ), - array( 'many', 10 ), - array( 'many', 11 ), - array( 'many', 21 ), - array( 'few', 22 ), - array( 'few', 23 ), - array( 'few', 24 ), - array( 'many', 25 ), - array( 'many', 200 ), - array( 'many', 201 ), + public static function providePlural() { + return array( + array( 'many', 0 ), + array( 'one', 1 ), + array( 'few', 2 ), + array( 'few', 3 ), + array( 'few', 4 ), + array( 'many', 5 ), + array( 'many', 9 ), + array( 'many', 10 ), + array( 'many', 11 ), + array( 'many', 21 ), + array( 'few', 22 ), + array( 'few', 23 ), + array( 'few', 24 ), + array( 'many', 25 ), + array( 'many', 200 ), + array( 'many', 201 ), ); } - /** @dataProvider providerPlural */ - function testPlural( $result, $value ) { - $forms = array( 'one', 'many' ); - $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) ); + /** + * @dataProvider providePluralTwoForms + * @covers Language::convertPlural + */ + public function testPluralTwoForms( $result, $value ) { + $forms = array( 'one', 'other' ); + $this->assertEquals( $result, $this->getLang()->convertPlural( $value, $forms ) ); } - function providerPlural() { - return array ( - array( 'many', 0 ), - array( 'one', 1 ), - array( 'many', 2 ), - array( 'many', 3 ), - array( 'many', 4 ), - array( 'many', 5 ), - array( 'many', 9 ), - array( 'many', 10 ), - array( 'many', 11 ), - array( 'many', 21 ), - array( 'many', 22 ), - array( 'many', 23 ), - array( 'many', 24 ), - array( 'many', 25 ), - array( 'many', 200 ), - array( 'many', 201 ), + public static function providePluralTwoForms() { + return array( + array( 'other', 0 ), + array( 'one', 1 ), + array( 'other', 2 ), + array( 'other', 3 ), + array( 'other', 4 ), + array( 'other', 5 ), + array( 'other', 9 ), + array( 'other', 10 ), + array( 'other', 11 ), + array( 'other', 21 ), + array( 'other', 22 ), + array( 'other', 23 ), + array( 'other', 24 ), + array( 'other', 25 ), + array( 'other', 200 ), + array( 'other', 201 ), ); } } diff --git a/tests/phpunit/languages/LanguageRoTest.php b/tests/phpunit/languages/LanguageRoTest.php index 5270f6fe..ae7816bc 100644 --- a/tests/phpunit/languages/LanguageRoTest.php +++ b/tests/phpunit/languages/LanguageRoTest.php @@ -6,37 +6,39 @@ */ /** Tests for MediaWiki languages/classes/LanguageRo.php */ -class LanguageRoTest extends MediaWikiTestCase { - private $lang; - - function setUp() { - $this->lang = Language::factory( 'ro' ); - } - function tearDown() { - unset( $this->lang ); +class LanguageRoTest extends LanguageClassesTestCase { + /** + * @dataProvider providePlural + * @covers Language::convertPlural + */ + public function testPlural( $result, $value ) { + $forms = array( 'one', 'few', 'other' ); + $this->assertEquals( $result, $this->getLang()->convertPlural( $value, $forms ) ); } - /** @dataProvider providerPlural */ - function testPlural( $result, $value ) { - $forms = array( 'one', 'few', 'other' ); - $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) ); + /** + * @dataProvider providePlural + * @covers Language::getPluralRuleType + */ + public function testGetPluralRuleType( $result, $value ) { + $this->assertEquals( $result, $this->getLang()->getPluralRuleType( $value ) ); } - function providerPlural() { - return array ( - array( 'few', 0 ), - array( 'one', 1 ), - array( 'few', 2 ), - array( 'few', 19 ), + public static function providePlural() { + return array( + array( 'few', 0 ), + array( 'one', 1 ), + array( 'few', 2 ), + array( 'few', 19 ), array( 'other', 20 ), array( 'other', 99 ), array( 'other', 100 ), - array( 'few', 101 ), - array( 'few', 119 ), + array( 'few', 101 ), + array( 'few', 119 ), array( 'other', 120 ), array( 'other', 200 ), - array( 'few', 201 ), - array( 'few', 219 ), + array( 'few', 201 ), + array( 'few', 219 ), array( 'other', 220 ), ); } diff --git a/tests/phpunit/languages/LanguageRuTest.php b/tests/phpunit/languages/LanguageRuTest.php index 7a1f193b..e17c7085 100644 --- a/tests/phpunit/languages/LanguageRuTest.php +++ b/tests/phpunit/languages/LanguageRuTest.php @@ -7,48 +7,99 @@ */ /** Tests for MediaWiki languages/classes/LanguageRu.php */ -class LanguageRuTest extends MediaWikiTestCase { - private $lang; - - function setUp() { - $this->lang = Language::factory( 'ru' ); +class LanguageRuTest extends LanguageClassesTestCase { + /** + * @dataProvider providePlural + * @covers Language::convertPlural + */ + public function testPlural( $result, $value ) { + $forms = array( 'one', 'many', 'other' ); + $this->assertEquals( $result, $this->getLang()->convertPlural( $value, $forms ) ); } - function tearDown() { - unset( $this->lang ); + + /** + * Test explicit plural forms - n=FormN forms + * @covers Language::convertPlural + */ + public function testExplicitPlural() { + $forms = array( 'one','many', 'other', '12=dozen' ); + $this->assertEquals( 'dozen', $this->getLang()->convertPlural( 12, $forms ) ); + $forms = array( 'one', 'many', '100=hundred', 'other', '12=dozen' ); + $this->assertEquals( 'hundred', $this->getLang()->convertPlural( 100, $forms ) ); } - /** @dataProvider providePluralFourForms */ - function testPluralFourForms( $result, $value ) { - $forms = array( 'one', 'few', 'many', 'other' ); - $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) ); + /** + * @dataProvider providePlural + * @covers Language::getPluralRuleType + */ + public function testGetPluralRuleType( $result, $value ) { + $this->assertEquals( $result, $this->getLang()->getPluralRuleType( $value ) ); } - function providePluralFourForms() { - return array ( + public static function providePlural() { + return array( array( 'one', 1 ), array( 'many', 11 ), array( 'one', 91 ), array( 'one', 121 ), - array( 'few', 2 ), - array( 'few', 3 ), - array( 'few', 4 ), - array( 'few', 334 ), + array( 'other', 2 ), + array( 'other', 3 ), + array( 'other', 4 ), + array( 'other', 334 ), array( 'many', 5 ), array( 'many', 15 ), array( 'many', 120 ), ); } - /** @dataProvider providePluralTwoForms */ - function testPluralTwoForms( $result, $value ) { - $forms = array( 'one', 'several' ); - $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) ); + + /** + * @dataProvider providePluralTwoForms + * @covers Language::convertPlural + */ + public function testPluralTwoForms( $result, $value ) { + $forms = array( '1=one', 'other' ); + $this->assertEquals( $result, $this->getLang()->convertPlural( $value, $forms ) ); } - function providePluralTwoForms() { - return array ( + + public static function providePluralTwoForms() { + return array( array( 'one', 1 ), - array( 'several', 11 ), - array( 'several', 91 ), - array( 'several', 121 ), + array( 'other', 11 ), + array( 'other', 91 ), + array( 'other', 121 ), + ); + } + + /** + * @dataProvider providerGrammar + * @covers Language::convertGrammar + */ + public function testGrammar( $result, $word, $case ) { + $this->assertEquals( $result, $this->getLang()->convertGrammar( $word, $case ) ); + } + + public static function providerGrammar() { + return array( + array( + 'Википедии', + 'Википедия', + 'genitive', + ), + array( + 'Викитеки', + 'Викитека', + 'genitive', + ), + array( + 'Викитеке', + 'Викитека', + 'prepositional', + ), + array( + 'Викиданных', + 'Викиданные', + 'prepositional', + ), ); } } diff --git a/tests/phpunit/languages/LanguageSeTest.php b/tests/phpunit/languages/LanguageSeTest.php index 065ec29e..533aa2bc 100644 --- a/tests/phpunit/languages/LanguageSeTest.php +++ b/tests/phpunit/languages/LanguageSeTest.php @@ -6,41 +6,46 @@ */ /** Tests for MediaWiki languages/classes/LanguageSe.php */ -class LanguageSeTest extends MediaWikiTestCase { - private $lang; - - function setUp() { - $this->lang = Language::factory( 'se' ); - } - function tearDown() { - unset( $this->lang ); +class LanguageSeTest extends LanguageClassesTestCase { + /** + * @dataProvider providePlural + * @covers Language::convertPlural + */ + public function testPlural( $result, $value ) { + $forms = array( 'one', 'two', 'other' ); + $this->assertEquals( $result, $this->getLang()->convertPlural( $value, $forms ) ); } - /** @dataProvider providerPluralThreeForms */ - function testPluralThreeForms( $result, $value ) { - $forms = array( 'one', 'two', 'other' ); - $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) ); + /** + * @dataProvider providePlural + * @covers Language::getPluralRuleType + */ + public function testGetPluralRuleType( $result, $value ) { + $this->assertEquals( $result, $this->getLang()->getPluralRuleType( $value ) ); } - function providerPluralThreeForms() { - return array ( + public static function providePlural() { + return array( array( 'other', 0 ), - array( 'one', 1 ), - array( 'two', 2 ), + array( 'one', 1 ), + array( 'two', 2 ), array( 'other', 3 ), ); } - /** @dataProvider providerPlural */ - function testPlural( $result, $value ) { + /** + * @dataProvider providePluralTwoForms + * @covers Language::convertPlural + */ + public function testPluralTwoForms( $result, $value ) { $forms = array( 'one', 'other' ); - $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) ); + $this->assertEquals( $result, $this->getLang()->convertPlural( $value, $forms ) ); } - function providerPlural() { - return array ( + public static function providePluralTwoForms() { + return array( array( 'other', 0 ), - array( 'one', 1 ), + array( 'one', 1 ), array( 'other', 2 ), array( 'other', 3 ), ); diff --git a/tests/phpunit/languages/LanguageSgsTest.php b/tests/phpunit/languages/LanguageSgsTest.php index 931c82f0..fa49a4dd 100644 --- a/tests/phpunit/languages/LanguageSgsTest.php +++ b/tests/phpunit/languages/LanguageSgsTest.php @@ -5,51 +5,56 @@ * @file */ -/** Tests for MediaWiki languages/classes/LanguageSgs.php */ -class LanguageSgsTest extends MediaWikiTestCase { - private $lang; - - function setUp() { - $this->lang = Language::factory( 'Sgs' ); - } - function tearDown() { - unset( $this->lang ); +/** Tests for Samogitian */ +class LanguageSgsTest extends LanguageClassesTestCase { + /** + * @dataProvider providePluralAllForms + * @covers Language::convertPlural + */ + public function testPluralAllForms( $result, $value ) { + $forms = array( 'one', 'two', 'few', 'other' ); + $this->assertEquals( $result, $this->getLang()->convertPlural( $value, $forms ) ); } - /** @dataProvider providePluralAllForms */ - function testPluralAllForms( $result, $value ) { - $forms = array( 'one', 'few', 'many', 'other' ); - $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) ); + /** + * @dataProvider providePluralAllForms + * @covers Language::getPluralRuleType + */ + public function testGetPluralRuleType( $result, $value ) { + $this->assertEquals( $result, $this->getLang()->getPluralRuleType( $value ) ); } - function providePluralAllForms() { - return array ( - array( 'many', 0 ), - array( 'one', 1 ), - array( 'few', 2 ), + public static function providePluralAllForms() { + return array( + array( 'few', 0 ), + array( 'one', 1 ), + array( 'two', 2 ), array( 'other', 3 ), - array( 'many', 10 ), - array( 'many', 11 ), - array( 'many', 12 ), - array( 'many', 19 ), + array( 'few', 10 ), + array( 'few', 11 ), + array( 'few', 12 ), + array( 'few', 19 ), array( 'other', 20 ), - array( 'many', 100 ), - array( 'one', 101 ), - array( 'many', 111 ), - array( 'many', 112 ), + array( 'few', 100 ), + array( 'one', 101 ), + array( 'few', 111 ), + array( 'few', 112 ), ); } - /** @dataProvider providePluralTwoForms */ - function testPluralTwoForms( $result, $value ) { - $forms = array( 'one', 'other' ); - $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) ); + /** + * @dataProvider providePluralTwoForms + * @covers Language::convertPlural + */ + public function testPluralTwoForms( $result, $value ) { + $forms = array( 'one', 'other' ); + $this->assertEquals( $result, $this->getLang()->convertPlural( $value, $forms ) ); } - function providePluralTwoForms() { - return array ( + public static function providePluralTwoForms() { + return array( array( 'other', 0 ), - array( 'one', 1 ), + array( 'one', 1 ), array( 'other', 2 ), array( 'other', 3 ), array( 'other', 10 ), @@ -58,7 +63,7 @@ class LanguageSgsTest extends MediaWikiTestCase { array( 'other', 19 ), array( 'other', 20 ), array( 'other', 100 ), - array( 'one', 101 ), + array( 'one', 101 ), array( 'other', 111 ), array( 'other', 112 ), ); diff --git a/tests/phpunit/languages/LanguageShTest.php b/tests/phpunit/languages/LanguageShTest.php index b8169aed..1b390872 100644 --- a/tests/phpunit/languages/LanguageShTest.php +++ b/tests/phpunit/languages/LanguageShTest.php @@ -5,28 +5,38 @@ * @file */ -/** Tests for MediaWiki languages/classes/LanguageSh.php */ -class LanguageShTest extends MediaWikiTestCase { - private $lang; - - function setUp() { - $this->lang = Language::factory( 'sh' ); - } - function tearDown() { - unset( $this->lang ); +/** Tests for srpskohrvatski / српскохрватски / Serbocroatian */ +class LanguageShTest extends LanguageClassesTestCase { + /** + * @dataProvider providePlural + * @covers Language::convertPlural + */ + public function testPlural( $result, $value ) { + $forms = array( 'one', 'few', 'other' ); + $this->assertEquals( $result, $this->getLang()->convertPlural( $value, $forms ) ); } - /** @dataProvider providerPlural */ - function testPlural( $result, $value ) { - $forms = array( 'one', 'many' ); - $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) ); + /** + * @dataProvider providePlural + * @covers Language::getPluralRuleType + */ + public function testGetPluralRuleType( $result, $value ) { + $this->assertEquals( $result, $this->getLang()->getPluralRuleType( $value ) ); } - function providerPlural() { - return array ( - array( 'many', 0 ), - array( 'one', 1 ), - array( 'many', 2 ), + public static function providePlural() { + return array( + array( 'other', 0 ), + array( 'one', 1 ), + array( 'few', 2 ), + array( 'few', 4 ), + array( 'other', 5 ), + array( 'other', 10 ), + array( 'other', 11 ), + array( 'other', 12 ), + array( 'one', 101 ), + array( 'few', 102 ), + array( 'other', 111 ), ); } } diff --git a/tests/phpunit/languages/LanguageSkTest.php b/tests/phpunit/languages/LanguageSkTest.php index 4cfd840e..cb8a13b8 100644 --- a/tests/phpunit/languages/LanguageSkTest.php +++ b/tests/phpunit/languages/LanguageSkTest.php @@ -7,24 +7,26 @@ */ /** Tests for MediaWiki languages/classes/LanguageSk.php */ -class LanguageSkTest extends MediaWikiTestCase { - private $lang; - - function setUp() { - $this->lang = Language::factory( 'sk' ); - } - function tearDown() { - unset( $this->lang ); +class LanguageSkTest extends LanguageClassesTestCase { + /** + * @dataProvider providePlural + * @covers Language::convertPlural + */ + public function testPlural( $result, $value ) { + $forms = array( 'one', 'few', 'other' ); + $this->assertEquals( $result, $this->getLang()->convertPlural( $value, $forms ) ); } - /** @dataProvider providerPlural */ - function testPlural( $result, $value ) { - $forms = array( 'one', 'few', 'other' ); - $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) ); + /** + * @dataProvider providePlural + * @covers Language::getPluralRuleType + */ + public function testGetPluralRuleType( $result, $value ) { + $this->assertEquals( $result, $this->getLang()->getPluralRuleType( $value ) ); } - function providerPlural() { - return array ( + public static function providePlural() { + return array( array( 'other', 0 ), array( 'one', 1 ), array( 'few', 2 ), diff --git a/tests/phpunit/languages/LanguageSlTest.php b/tests/phpunit/languages/LanguageSlTest.php index c1f75691..9783dd80 100644 --- a/tests/phpunit/languages/LanguageSlTest.php +++ b/tests/phpunit/languages/LanguageSlTest.php @@ -7,36 +7,38 @@ */ /** Tests for MediaWiki languages/classes/LanguageSl.php */ -class LanguageSlTest extends MediaWikiTestCase { - private $lang; - - function setUp() { - $this->lang = Language::factory( 'sl' ); - } - function tearDown() { - unset( $this->lang ); +class LanguageSlTest extends LanguageClassesTestCase { + /** + * @dataProvider providerPlural + * @covers Language::convertPlural + */ + public function testPlural( $result, $value ) { + $forms = array( 'one', 'two', 'few', 'other' ); + $this->assertEquals( $result, $this->getLang()->convertPlural( $value, $forms ) ); } - /** @dataProvider providerPlural */ - function testPlural( $result, $value ) { - $forms = array( 'one', 'two', 'few', 'other', 'zero' ); - $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) ); + /** + * @dataProvider providerPlural + * @covers Language::getPluralRuleType + */ + public function testGetPluralRuleType( $result, $value ) { + $this->assertEquals( $result, $this->getLang()->getPluralRuleType( $value ) ); } - function providerPlural() { - return array ( - array( 'zero', 0 ), - array( 'one', 1 ), - array( 'two', 2 ), - array( 'few', 3 ), - array( 'few', 4 ), + public static function providerPlural() { + return array( + array( 'other', 0 ), + array( 'one', 1 ), + array( 'two', 2 ), + array( 'few', 3 ), + array( 'few', 4 ), array( 'other', 5 ), array( 'other', 99 ), array( 'other', 100 ), - array( 'one', 101 ), - array( 'two', 102 ), - array( 'few', 103 ), - array( 'one', 201 ), + array( 'one', 101 ), + array( 'two', 102 ), + array( 'few', 103 ), + array( 'one', 201 ), ); } } diff --git a/tests/phpunit/languages/LanguageSmaTest.php b/tests/phpunit/languages/LanguageSmaTest.php index b7e72e97..95cb333c 100644 --- a/tests/phpunit/languages/LanguageSmaTest.php +++ b/tests/phpunit/languages/LanguageSmaTest.php @@ -6,41 +6,46 @@ */ /** Tests for MediaWiki languages/classes/LanguageSma.php */ -class LanguageSmaTest extends MediaWikiTestCase { - private $lang; - - function setUp() { - $this->lang = Language::factory( 'sma' ); - } - function tearDown() { - unset( $this->lang ); +class LanguageSmaTest extends LanguageClassesTestCase { + /** + * @dataProvider providePlural + * @covers Language::convertPlural + */ + public function testPlural( $result, $value ) { + $forms = array( 'one', 'two', 'other' ); + $this->assertEquals( $result, $this->getLang()->convertPlural( $value, $forms ) ); } - /** @dataProvider providerPluralThreeForms */ - function testPluralThreeForms( $result, $value ) { - $forms = array( 'one', 'two', 'other' ); - $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) ); + /** + * @dataProvider providePlural + * @covers Language::getPluralRuleType + */ + public function testGetPluralRuleType( $result, $value ) { + $this->assertEquals( $result, $this->getLang()->getPluralRuleType( $value ) ); } - function providerPluralThreeForms() { - return array ( + public static function providePlural() { + return array( array( 'other', 0 ), - array( 'one', 1 ), - array( 'two', 2 ), + array( 'one', 1 ), + array( 'two', 2 ), array( 'other', 3 ), ); } - /** @dataProvider providerPlural */ - function testPlural( $result, $value ) { + /** + * @dataProvider providePluralTwoForms + * @covers Language::convertPlural + */ + public function testPluralTwoForms( $result, $value ) { $forms = array( 'one', 'other' ); - $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) ); + $this->assertEquals( $result, $this->getLang()->convertPlural( $value, $forms ) ); } - function providerPlural() { - return array ( + public static function providePluralTwoForms() { + return array( array( 'other', 0 ), - array( 'one', 1 ), + array( 'one', 1 ), array( 'other', 2 ), array( 'other', 3 ), ); diff --git a/tests/phpunit/languages/LanguageSrTest.php b/tests/phpunit/languages/LanguageSrTest.php index d44ecf8e..d6fedb57 100644 --- a/tests/phpunit/languages/LanguageSrTest.php +++ b/tests/phpunit/languages/LanguageSrTest.php @@ -10,25 +10,18 @@ * @author Antoine Musso <hashar at free dot fr> * @copyright Copyright © 2011, Antoine Musso <hashar at free dot fr> * @file + * + * @todo methods in test class should be tidied: + * - Should be split into separate test methods and data providers + * - Tests for LanguageConverter and Language should probably be separate.. */ -require_once dirname( __DIR__ ) . '/bootstrap.php'; - /** Tests for MediaWiki languages/LanguageSr.php */ -class LanguageSrTest extends MediaWikiTestCase { - /* Language object. Initialized before each test */ - private $lang; - - function setUp() { - $this->lang = Language::factory( 'sr' ); - } - function tearDown() { - unset( $this->lang ); - } - - ##### TESTS ####################################################### - - function testEasyConversions( ) { +class LanguageSrTest extends LanguageClassesTestCase { + /** + * @covers LanguageConverter::convertTo + */ + public function testEasyConversions() { $this->assertCyrillic( 'шђчћжШЂЧЋЖ', 'Cyrillic guessing characters' @@ -39,7 +32,10 @@ class LanguageSrTest extends MediaWikiTestCase { ); } - function testMixedConversions() { + /** + * @covers LanguageConverter::convertTo + */ + public function testMixedConversions() { $this->assertCyrillic( 'шђчћжШЂЧЋЖ - šđčćž', 'Mostly cyrillic characters' @@ -50,7 +46,10 @@ class LanguageSrTest extends MediaWikiTestCase { ); } - function testSameAmountOfLatinAndCyrillicGetConverted() { + /** + * @covers LanguageConverter::convertTo + */ + public function testSameAmountOfLatinAndCyrillicGetConverted() { $this->assertConverted( '4 latin: šđčć | 4 cyrillic: шђчћ', 'sr-ec' @@ -63,8 +62,9 @@ class LanguageSrTest extends MediaWikiTestCase { /** * @author Nikola Smolenski + * @covers LanguageConverter::convertTo */ - function testConversionToCyrillic() { + public function testConversionToCyrillic() { //A simple convertion of Latin to Cyrillic $this->assertEquals( 'абвг', $this->convertToCyrillic( 'abvg' ) @@ -103,7 +103,10 @@ class LanguageSrTest extends MediaWikiTestCase { ); } - function testConversionToLatin() { + /** + * @covers LanguageConverter::convertTo + */ + public function testConversionToLatin() { //A simple convertion of Latin to Latin $this->assertEquals( 'abcd', $this->convertToLatin( 'abcd' ) @@ -122,38 +125,55 @@ class LanguageSrTest extends MediaWikiTestCase { ); } - /** @dataProvider providePluralFourForms */ - function testPluralFourForms( $result, $value ) { - $forms = array( 'one', 'few', 'many', 'other' ); - $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) ); + /** + * @dataProvider providePlural + * @covers Language::convertPlural + */ + public function testPlural( $result, $value ) { + $forms = array( 'one', 'few', 'other' ); + $this->assertEquals( $result, $this->getLang()->convertPlural( $value, $forms ) ); + } + + /** + * @dataProvider providePlural + * @covers Language::getPluralRuleType + */ + public function testGetPluralRuleType( $result, $value ) { + $this->assertEquals( $result, $this->getLang()->getPluralRuleType( $value ) ); } - function providePluralFourForms() { - return array ( + public static function providePlural() { + return array( array( 'one', 1 ), - array( 'many', 11 ), + array( 'other', 11 ), array( 'one', 91 ), array( 'one', 121 ), array( 'few', 2 ), array( 'few', 3 ), array( 'few', 4 ), array( 'few', 334 ), - array( 'many', 5 ), - array( 'many', 15 ), - array( 'many', 120 ), + array( 'other', 5 ), + array( 'other', 15 ), + array( 'other', 120 ), ); } - /** @dataProvider providePluralTwoForms */ - function testPluralTwoForms( $result, $value ) { - $forms = array( 'one', 'several' ); - $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) ); + + /** + * @dataProvider providePluralTwoForms + * @covers Language::convertPlural + */ + public function testPluralTwoForms( $result, $value ) { + $forms = array( 'one', 'other' ); + $this->assertEquals( $result, $this->getLang()->convertPlural( $value, $forms ) ); } - function providePluralTwoForms() { - return array ( + + public static function providePluralTwoForms() { + return array( array( 'one', 1 ), - array( 'several', 11 ), - array( 'several', 91 ), - array( 'several', 121 ), + array( 'other', 11 ), + array( 'other', 4 ), + array( 'one', 91 ), + array( 'one', 121 ), ); } @@ -164,20 +184,21 @@ class LanguageSrTest extends MediaWikiTestCase { * @param $variant string Language variant 'sr-ec' or 'sr-el' * @param $msg string Optional message */ - function assertUnConverted( $text, $variant, $msg = '' ) { + protected function assertUnConverted( $text, $variant, $msg = '' ) { $this->assertEquals( $text, $this->convertTo( $text, $variant ), $msg ); } + /** * Wrapper to verify a text is different once converted to a variant. * @param $text string Text to convert * @param $variant string Language variant 'sr-ec' or 'sr-el' * @param $msg string Optional message */ - function assertConverted( $text, $variant, $msg = '' ) { + protected function assertConverted( $text, $variant, $msg = '' ) { $this->assertNotEquals( $text, $this->convertTo( $text, $variant ), @@ -190,34 +211,36 @@ class LanguageSrTest extends MediaWikiTestCase { * using the cyrillic variant and converted to Latin when using * the Latin variant. */ - function assertCyrillic( $text, $msg = '' ) { + protected function assertCyrillic( $text, $msg = '' ) { $this->assertUnConverted( $text, 'sr-ec', $msg ); $this->assertConverted( $text, 'sr-el', $msg ); } + /** * Verifiy the given Latin text is not converted when using * using the Latin variant and converted to Cyrillic when using * the Cyrillic variant. */ - function assertLatin( $text, $msg = '' ) { + protected function assertLatin( $text, $msg = '' ) { $this->assertUnConverted( $text, 'sr-el', $msg ); $this->assertConverted( $text, 'sr-ec', $msg ); } /** Wrapper for converter::convertTo() method*/ - function convertTo( $text, $variant ) { - return $this - ->lang + protected function convertTo( $text, $variant ) { + return $this->getLang() ->mConverter ->convertTo( $text, $variant ); } - function convertToCyrillic( $text ) { + + protected function convertToCyrillic( $text ) { return $this->convertTo( $text, 'sr-ec' ); } - function convertToLatin( $text ) { + + protected function convertToLatin( $text ) { return $this->convertTo( $text, 'sr-el' ); } } diff --git a/tests/phpunit/languages/LanguageTest.php b/tests/phpunit/languages/LanguageTest.php index 2fa3e292..78929e23 100644 --- a/tests/phpunit/languages/LanguageTest.php +++ b/tests/phpunit/languages/LanguageTest.php @@ -1,23 +1,14 @@ <?php -class LanguageTest extends MediaWikiTestCase { - +class LanguageTest extends LanguageClassesTestCase { /** - * @var Language + * @covers Language::convertDoubleWidth + * @covers Language::normalizeForSearch */ - private $lang; - - function setUp() { - $this->lang = Language::factory( 'en' ); - } - function tearDown() { - unset( $this->lang ); - } - - function testLanguageConvertDoubleWidthToSingleWidth() { + public function testLanguageConvertDoubleWidthToSingleWidth() { $this->assertEquals( "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz", - $this->lang->normalizeForSearch( + $this->getLang()->normalizeForSearch( "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" ), 'convertDoubleWidth() with the full alphabet and digits' @@ -25,13 +16,14 @@ class LanguageTest extends MediaWikiTestCase { } /** - * @dataProvider provideFormattableTimes + * @dataProvider provideFormattableTimes# + * @covers Language::formatTimePeriod */ - function testFormatTimePeriod( $seconds, $format, $expected, $desc ) { - $this->assertEquals( $expected, $this->lang->formatTimePeriod( $seconds, $format ), $desc ); + public function testFormatTimePeriod( $seconds, $format, $expected, $desc ) { + $this->assertEquals( $expected, $this->getLang()->formatTimePeriod( $seconds, $format ), $desc ); } - function provideFormattableTimes() { + public static function provideFormattableTimes() { return array( array( 9.45, @@ -214,56 +206,59 @@ class LanguageTest extends MediaWikiTestCase { 'formatTimePeriod() rounding, recursion, (>48h)' ), ); - } - function testTruncate() { + /** + * @covers Language::truncate + */ + public function testTruncate() { $this->assertEquals( "XXX", - $this->lang->truncate( "1234567890", 0, 'XXX' ), + $this->getLang()->truncate( "1234567890", 0, 'XXX' ), 'truncate prefix, len 0, small ellipsis' ); $this->assertEquals( "12345XXX", - $this->lang->truncate( "1234567890", 8, 'XXX' ), + $this->getLang()->truncate( "1234567890", 8, 'XXX' ), 'truncate prefix, small ellipsis' ); $this->assertEquals( "123456789", - $this->lang->truncate( "123456789", 5, 'XXXXXXXXXXXXXXX' ), + $this->getLang()->truncate( "123456789", 5, 'XXXXXXXXXXXXXXX' ), 'truncate prefix, large ellipsis' ); $this->assertEquals( "XXX67890", - $this->lang->truncate( "1234567890", -8, 'XXX' ), + $this->getLang()->truncate( "1234567890", -8, 'XXX' ), 'truncate suffix, small ellipsis' ); $this->assertEquals( "123456789", - $this->lang->truncate( "123456789", -5, 'XXXXXXXXXXXXXXX' ), + $this->getLang()->truncate( "123456789", -5, 'XXXXXXXXXXXXXXX' ), 'truncate suffix, large ellipsis' ); } /** - * @dataProvider provideHTMLTruncateData() + * @dataProvider provideHTMLTruncateData + * @covers Language::truncateHTML */ - function testTruncateHtml( $len, $ellipsis, $input, $expected ) { + public function testTruncateHtml( $len, $ellipsis, $input, $expected ) { // Actual HTML... $this->assertEquals( $expected, - $this->lang->truncateHTML( $input, $len, $ellipsis ) + $this->getLang()->truncateHTML( $input, $len, $ellipsis ) ); } /** - * Array format is ($len, $ellipsis, $input, $expected) + * @return array format is ($len, $ellipsis, $input, $expected) */ - function provideHTMLTruncateData() { + public static function provideHTMLTruncateData() { return array( array( 0, 'XXX', "1234567890", "XXX" ), array( 8, 'XXX', "1234567890", "12345XXX" ), @@ -320,29 +315,148 @@ class LanguageTest extends MediaWikiTestCase { } /** + * Test Language::isWellFormedLanguageTag() + * @dataProvider provideWellFormedLanguageTags + * @covers Language::isWellFormedLanguageTag + */ + public function testWellFormedLanguageTag( $code, $message = '' ) { + $this->assertTrue( + Language::isWellFormedLanguageTag( $code ), + "validating code $code $message" + ); + } + + /** + * The test cases are based on the tests in the GaBuZoMeu parser + * written by Stéphane Bortzmeyer <bortzmeyer@nic.fr> + * and distributed as free software, under the GNU General Public Licence. + * http://www.bortzmeyer.org/gabuzomeu-parsing-language-tags.html + */ + public static function provideWellFormedLanguageTags() { + return array( + array( 'fr', 'two-letter code' ), + array( 'fr-latn', 'two-letter code with lower case script code' ), + array( 'fr-Latn-FR', 'two-letter code with title case script code and uppercase country code' ), + array( 'fr-Latn-419', 'two-letter code with title case script code and region number' ), + array( 'fr-FR', 'two-letter code with uppercase' ), + array( 'ax-TZ', 'Not in the registry, but well-formed' ), + array( 'fr-shadok', 'two-letter code with variant' ), + array( 'fr-y-myext-myext2', 'non-x singleton' ), + array( 'fra-Latn', 'ISO 639 can be 3-letters' ), + array( 'fra', 'three-letter language code' ), + array( 'fra-FX', 'three-letter language code with country code' ), + array( 'i-klingon', 'grandfathered with singleton' ), + array( 'I-kLINgon', 'tags are case-insensitive...' ), + array( 'no-bok', 'grandfathered without singleton' ), + array( 'i-enochian', 'Grandfathered' ), + array( 'x-fr-CH', 'private use' ), + array( 'es-419', 'two-letter code with region number' ), + array( 'en-Latn-GB-boont-r-extended-sequence-x-private', 'weird, but well-formed' ), + array( 'ab-x-abc-x-abc', 'anything goes after x' ), + array( 'ab-x-abc-a-a', 'anything goes after x, including several non-x singletons' ), + array( 'i-default', 'grandfathered' ), + array( 'abcd-Latn', 'Language of 4 chars reserved for future use' ), + array( 'AaBbCcDd-x-y-any-x', 'Language of 5-8 chars, registered' ), + array( 'de-CH-1901', 'with country and year' ), + array( 'en-US-x-twain', 'with country and singleton' ), + array( 'zh-cmn', 'three-letter variant' ), + array( 'zh-cmn-Hant', 'three-letter variant and script' ), + array( 'zh-cmn-Hant-HK', 'three-letter variant, script and country' ), + array( 'xr-p-lze', 'Extension' ), + ); + } + + /** + * Negative test for Language::isWellFormedLanguageTag() + * @dataProvider provideMalformedLanguageTags + * @covers Language::isWellFormedLanguageTag + */ + public function testMalformedLanguageTag( $code, $message = '' ) { + $this->assertFalse( + Language::isWellFormedLanguageTag( $code ), + "validating that code $code is a malformed language tag - $message" + ); + } + + /** + * The test cases are based on the tests in the GaBuZoMeu parser + * written by Stéphane Bortzmeyer <bortzmeyer@nic.fr> + * and distributed as free software, under the GNU General Public Licence. + * http://www.bortzmeyer.org/gabuzomeu-parsing-language-tags.html + */ + public static function provideMalformedLanguageTags() { + return array( + array( 'f', 'language too short' ), + array( 'f-Latn', 'language too short with script' ), + array( 'xr-lxs-qut', 'variants too short' ), # extlangS + array( 'fr-Latn-F', 'region too short' ), + array( 'a-value', 'language too short with region' ), + array( 'tlh-a-b-foo', 'valid three-letter with wrong variant' ), + array( 'i-notexist', 'grandfathered but not registered: invalid, even if we only test well-formedness' ), + array( 'abcdefghi-012345678', 'numbers too long' ), + array( 'ab-abc-abc-abc-abc', 'invalid extensions' ), + array( 'ab-abcd-abc', 'invalid extensions' ), + array( 'ab-ab-abc', 'invalid extensions' ), + array( 'ab-123-abc', 'invalid extensions' ), + array( 'a-Hant-ZH', 'short language with valid extensions' ), + array( 'a1-Hant-ZH', 'invalid character in language' ), + array( 'ab-abcde-abc', 'invalid extensions' ), + array( 'ab-1abc-abc', 'invalid characters in extensions' ), + array( 'ab-ab-abcd', 'invalid order of extensions' ), + array( 'ab-123-abcd', 'invalid order of extensions' ), + array( 'ab-abcde-abcd', 'invalid extensions' ), + array( 'ab-1abc-abcd', 'invalid characters in extensions' ), + array( 'ab-a-b', 'extensions too short' ), + array( 'ab-a-x', 'extensions too short, even with singleton' ), + array( 'ab--ab', 'two separators' ), + array( 'ab-abc-', 'separator in the end' ), + array( '-ab-abc', 'separator in the beginning' ), + array( 'abcd-efg', 'language too long' ), + array( 'aabbccddE', 'tag too long' ), + array( 'pa_guru', 'A tag with underscore is invalid in strict mode' ), + array( 'de-f', 'subtag too short' ), + ); + } + + /** + * Negative test for Language::isWellFormedLanguageTag() + * @covers Language::isWellFormedLanguageTag + */ + public function testLenientLanguageTag() { + $this->assertTrue( + Language::isWellFormedLanguageTag( 'pa_guru', true ), + 'pa_guru is a well-formed language tag in lenient mode' + ); + } + + /** * Test Language::isValidBuiltInCode() * @dataProvider provideLanguageCodes + * @covers Language::isValidBuiltInCode */ - function testBuiltInCodeValidation( $code, $message = '' ) { + public function testBuiltInCodeValidation( $code, $message = '' ) { $this->assertTrue( - (bool) Language::isValidBuiltInCode( $code ), + (bool)Language::isValidBuiltInCode( $code ), "validating code $code $message" ); } - function testBuiltInCodeValidationRejectUnderscore() { + /** + * @covers Language::isValidBuiltInCode + */ + public function testBuiltInCodeValidationRejectUnderscore() { $this->assertFalse( - (bool) Language::isValidBuiltInCode( 'be_tarask' ), + (bool)Language::isValidBuiltInCode( 'be_tarask' ), "reject underscore in language code" ); } - function provideLanguageCodes() { + public static function provideLanguageCodes() { return array( - array( 'fr' , 'Two letters, minor case' ), - array( 'EN' , 'Two letters, upper case' ), - array( 'tyv' , 'Three letters' ), - array( 'tokipona' , 'long language code' ), + array( 'fr', 'Two letters, minor case' ), + array( 'EN', 'Two letters, upper case' ), + array( 'tyv', 'Three letters' ), + array( 'tokipona', 'long language code' ), array( 'be-tarask', 'With dash' ), array( 'Zh-classical', 'Begin with upper case, dash' ), array( 'Be-x-old', 'With extension (two dashes)' ), @@ -350,20 +464,103 @@ class LanguageTest extends MediaWikiTestCase { } /** + * Test Language::isKnownLanguageTag() + * @dataProvider provideKnownLanguageTags + * @covers Language::isKnownLanguageTag + */ + public function testKnownLanguageTag( $code, $message = '' ) { + $this->assertTrue( + (bool)Language::isKnownLanguageTag( $code ), + "validating code $code - $message" + ); + } + + public static function provideKnownLanguageTags() { + return array( + array( 'fr', 'simple code' ), + array( 'bat-smg', 'an MW legacy tag' ), + array( 'sgs', 'an internal standard MW name, for which a legacy tag is used externally' ), + ); + } + + /** + * @covers Language::isKnownLanguageTag + */ + public function testKnownCldrLanguageTag() { + if ( !class_exists( 'LanguageNames' ) ) { + $this->markTestSkipped( 'The LanguageNames class is not available. The cldr extension is probably not installed.' ); + } + + $this->assertTrue( + (bool)Language::isKnownLanguageTag( 'pal' ), + 'validating code "pal" an ancient language, which probably will not appear in Names.php, but appears in CLDR in English' + ); + } + + /** + * Negative tests for Language::isKnownLanguageTag() + * @dataProvider provideUnKnownLanguageTags + * @covers Language::isKnownLanguageTag + */ + public function testUnknownLanguageTag( $code, $message = '' ) { + $this->assertFalse( + (bool)Language::isKnownLanguageTag( $code ), + "checking that code $code is invalid - $message" + ); + } + + public static function provideUnknownLanguageTags() { + return array( + array( 'mw', 'non-existent two-letter code' ), + array( 'foo"<bar', 'very invalid language code' ), + ); + } + + /** + * Test too short timestamp + * @expectedException MWException + * @covers Language::sprintfDate + */ + public function testSprintfDateTooShortTimestamp() { + $this->getLang()->sprintfDate( 'xiY', '1234567890123' ); + } + + /** + * Test too long timestamp + * @expectedException MWException + * @covers Language::sprintfDate + */ + public function testSprintfDateTooLongTimestamp() { + $this->getLang()->sprintfDate( 'xiY', '123456789012345' ); + } + + /** + * Test too short timestamp + * @expectedException MWException + * @covers Language::sprintfDate + */ + public function testSprintfDateNotAllDigitTimestamp() { + $this->getLang()->sprintfDate( 'xiY', '-1234567890123' ); + } + + /** * @dataProvider provideSprintfDateSamples + * @covers Language::sprintfDate */ - function testSprintfDate( $format, $ts, $expected, $msg ) { + public function testSprintfDate( $format, $ts, $expected, $msg ) { $this->assertEquals( $expected, - $this->lang->sprintfDate( $format, $ts ), + $this->getLang()->sprintfDate( $format, $ts ), "sprintfDate('$format', '$ts'): $msg" ); } + /** - * bug 33454. sprintfDate should always use UTC. + * sprintfDate should always use UTC when no zone is given. * @dataProvider provideSprintfDateSamples + * @covers Language::sprintfDate */ - function testSprintfDateTZ( $format, $ts, $expected, $msg ) { + public function testSprintfDateNoZone( $format, $ts, $expected, $ignore, $msg ) { $oldTZ = date_default_timezone_get(); $res = date_default_timezone_set( 'Asia/Seoul' ); if ( !$res ) { @@ -372,49 +569,73 @@ class LanguageTest extends MediaWikiTestCase { $this->assertEquals( $expected, - $this->lang->sprintfDate( $format, $ts ), + $this->getLang()->sprintfDate( $format, $ts ), "sprintfDate('$format', '$ts'): $msg" ); date_default_timezone_set( $oldTZ ); } - function provideSprintfDateSamples() { + /** + * sprintfDate should use passed timezone + * @dataProvider provideSprintfDateSamples + * @covers Language::sprintfDate + */ + public function testSprintfDateTZ( $format, $ts, $ignore, $expected, $msg ) { + $tz = new DateTimeZone( 'Asia/Seoul' ); + if ( !$tz ) { + $this->markTestSkipped( "Error getting Timezone" ); + } + + $this->assertEquals( + $expected, + $this->getLang()->sprintfDate( $format, $ts, $tz ), + "sprintfDate('$format', '$ts', 'Asia/Seoul'): $msg" + ); + } + + public static function provideSprintfDateSamples() { return array( array( 'xiY', '20111212000000', '1390', // note because we're testing English locale we get Latin-standard digits + '1390', 'Iranian calendar full year' ), array( 'xiy', '20111212000000', '90', + '90', 'Iranian calendar short year' ), array( 'o', '20120101235000', '2011', + '2011', 'ISO 8601 (week) year' ), array( 'W', '20120101235000', '52', + '52', 'Week number' ), array( 'W', '20120102235000', '1', + '1', 'Week number' ), array( 'o-\\WW-N', '20091231235000', '2009-W53-4', + '2009-W53-4', 'leap week' ), // What follows is mostly copied from http://www.mediawiki.org/wiki/Help:Extension:ParserFunctions#.23time @@ -422,252 +643,336 @@ class LanguageTest extends MediaWikiTestCase { 'Y', '20120102090705', '2012', + '2012', 'Full year' ), array( 'y', '20120102090705', '12', + '12', '2 digit year' ), array( 'L', '20120102090705', '1', + '1', 'Leap year' ), array( 'n', '20120102090705', '1', + '1', 'Month index, not zero pad' ), array( 'N', '20120102090705', '01', + '01', 'Month index. Zero pad' ), array( 'M', '20120102090705', 'Jan', + 'Jan', 'Month abbrev' ), array( 'F', '20120102090705', 'January', + 'January', 'Full month' ), array( 'xg', '20120102090705', 'January', + 'January', 'Genitive month name (same in EN)' ), array( 'j', '20120102090705', '2', + '2', 'Day of month (not zero pad)' ), array( 'd', '20120102090705', '02', + '02', 'Day of month (zero-pad)' ), array( 'z', '20120102090705', '1', + '1', 'Day of year (zero-indexed)' ), array( 'D', '20120102090705', 'Mon', + 'Mon', 'Day of week (abbrev)' ), array( 'l', '20120102090705', 'Monday', + 'Monday', 'Full day of week' ), array( 'N', '20120101090705', '7', + '7', 'Day of week (Mon=1, Sun=7)' ), array( 'w', '20120101090705', '0', + '0', 'Day of week (Sun=0, Sat=6)' ), array( 'N', '20120102090705', '1', + '1', 'Day of week' ), array( 'a', '20120102090705', 'am', + 'am', 'am vs pm' ), array( 'A', '20120102120000', 'PM', + 'PM', 'AM vs PM' ), array( 'a', '20120102000000', 'am', + 'am', 'AM vs PM' ), array( 'g', '20120102090705', '9', + '9', '12 hour, not Zero' ), array( 'h', '20120102090705', '09', + '09', '12 hour, zero padded' ), array( 'G', '20120102090705', '9', + '9', '24 hour, not zero' ), array( 'H', '20120102090705', '09', + '09', '24 hour, zero' ), array( 'H', '20120102110705', '11', + '11', '24 hour, zero' ), array( 'i', '20120102090705', '07', + '07', 'Minutes' ), array( 's', '20120102090705', '05', + '05', 'seconds' ), array( 'U', '20120102090705', '1325495225', + '1325462825', 'unix time' ), array( 't', '20120102090705', '31', + '31', 'Days in current month' ), array( 'c', '20120102090705', '2012-01-02T09:07:05+00:00', + '2012-01-02T09:07:05+09:00', 'ISO 8601 timestamp' ), array( 'r', '20120102090705', 'Mon, 02 Jan 2012 09:07:05 +0000', + 'Mon, 02 Jan 2012 09:07:05 +0900', 'RFC 5322' ), array( + 'e', + '20120102090705', + 'UTC', + 'Asia/Seoul', + 'Timezone identifier' + ), + array( + 'I', + '19880602090705', + '0', + '1', + 'DST indicator' + ), + array( + 'O', + '20120102090705', + '+0000', + '+0900', + 'Timezone offset' + ), + array( + 'P', + '20120102090705', + '+00:00', + '+09:00', + 'Timezone offset with colon' + ), + array( + 'T', + '20120102090705', + 'UTC', + 'KST', + 'Timezone abbreviation' + ), + array( + 'Z', + '20120102090705', + '0', + '32400', + 'Timezone offset in seconds' + ), + array( 'xmj xmF xmn xmY', '20120102090705', '7 Safar 2 1433', + '7 Safar 2 1433', 'Islamic' ), array( 'xij xiF xin xiY', '20120102090705', '12 Dey 10 1390', + '12 Dey 10 1390', 'Iranian' ), array( 'xjj xjF xjn xjY', '20120102090705', '7 Tevet 4 5772', + '7 Tevet 4 5772', 'Hebrew' ), array( 'xjt', '20120102090705', '29', + '29', 'Hebrew number of days in month' ), array( 'xjx', '20120102090705', 'Tevet', + 'Tevet', 'Hebrew genitive month name (No difference in EN)' ), array( 'xkY', '20120102090705', '2555', + '2555', 'Thai year' ), array( 'xoY', '20120102090705', '101', + '101', 'Minguo' ), array( 'xtY', '20120102090705', '平成24', + '平成24', 'nengo' ), array( 'xrxkYY', '20120102090705', 'MMDLV2012', + 'MMDLV2012', 'Roman numerals' ), array( 'xhxjYY', '20120102090705', 'ה\'תשע"ב2012', + 'ה\'תשע"ב2012', 'Hebrew numberals' ), array( 'xnY', '20120102090705', '2012', + '2012', 'Raw numerals (doesn\'t mean much in EN)' ), array( '[[Y "(yea"\\r)]] \\"xx\\"', '20120102090705', '[[2012 (year)]] "x"', + '[[2012 (year)]] "x"', 'Various escaping' ), @@ -676,16 +981,17 @@ class LanguageTest extends MediaWikiTestCase { /** * @dataProvider provideFormatSizes + * @covers Language::formatSize */ - function testFormatSize( $size, $expected, $msg ) { + public function testFormatSize( $size, $expected, $msg ) { $this->assertEquals( $expected, - $this->lang->formatSize( $size ), + $this->getLang()->formatSize( $size ), "formatSize('$size'): $msg" ); } - function provideFormatSizes() { + public static function provideFormatSizes() { return array( array( 0, @@ -738,16 +1044,17 @@ class LanguageTest extends MediaWikiTestCase { /** * @dataProvider provideFormatBitrate + * @covers Language::formatBitrate */ - function testFormatBitrate( $bps, $expected, $msg ) { + public function testFormatBitrate( $bps, $expected, $msg ) { $this->assertEquals( $expected, - $this->lang->formatBitrate( $bps ), + $this->getLang()->formatBitrate( $bps ), "formatBitrate('$bps'): $msg" ); } - function provideFormatBitrate() { + public static function provideFormatBitrate() { return array( array( 0, @@ -808,19 +1115,19 @@ class LanguageTest extends MediaWikiTestCase { } - /** * @dataProvider provideFormatDuration + * @covers Language::formatDuration */ - function testFormatDuration( $duration, $expected, $intervals = array() ) { + public function testFormatDuration( $duration, $expected, $intervals = array() ) { $this->assertEquals( $expected, - $this->lang->formatDuration( $duration, $intervals ), + $this->getLang()->formatDuration( $duration, $intervals ), "formatDuration('$duration'): $expected" ); } - function provideFormatDuration() { + public static function provideFormatDuration() { return array( array( 0, @@ -859,35 +1166,36 @@ class LanguageTest extends MediaWikiTestCase { '2 days', ), array( - 365.25 * 86400, // 365.25 * 86400 = 31557600 + // ( 365 + ( 24 * 3 + 25 ) / 400 ) * 86400 = 31556952 + ( 365 + ( 24 * 3 + 25 ) / 400.0 ) * 86400, '1 year', ), array( - 2 * 31557600, + 2 * 31556952, '2 years', ), array( - 10 * 31557600, + 10 * 31556952, '1 decade', ), array( - 20 * 31557600, + 20 * 31556952, '2 decades', ), array( - 100 * 31557600, + 100 * 31556952, '1 century', ), array( - 200 * 31557600, + 200 * 31556952, '2 centuries', ), array( - 1000 * 31557600, + 1000 * 31556952, '1 millennium', ), array( - 2000 * 31557600, + 2000 * 31556952, '2 millennia', ), array( @@ -899,11 +1207,11 @@ class LanguageTest extends MediaWikiTestCase { '1 hour and 1 second' ), array( - 31557600 + 2 * 86400 + 9000, + 31556952 + 2 * 86400 + 9000, '1 year, 2 days, 2 hours and 30 minutes' ), array( - 42 * 1000 * 31557600 + 42, + 42 * 1000 * 31556952 + 42, '42 millennia and 42 seconds' ), array( @@ -922,7 +1230,7 @@ class LanguageTest extends MediaWikiTestCase { array( 'seconds' ), ), array( - 31557600 + 2 * 86400 + 9000, + 31556952 + 2 * 86400 + 9000, '1 year, 2 days and 150 minutes', array( 'years', 'days', 'minutes' ), ), @@ -932,7 +1240,7 @@ class LanguageTest extends MediaWikiTestCase { array( 'years', 'days' ), ), array( - 31557600 + 2 * 86400 + 9000, + 31556952 + 2 * 86400 + 9000, '1 year, 2 days and 150 minutes', array( 'minutes', 'days', 'years' ), ), @@ -946,17 +1254,18 @@ class LanguageTest extends MediaWikiTestCase { /** * @dataProvider provideCheckTitleEncodingData + * @covers Language::checkTitleEncoding */ - function testCheckTitleEncoding( $s ) { + public function testCheckTitleEncoding( $s ) { $this->assertEquals( $s, - $this->lang->checkTitleEncoding($s), + $this->getLang()->checkTitleEncoding( $s ), "checkTitleEncoding('$s')" ); } - function provideCheckTitleEncodingData() { - return array ( + public static function provideCheckTitleEncodingData() { + return array( array( "" ), array( "United States of America" ), // 7bit ASCII array( rawurldecode( "S%C3%A9rie%20t%C3%A9l%C3%A9vis%C3%A9e" ) ), @@ -1010,8 +1319,9 @@ class LanguageTest extends MediaWikiTestCase { /** * @dataProvider provideRomanNumeralsData + * @covers Language::romanNumeral */ - function testRomanNumerals( $num, $numerals ) { + public function testRomanNumerals( $num, $numerals ) { $this->assertEquals( $numerals, Language::romanNumeral( $num ), @@ -1019,7 +1329,7 @@ class LanguageTest extends MediaWikiTestCase { ); } - function provideRomanNumeralsData() { + public static function provideRomanNumeralsData() { return array( array( 1, 'I' ), array( 2, 'II' ), @@ -1061,9 +1371,197 @@ class LanguageTest extends MediaWikiTestCase { array( 7000, 'MMMMMMM' ), array( 8000, 'MMMMMMMM' ), array( 9000, 'MMMMMMMMM' ), - array( 9999, 'MMMMMMMMMCMXCIX'), + array( 9999, 'MMMMMMMMMCMXCIX' ), array( 10000, 'MMMMMMMMMM' ), ); } -} + /** + * @dataProvider providePluralData + * @covers Language::convertPlural + */ + public function testConvertPlural( $expected, $number, $forms ) { + $chosen = $this->getLang()->convertPlural( $number, $forms ); + $this->assertEquals( $expected, $chosen ); + } + + public static function providePluralData() { + // Params are: [expected text, number given, [the plural forms]] + return array( + array( 'plural', 0, array( + 'singular', 'plural' + ) ), + array( 'explicit zero', 0, array( + '0=explicit zero', 'singular', 'plural' + ) ), + array( 'explicit one', 1, array( + 'singular', 'plural', '1=explicit one', + ) ), + array( 'singular', 1, array( + 'singular', 'plural', '0=explicit zero', + ) ), + array( 'plural', 3, array( + '0=explicit zero', '1=explicit one', 'singular', 'plural' + ) ), + array( 'explicit eleven', 11, array( + 'singular', 'plural', '11=explicit eleven', + ) ), + array( 'plural', 12, array( + 'singular', 'plural', '11=explicit twelve', + ) ), + array( 'plural', 12, array( + 'singular', 'plural', '=explicit form', + ) ), + array( 'other', 2, array( + 'kissa=kala', '1=2=3', 'other', + ) ), + array( '', 2, array( + '0=explicit zero', '1=explicit one', + ) ), + ); + } + + /** + * @covers Language::translateBlockExpiry() + * @dataProvider provideTranslateBlockExpiry + */ + public function testTranslateBlockExpiry( $expectedData, $str, $desc ) { + $lang = $this->getLang(); + if ( is_array( $expectedData ) ) { + list( $func, $arg ) = $expectedData; + $expected = $lang->$func( $arg ); + } else { + $expected = $expectedData; + } + $this->assertEquals( $expected, $lang->translateBlockExpiry( $str ), $desc ); + } + + public static function provideTranslateBlockExpiry() { + return array( + array( '2 hours', '2 hours', 'simple data from ipboptions' ), + array( 'indefinite', 'infinite', 'infinite from ipboptions' ), + array( 'indefinite', 'infinity', 'alternative infinite from ipboptions' ), + array( 'indefinite', 'indefinite', 'another alternative infinite from ipboptions' ), + array( array( 'formatDuration', 1023 * 60 * 60 ), '1023 hours', 'relative' ), + array( array( 'formatDuration', -1023 ), '-1023 seconds', 'negative relative' ), + array( array( 'formatDuration', 0 ), 'now', 'now' ), + array( array( 'timeanddate', '20120102070000' ), '2012-1-1 7:00 +1 day', 'mixed, handled as absolute' ), + array( array( 'timeanddate', '19910203040506' ), '1991-2-3 4:05:06', 'absolute' ), + array( array( 'timeanddate', '19700101000000' ), '1970-1-1 0:00:00', 'absolute at epoch' ), + array( array( 'timeanddate', '19691231235959' ), '1969-12-31 23:59:59', 'time before epoch' ), + array( 'dummy', 'dummy', 'return garbage as is' ), + ); + } + + /** + * @covers Language::commafy() + * @dataProvider provideCommafyData + */ + public function testCommafy( $number, $numbersWithCommas ) { + $this->assertEquals( + $numbersWithCommas, + $this->getLang()->commafy( $number ), + "commafy('$number')" + ); + } + + public static function provideCommafyData() { + return array( + array( 1, '1' ), + array( 10, '10' ), + array( 100, '100' ), + array( 1000, '1,000' ), + array( 10000, '10,000' ), + array( 100000, '100,000' ), + array( 1000000, '1,000,000' ), + array( 1.0001, '1.0001' ), + array( 10.0001, '10.0001' ), + array( 100.0001, '100.0001' ), + array( 1000.0001, '1,000.0001' ), + array( 10000.0001, '10,000.0001' ), + array( 100000.0001, '100,000.0001' ), + array( 1000000.0001, '1,000,000.0001' ), + ); + } + + /** + * @covers Language::listToText + */ + public function testListToText() { + $lang = $this->getLang(); + $and = $lang->getMessageFromDB( 'and' ); + $s = $lang->getMessageFromDB( 'word-separator' ); + $c = $lang->getMessageFromDB( 'comma-separator' ); + + $this->assertEquals( '', $lang->listToText( array() ) ); + $this->assertEquals( 'a', $lang->listToText( array( 'a' ) ) ); + $this->assertEquals( "a{$and}{$s}b", $lang->listToText( array( 'a', 'b' ) ) ); + $this->assertEquals( "a{$c}b{$and}{$s}c", $lang->listToText( array( 'a', 'b', 'c' ) ) ); + $this->assertEquals( "a{$c}b{$c}c{$and}{$s}d", $lang->listToText( array( 'a', 'b', 'c', 'd' ) ) ); + } + + /** + * @dataProvider provideIsSupportedLanguage + * @covers Language::isSupportedLanguage + */ + public function testIsSupportedLanguage( $code, $expected, $comment ) { + $this->assertEquals( $expected, Language::isSupportedLanguage( $code ), $comment ); + } + + public static function provideIsSupportedLanguage() { + return array( + array( 'en', true, 'is supported language' ), + array( 'fi', true, 'is supported language' ), + array( 'bunny', false, 'is not supported language' ), + array( 'FI', false, 'is not supported language, input should be in lower case' ), + ); + } + + /** + * @dataProvider provideGetParentLanguage + * @covers Language::getParentLanguage + */ + public function testGetParentLanguage( $code, $expected, $comment ) { + $lang = Language::factory( $code ); + if ( is_null( $expected ) ) { + $this->assertNull( $lang->getParentLanguage(), $comment ); + } else { + $this->assertEquals( $expected, $lang->getParentLanguage()->getCode(), $comment ); + } + } + + public static function provideGetParentLanguage() { + return array( + array( 'zh-cn', 'zh', 'zh is the parent language of zh-cn' ), + array( 'zh', 'zh', 'zh is defined as the parent language of zh, because zh converter can convert zh-cn to zh' ), + array( 'zh-invalid', null, 'do not be fooled by arbitrarily composed language codes' ), + array( 'en-gb', null, 'en does not have converter' ), + array( 'en', null, 'en does not have converter. Although FakeConverter handles en -> en conversion but it is useless' ), + ); + } + + /** + * @dataProvider provideGetNamespaceAliases + * @covers Language::getNamespaceAliases + */ + public function testGetNamespaceAliases( $languageCode, $subset ) { + $language = Language::factory( $languageCode ); + $aliases = $language->getNamespaceAliases(); + foreach ( $subset as $alias => $nsId ) { + $this->assertEquals( $nsId, $aliases[$alias] ); + } + } + + public static function provideGetNamespaceAliases() { + // TODO: Add tests for NS_PROJECT_TALK and GenderNamespaces + return array( + array( + 'zh', + array( + '文件' => NS_FILE, + '檔案' => NS_FILE, + ), + ), + ); + } +} diff --git a/tests/phpunit/languages/LanguageTiTest.php b/tests/phpunit/languages/LanguageTiTest.php index 4bfaa009..e225af97 100644 --- a/tests/phpunit/languages/LanguageTiTest.php +++ b/tests/phpunit/languages/LanguageTiTest.php @@ -6,27 +6,29 @@ */ /** Tests for MediaWiki languages/classes/LanguageTi.php */ -class LanguageTiTest extends MediaWikiTestCase { - private $lang; - - function setUp() { - $this->lang = Language::factory( 'Ti' ); - } - function tearDown() { - unset( $this->lang ); +class LanguageTiTest extends LanguageClassesTestCase { + /** + * @dataProvider providePlural + * @covers Language::convertPlural + */ + public function testPlural( $result, $value ) { + $forms = array( 'one', 'other' ); + $this->assertEquals( $result, $this->getLang()->convertPlural( $value, $forms ) ); } - /** @dataProvider providerPlural */ - function testPlural( $result, $value ) { - $forms = array( 'one', 'many' ); - $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) ); + /** + * @dataProvider providePlural + * @covers Language::getPluralRuleType + */ + public function testGetPluralRuleType( $result, $value ) { + $this->assertEquals( $result, $this->getLang()->getPluralRuleType( $value ) ); } - function providerPlural() { - return array ( - array( 'one', 0 ), - array( 'one', 1 ), - array( 'many', 2 ), + public static function providePlural() { + return array( + array( 'one', 0 ), + array( 'one', 1 ), + array( 'other', 2 ), ); } } diff --git a/tests/phpunit/languages/LanguageTlTest.php b/tests/phpunit/languages/LanguageTlTest.php index a1facd14..7ac51c69 100644 --- a/tests/phpunit/languages/LanguageTlTest.php +++ b/tests/phpunit/languages/LanguageTlTest.php @@ -6,27 +6,29 @@ */ /** Tests for MediaWiki languages/classes/LanguageTl.php */ -class LanguageTlTest extends MediaWikiTestCase { - private $lang; - - function setUp() { - $this->lang = Language::factory( 'Tl' ); - } - function tearDown() { - unset( $this->lang ); +class LanguageTlTest extends LanguageClassesTestCase { + /** + * @dataProvider providePlural + * @covers Language::convertPlural + */ + public function testPlural( $result, $value ) { + $forms = array( 'one', 'other' ); + $this->assertEquals( $result, $this->getLang()->convertPlural( $value, $forms ) ); } - /** @dataProvider providerPlural */ - function testPlural( $result, $value ) { - $forms = array( 'one', 'many' ); - $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) ); + /** + * @dataProvider providePlural + * @covers Language::getPluralRuleType + */ + public function testGetPluralRuleType( $result, $value ) { + $this->assertEquals( $result, $this->getLang()->getPluralRuleType( $value ) ); } - function providerPlural() { - return array ( - array( 'one', 0 ), - array( 'one', 1 ), - array( 'many', 2 ), + public static function providePlural() { + return array( + array( 'one', 0 ), + array( 'one', 1 ), + array( 'other', 2 ), ); } } diff --git a/tests/phpunit/languages/LanguageTrTest.php b/tests/phpunit/languages/LanguageTrTest.php index bda4c9d9..8fc2795c 100644 --- a/tests/phpunit/languages/LanguageTrTest.php +++ b/tests/phpunit/languages/LanguageTrTest.php @@ -6,15 +6,7 @@ */ /** Tests for MediaWiki languages/LanguageTr.php */ -class LanguageTrTest extends MediaWikiTestCase { - private $lang; - - function setUp() { - $this->lang = Language::factory( 'Tr' ); - } - function tearDown() { - unset( $this->lang ); - } +class LanguageTrTest extends LanguageClassesTestCase { /** * See @bug 28040 @@ -25,11 +17,11 @@ class LanguageTrTest extends MediaWikiTestCase { * @see http://en.wikipedia.org/wiki/Dotted_and_dotless_I * @dataProvider provideDottedAndDotlessI */ - function testDottedAndDotlessI( $func, $input, $inputCase, $expected ) { - if( $func == 'ucfirst' ) { - $res = $this->lang->ucfirst( $input ); - } elseif( $func == 'lcfirst' ) { - $res = $this->lang->lcfirst( $input ); + public function testDottedAndDotlessI( $func, $input, $inputCase, $expected ) { + if ( $func == 'ucfirst' ) { + $res = $this->getLang()->ucfirst( $input ); + } elseif ( $func == 'lcfirst' ) { + $res = $this->getLang()->lcfirst( $input ); } else { throw new MWException( __METHOD__ . " given an invalid function name '$func'" ); } @@ -39,7 +31,7 @@ class LanguageTrTest extends MediaWikiTestCase { $this->assertEquals( $expected, $res, $msg ); } - function provideDottedAndDotlessI() { + public static function provideDottedAndDotlessI() { return array( # function, input, input case, expected # Case changed: @@ -64,5 +56,4 @@ class LanguageTrTest extends MediaWikiTestCase { ); } - } diff --git a/tests/phpunit/languages/LanguageUkTest.php b/tests/phpunit/languages/LanguageUkTest.php index 60fafb0d..9051bcff 100644 --- a/tests/phpunit/languages/LanguageUkTest.php +++ b/tests/phpunit/languages/LanguageUkTest.php @@ -6,25 +6,38 @@ * @file */ -/** Tests for MediaWiki languages/classes/LanguageUk.php */ -class LanguageUkTest extends MediaWikiTestCase { - private $lang; - - function setUp() { - $this->lang = Language::factory( 'Uk' ); +/** Tests for Ukrainian */ +class LanguageUkTest extends LanguageClassesTestCase { + /** + * @dataProvider providePlural + * @covers Language::convertPlural + */ + public function testPlural( $result, $value ) { + $forms = array( 'one', 'few', 'many', 'other' ); + $this->assertEquals( $result, $this->getLang()->convertPlural( $value, $forms ) ); } - function tearDown() { - unset( $this->lang ); + + /** + * Test explicit plural forms - n=FormN forms + * @covers Language::convertPlural + */ + public function testExplicitPlural() { + $forms = array( 'one', 'few', 'many', 'other', '12=dozen' ); + $this->assertEquals( 'dozen', $this->getLang()->convertPlural( 12, $forms ) ); + $forms = array( 'one', 'few', 'many', '100=hundred', 'other', '12=dozen' ); + $this->assertEquals( 'hundred', $this->getLang()->convertPlural( 100, $forms ) ); } - /** @dataProvider providePluralFourForms */ - function testPluralFourForms( $result, $value ) { - $forms = array( 'one', 'few', 'many', 'other' ); - $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) ); + /** + * @dataProvider providePlural + * @covers Language::getPluralRuleType + */ + public function testGetPluralRuleType( $result, $value ) { + $this->assertEquals( $result, $this->getLang()->getPluralRuleType( $value ) ); } - function providePluralFourForms() { - return array ( + public static function providePlural() { + return array( array( 'one', 1 ), array( 'many', 11 ), array( 'one', 91 ), @@ -38,17 +51,22 @@ class LanguageUkTest extends MediaWikiTestCase { array( 'many', 120 ), ); } - /** @dataProvider providePluralTwoForms */ - function testPluralTwoForms( $result, $value ) { - $forms = array( 'one', 'several' ); - $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) ); + + /** + * @dataProvider providePluralTwoForms + * @covers Language::convertPlural + */ + public function testPluralTwoForms( $result, $value ) { + $forms = array( '1=one', 'other' ); + $this->assertEquals( $result, $this->getLang()->convertPlural( $value, $forms ) ); } - function providePluralTwoForms() { - return array ( + + public static function providePluralTwoForms() { + return array( array( 'one', 1 ), - array( 'several', 11 ), - array( 'several', 91 ), - array( 'several', 121 ), + array( 'other', 11 ), + array( 'other', 91 ), + array( 'other', 121 ), ); } } diff --git a/tests/phpunit/languages/LanguageUzTest.php b/tests/phpunit/languages/LanguageUzTest.php index 72387283..13f57c16 100644 --- a/tests/phpunit/languages/LanguageUzTest.php +++ b/tests/phpunit/languages/LanguageUzTest.php @@ -10,26 +10,20 @@ * @copyright Copyright © 2012, Robin Pepermans * @copyright Copyright © 2011, Antoine Musso <hashar at free dot fr> * @file + * + * @todo methods in test class should be tidied: + * - Should be split into separate test methods and data providers + * - Tests for LanguageConverter and Language should probably be separate.. */ -require_once dirname( __DIR__ ) . '/bootstrap.php'; - /** Tests for MediaWiki languages/LanguageUz.php */ -class LanguageUzTest extends MediaWikiTestCase { - /* Language object. Initialized before each test */ - private $lang; - - function setUp() { - $this->lang = Language::factory( 'uz' ); - } - function tearDown() { - unset( $this->lang ); - } +class LanguageUzTest extends LanguageClassesTestCase { /** * @author Nikola Smolenski + * @covers LanguageConverter::convertTo */ - function testConversionToCyrillic() { + public function testConversionToCyrillic() { // A convertion of Latin to Cyrillic $this->assertEquals( 'абвгғ', $this->convertToCyrillic( 'abvggʻ' ) @@ -48,7 +42,10 @@ class LanguageUzTest extends MediaWikiTestCase { ); } - function testConversionToLatin() { + /** + * @covers LanguageConverter::convertTo + */ + public function testConversionToLatin() { // A simple convertion of Latin to Latin $this->assertEquals( 'abdef', $this->convertToLatin( 'abdef' ) @@ -66,20 +63,21 @@ class LanguageUzTest extends MediaWikiTestCase { * @param $variant string Language variant 'uz-cyrl' or 'uz-latn' * @param $msg string Optional message */ - function assertUnConverted( $text, $variant, $msg = '' ) { + protected function assertUnConverted( $text, $variant, $msg = '' ) { $this->assertEquals( $text, $this->convertTo( $text, $variant ), $msg ); } + /** * Wrapper to verify a text is different once converted to a variant. * @param $text string Text to convert * @param $variant string Language variant 'uz-cyrl' or 'uz-latn' * @param $msg string Optional message */ - function assertConverted( $text, $variant, $msg = '' ) { + protected function assertConverted( $text, $variant, $msg = '' ) { $this->assertNotEquals( $text, $this->convertTo( $text, $variant ), @@ -92,29 +90,32 @@ class LanguageUzTest extends MediaWikiTestCase { * using the cyrillic variant and converted to Latin when using * the Latin variant. */ - function assertCyrillic( $text, $msg = '' ) { + protected function assertCyrillic( $text, $msg = '' ) { $this->assertUnConverted( $text, 'uz-cyrl', $msg ); $this->assertConverted( $text, 'uz-latn', $msg ); } + /** * Verifiy the given Latin text is not converted when using * using the Latin variant and converted to Cyrillic when using * the Cyrillic variant. */ - function assertLatin( $text, $msg = '' ) { + protected function assertLatin( $text, $msg = '' ) { $this->assertUnConverted( $text, 'uz-latn', $msg ); $this->assertConverted( $text, 'uz-cyrl', $msg ); } /** Wrapper for converter::convertTo() method*/ - function convertTo( $text, $variant ) { - return $this->lang->mConverter->convertTo( $text, $variant ); + protected function convertTo( $text, $variant ) { + return $this->getLang()->mConverter->convertTo( $text, $variant ); } - function convertToCyrillic( $text ) { + + protected function convertToCyrillic( $text ) { return $this->convertTo( $text, 'uz-cyrl' ); } - function convertToLatin( $text ) { + + protected function convertToLatin( $text ) { return $this->convertTo( $text, 'uz-latn' ); } } diff --git a/tests/phpunit/languages/LanguageWaTest.php b/tests/phpunit/languages/LanguageWaTest.php index 172f19b9..d05196c0 100644 --- a/tests/phpunit/languages/LanguageWaTest.php +++ b/tests/phpunit/languages/LanguageWaTest.php @@ -6,27 +6,29 @@ */ /** Tests for MediaWiki languages/classes/LanguageWa.php */ -class LanguageWaTest extends MediaWikiTestCase { - private $lang; - - function setUp() { - $this->lang = Language::factory( 'Wa' ); - } - function tearDown() { - unset( $this->lang ); +class LanguageWaTest extends LanguageClassesTestCase { + /** + * @dataProvider providePlural + * @covers Language::convertPlural + */ + public function testPlural( $result, $value ) { + $forms = array( 'one', 'other' ); + $this->assertEquals( $result, $this->getLang()->convertPlural( $value, $forms ) ); } - /** @dataProvider providerPlural */ - function testPlural( $result, $value ) { - $forms = array( 'one', 'many' ); - $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) ); + /** + * @dataProvider providePlural + * @covers Language::getPluralRuleType + */ + public function testGetPluralRuleType( $result, $value ) { + $this->assertEquals( $result, $this->getLang()->getPluralRuleType( $value ) ); } - function providerPlural() { - return array ( - array( 'one', 0 ), - array( 'one', 1 ), - array( 'many', 2 ), + public static function providePlural() { + return array( + array( 'one', 0 ), + array( 'one', 1 ), + array( 'other', 2 ), ); } } diff --git a/tests/phpunit/languages/utils/CLDRPluralRuleEvaluatorTest.php b/tests/phpunit/languages/utils/CLDRPluralRuleEvaluatorTest.php index 033164b0..bd3809d7 100644 --- a/tests/phpunit/languages/utils/CLDRPluralRuleEvaluatorTest.php +++ b/tests/phpunit/languages/utils/CLDRPluralRuleEvaluatorTest.php @@ -9,7 +9,7 @@ class CLDRPluralRuleEvaluatorTest extends MediaWikiTestCase { * @dataProvider validTestCases */ function testValidRules( $expected, $rules, $number, $comment ) { - $result = CLDRPluralRuleEvaluator::evaluate( $number, (array) $rules ); + $result = CLDRPluralRuleEvaluator::evaluate( $number, (array)$rules ); $this->assertEquals( $expected, $result, $comment ); } @@ -18,7 +18,7 @@ class CLDRPluralRuleEvaluatorTest extends MediaWikiTestCase { * @expectedException CLDRPluralRuleError */ function testInvalidRules( $rules, $comment ) { - CLDRPluralRuleEvaluator::evaluate( 1, (array) $rules ); + CLDRPluralRuleEvaluator::evaluate( 1, (array)$rules ); } function validTestCases() { @@ -31,19 +31,19 @@ class CLDRPluralRuleEvaluatorTest extends MediaWikiTestCase { array( 1, 'n is 1', 1.1, 'float number and is' ), array( 1, 'n is 1', 2, 'float number and is' ), - array( 0, 'n in 1,3,5', 3, '' ), + array( 0, 'n in 1,3,5', 3, '' ), array( 1, 'n not in 1,3,5', 5, '' ), - array( 1, 'n in 1,3,5', 2, '' ), + array( 1, 'n in 1,3,5', 2, '' ), array( 0, 'n not in 1,3,5', 4, '' ), - array( 0, 'n in 1..3', 2, '' ), - array( 0, 'n in 1..3', 3, 'in is inclusive' ), - array( 1, 'n in 1..3', 0, '' ), + array( 0, 'n in 1..3', 2, '' ), + array( 0, 'n in 1..3', 3, 'in is inclusive' ), + array( 1, 'n in 1..3', 0, '' ), - array( 1, 'n not in 1..3', 2, '' ), - array( 1, 'n not in 1..3', 3, 'in is inclusive' ), - array( 0, 'n not in 1..3', 0, '' ), + array( 1, 'n not in 1..3', 2, '' ), + array( 1, 'n not in 1..3', 3, 'in is inclusive' ), + array( 0, 'n not in 1..3', 0, '' ), array( 1, 'n is not 1 and n is not 2 and n is not 3', 1, 'and relation' ), array( 0, 'n is not 1 and n is not 2 and n is not 4', 3, 'and relation' ), @@ -78,6 +78,56 @@ class CLDRPluralRuleEvaluatorTest extends MediaWikiTestCase { array( 0, 'n in 3..10,13..19', 13, 'scottish rule - ranges with comma' ), array( 0, '5 mod 3 is n', 2, 'n as result of mod - no need to pass' ), + + # Revision 33 new operand examples + # expected, rule, number, comment + array( 0, 'i is 1', '1.00', 'new operand i' ), + array( 0, 'v is 2', '1.00', 'new operand v' ), + array( 0, 'w is 0', '1.00', 'new operand w' ), + array( 0, 'f is 0', '1.00', 'new operand f' ), + array( 0, 't is 0', '1.00', 'new operand t' ), + + array( 0, 'i is 1', '1.30', 'new operand i' ), + array( 0, 'v is 2', '1.30', 'new operand v' ), + array( 0, 'w is 1', '1.30', 'new operand w' ), + array( 0, 'f is 30', '1.30', 'new operand f' ), + array( 0, 't is 3', '1.30', 'new operand t' ), + + array( 0, 'i is 1', '1.03', 'new operand i' ), + array( 0, 'v is 2', '1.03', 'new operand v' ), + array( 0, 'w is 2', '1.03', 'new operand w' ), + array( 0, 'f is 3', '1.03', 'new operand f' ), + array( 0, 't is 3', '1.03', 'new operand t' ), + + # Revision 33 new operator aliases + # expected, rule, number, comment + array( 0, 'n % 3 is 1', 7, 'new % operator' ), + array( 0, 'n = 1,3,5', 3, 'new = operator' ), + array( 1, 'n != 1,3,5', 5, 'new != operator' ), + + # Revision 33 samples + # expected, rule, number, comment + array( 0, 'n in 1,3,5@integer 3~10, 103~110, 1003, … @decimal 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 103.0, 1003.0, …', 3, 'samples' ), + + # Revision 33 some test cases from CLDR + array( 0, 'i = 1 and v = 0 or i = 0 and t = 1', '0.1', 'pt one' ), + array( 0, 'i = 1 and v = 0 or i = 0 and t = 1', '0.01', 'pt one' ), + array( 0, 'i = 1 and v = 0 or i = 0 and t = 1', '0.10', 'pt one' ), + array( 0, 'i = 1 and v = 0 or i = 0 and t = 1', '0.010', 'pt one' ), + array( 0, 'i = 1 and v = 0 or i = 0 and t = 1', '0.100', 'pt one' ), + array( 1, 'i = 1 and v = 0 or i = 0 and t = 1', '0.0', 'pt other' ), + array( 1, 'i = 1 and v = 0 or i = 0 and t = 1', '0.2', 'pt other' ), + array( 1, 'i = 1 and v = 0 or i = 0 and t = 1', '10.0', 'pt other' ), + array( 1, 'i = 1 and v = 0 or i = 0 and t = 1', '100.0', 'pt other' ), + array( 0, 'v = 0 and i % 10 = 2..4 and i % 100 != 12..14 or f % 10 = 2..4 and f % 100 != 12..14', '2', 'bs few' ), + array( 0, 'v = 0 and i % 10 = 2..4 and i % 100 != 12..14 or f % 10 = 2..4 and f % 100 != 12..14', '4', 'bs few' ), + array( 0, 'v = 0 and i % 10 = 2..4 and i % 100 != 12..14 or f % 10 = 2..4 and f % 100 != 12..14', '22', 'bs few' ), + array( 0, 'v = 0 and i % 10 = 2..4 and i % 100 != 12..14 or f % 10 = 2..4 and f % 100 != 12..14', '102', 'bs few' ), + array( 0, 'v = 0 and i % 10 = 2..4 and i % 100 != 12..14 or f % 10 = 2..4 and f % 100 != 12..14', '0.2', 'bs few' ), + array( 0, 'v = 0 and i % 10 = 2..4 and i % 100 != 12..14 or f % 10 = 2..4 and f % 100 != 12..14', '0.4', 'bs few' ), + array( 0, 'v = 0 and i % 10 = 2..4 and i % 100 != 12..14 or f % 10 = 2..4 and f % 100 != 12..14', '10.2', 'bs few' ), + array( 1, 'v = 0 and i % 10 = 2..4 and i % 100 != 12..14 or f % 10 = 2..4 and f % 100 != 12..14', '10.0', 'bs other' ), + ); return $tests; @@ -89,7 +139,7 @@ class CLDRPluralRuleEvaluatorTest extends MediaWikiTestCase { array( 'n', 'just n' ), array( 'n is in 5', 'is in' ), ); + return $tests; } - } diff --git a/tests/phpunit/maintenance/DumpTestCase.php b/tests/phpunit/maintenance/DumpTestCase.php index d1344389..83d8c71d 100644 --- a/tests/phpunit/maintenance/DumpTestCase.php +++ b/tests/phpunit/maintenance/DumpTestCase.php @@ -35,7 +35,7 @@ abstract class DumpTestCase extends MediaWikiLangTestCase { * @throws MWExcepion */ protected function addRevision( Page $page, $text, $summary ) { - $status = $page->doEdit( $text, $summary ); + $status = $page->doEditContent( ContentHandler::makeContent( $text, $page->getTitle() ), $summary ); if ( $status->isGood() ) { $value = $status->getValue(); $revision = $value['revision']; @@ -57,14 +57,17 @@ abstract class DumpTestCase extends MediaWikiLangTestCase { */ protected function gunzip( $fname ) { $gzipped_contents = file_get_contents( $fname ); - if ( $gzipped_contents === FALSE ) { + if ( $gzipped_contents === false ) { $this->fail( "Could not get contents of $fname" ); } - // We resort to use gzinflate instead of gzdecode, as gzdecode - // need not be available - $contents = gzinflate( substr( $gzipped_contents, 10, -8 ) ); - $this->assertEquals( strlen( $contents ), - file_put_contents( $fname, $contents ), "# bytes written" ); + + $contents = gzdecode( $gzipped_contents ); + + $this->assertEquals( + strlen( $contents ), + file_put_contents( $fname, $contents ), + '# bytes written' + ); } /** @@ -72,9 +75,7 @@ abstract class DumpTestCase extends MediaWikiLangTestCase { * * Clears $wgUser, and reports errors from addDBData to PHPUnit */ - public function setUp() { - global $wgUser; - + protected function setUp() { parent::setUp(); // Check if any Exception is stored for rethrowing from addDBData @@ -83,7 +84,7 @@ abstract class DumpTestCase extends MediaWikiLangTestCase { throw $this->exceptionFromAddDBData; } - $wgUser = new User(); + $this->setMwGlobals( 'wgUser', new User() ); } /** @@ -116,15 +117,17 @@ abstract class DumpTestCase extends MediaWikiLangTestCase { * @param $name string: name of the closing element to look for * (e.g.: "mediawiki" when looking for </mediawiki>) * - * @return bool: true iff the end node could be found. false otherwise. + * @return bool: true if the end node could be found. false otherwise. */ protected function skipToNodeEnd( $name ) { while ( $this->xml->read() ) { if ( $this->xml->nodeType == XMLReader::END_ELEMENT && - $this->xml->name == $name ) { + $this->xml->name == $name + ) { return true; } } + return false; } @@ -146,6 +149,7 @@ abstract class DumpTestCase extends MediaWikiLangTestCase { return true; } } + return false; } @@ -189,7 +193,7 @@ abstract class DumpTestCase extends MediaWikiLangTestCase { protected function skipWhitespace() { $cont = true; while ( $cont && ( ( $this->xml->nodeType == XMLReader::WHITESPACE ) - || ( $this->xml->nodeType == XMLReader::SIGNIFICANT_WHITESPACE ) ) ) { + || ( $this->xml->nodeType == XMLReader::SIGNIFICANT_WHITESPACE ) ) ) { $cont = $this->xml->read(); } } @@ -272,7 +276,6 @@ abstract class DumpTestCase extends MediaWikiLangTestCase { $this->assertTextNode( "title", $name ); $this->assertTextNode( "ns", $ns ); $this->assertTextNode( "id", $id ); - } /** @@ -295,10 +298,13 @@ abstract class DumpTestCase extends MediaWikiLangTestCase { * @param $text_sha1 string: the base36 SHA-1 of the revision's text * @param $text string|false: (optional) The revision's string, or false to check for a * revision stub + * @param $model String: the expected content model id (default: CONTENT_MODEL_WIKITEXT) + * @param $format String: the expected format model id (default: CONTENT_FORMAT_WIKITEXT) * @param $parentid int|false: (optional) id of the parent revision */ - protected function assertRevision( $id, $summary, $text_id, $text_bytes, $text_sha1, $text = false, $parentid = false ) { - + protected function assertRevision( $id, $summary, $text_id, $text_bytes, $text_sha1, $text = false, $parentid = false, + $model = CONTENT_MODEL_WIKITEXT, $format = CONTENT_FORMAT_WIKITEXT + ) { $this->assertNodeStart( "revision" ); $this->skipWhitespace(); @@ -315,9 +321,33 @@ abstract class DumpTestCase extends MediaWikiLangTestCase { $this->skipWhitespace(); $this->assertTextNode( "comment", $summary ); + $this->skipWhitespace(); + + if ( $this->xml->name == "text" ) { + // note: <text> tag may occur here or at the very end. + $text_found = true; + $this->assertText( $id, $text_id, $text_bytes, $text ); + } else { + $text_found = false; + } $this->assertTextNode( "sha1", $text_sha1 ); + $this->assertTextNode( "model", $model ); + $this->skipWhitespace(); + + $this->assertTextNode( "format", $format ); + $this->skipWhitespace(); + + if ( !$text_found ) { + $this->assertText( $id, $text_id, $text_bytes, $text ); + } + + $this->assertNodeEnd( "revision" ); + $this->skipWhitespace(); + } + + protected function assertText( $id, $text_id, $text_bytes, $text ) { $this->assertNodeStart( "text", false ); if ( $text_bytes !== false ) { $this->assertEquals( $this->xml->getAttribute( "bytes" ), $text_bytes, @@ -331,7 +361,8 @@ abstract class DumpTestCase extends MediaWikiLangTestCase { $this->assertFalse( $this->xml->hasValue, "Revision has text" ); $this->assertTrue( $this->xml->read(), "Skipping text start tag" ); if ( ( $this->xml->nodeType == XMLReader::END_ELEMENT ) - && ( $this->xml->name == "text" ) ) { + && ( $this->xml->name == "text" ) + ) { $this->xml->read(); } @@ -344,9 +375,5 @@ abstract class DumpTestCase extends MediaWikiLangTestCase { $this->assertNodeEnd( "text" ); $this->skipWhitespace(); } - - $this->assertNodeEnd( "revision" ); - $this->skipWhitespace(); } - } diff --git a/tests/phpunit/maintenance/MaintenanceTest.php b/tests/phpunit/maintenance/MaintenanceTest.php index 4a6f08fa..318ce0da 100644 --- a/tests/phpunit/maintenance/MaintenanceTest.php +++ b/tests/phpunit/maintenance/MaintenanceTest.php @@ -43,7 +43,7 @@ class MaintenanceFixup extends Maintenance { private $testCase; /** - * shutdownSimulated === true iff simulateShutdown has done it's work + * shutdownSimulated === true if simulateShutdown has done it's work * * @var bool */ @@ -81,22 +81,23 @@ class MaintenanceFixup extends Maintenance { return; } - return call_user_func_array ( array( "parent", __FUNCTION__ ), func_get_args() ); + return call_user_func_array( array( "parent", __FUNCTION__ ), func_get_args() ); } /** * Safety net around register_shutdown_function of Maintenance.php */ public function __destruct() { - if ( ( ! $this->shutdownSimulated ) && ( ! $this->testCase->hasFailed() ) ) { + if ( !$this->shutdownSimulated ) { // Someone generated a MaintenanceFixup instance without calling // simulateShutdown. We'd have to raise a PHPUnit exception to correctly // flag this illegal usage. However, we are already in a destruktor, which - // would trigger undefined behaviour. Hence, we can only report to the + // would trigger undefined behavior. Hence, we can only report to the // error output :( Hopefully people read the PHPUnit output. - fwrite( STDERR, "ERROR! Instance of " . __CLASS__ . " destructed without " - . "calling simulateShutdown method. Call simulateShutdown on the " - . "instance before it gets destructed." ); + $name = $this->testCase->getName(); + fwrite( STDERR, "ERROR! Instance of " . __CLASS__ . " for test $name " + . "destructed without calling simulateShutdown method. Call " + . "simulateShutdown on the instance before it gets destructed." ); } // The following guard is required, as PHP does not offer default destructors :( @@ -111,7 +112,6 @@ class MaintenanceFixup extends Maintenance { } - // --- Making protected functions visible for test public function output( $out, $channel = null ) { @@ -119,17 +119,15 @@ class MaintenanceFixup extends Maintenance { // Maintenance::output signature. However, we do not use (or rely on) // those variables. Instead we pass to Maintenance::output whatever we // receive at runtime. - return call_user_func_array ( array( "parent", __FUNCTION__ ), func_get_args() ); + return call_user_func_array( array( "parent", __FUNCTION__ ), func_get_args() ); } - // --- Requirements for getting instance of abstract class public function execute() { $this->testCase->fail( __METHOD__ . " called unexpectedly" ); } - } class MaintenanceTest extends MediaWikiTestCase { @@ -148,6 +146,14 @@ class MaintenanceTest extends MediaWikiTestCase { $this->m = new MaintenanceFixup( $this ); } + protected function tearDown() { + if ( $this->m ) { + $this->m->simulateShutdown(); + $this->m = null; + } + parent::tearDown(); + } + /** * asserts the output before and after simulating shutdown @@ -164,9 +170,10 @@ class MaintenanceTest extends MediaWikiTestCase { private function assertOutputPrePostShutdown( $preShutdownOutput, $expectNLAppending ) { $this->assertEquals( $preShutdownOutput, $this->getActualOutput(), - "Output before shutdown simulation" ); + "Output before shutdown simulation" ); $this->m->simulateShutdown(); + $this->m = null; $postShutdownOutput = $preShutdownOutput . ( $expectNLAppending ? "\n" : "" ); $this->expectOutputString( $postShutdownOutput ); @@ -176,49 +183,48 @@ class MaintenanceTest extends MediaWikiTestCase { // Although the following tests do not seem to be too consistent (compare for // example the newlines within the test.*StringString tests, or the // test.*Intermittent.* tests), the objective of these tests is not to describe - // consistent behaviour, but rather currently existing behaviour. - + // consistent behavior, but rather currently existing behavior. function testOutputEmpty() { $this->m->output( "" ); - $this->assertOutputPrePostShutdown( "", False ); + $this->assertOutputPrePostShutdown( "", false ); } function testOutputString() { $this->m->output( "foo" ); - $this->assertOutputPrePostShutdown( "foo", False ); + $this->assertOutputPrePostShutdown( "foo", false ); } function testOutputStringString() { $this->m->output( "foo" ); $this->m->output( "bar" ); - $this->assertOutputPrePostShutdown( "foobar", False ); + $this->assertOutputPrePostShutdown( "foobar", false ); } function testOutputStringNL() { $this->m->output( "foo\n" ); - $this->assertOutputPrePostShutdown( "foo\n", False ); + $this->assertOutputPrePostShutdown( "foo\n", false ); } function testOutputStringNLNL() { $this->m->output( "foo\n\n" ); - $this->assertOutputPrePostShutdown( "foo\n\n", False ); + $this->assertOutputPrePostShutdown( "foo\n\n", false ); } function testOutputStringNLString() { $this->m->output( "foo\nbar" ); - $this->assertOutputPrePostShutdown( "foo\nbar", False ); + $this->assertOutputPrePostShutdown( "foo\nbar", false ); } function testOutputStringNLStringNL() { $this->m->output( "foo\nbar\n" ); - $this->assertOutputPrePostShutdown( "foo\nbar\n", False ); + $this->assertOutputPrePostShutdown( "foo\nbar\n", false ); } function testOutputStringNLStringNLLinewise() { $this->m->output( "foo\n" ); $this->m->output( "bar\n" ); - $this->assertOutputPrePostShutdown( "foo\nbar\n", False ); + $this->assertOutputPrePostShutdown( "foo\nbar\n", false ); } function testOutputStringNLStringNLArbitrary() { @@ -229,7 +235,7 @@ class MaintenanceTest extends MediaWikiTestCase { $this->m->output( "ba" ); $this->m->output( "" ); $this->m->output( "r\n" ); - $this->assertOutputPrePostShutdown( "foo\nbar\n", False ); + $this->assertOutputPrePostShutdown( "foo\nbar\n", false ); } function testOutputStringNLStringNLArbitraryAgain() { @@ -240,49 +246,49 @@ class MaintenanceTest extends MediaWikiTestCase { $this->m->output( "a" ); $this->m->output( "" ); $this->m->output( "r\n" ); - $this->assertOutputPrePostShutdown( "foo\nbar\n", False ); + $this->assertOutputPrePostShutdown( "foo\nbar\n", false ); } function testOutputWNullChannelEmpty() { $this->m->output( "", null ); - $this->assertOutputPrePostShutdown( "", False ); + $this->assertOutputPrePostShutdown( "", false ); } function testOutputWNullChannelString() { $this->m->output( "foo", null ); - $this->assertOutputPrePostShutdown( "foo", False ); + $this->assertOutputPrePostShutdown( "foo", false ); } function testOutputWNullChannelStringString() { $this->m->output( "foo", null ); $this->m->output( "bar", null ); - $this->assertOutputPrePostShutdown( "foobar", False ); + $this->assertOutputPrePostShutdown( "foobar", false ); } function testOutputWNullChannelStringNL() { $this->m->output( "foo\n", null ); - $this->assertOutputPrePostShutdown( "foo\n", False ); + $this->assertOutputPrePostShutdown( "foo\n", false ); } function testOutputWNullChannelStringNLNL() { $this->m->output( "foo\n\n", null ); - $this->assertOutputPrePostShutdown( "foo\n\n", False ); + $this->assertOutputPrePostShutdown( "foo\n\n", false ); } function testOutputWNullChannelStringNLString() { $this->m->output( "foo\nbar", null ); - $this->assertOutputPrePostShutdown( "foo\nbar", False ); + $this->assertOutputPrePostShutdown( "foo\nbar", false ); } function testOutputWNullChannelStringNLStringNL() { $this->m->output( "foo\nbar\n", null ); - $this->assertOutputPrePostShutdown( "foo\nbar\n", False ); + $this->assertOutputPrePostShutdown( "foo\nbar\n", false ); } function testOutputWNullChannelStringNLStringNLLinewise() { $this->m->output( "foo\n", null ); $this->m->output( "bar\n", null ); - $this->assertOutputPrePostShutdown( "foo\nbar\n", False ); + $this->assertOutputPrePostShutdown( "foo\nbar\n", false ); } function testOutputWNullChannelStringNLStringNLArbitrary() { @@ -293,7 +299,7 @@ class MaintenanceTest extends MediaWikiTestCase { $this->m->output( "ba", null ); $this->m->output( "", null ); $this->m->output( "r\n", null ); - $this->assertOutputPrePostShutdown( "foo\nbar\n", False ); + $this->assertOutputPrePostShutdown( "foo\nbar\n", false ); } function testOutputWNullChannelStringNLStringNLArbitraryAgain() { @@ -304,17 +310,17 @@ class MaintenanceTest extends MediaWikiTestCase { $this->m->output( "a", null ); $this->m->output( "", null ); $this->m->output( "r\n", null ); - $this->assertOutputPrePostShutdown( "foo\nbar\n", False ); + $this->assertOutputPrePostShutdown( "foo\nbar\n", false ); } function testOutputWChannelString() { $this->m->output( "foo", "bazChannel" ); - $this->assertOutputPrePostShutdown( "foo", True ); + $this->assertOutputPrePostShutdown( "foo", true ); } function testOutputWChannelStringNL() { $this->m->output( "foo\n", "bazChannel" ); - $this->assertOutputPrePostShutdown( "foo", True ); + $this->assertOutputPrePostShutdown( "foo", true ); } function testOutputWChannelStringNLNL() { @@ -323,23 +329,23 @@ class MaintenanceTest extends MediaWikiTestCase { // outputChanneled with a string ending in a nl ... which is not allowed // according to the documentation of outputChanneled) $this->m->output( "foo\n\n", "bazChannel" ); - $this->assertOutputPrePostShutdown( "foo\n", True ); + $this->assertOutputPrePostShutdown( "foo\n", true ); } function testOutputWChannelStringNLString() { $this->m->output( "foo\nbar", "bazChannel" ); - $this->assertOutputPrePostShutdown( "foo\nbar", True ); + $this->assertOutputPrePostShutdown( "foo\nbar", true ); } function testOutputWChannelStringNLStringNL() { $this->m->output( "foo\nbar\n", "bazChannel" ); - $this->assertOutputPrePostShutdown( "foo\nbar", True ); + $this->assertOutputPrePostShutdown( "foo\nbar", true ); } function testOutputWChannelStringNLStringNLLinewise() { $this->m->output( "foo\n", "bazChannel" ); $this->m->output( "bar\n", "bazChannel" ); - $this->assertOutputPrePostShutdown( "foobar", True ); + $this->assertOutputPrePostShutdown( "foobar", true ); } function testOutputWChannelStringNLStringNLArbitrary() { @@ -350,7 +356,7 @@ class MaintenanceTest extends MediaWikiTestCase { $this->m->output( "ba", "bazChannel" ); $this->m->output( "", "bazChannel" ); $this->m->output( "r\n", "bazChannel" ); - $this->assertOutputPrePostShutdown( "foobar", True ); + $this->assertOutputPrePostShutdown( "foobar", true ); } function testOutputWChannelStringNLStringNLArbitraryAgain() { @@ -361,7 +367,7 @@ class MaintenanceTest extends MediaWikiTestCase { $this->m->output( "a", "bazChannel" ); $this->m->output( "", "bazChannel" ); $this->m->output( "r\n", "bazChannel" ); - $this->assertOutputPrePostShutdown( "foo\nbar", True ); + $this->assertOutputPrePostShutdown( "foo\nbar", true ); } function testOutputWMultipleChannelsChannelChange() { @@ -369,7 +375,7 @@ class MaintenanceTest extends MediaWikiTestCase { $this->m->output( "bar", "bazChannel" ); $this->m->output( "qux", "quuxChannel" ); $this->m->output( "corge", "bazChannel" ); - $this->assertOutputPrePostShutdown( "foobar\nqux\ncorge", True ); + $this->assertOutputPrePostShutdown( "foobar\nqux\ncorge", true ); } function testOutputWMultipleChannelsChannelChangeNL() { @@ -377,7 +383,7 @@ class MaintenanceTest extends MediaWikiTestCase { $this->m->output( "bar\n", "bazChannel" ); $this->m->output( "qux\n", "quuxChannel" ); $this->m->output( "corge", "bazChannel" ); - $this->assertOutputPrePostShutdown( "foobar\nqux\ncorge", True ); + $this->assertOutputPrePostShutdown( "foobar\nqux\ncorge", true ); } function testOutputWAndWOChannelStringStartWO() { @@ -385,7 +391,7 @@ class MaintenanceTest extends MediaWikiTestCase { $this->m->output( "bar", "bazChannel" ); $this->m->output( "qux" ); $this->m->output( "quux", "bazChannel" ); - $this->assertOutputPrePostShutdown( "foobar\nquxquux", True ); + $this->assertOutputPrePostShutdown( "foobar\nquxquux", true ); } function testOutputWAndWOChannelStringStartW() { @@ -393,27 +399,27 @@ class MaintenanceTest extends MediaWikiTestCase { $this->m->output( "bar" ); $this->m->output( "qux", "bazChannel" ); $this->m->output( "quux" ); - $this->assertOutputPrePostShutdown( "foo\nbarqux\nquux", False ); + $this->assertOutputPrePostShutdown( "foo\nbarqux\nquux", false ); } function testOutputWChannelTypeSwitch() { $this->m->output( "foo", 1 ); $this->m->output( "bar", 1.0 ); - $this->assertOutputPrePostShutdown( "foo\nbar", True ); + $this->assertOutputPrePostShutdown( "foo\nbar", true ); } function testOutputIntermittentEmpty() { $this->m->output( "foo" ); $this->m->output( "" ); $this->m->output( "bar" ); - $this->assertOutputPrePostShutdown( "foobar", False ); + $this->assertOutputPrePostShutdown( "foobar", false ); } function testOutputIntermittentFalse() { $this->m->output( "foo" ); $this->m->output( false ); $this->m->output( "bar" ); - $this->assertOutputPrePostShutdown( "foobar", False ); + $this->assertOutputPrePostShutdown( "foobar", false ); } function testOutputIntermittentFalseAfterOtherChannel() { @@ -421,35 +427,35 @@ class MaintenanceTest extends MediaWikiTestCase { $this->m->output( "foo" ); $this->m->output( false ); $this->m->output( "bar" ); - $this->assertOutputPrePostShutdown( "qux\nfoobar", False ); + $this->assertOutputPrePostShutdown( "qux\nfoobar", false ); } function testOutputWNullChannelIntermittentEmpty() { $this->m->output( "foo", null ); $this->m->output( "", null ); $this->m->output( "bar", null ); - $this->assertOutputPrePostShutdown( "foobar", False ); + $this->assertOutputPrePostShutdown( "foobar", false ); } function testOutputWNullChannelIntermittentFalse() { $this->m->output( "foo", null ); $this->m->output( false, null ); $this->m->output( "bar", null ); - $this->assertOutputPrePostShutdown( "foobar", False ); + $this->assertOutputPrePostShutdown( "foobar", false ); } function testOutputWChannelIntermittentEmpty() { $this->m->output( "foo", "bazChannel" ); $this->m->output( "", "bazChannel" ); $this->m->output( "bar", "bazChannel" ); - $this->assertOutputPrePostShutdown( "foobar", True ); + $this->assertOutputPrePostShutdown( "foobar", true ); } function testOutputWChannelIntermittentFalse() { $this->m->output( "foo", "bazChannel" ); $this->m->output( false, "bazChannel" ); $this->m->output( "bar", "bazChannel" ); - $this->assertOutputPrePostShutdown( "foobar", True ); + $this->assertOutputPrePostShutdown( "foobar", true ); } // Note that (per documentation) outputChanneled does take strings that end @@ -457,23 +463,23 @@ class MaintenanceTest extends MediaWikiTestCase { function testOutputChanneledEmpty() { $this->m->outputChanneled( "" ); - $this->assertOutputPrePostShutdown( "\n", False ); + $this->assertOutputPrePostShutdown( "\n", false ); } function testOutputChanneledString() { $this->m->outputChanneled( "foo" ); - $this->assertOutputPrePostShutdown( "foo\n", False ); + $this->assertOutputPrePostShutdown( "foo\n", false ); } function testOutputChanneledStringString() { $this->m->outputChanneled( "foo" ); $this->m->outputChanneled( "bar" ); - $this->assertOutputPrePostShutdown( "foo\nbar\n", False ); + $this->assertOutputPrePostShutdown( "foo\nbar\n", false ); } function testOutputChanneledStringNLString() { $this->m->outputChanneled( "foo\nbar" ); - $this->assertOutputPrePostShutdown( "foo\nbar\n", False ); + $this->assertOutputPrePostShutdown( "foo\nbar\n", false ); } function testOutputChanneledStringNLStringNLArbitraryAgain() { @@ -484,28 +490,28 @@ class MaintenanceTest extends MediaWikiTestCase { $this->m->outputChanneled( "a" ); $this->m->outputChanneled( "" ); $this->m->outputChanneled( "r" ); - $this->assertOutputPrePostShutdown( "\nfoo\n\n\nb\na\n\nr\n", False ); + $this->assertOutputPrePostShutdown( "\nfoo\n\n\nb\na\n\nr\n", false ); } function testOutputChanneledWNullChannelEmpty() { $this->m->outputChanneled( "", null ); - $this->assertOutputPrePostShutdown( "\n", False ); + $this->assertOutputPrePostShutdown( "\n", false ); } function testOutputChanneledWNullChannelString() { $this->m->outputChanneled( "foo", null ); - $this->assertOutputPrePostShutdown( "foo\n", False ); + $this->assertOutputPrePostShutdown( "foo\n", false ); } function testOutputChanneledWNullChannelStringString() { $this->m->outputChanneled( "foo", null ); $this->m->outputChanneled( "bar", null ); - $this->assertOutputPrePostShutdown( "foo\nbar\n", False ); + $this->assertOutputPrePostShutdown( "foo\nbar\n", false ); } function testOutputChanneledWNullChannelStringNLString() { $this->m->outputChanneled( "foo\nbar", null ); - $this->assertOutputPrePostShutdown( "foo\nbar\n", False ); + $this->assertOutputPrePostShutdown( "foo\nbar\n", false ); } function testOutputChanneledWNullChannelStringNLStringNLArbitraryAgain() { @@ -516,23 +522,23 @@ class MaintenanceTest extends MediaWikiTestCase { $this->m->outputChanneled( "a", null ); $this->m->outputChanneled( "", null ); $this->m->outputChanneled( "r", null ); - $this->assertOutputPrePostShutdown( "\nfoo\n\n\nb\na\n\nr\n", False ); + $this->assertOutputPrePostShutdown( "\nfoo\n\n\nb\na\n\nr\n", false ); } function testOutputChanneledWChannelString() { $this->m->outputChanneled( "foo", "bazChannel" ); - $this->assertOutputPrePostShutdown( "foo", True ); + $this->assertOutputPrePostShutdown( "foo", true ); } function testOutputChanneledWChannelStringNLString() { $this->m->outputChanneled( "foo\nbar", "bazChannel" ); - $this->assertOutputPrePostShutdown( "foo\nbar", True ); + $this->assertOutputPrePostShutdown( "foo\nbar", true ); } function testOutputChanneledWChannelStringString() { $this->m->outputChanneled( "foo", "bazChannel" ); $this->m->outputChanneled( "bar", "bazChannel" ); - $this->assertOutputPrePostShutdown( "foobar", True ); + $this->assertOutputPrePostShutdown( "foobar", true ); } function testOutputChanneledWChannelStringNLStringNLArbitraryAgain() { @@ -543,7 +549,7 @@ class MaintenanceTest extends MediaWikiTestCase { $this->m->outputChanneled( "a", "bazChannel" ); $this->m->outputChanneled( "", "bazChannel" ); $this->m->outputChanneled( "r", "bazChannel" ); - $this->assertOutputPrePostShutdown( "foo\nbar", True ); + $this->assertOutputPrePostShutdown( "foo\nbar", true ); } function testOutputChanneledWMultipleChannelsChannelChange() { @@ -551,7 +557,7 @@ class MaintenanceTest extends MediaWikiTestCase { $this->m->outputChanneled( "bar", "bazChannel" ); $this->m->outputChanneled( "qux", "quuxChannel" ); $this->m->outputChanneled( "corge", "bazChannel" ); - $this->assertOutputPrePostShutdown( "foobar\nqux\ncorge", True ); + $this->assertOutputPrePostShutdown( "foobar\nqux\ncorge", true ); } function testOutputChanneledWMultipleChannelsChannelChangeEnclosedNull() { @@ -559,7 +565,7 @@ class MaintenanceTest extends MediaWikiTestCase { $this->m->outputChanneled( "bar", null ); $this->m->outputChanneled( "qux", null ); $this->m->outputChanneled( "corge", "bazChannel" ); - $this->assertOutputPrePostShutdown( "foo\nbar\nqux\ncorge", True ); + $this->assertOutputPrePostShutdown( "foo\nbar\nqux\ncorge", true ); } function testOutputChanneledWMultipleChannelsChannelAfterNullChange() { @@ -567,7 +573,7 @@ class MaintenanceTest extends MediaWikiTestCase { $this->m->outputChanneled( "bar", null ); $this->m->outputChanneled( "qux", null ); $this->m->outputChanneled( "corge", "quuxChannel" ); - $this->assertOutputPrePostShutdown( "foo\nbar\nqux\ncorge", True ); + $this->assertOutputPrePostShutdown( "foo\nbar\nqux\ncorge", true ); } function testOutputChanneledWAndWOChannelStringStartWO() { @@ -575,7 +581,7 @@ class MaintenanceTest extends MediaWikiTestCase { $this->m->outputChanneled( "bar", "bazChannel" ); $this->m->outputChanneled( "qux" ); $this->m->outputChanneled( "quux", "bazChannel" ); - $this->assertOutputPrePostShutdown( "foo\nbar\nqux\nquux", True ); + $this->assertOutputPrePostShutdown( "foo\nbar\nqux\nquux", true ); } function testOutputChanneledWAndWOChannelStringStartW() { @@ -583,114 +589,114 @@ class MaintenanceTest extends MediaWikiTestCase { $this->m->outputChanneled( "bar" ); $this->m->outputChanneled( "qux", "bazChannel" ); $this->m->outputChanneled( "quux" ); - $this->assertOutputPrePostShutdown( "foo\nbar\nqux\nquux\n", False ); + $this->assertOutputPrePostShutdown( "foo\nbar\nqux\nquux\n", false ); } function testOutputChanneledWChannelTypeSwitch() { $this->m->outputChanneled( "foo", 1 ); $this->m->outputChanneled( "bar", 1.0 ); - $this->assertOutputPrePostShutdown( "foo\nbar", True ); + $this->assertOutputPrePostShutdown( "foo\nbar", true ); } function testOutputChanneledWOChannelIntermittentEmpty() { $this->m->outputChanneled( "foo" ); $this->m->outputChanneled( "" ); $this->m->outputChanneled( "bar" ); - $this->assertOutputPrePostShutdown( "foo\n\nbar\n", False ); + $this->assertOutputPrePostShutdown( "foo\n\nbar\n", false ); } function testOutputChanneledWOChannelIntermittentFalse() { $this->m->outputChanneled( "foo" ); $this->m->outputChanneled( false ); $this->m->outputChanneled( "bar" ); - $this->assertOutputPrePostShutdown( "foo\nbar\n", False ); + $this->assertOutputPrePostShutdown( "foo\nbar\n", false ); } function testOutputChanneledWNullChannelIntermittentEmpty() { $this->m->outputChanneled( "foo", null ); $this->m->outputChanneled( "", null ); $this->m->outputChanneled( "bar", null ); - $this->assertOutputPrePostShutdown( "foo\n\nbar\n", False ); + $this->assertOutputPrePostShutdown( "foo\n\nbar\n", false ); } function testOutputChanneledWNullChannelIntermittentFalse() { $this->m->outputChanneled( "foo", null ); $this->m->outputChanneled( false, null ); $this->m->outputChanneled( "bar", null ); - $this->assertOutputPrePostShutdown( "foo\nbar\n", False ); + $this->assertOutputPrePostShutdown( "foo\nbar\n", false ); } function testOutputChanneledWChannelIntermittentEmpty() { $this->m->outputChanneled( "foo", "bazChannel" ); $this->m->outputChanneled( "", "bazChannel" ); $this->m->outputChanneled( "bar", "bazChannel" ); - $this->assertOutputPrePostShutdown( "foobar", True ); + $this->assertOutputPrePostShutdown( "foobar", true ); } function testOutputChanneledWChannelIntermittentFalse() { $this->m->outputChanneled( "foo", "bazChannel" ); $this->m->outputChanneled( false, "bazChannel" ); $this->m->outputChanneled( "bar", "bazChannel" ); - $this->assertOutputPrePostShutdown( "foo\nbar", True ); + $this->assertOutputPrePostShutdown( "foo\nbar", true ); } function testCleanupChanneledClean() { $this->m->cleanupChanneled(); - $this->assertOutputPrePostShutdown( "", False ); + $this->assertOutputPrePostShutdown( "", false ); } function testCleanupChanneledAfterOutput() { $this->m->output( "foo" ); $this->m->cleanupChanneled(); - $this->assertOutputPrePostShutdown( "foo", False ); + $this->assertOutputPrePostShutdown( "foo", false ); } function testCleanupChanneledAfterOutputWNullChannel() { $this->m->output( "foo", null ); $this->m->cleanupChanneled(); - $this->assertOutputPrePostShutdown( "foo", False ); + $this->assertOutputPrePostShutdown( "foo", false ); } function testCleanupChanneledAfterOutputWChannel() { $this->m->output( "foo", "bazChannel" ); $this->m->cleanupChanneled(); - $this->assertOutputPrePostShutdown( "foo\n", False ); + $this->assertOutputPrePostShutdown( "foo\n", false ); } function testCleanupChanneledAfterNLOutput() { $this->m->output( "foo\n" ); $this->m->cleanupChanneled(); - $this->assertOutputPrePostShutdown( "foo\n", False ); + $this->assertOutputPrePostShutdown( "foo\n", false ); } function testCleanupChanneledAfterNLOutputWNullChannel() { $this->m->output( "foo\n", null ); $this->m->cleanupChanneled(); - $this->assertOutputPrePostShutdown( "foo\n", False ); + $this->assertOutputPrePostShutdown( "foo\n", false ); } function testCleanupChanneledAfterNLOutputWChannel() { $this->m->output( "foo\n", "bazChannel" ); $this->m->cleanupChanneled(); - $this->assertOutputPrePostShutdown( "foo\n", False ); + $this->assertOutputPrePostShutdown( "foo\n", false ); } function testCleanupChanneledAfterOutputChanneledWOChannel() { $this->m->outputChanneled( "foo" ); $this->m->cleanupChanneled(); - $this->assertOutputPrePostShutdown( "foo\n", False ); + $this->assertOutputPrePostShutdown( "foo\n", false ); } function testCleanupChanneledAfterOutputChanneledWNullChannel() { $this->m->outputChanneled( "foo", null ); $this->m->cleanupChanneled(); - $this->assertOutputPrePostShutdown( "foo\n", False ); + $this->assertOutputPrePostShutdown( "foo\n", false ); } function testCleanupChanneledAfterOutputChanneledWChannel() { $this->m->outputChanneled( "foo", "bazChannel" ); $this->m->cleanupChanneled(); - $this->assertOutputPrePostShutdown( "foo\n", False ); + $this->assertOutputPrePostShutdown( "foo\n", false ); } function testMultipleMaintenanceObjectsInteractionOutput() { @@ -700,9 +706,9 @@ class MaintenanceTest extends MediaWikiTestCase { $m2->output( "bar" ); $this->assertEquals( "foobar", $this->getActualOutput(), - "Output before shutdown simulation (m2)" ); + "Output before shutdown simulation (m2)" ); $m2->simulateShutdown(); - $this->assertOutputPrePostShutdown( "foobar", False ); + $this->assertOutputPrePostShutdown( "foobar", false ); } function testMultipleMaintenanceObjectsInteractionOutputWNullChannel() { @@ -712,9 +718,9 @@ class MaintenanceTest extends MediaWikiTestCase { $m2->output( "bar", null ); $this->assertEquals( "foobar", $this->getActualOutput(), - "Output before shutdown simulation (m2)" ); + "Output before shutdown simulation (m2)" ); $m2->simulateShutdown(); - $this->assertOutputPrePostShutdown( "foobar", False ); + $this->assertOutputPrePostShutdown( "foobar", false ); } function testMultipleMaintenanceObjectsInteractionOutputWChannel() { @@ -724,9 +730,9 @@ class MaintenanceTest extends MediaWikiTestCase { $m2->output( "bar", "bazChannel" ); $this->assertEquals( "foobar", $this->getActualOutput(), - "Output before shutdown simulation (m2)" ); + "Output before shutdown simulation (m2)" ); $m2->simulateShutdown(); - $this->assertOutputPrePostShutdown( "foobar\n", True ); + $this->assertOutputPrePostShutdown( "foobar\n", true ); } function testMultipleMaintenanceObjectsInteractionOutputWNullChannelNL() { @@ -736,9 +742,9 @@ class MaintenanceTest extends MediaWikiTestCase { $m2->output( "bar\n", null ); $this->assertEquals( "foo\nbar\n", $this->getActualOutput(), - "Output before shutdown simulation (m2)" ); + "Output before shutdown simulation (m2)" ); $m2->simulateShutdown(); - $this->assertOutputPrePostShutdown( "foo\nbar\n", False ); + $this->assertOutputPrePostShutdown( "foo\nbar\n", false ); } function testMultipleMaintenanceObjectsInteractionOutputWChannelNL() { @@ -748,9 +754,9 @@ class MaintenanceTest extends MediaWikiTestCase { $m2->output( "bar\n", "bazChannel" ); $this->assertEquals( "foobar", $this->getActualOutput(), - "Output before shutdown simulation (m2)" ); + "Output before shutdown simulation (m2)" ); $m2->simulateShutdown(); - $this->assertOutputPrePostShutdown( "foobar\n", True ); + $this->assertOutputPrePostShutdown( "foobar\n", true ); } function testMultipleMaintenanceObjectsInteractionOutputChanneled() { @@ -760,9 +766,9 @@ class MaintenanceTest extends MediaWikiTestCase { $m2->outputChanneled( "bar" ); $this->assertEquals( "foo\nbar\n", $this->getActualOutput(), - "Output before shutdown simulation (m2)" ); + "Output before shutdown simulation (m2)" ); $m2->simulateShutdown(); - $this->assertOutputPrePostShutdown( "foo\nbar\n", False ); + $this->assertOutputPrePostShutdown( "foo\nbar\n", false ); } function testMultipleMaintenanceObjectsInteractionOutputChanneledWNullChannel() { @@ -772,9 +778,9 @@ class MaintenanceTest extends MediaWikiTestCase { $m2->outputChanneled( "bar", null ); $this->assertEquals( "foo\nbar\n", $this->getActualOutput(), - "Output before shutdown simulation (m2)" ); + "Output before shutdown simulation (m2)" ); $m2->simulateShutdown(); - $this->assertOutputPrePostShutdown( "foo\nbar\n", False ); + $this->assertOutputPrePostShutdown( "foo\nbar\n", false ); } function testMultipleMaintenanceObjectsInteractionOutputChanneledWChannel() { @@ -784,9 +790,9 @@ class MaintenanceTest extends MediaWikiTestCase { $m2->outputChanneled( "bar", "bazChannel" ); $this->assertEquals( "foobar", $this->getActualOutput(), - "Output before shutdown simulation (m2)" ); + "Output before shutdown simulation (m2)" ); $m2->simulateShutdown(); - $this->assertOutputPrePostShutdown( "foobar\n", True ); + $this->assertOutputPrePostShutdown( "foobar\n", true ); } function testMultipleMaintenanceObjectsInteractionCleanupChanneledWChannel() { @@ -796,17 +802,15 @@ class MaintenanceTest extends MediaWikiTestCase { $m2->outputChanneled( "bar", "bazChannel" ); $this->assertEquals( "foobar", $this->getActualOutput(), - "Output before first cleanup" ); + "Output before first cleanup" ); $this->m->cleanupChanneled(); $this->assertEquals( "foobar\n", $this->getActualOutput(), - "Output after first cleanup" ); + "Output after first cleanup" ); $m2->cleanupChanneled(); $this->assertEquals( "foobar\n\n", $this->getActualOutput(), - "Output after second cleanup" ); + "Output after second cleanup" ); $m2->simulateShutdown(); - $this->assertOutputPrePostShutdown( "foobar\n\n", False ); + $this->assertOutputPrePostShutdown( "foobar\n\n", false ); } - - -}
\ No newline at end of file +} diff --git a/tests/phpunit/maintenance/backupPrefetchTest.php b/tests/phpunit/maintenance/backupPrefetchTest.php index 8ff85574..bc2d7375 100644 --- a/tests/phpunit/maintenance/backupPrefetchTest.php +++ b/tests/phpunit/maintenance/backupPrefetchTest.php @@ -36,7 +36,6 @@ class BaseDumpTest extends MediaWikiTestCase { private function assertPrefetchEquals( $expected, $page, $revision ) { $this->assertEquals( $expected, $this->dump->prefetch( $page, $revision ), "Prefetch of page $page revision $revision" ); - } function testSequential() { @@ -156,7 +155,7 @@ class BaseDumpTest extends MediaWikiTestCase { <siteinfo> <sitename>wikisvn</sitename> <base>http://localhost/wiki-svn/index.php/Main_Page</base> - <generator>MediaWiki 1.20alpha</generator> + <generator>MediaWiki 1.21alpha</generator> <case>first-letter</case> <namespaces> <namespace key="-2" case="first-letter">Media</namespace> @@ -181,7 +180,6 @@ class BaseDumpTest extends MediaWikiTestCase { </siteinfo> '; - // An array holding the pages that are available for prefetch $available_pages = array(); @@ -199,6 +197,8 @@ class BaseDumpTest extends MediaWikiTestCase { <comment>BackupDumperTestP1Summary1</comment> <sha1>0bolhl6ol7i6x0e7yq91gxgaan39j87</sha1> <text xml:space="preserve">BackupDumperTestP1Text1</text> + <model name="wikitext">1</model> + <format mime="text/x-wiki">1</format> </revision> </page> '; @@ -216,6 +216,8 @@ class BaseDumpTest extends MediaWikiTestCase { <comment>BackupDumperTestP2Summary1</comment> <sha1>jprywrymfhysqllua29tj3sc7z39dl2</sha1> <text xml:space="preserve">BackupDumperTestP2Text1</text> + <model name="wikitext">1</model> + <format mime="text/x-wiki">1</format> </revision> <revision> <id>5</id> @@ -227,6 +229,8 @@ class BaseDumpTest extends MediaWikiTestCase { <comment>BackupDumperTestP2Summary4 extra</comment> <sha1>6o1ciaxa6pybnqprmungwofc4lv00wv</sha1> <text xml:space="preserve">BackupDumperTestP2Text4 some additional Text</text> + <model name="wikitext">1</model> + <format mime="text/x-wiki">1</format> </revision> </page> '; @@ -243,6 +247,8 @@ class BaseDumpTest extends MediaWikiTestCase { </contributor> <comment>Talk BackupDumperTestP1 Summary1</comment> <sha1>nktofwzd0tl192k3zfepmlzxoax1lpe</sha1> + <model name="wikitext">1</model> + <format mime="text/x-wiki">1</format> <text xml:space="preserve">Talk about BackupDumperTestP1 Text1</text> </revision> </page> @@ -257,14 +263,13 @@ class BaseDumpTest extends MediaWikiTestCase { foreach ( $requested_pages as $i ) { $this->assertTrue( array_key_exists( $i, $available_pages ), "Check for availability of requested page " . $i ); - $content .= $available_pages[ $i ]; + $content .= $available_pages[$i]; } $content .= $tail; $this->assertEquals( strlen( $content ), file_put_contents( - $fname, $content ), "Length of prepared prefetch" ); + $fname, $content ), "Length of prepared prefetch" ); return $fname; } - } diff --git a/tests/phpunit/maintenance/backupTextPassTest.php b/tests/phpunit/maintenance/backupTextPassTest.php index a0bbadf9..653a1145 100644 --- a/tests/phpunit/maintenance/backupTextPassTest.php +++ b/tests/phpunit/maintenance/backupTextPassTest.php @@ -26,16 +26,18 @@ class TextPassDumperTest extends DumpTestCase { $this->tablesUsed[] = 'revision'; $this->tablesUsed[] = 'text'; + $ns = $this->getDefaultWikitextNS(); + try { // Simple page - $title = Title::newFromText( 'BackupDumperTestP1' ); + $title = Title::newFromText( 'BackupDumperTestP1', $ns ); $page = WikiPage::factory( $title ); list( $this->revId1_1, $this->textId1_1 ) = $this->addRevision( $page, "BackupDumperTestP1Text1", "BackupDumperTestP1Summary1" ); $this->pageId1 = $page->getId(); // Page with more than one revision - $title = Title::newFromText( 'BackupDumperTestP2' ); + $title = Title::newFromText( 'BackupDumperTestP2', $ns ); $page = WikiPage::factory( $title ); list( $this->revId2_1, $this->textId2_1 ) = $this->addRevision( $page, "BackupDumperTestP2Text1", "BackupDumperTestP2Summary1" ); @@ -49,7 +51,7 @@ class TextPassDumperTest extends DumpTestCase { $this->pageId2 = $page->getId(); // Deleted page. - $title = Title::newFromText( 'BackupDumperTestP3' ); + $title = Title::newFromText( 'BackupDumperTestP3', $ns ); $page = WikiPage::factory( $title ); list( $this->revId3_1, $this->textId3_1 ) = $this->addRevision( $page, "BackupDumperTestP3Text1", "BackupDumperTestP2Summary1" ); @@ -59,6 +61,13 @@ class TextPassDumperTest extends DumpTestCase { $page->doDeleteArticle( "Testing ;)" ); // Page from non-default namespace + + if ( $ns === NS_TALK ) { + // @todo work around this. + throw new MWException( "The default wikitext namespace is the talk namespace. " + . " We can't currently deal with that." ); + } + $title = Title::newFromText( 'BackupDumperTestP1', NS_TALK ); $page = WikiPage::factory( $title ); list( $this->revId4_1, $this->textId4_1 ) = $this->addRevision( $page, @@ -71,10 +80,9 @@ class TextPassDumperTest extends DumpTestCase { // DumpTestCase $this->exceptionFromAddDBData = $e; } - } - public function setUp() { + protected function setUp() { parent::setUp(); // Since we will restrict dumping by page ranges (to allow @@ -85,15 +93,14 @@ class TextPassDumperTest extends DumpTestCase { array( $this->pageId2, $this->pageId3, $this->pageId4 ), array( $this->pageId1 + 1, $this->pageId2 + 1, $this->pageId3 + 1 ), "Page ids increasing without holes" ); - } function testPlain() { // Setting up the dump $nameStub = $this->setUpStub(); $nameFull = $this->getNewTempFile(); - $dumper = new TextPassDumper( array ( "--stub=file:" . $nameStub, - "--output=file:" . $nameFull ) ); + $dumper = new TextPassDumper( array( "--stub=file:" . $nameStub, + "--output=file:" . $nameFull ) ); $dumper->reporting = false; $dumper->setDb( $this->db ); @@ -147,7 +154,7 @@ class TextPassDumperTest extends DumpTestCase { ); // The mock itself - $prefetchMock = $this->getMock( 'BaseDump', array( 'prefetch' ), array(), '', FALSE ); + $prefetchMock = $this->getMock( 'BaseDump', array( 'prefetch' ), array(), '', false ); $prefetchMock->expects( $this->exactly( 6 ) ) ->method( 'prefetch' ) ->will( $this->returnValueMap( $prefetchMap ) ); @@ -155,8 +162,8 @@ class TextPassDumperTest extends DumpTestCase { // Setting up of the dump $nameStub = $this->setUpStub(); $nameFull = $this->getNewTempFile(); - $dumper = new TextPassDumper( array ( "--stub=file:" - . $nameStub, "--output=file:" . $nameFull ) ); + $dumper = new TextPassDumper( array( "--stub=file:" + . $nameStub, "--output=file:" . $nameFull ) ); $dumper->prefetch = $prefetchMock; $dumper->reporting = false; $dumper->setDb( $this->db ); @@ -205,7 +212,6 @@ class TextPassDumperTest extends DumpTestCase { $this->assertPageEnd(); $this->assertDumpEnd(); - } /** @@ -221,7 +227,7 @@ class TextPassDumperTest extends DumpTestCase { $nameOutputDir = $this->getNewTempDirectory(); $stderr = fopen( 'php://output', 'a' ); - if ( $stderr === FALSE ) { + if ( $stderr === false ) { $this->fail( "Could not open stream for stderr" ); } @@ -230,7 +236,6 @@ class TextPassDumperTest extends DumpTestCase { $minDuration = 2; // We want the dump to take at least this many seconds $checkpointAfter = 0.5; // Generate checkpoint after this many seconds - // Until a dump takes at least $minDuration seconds, perform a dump and check // duration. If the dump did not take long enough increase the iteration // count, to generate a bigger stub file next time. @@ -241,10 +246,10 @@ class TextPassDumperTest extends DumpTestCase { $this->assertTrue( wfMkdirParents( $nameOutputDir ), "Creating temporary output directory " ); $this->setUpStub( $nameStub, $iterations ); - $dumper = new TextPassDumper( array ( "--stub=file:" . $nameStub, - "--output=" . $checkpointFormat . ":" . $nameOutputDir . "/full", - "--maxtime=1" /*This is in minutes. Fixup is below*/, - "--checkpointfile=checkpoint-%s-%s.xml.gz" ) ); + $dumper = new TextPassDumper( array( "--stub=file:" . $nameStub, + "--output=" . $checkpointFormat . ":" . $nameOutputDir . "/full", + "--maxtime=1" /*This is in minutes. Fixup is below*/, + "--checkpointfile=checkpoint-%s-%s.xml.gz" ) ); $dumper->setDb( $this->db ); $dumper->maxTimeAllowed = $checkpointAfter; // Patching maxTime from 1 minute $dumper->stderr = $stderr; @@ -274,7 +279,7 @@ class TextPassDumperTest extends DumpTestCase { $this->assertLessThan( 50000, $iterations, "Emergency stop against infinitely increasing iteration " - . "count ( last duration: $lastDuration )" ); + . "count ( last duration: $lastDuration )" ); } } @@ -291,11 +296,11 @@ class TextPassDumperTest extends DumpTestCase { // Each run of the following loop body tries to handle exactly 1 /page/ (not // iteration of stub content). $i is only increased after having treated page 4. - for ( $i = 0 ; $i < $iterations ; ) { + for ( $i = 0; $i < $iterations; ) { // 1. Assuring a file is opened and ready. Skipping across header if // necessary. - if ( ! $fileOpened ) { + if ( !$fileOpened ) { $this->assertNotEmpty( $files, "No more existing dump files, " . "but not yet all pages found" ); $fname = array_shift( $files ); @@ -314,65 +319,65 @@ class TextPassDumperTest extends DumpTestCase { // 2. Performing a single page check switch ( $lookingForPage ) { - case 1: - // Page 1 - $this->assertPageStart( $this->pageId1 + $i * self::$numOfPages, NS_MAIN, - "BackupDumperTestP1" ); - $this->assertRevision( $this->revId1_1 + $i * self::$numOfRevs, "BackupDumperTestP1Summary1", - $this->textId1_1, false, "0bolhl6ol7i6x0e7yq91gxgaan39j87", - "BackupDumperTestP1Text1" ); - $this->assertPageEnd(); - - $lookingForPage = 2; - break; - - case 2: - // Page 2 - $this->assertPageStart( $this->pageId2 + $i * self::$numOfPages, NS_MAIN, - "BackupDumperTestP2" ); - $this->assertRevision( $this->revId2_1 + $i * self::$numOfRevs, "BackupDumperTestP2Summary1", - $this->textId2_1, false, "jprywrymfhysqllua29tj3sc7z39dl2", - "BackupDumperTestP2Text1" ); - $this->assertRevision( $this->revId2_2 + $i * self::$numOfRevs, "BackupDumperTestP2Summary2", - $this->textId2_2, false, "b7vj5ks32po5m1z1t1br4o7scdwwy95", - "BackupDumperTestP2Text2", $this->revId2_1 + $i * self::$numOfRevs ); - $this->assertRevision( $this->revId2_3 + $i * self::$numOfRevs, "BackupDumperTestP2Summary3", - $this->textId2_3, false, "jfunqmh1ssfb8rs43r19w98k28gg56r", - "BackupDumperTestP2Text3", $this->revId2_2 + $i * self::$numOfRevs ); - $this->assertRevision( $this->revId2_4 + $i * self::$numOfRevs, - "BackupDumperTestP2Summary4 extra", - $this->textId2_4, false, "6o1ciaxa6pybnqprmungwofc4lv00wv", - "BackupDumperTestP2Text4 some additional Text", - $this->revId2_3 + $i * self::$numOfRevs ); - $this->assertPageEnd(); - - $lookingForPage = 4; - break; - - case 4: - // Page 4 - $this->assertPageStart( $this->pageId4 + $i * self::$numOfPages, NS_TALK, - "Talk:BackupDumperTestP1" ); - $this->assertRevision( $this->revId4_1 + $i * self::$numOfRevs, - "Talk BackupDumperTestP1 Summary1", - $this->textId4_1, false, "nktofwzd0tl192k3zfepmlzxoax1lpe", - "Talk about BackupDumperTestP1 Text1" ); - $this->assertPageEnd(); - - $lookingForPage = 1; - - // We dealt with the whole iteration. - $i++; - break; - - default: - $this->fail( "Bad setting for lookingForPage ($lookingForPage)" ); + case 1: + // Page 1 + $this->assertPageStart( $this->pageId1 + $i * self::$numOfPages, NS_MAIN, + "BackupDumperTestP1" ); + $this->assertRevision( $this->revId1_1 + $i * self::$numOfRevs, "BackupDumperTestP1Summary1", + $this->textId1_1, false, "0bolhl6ol7i6x0e7yq91gxgaan39j87", + "BackupDumperTestP1Text1" ); + $this->assertPageEnd(); + + $lookingForPage = 2; + break; + + case 2: + // Page 2 + $this->assertPageStart( $this->pageId2 + $i * self::$numOfPages, NS_MAIN, + "BackupDumperTestP2" ); + $this->assertRevision( $this->revId2_1 + $i * self::$numOfRevs, "BackupDumperTestP2Summary1", + $this->textId2_1, false, "jprywrymfhysqllua29tj3sc7z39dl2", + "BackupDumperTestP2Text1" ); + $this->assertRevision( $this->revId2_2 + $i * self::$numOfRevs, "BackupDumperTestP2Summary2", + $this->textId2_2, false, "b7vj5ks32po5m1z1t1br4o7scdwwy95", + "BackupDumperTestP2Text2", $this->revId2_1 + $i * self::$numOfRevs ); + $this->assertRevision( $this->revId2_3 + $i * self::$numOfRevs, "BackupDumperTestP2Summary3", + $this->textId2_3, false, "jfunqmh1ssfb8rs43r19w98k28gg56r", + "BackupDumperTestP2Text3", $this->revId2_2 + $i * self::$numOfRevs ); + $this->assertRevision( $this->revId2_4 + $i * self::$numOfRevs, + "BackupDumperTestP2Summary4 extra", + $this->textId2_4, false, "6o1ciaxa6pybnqprmungwofc4lv00wv", + "BackupDumperTestP2Text4 some additional Text", + $this->revId2_3 + $i * self::$numOfRevs ); + $this->assertPageEnd(); + + $lookingForPage = 4; + break; + + case 4: + // Page 4 + $this->assertPageStart( $this->pageId4 + $i * self::$numOfPages, NS_TALK, + "Talk:BackupDumperTestP1" ); + $this->assertRevision( $this->revId4_1 + $i * self::$numOfRevs, + "Talk BackupDumperTestP1 Summary1", + $this->textId4_1, false, "nktofwzd0tl192k3zfepmlzxoax1lpe", + "Talk about BackupDumperTestP1 Text1" ); + $this->assertPageEnd(); + + $lookingForPage = 1; + + // We dealt with the whole iteration. + $i++; + break; + + default: + $this->fail( "Bad setting for lookingForPage ($lookingForPage)" ); } // 3. Checking for the end of the current checkpoint file if ( $this->xml->nodeType == XMLReader::END_ELEMENT - && $this->xml->name == "mediawiki" ) { - + && $this->xml->name == "mediawiki" + ) { $this->assertDumpEnd(); $fileOpened = false; } @@ -383,7 +388,7 @@ class TextPassDumperTest extends DumpTestCase { $this->assertEmpty( $files, "Remaining unchecked files" ); // ... and have dealt with more than one checkpoint file - $this->assertGreaterThan( 1, $checkpointFiles, "# of checkpoint files" ); + $this->assertGreaterThan( 1, $checkpointFiles, "expected more than 1 checkpoint to have been created. Checkpoint interval is $checkpointAfter seconds, maybe your computer is too fast?" ); $this->expectETAOutput(); } @@ -408,6 +413,7 @@ class TextPassDumperTest extends DumpTestCase { * @group large */ function testCheckpointGzip() { + $this->checkHasGzip(); $this->checkpointHelper( "gzip" ); } @@ -438,7 +444,7 @@ class TextPassDumperTest extends DumpTestCase { <siteinfo> <sitename>wikisvn</sitename> <base>http://localhost/wiki-svn/index.php/Main_Page</base> - <generator>MediaWiki 1.20alpha</generator> + <generator>MediaWiki 1.21alpha</generator> <case>first-letter</case> <namespaces> <namespace key="-2" case="first-letter">Media</namespace> @@ -481,6 +487,8 @@ class TextPassDumperTest extends DumpTestCase { </contributor> <comment>BackupDumperTestP1Summary1</comment> <sha1>0bolhl6ol7i6x0e7yq91gxgaan39j87</sha1> + <model>wikitext</model> + <format>text/x-wiki</format> <text id="' . $this->textId1_1 . '" bytes="23" /> </revision> </page> @@ -497,6 +505,8 @@ class TextPassDumperTest extends DumpTestCase { </contributor> <comment>BackupDumperTestP2Summary1</comment> <sha1>jprywrymfhysqllua29tj3sc7z39dl2</sha1> + <model>wikitext</model> + <format>text/x-wiki</format> <text id="' . $this->textId2_1 . '" bytes="23" /> </revision> <revision> @@ -508,6 +518,8 @@ class TextPassDumperTest extends DumpTestCase { </contributor> <comment>BackupDumperTestP2Summary2</comment> <sha1>b7vj5ks32po5m1z1t1br4o7scdwwy95</sha1> + <model>wikitext</model> + <format>text/x-wiki</format> <text id="' . $this->textId2_2 . '" bytes="23" /> </revision> <revision> @@ -519,6 +531,8 @@ class TextPassDumperTest extends DumpTestCase { </contributor> <comment>BackupDumperTestP2Summary3</comment> <sha1>jfunqmh1ssfb8rs43r19w98k28gg56r</sha1> + <model>wikitext</model> + <format>text/x-wiki</format> <text id="' . $this->textId2_3 . '" bytes="23" /> </revision> <revision> @@ -530,6 +544,8 @@ class TextPassDumperTest extends DumpTestCase { </contributor> <comment>BackupDumperTestP2Summary4 extra</comment> <sha1>6o1ciaxa6pybnqprmungwofc4lv00wv</sha1> + <model>wikitext</model> + <format>text/x-wiki</format> <text id="' . $this->textId2_4 . '" bytes="44" /> </revision> </page> @@ -548,6 +564,8 @@ class TextPassDumperTest extends DumpTestCase { </contributor> <comment>Talk BackupDumperTestP1 Summary1</comment> <sha1>nktofwzd0tl192k3zfepmlzxoax1lpe</sha1> + <model>wikitext</model> + <format>text/x-wiki</format> <text id="' . $this->textId4_1 . '" bytes="35" /> </revision> </page> @@ -556,8 +574,8 @@ class TextPassDumperTest extends DumpTestCase { } $content .= $tail; $this->assertEquals( strlen( $content ), file_put_contents( - $fname, $content ), "Length of prepared stub" ); + $fname, $content ), "Length of prepared stub" ); + return $fname; } - } diff --git a/tests/phpunit/maintenance/backup_LogTest.php b/tests/phpunit/maintenance/backup_LogTest.php index 8a8dea5a..98d81653 100644 --- a/tests/phpunit/maintenance/backup_LogTest.php +++ b/tests/phpunit/maintenance/backup_LogTest.php @@ -28,18 +28,19 @@ class BackupDumperLoggerTest extends DumpTestCase { * @return int id of the added log entry */ private function addLogEntry( $type, $subtype, User $user, $ns, $title, - $comment = null, $parameters = null ) { - - $logEntry = new ManualLogEntry( $type, $subtype ); + $comment = null, $parameters = null + ) { + $logEntry = new ManualLogEntry( $type, $subtype ); $logEntry->setPerformer( $user ); $logEntry->setTarget( Title::newFromText( $title, $ns ) ); - if ( $comment !== null ) { + if ( $comment !== null ) { $logEntry->setComment( $comment ); } - if ( $parameters !== null ) { + if ( $parameters !== null ) { $logEntry->setParameters( $parameters ); } - return $logEntry->insert(); + + return $logEntry->insert(); } function addDBData() { @@ -73,16 +74,14 @@ class BackupDumperLoggerTest extends DumpTestCase { $this->logId3 = $this->addLogEntry( 'move', 'delete', $user2, NS_MAIN, "PageA", "SomeOtherComment", - array( 'key1' => 1, 3 => 'value3' ) ); + array( 'key1' => 1, 3 => 'value3' ) ); $this->assertGreaterThan( 0, $this->logId3 ); - } catch ( Exception $e ) { // We'd love to pass $e directly. However, ... see // documentation of exceptionFromAddDBData in // DumpTestCase $this->exceptionFromAddDBData = $e; } - } @@ -101,7 +100,8 @@ class BackupDumperLoggerTest extends DumpTestCase { * @param $parameters array: (optional) unserialized data accompanying the log entry */ private function assertLogItem( $id, $user_name, $user_id, $comment, $type, - $subtype, $title, $parameters = array() ) { + $subtype, $title, $parameters = array() + ) { $this->assertNodeStart( "logitem" ); $this->skipWhitespace(); @@ -134,12 +134,12 @@ class BackupDumperLoggerTest extends DumpTestCase { $this->skipWhitespace(); } - function testPlain () { + function testPlain() { global $wgContLang; // Preparing the dump $fname = $this->getNewTempFile(); - $dumper = new BackupDumper( array ( "--output=file:" . $fname ) ); + $dumper = new BackupDumper( array( "--output=file:" . $fname ) ); $dumper->startId = $this->logId1; $dumper->endId = $this->logId3 + 1; $dumper->reporting = false; @@ -172,10 +172,12 @@ class BackupDumperLoggerTest extends DumpTestCase { function testXmlDumpsBackupUseCaseLogging() { global $wgContLang; + $this->checkHasGzip(); + // Preparing the dump $fname = $this->getNewTempFile(); - $dumper = new BackupDumper( array ( "--output=gzip:" . $fname, - "--reporting=2" ) ); + $dumper = new BackupDumper( array( "--output=gzip:" . $fname, + "--reporting=2" ) ); $dumper->startId = $this->logId1; $dumper->endId = $this->logId3 + 1; $dumper->setDb( $this->db ); @@ -186,7 +188,7 @@ class BackupDumperLoggerTest extends DumpTestCase { // to be able to alert (once dumping produces reports) that this test // needs updates. $dumper->stderr = fopen( 'php://output', 'a' ); - if ( $dumper->stderr === FALSE ) { + if ( $dumper->stderr === false ) { $this->fail( "Could not open stream for stderr" ); } @@ -223,5 +225,4 @@ class BackupDumperLoggerTest extends DumpTestCase { // the following statement to catch good output $this->expectOutputString( '' ); } - } diff --git a/tests/phpunit/maintenance/backup_PageTest.php b/tests/phpunit/maintenance/backup_PageTest.php index 925e277d..99bd2700 100644 --- a/tests/phpunit/maintenance/backup_PageTest.php +++ b/tests/phpunit/maintenance/backup_PageTest.php @@ -10,26 +10,43 @@ class BackupDumperPageTest extends DumpTestCase { // We'll add several pages, revision and texts. The following variables hold the // corresponding ids. private $pageId1, $pageId2, $pageId3, $pageId4, $pageId5; + private $pageTitle1, $pageTitle2, $pageTitle3, $pageTitle4, $pageTitle5; private $revId1_1, $textId1_1; private $revId2_1, $textId2_1, $revId2_2, $textId2_2; private $revId2_3, $textId2_3, $revId2_4, $textId2_4; private $revId3_1, $textId3_1, $revId3_2, $textId3_2; private $revId4_1, $textId4_1; + private $namespace, $talk_namespace; function addDBData() { + // be sure, titles created here using english namespace names + $this->setMwGlobals( array( + 'wgLanguageCode' => 'en', + 'wgContLang' => Language::factory( 'en' ), + ) ); + $this->tablesUsed[] = 'page'; $this->tablesUsed[] = 'revision'; $this->tablesUsed[] = 'text'; try { - $title = Title::newFromText( 'BackupDumperTestP1' ); - $page = WikiPage::factory( $title ); + $this->namespace = $this->getDefaultWikitextNS(); + $this->talk_namespace = NS_TALK; + + if ( $this->namespace === $this->talk_namespace ) { + // @todo work around this. + throw new MWException( "The default wikitext namespace is the talk namespace. " + . " We can't currently deal with that." ); + } + + $this->pageTitle1 = Title::newFromText( 'BackupDumperTestP1', $this->namespace ); + $page = WikiPage::factory( $this->pageTitle1 ); list( $this->revId1_1, $this->textId1_1 ) = $this->addRevision( $page, "BackupDumperTestP1Text1", "BackupDumperTestP1Summary1" ); $this->pageId1 = $page->getId(); - $title = Title::newFromText( 'BackupDumperTestP2' ); - $page = WikiPage::factory( $title ); + $this->pageTitle2 = Title::newFromText( 'BackupDumperTestP2', $this->namespace ); + $page = WikiPage::factory( $this->pageTitle2 ); list( $this->revId2_1, $this->textId2_1 ) = $this->addRevision( $page, "BackupDumperTestP2Text1", "BackupDumperTestP2Summary1" ); list( $this->revId2_2, $this->textId2_2 ) = $this->addRevision( $page, @@ -41,8 +58,8 @@ class BackupDumperPageTest extends DumpTestCase { "BackupDumperTestP2Summary4 extra " ); $this->pageId2 = $page->getId(); - $title = Title::newFromText( 'BackupDumperTestP3' ); - $page = WikiPage::factory( $title ); + $this->pageTitle3 = Title::newFromText( 'BackupDumperTestP3', $this->namespace ); + $page = WikiPage::factory( $this->pageTitle3 ); list( $this->revId3_1, $this->textId3_1 ) = $this->addRevision( $page, "BackupDumperTestP3Text1", "BackupDumperTestP2Summary1" ); list( $this->revId3_2, $this->textId3_2 ) = $this->addRevision( $page, @@ -50,8 +67,8 @@ class BackupDumperPageTest extends DumpTestCase { $this->pageId3 = $page->getId(); $page->doDeleteArticle( "Testing ;)" ); - $title = Title::newFromText( 'BackupDumperTestP1', NS_TALK ); - $page = WikiPage::factory( $title ); + $this->pageTitle4 = Title::newFromText( 'BackupDumperTestP1', $this->talk_namespace ); + $page = WikiPage::factory( $this->pageTitle4 ); list( $this->revId4_1, $this->textId4_1 ) = $this->addRevision( $page, "Talk about BackupDumperTestP1 Text1", "Talk BackupDumperTestP1 Summary1" ); @@ -62,10 +79,9 @@ class BackupDumperPageTest extends DumpTestCase { // DumpTestCase $this->exceptionFromAddDBData = $e; } - } - public function setUp() { + protected function setUp() { parent::setUp(); // Since we will restrict dumping by page ranges (to allow @@ -76,13 +92,12 @@ class BackupDumperPageTest extends DumpTestCase { array( $this->pageId2, $this->pageId3, $this->pageId4 ), array( $this->pageId1 + 1, $this->pageId2 + 1, $this->pageId3 + 1 ), "Page ids increasing without holes" ); - } - function testFullTextPlain () { + function testFullTextPlain() { // Preparing the dump $fname = $this->getNewTempFile(); - $dumper = new BackupDumper( array ( "--output=file:" . $fname ) ); + $dumper = new BackupDumper( array( "--output=file:" . $fname ) ); $dumper->startId = $this->pageId1; $dumper->endId = $this->pageId4 + 1; $dumper->reporting = false; @@ -95,14 +110,14 @@ class BackupDumperPageTest extends DumpTestCase { $this->assertDumpStart( $fname ); // Page 1 - $this->assertPageStart( $this->pageId1, NS_MAIN, "BackupDumperTestP1" ); + $this->assertPageStart( $this->pageId1, $this->namespace, $this->pageTitle1->getPrefixedText() ); $this->assertRevision( $this->revId1_1, "BackupDumperTestP1Summary1", $this->textId1_1, 23, "0bolhl6ol7i6x0e7yq91gxgaan39j87", "BackupDumperTestP1Text1" ); $this->assertPageEnd(); // Page 2 - $this->assertPageStart( $this->pageId2, NS_MAIN, "BackupDumperTestP2" ); + $this->assertPageStart( $this->pageId2, $this->namespace, $this->pageTitle2->getPrefixedText() ); $this->assertRevision( $this->revId2_1, "BackupDumperTestP2Summary1", $this->textId2_1, 23, "jprywrymfhysqllua29tj3sc7z39dl2", "BackupDumperTestP2Text1" ); @@ -121,7 +136,7 @@ class BackupDumperPageTest extends DumpTestCase { // -> Page is marked deleted. Hence not visible // Page 4 - $this->assertPageStart( $this->pageId4, NS_TALK, "Talk:BackupDumperTestP1" ); + $this->assertPageStart( $this->pageId4, $this->talk_namespace, $this->pageTitle4->getPrefixedText() ); $this->assertRevision( $this->revId4_1, "Talk BackupDumperTestP1 Summary1", $this->textId4_1, 35, "nktofwzd0tl192k3zfepmlzxoax1lpe", "Talk about BackupDumperTestP1 Text1" ); @@ -130,10 +145,10 @@ class BackupDumperPageTest extends DumpTestCase { $this->assertDumpEnd(); } - function testFullStubPlain () { + function testFullStubPlain() { // Preparing the dump $fname = $this->getNewTempFile(); - $dumper = new BackupDumper( array ( "--output=file:" . $fname ) ); + $dumper = new BackupDumper( array( "--output=file:" . $fname ) ); $dumper->startId = $this->pageId1; $dumper->endId = $this->pageId4 + 1; $dumper->reporting = false; @@ -146,13 +161,13 @@ class BackupDumperPageTest extends DumpTestCase { $this->assertDumpStart( $fname ); // Page 1 - $this->assertPageStart( $this->pageId1, NS_MAIN, "BackupDumperTestP1" ); + $this->assertPageStart( $this->pageId1, $this->namespace, $this->pageTitle1->getPrefixedText() ); $this->assertRevision( $this->revId1_1, "BackupDumperTestP1Summary1", $this->textId1_1, 23, "0bolhl6ol7i6x0e7yq91gxgaan39j87" ); $this->assertPageEnd(); // Page 2 - $this->assertPageStart( $this->pageId2, NS_MAIN, "BackupDumperTestP2" ); + $this->assertPageStart( $this->pageId2, $this->namespace, $this->pageTitle2->getPrefixedText() ); $this->assertRevision( $this->revId2_1, "BackupDumperTestP2Summary1", $this->textId2_1, 23, "jprywrymfhysqllua29tj3sc7z39dl2" ); $this->assertRevision( $this->revId2_2, "BackupDumperTestP2Summary2", @@ -167,7 +182,7 @@ class BackupDumperPageTest extends DumpTestCase { // -> Page is marked deleted. Hence not visible // Page 4 - $this->assertPageStart( $this->pageId4, NS_TALK, "Talk:BackupDumperTestP1" ); + $this->assertPageStart( $this->pageId4, $this->talk_namespace, $this->pageTitle4->getPrefixedText() ); $this->assertRevision( $this->revId4_1, "Talk BackupDumperTestP1 Summary1", $this->textId4_1, 35, "nktofwzd0tl192k3zfepmlzxoax1lpe" ); $this->assertPageEnd(); @@ -175,10 +190,10 @@ class BackupDumperPageTest extends DumpTestCase { $this->assertDumpEnd(); } - function testCurrentStubPlain () { + function testCurrentStubPlain() { // Preparing the dump $fname = $this->getNewTempFile(); - $dumper = new BackupDumper( array ( "--output=file:" . $fname ) ); + $dumper = new BackupDumper( array( "--output=file:" . $fname ) ); $dumper->startId = $this->pageId1; $dumper->endId = $this->pageId4 + 1; $dumper->reporting = false; @@ -191,13 +206,13 @@ class BackupDumperPageTest extends DumpTestCase { $this->assertDumpStart( $fname ); // Page 1 - $this->assertPageStart( $this->pageId1, NS_MAIN, "BackupDumperTestP1" ); + $this->assertPageStart( $this->pageId1, $this->namespace, $this->pageTitle1->getPrefixedText() ); $this->assertRevision( $this->revId1_1, "BackupDumperTestP1Summary1", $this->textId1_1, 23, "0bolhl6ol7i6x0e7yq91gxgaan39j87" ); $this->assertPageEnd(); // Page 2 - $this->assertPageStart( $this->pageId2, NS_MAIN, "BackupDumperTestP2" ); + $this->assertPageStart( $this->pageId2, $this->namespace, $this->pageTitle2->getPrefixedText() ); $this->assertRevision( $this->revId2_4, "BackupDumperTestP2Summary4 extra", $this->textId2_4, 44, "6o1ciaxa6pybnqprmungwofc4lv00wv", false, $this->revId2_3 ); $this->assertPageEnd(); @@ -206,7 +221,7 @@ class BackupDumperPageTest extends DumpTestCase { // -> Page is marked deleted. Hence not visible // Page 4 - $this->assertPageStart( $this->pageId4, NS_TALK, "Talk:BackupDumperTestP1" ); + $this->assertPageStart( $this->pageId4, $this->talk_namespace, $this->pageTitle4->getPrefixedText() ); $this->assertRevision( $this->revId4_1, "Talk BackupDumperTestP1 Summary1", $this->textId4_1, 35, "nktofwzd0tl192k3zfepmlzxoax1lpe" ); $this->assertPageEnd(); @@ -214,10 +229,12 @@ class BackupDumperPageTest extends DumpTestCase { $this->assertDumpEnd(); } - function testCurrentStubGzip () { + function testCurrentStubGzip() { + $this->checkHasGzip(); + // Preparing the dump $fname = $this->getNewTempFile(); - $dumper = new BackupDumper( array ( "--output=gzip:" . $fname ) ); + $dumper = new BackupDumper( array( "--output=gzip:" . $fname ) ); $dumper->startId = $this->pageId1; $dumper->endId = $this->pageId4 + 1; $dumper->reporting = false; @@ -231,13 +248,13 @@ class BackupDumperPageTest extends DumpTestCase { $this->assertDumpStart( $fname ); // Page 1 - $this->assertPageStart( $this->pageId1, NS_MAIN, "BackupDumperTestP1" ); + $this->assertPageStart( $this->pageId1, $this->namespace, $this->pageTitle1->getPrefixedText() ); $this->assertRevision( $this->revId1_1, "BackupDumperTestP1Summary1", $this->textId1_1, 23, "0bolhl6ol7i6x0e7yq91gxgaan39j87" ); $this->assertPageEnd(); // Page 2 - $this->assertPageStart( $this->pageId2, NS_MAIN, "BackupDumperTestP2" ); + $this->assertPageStart( $this->pageId2, $this->namespace, $this->pageTitle2->getPrefixedText() ); $this->assertRevision( $this->revId2_4, "BackupDumperTestP2Summary4 extra", $this->textId2_4, 44, "6o1ciaxa6pybnqprmungwofc4lv00wv", false, $this->revId2_3 ); $this->assertPageEnd(); @@ -246,7 +263,7 @@ class BackupDumperPageTest extends DumpTestCase { // -> Page is marked deleted. Hence not visible // Page 4 - $this->assertPageStart( $this->pageId4, NS_TALK, "Talk:BackupDumperTestP1" ); + $this->assertPageStart( $this->pageId4, $this->talk_namespace, $this->pageTitle4->getPrefixedText() ); $this->assertRevision( $this->revId4_1, "Talk BackupDumperTestP1 Summary1", $this->textId4_1, 35, "nktofwzd0tl192k3zfepmlzxoax1lpe" ); $this->assertPageEnd(); @@ -255,8 +272,7 @@ class BackupDumperPageTest extends DumpTestCase { } - - function testXmlDumpsBackupUseCase () { + function testXmlDumpsBackupUseCase() { // xmldumps-backup typically performs a single dump that that writes // out three files // * gzipped stubs of everything (meta-history) @@ -267,15 +283,17 @@ class BackupDumperPageTest extends DumpTestCase { // We reproduce such a setup with our mini fixture, although we omit // chunks, and all the other gimmicks of xmldumps-backup. // + $this->checkHasGzip(); + $fnameMetaHistory = $this->getNewTempFile(); $fnameMetaCurrent = $this->getNewTempFile(); $fnameArticles = $this->getNewTempFile(); - $dumper = new BackupDumper( array ( "--output=gzip:" . $fnameMetaHistory, - "--output=gzip:" . $fnameMetaCurrent, "--filter=latest", - "--output=gzip:" . $fnameArticles, "--filter=latest", - "--filter=notalk", "--filter=namespace:!NS_USER", - "--reporting=1000" ) ); + $dumper = new BackupDumper( array( "--output=gzip:" . $fnameMetaHistory, + "--output=gzip:" . $fnameMetaCurrent, "--filter=latest", + "--output=gzip:" . $fnameArticles, "--filter=latest", + "--filter=notalk", "--filter=namespace:!NS_USER", + "--reporting=1000" ) ); $dumper->startId = $this->pageId1; $dumper->endId = $this->pageId4 + 1; $dumper->setDb( $this->db ); @@ -285,7 +303,7 @@ class BackupDumperPageTest extends DumpTestCase { // computer. We only check that reporting does not crash the dumping // and that something is reported $dumper->stderr = fopen( 'php://output', 'a' ); - if ( $dumper->stderr === FALSE ) { + if ( $dumper->stderr === false ) { $this->fail( "Could not open stream for stderr" ); } @@ -300,13 +318,13 @@ class BackupDumperPageTest extends DumpTestCase { $this->assertDumpStart( $fnameMetaHistory ); // Page 1 - $this->assertPageStart( $this->pageId1, NS_MAIN, "BackupDumperTestP1" ); + $this->assertPageStart( $this->pageId1, $this->namespace, $this->pageTitle1->getPrefixedText() ); $this->assertRevision( $this->revId1_1, "BackupDumperTestP1Summary1", $this->textId1_1, 23, "0bolhl6ol7i6x0e7yq91gxgaan39j87" ); $this->assertPageEnd(); // Page 2 - $this->assertPageStart( $this->pageId2, NS_MAIN, "BackupDumperTestP2" ); + $this->assertPageStart( $this->pageId2, $this->namespace, $this->pageTitle2->getPrefixedText() ); $this->assertRevision( $this->revId2_1, "BackupDumperTestP2Summary1", $this->textId2_1, 23, "jprywrymfhysqllua29tj3sc7z39dl2" ); $this->assertRevision( $this->revId2_2, "BackupDumperTestP2Summary2", @@ -321,7 +339,7 @@ class BackupDumperPageTest extends DumpTestCase { // -> Page is marked deleted. Hence not visible // Page 4 - $this->assertPageStart( $this->pageId4, NS_TALK, "Talk:BackupDumperTestP1" ); + $this->assertPageStart( $this->pageId4, $this->talk_namespace, $this->pageTitle4->getPrefixedText() ); $this->assertRevision( $this->revId4_1, "Talk BackupDumperTestP1 Summary1", $this->textId4_1, 35, "nktofwzd0tl192k3zfepmlzxoax1lpe" ); $this->assertPageEnd(); @@ -334,13 +352,13 @@ class BackupDumperPageTest extends DumpTestCase { $this->assertDumpStart( $fnameMetaCurrent ); // Page 1 - $this->assertPageStart( $this->pageId1, NS_MAIN, "BackupDumperTestP1" ); + $this->assertPageStart( $this->pageId1, $this->namespace, $this->pageTitle1->getPrefixedText() ); $this->assertRevision( $this->revId1_1, "BackupDumperTestP1Summary1", $this->textId1_1, 23, "0bolhl6ol7i6x0e7yq91gxgaan39j87" ); $this->assertPageEnd(); // Page 2 - $this->assertPageStart( $this->pageId2, NS_MAIN, "BackupDumperTestP2" ); + $this->assertPageStart( $this->pageId2, $this->namespace, $this->pageTitle2->getPrefixedText() ); $this->assertRevision( $this->revId2_4, "BackupDumperTestP2Summary4 extra", $this->textId2_4, 44, "6o1ciaxa6pybnqprmungwofc4lv00wv", false, $this->revId2_3 ); $this->assertPageEnd(); @@ -349,7 +367,7 @@ class BackupDumperPageTest extends DumpTestCase { // -> Page is marked deleted. Hence not visible // Page 4 - $this->assertPageStart( $this->pageId4, NS_TALK, "Talk:BackupDumperTestP1" ); + $this->assertPageStart( $this->pageId4, $this->talk_namespace, $this->pageTitle4->getPrefixedText() ); $this->assertRevision( $this->revId4_1, "Talk BackupDumperTestP1 Summary1", $this->textId4_1, 35, "nktofwzd0tl192k3zfepmlzxoax1lpe" ); $this->assertPageEnd(); @@ -362,13 +380,13 @@ class BackupDumperPageTest extends DumpTestCase { $this->assertDumpStart( $fnameArticles ); // Page 1 - $this->assertPageStart( $this->pageId1, NS_MAIN, "BackupDumperTestP1" ); + $this->assertPageStart( $this->pageId1, $this->namespace, $this->pageTitle1->getPrefixedText() ); $this->assertRevision( $this->revId1_1, "BackupDumperTestP1Summary1", $this->textId1_1, 23, "0bolhl6ol7i6x0e7yq91gxgaan39j87" ); $this->assertPageEnd(); // Page 2 - $this->assertPageStart( $this->pageId2, NS_MAIN, "BackupDumperTestP2" ); + $this->assertPageStart( $this->pageId2, $this->namespace, $this->pageTitle2->getPrefixedText() ); $this->assertRevision( $this->revId2_4, "BackupDumperTestP2Summary4 extra", $this->textId2_4, 44, "6o1ciaxa6pybnqprmungwofc4lv00wv", false, $this->revId2_3 ); $this->assertPageEnd(); @@ -377,13 +395,10 @@ class BackupDumperPageTest extends DumpTestCase { // -> Page is marked deleted. Hence not visible // Page 4 - // -> Page is not in NS_MAIN. Hence not visible + // -> Page is not in $this->namespace. Hence not visible $this->assertDumpEnd(); $this->expectETAOutput(); } - - - } diff --git a/tests/phpunit/maintenance/fetchTextTest.php b/tests/phpunit/maintenance/fetchTextTest.php index e7ffa01c..e8df199e 100644 --- a/tests/phpunit/maintenance/fetchTextTest.php +++ b/tests/phpunit/maintenance/fetchTextTest.php @@ -18,7 +18,7 @@ class SemiMockedFetchText extends FetchText { /** * @var bool Whether or not a text for stdin has been provided */ - private $mockSetUp = False; + private $mockSetUp = false; /** * @var Array Invocation counters for the mocked aspects @@ -26,16 +26,14 @@ class SemiMockedFetchText extends FetchText { private $mockInvocations = array( 'getStdin' => 0 ); - /** * Data for the fake stdin * * @param $stdin String The string to be used instead of stdin */ - function mockStdin( $stdin ) - { + function mockStdin( $stdin ) { $this->mockStdinText = $stdin; - $this->mockSetUp = True; + $this->mockSetUp = true; } /** @@ -44,30 +42,27 @@ class SemiMockedFetchText extends FetchText { * @return Array An array, whose keys are function names. The corresponding values * denote the number of times the function has been invoked. */ - function mockGetInvocations() - { + function mockGetInvocations() { return $this->mockInvocations; } // ----------------------------------------------------------------- // Mocked functions from FetchText follow. - function getStdin( $len = null ) - { + function getStdin( $len = null ) { $this->mockInvocations['getStdin']++; if ( $len !== null ) { throw new PHPUnit_Framework_ExpectationFailedException( "Tried to get stdin with non null parameter" ); } - if ( ! $this->mockSetUp ) { + if ( !$this->mockSetUp ) { throw new PHPUnit_Framework_ExpectationFailedException( "Tried to get stdin before setting up rerouting" ); } return fopen( 'data://text/plain,' . $this->mockStdinText, 'r' ); } - } /** @@ -111,7 +106,7 @@ class FetchTextTest extends MediaWikiTestCase { * @throws MWExcepion */ private function addRevision( $page, $text, $summary ) { - $status = $page->doEdit( $text, $summary ); + $status = $page->doEditContent( ContentHandler::makeContent( $text, $page->getTitle() ), $summary ); if ( $status->isGood() ) { $value = $status->getValue(); $revision = $value['revision']; @@ -129,12 +124,14 @@ class FetchTextTest extends MediaWikiTestCase { $this->tablesUsed[] = 'revision'; $this->tablesUsed[] = 'text'; + $wikitextNamespace = $this->getDefaultWikitextNS(); + try { - $title = Title::newFromText( 'FetchTextTestPage1' ); + $title = Title::newFromText( 'FetchTextTestPage1', $wikitextNamespace ); $page = WikiPage::factory( $title ); $this->textId1 = $this->addRevision( $page, "FetchTextTestPage1Text1", "FetchTextTestPage1Summary1" ); - $title = Title::newFromText( 'FetchTextTestPage2' ); + $title = Title::newFromText( 'FetchTextTestPage2', $wikitextNamespace ); $page = WikiPage::factory( $title ); $this->textId2 = $this->addRevision( $page, "FetchTextTestPage2Text1", "FetchTextTestPage2Summary1" ); $this->textId3 = $this->addRevision( $page, "FetchTextTestPage2Text2", "FetchTextTestPage2Summary2" ); @@ -173,7 +170,6 @@ class FetchTextTest extends MediaWikiTestCase { } - // Instead of the following functions, a data provider would be great. // However, as data providers are evaluated /before/ addDBData, a data // provider would not know the required ids. @@ -190,14 +186,14 @@ class FetchTextTest extends MediaWikiTestCase { function testExistingSeveral() { $this->assertFilter( "$this->textId1\n$this->textId5\n" - . "$this->textId3\n$this->textId3", + . "$this->textId3\n$this->textId3", implode( "", array( - $this->textId1 . "\n23\nFetchTextTestPage1Text1", - $this->textId5 . "\n44\nFetchTextTestPage2Text4 " + $this->textId1 . "\n23\nFetchTextTestPage1Text1", + $this->textId5 . "\n44\nFetchTextTestPage2Text4 " . "some additional Text", - $this->textId3 . "\n23\nFetchTextTestPage2Text2", - $this->textId3 . "\n23\nFetchTextTestPage2Text2" - ) ) ); + $this->textId3 . "\n23\nFetchTextTestPage2Text2", + $this->textId3 . "\n23\nFetchTextTestPage2Text2" + ) ) ); } function testEmpty() { @@ -229,15 +225,14 @@ class FetchTextTest extends MediaWikiTestCase { function testMix() { $this->assertFilter( "ab\n" . $this->textId4 . ".5cd\n\nefg\n" . $this->textId2 - . "\n" . $this->textId3, + . "\n" . $this->textId3, implode( "", array( - "0\n-1\n", - $this->textId4 . "\n23\nFetchTextTestPage2Text3", - "0\n-1\n", - "0\n-1\n", - $this->textId2 . "\n23\nFetchTextTestPage2Text1", - $this->textId3 . "\n23\nFetchTextTestPage2Text2" - ) ) ); + "0\n-1\n", + $this->textId4 . "\n23\nFetchTextTestPage2Text3", + "0\n-1\n", + "0\n-1\n", + $this->textId2 . "\n23\nFetchTextTestPage2Text1", + $this->textId3 . "\n23\nFetchTextTestPage2Text2" + ) ) ); } - } diff --git a/tests/phpunit/maintenance/getSlaveServerTest.php b/tests/phpunit/maintenance/getSlaveServerTest.php index 0b7c758c..2c848862 100644 --- a/tests/phpunit/maintenance/getSlaveServerTest.php +++ b/tests/phpunit/maintenance/getSlaveServerTest.php @@ -52,11 +52,11 @@ class GetSlaveServerTest extends MediaWikiTestCase { // The main answer $output = $this->getActualOutput(); - $firstLineEndPos = strpos( $output,"\n"); - if ( $firstLineEndPos === FALSE ) { + $firstLineEndPos = strpos( $output, "\n" ); + if ( $firstLineEndPos === false ) { $this->fail( "Could not find end of first line of output" ); } - $firstLine = substr( $output, 0 , $firstLineEndPos ); + $firstLine = substr( $output, 0, $firstLineEndPos ); $this->assertRegExp( "/^" . self::getServerRE() . "$/D", $firstLine, "DB Server" ); @@ -64,6 +64,4 @@ class GetSlaveServerTest extends MediaWikiTestCase { $this->expectOutputRegex( "/^[[:space:]]*\[wgDBprefix\][[:space:]]*=> " . $wgDBprefix . "$/m" ); } - - } diff --git a/tests/phpunit/mocks/filebackend/MockFSFile.php b/tests/phpunit/mocks/filebackend/MockFSFile.php new file mode 100644 index 00000000..e0463281 --- /dev/null +++ b/tests/phpunit/mocks/filebackend/MockFSFile.php @@ -0,0 +1,69 @@ +<?php +/** + * Mock of a filesystem file. + * + * 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 + * + * @file + * @ingroup FileBackend + */ + +/** + * Class representing an in memory fake file. + * This is intended for unit testing / developement when you do not want + * to hit the filesystem. + * + * It reimplements abstract methods with some hardcoded values. Might + * not be suitable for all tests but is good enough for the parser tests. + * + * @ingroup FileBackend + */ +class MockFSFile extends FSFile { + protected $sha1Base36 = null; // File Sha1Base36 + + public function exists() { + return true; + } + + /** + * August 22 – The theft of the Mona Lisa is discovered in the Louvre." + * @bug 20281 + */ + public function getSize() { + return 1911; + } + + public function getTimestamp() { + return wfTimestamp( TS_MW ); + } + + public function getMimeType() { + return 'text/mock'; + } + + public function getProps( $ext = true ) { + return array( + 'fileExists' => $this->exists(), + 'size' => $this->getSize(), + 'file-mime' => $this->getMimeType(), + 'sha1' => $this->getSha1Base36(), + ); + } + + public function getSha1Base36( $recache = false ) { + return '1234567890123456789012345678901'; + } +} diff --git a/tests/phpunit/mocks/filebackend/MockFileBackend.php b/tests/phpunit/mocks/filebackend/MockFileBackend.php new file mode 100644 index 00000000..49aefbd1 --- /dev/null +++ b/tests/phpunit/mocks/filebackend/MockFileBackend.php @@ -0,0 +1,122 @@ +<?php +/** + * Simulation (mock) of a backend storage. + * + * 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 + * + * @file + * @ingroup FileBackend + * @author Antoine Musso <hashar@free.fr> + */ + +/** + * Class simulating a backend store. + * + * @ingroup FileBackend + * @since 1.22 + */ +class MockFileBackend extends FileBackendStore { + + protected $mocked = array(); + + /** Poor man debugging */ + protected function debug( $msg = '' ) { + wfDebug( wfGetCaller() . "$msg\n" ); + } + + public function isPathUsableInternal( $storagePath ) { + return true; + } + + protected function doCreateInternal( array $params ) { + if ( isset( $params['content'] ) ) { + $content = $params['content']; + } else { + $content = 'Default mocked file content'; + } + $this->debug( serialize( $params ) ); + $dst = $params['dst']; + $this->mocked[$dst] = $content; + return Status::newGood(); + } + + protected function doStoreInternal( array $params ) { + $this->debug( serialize( $params ) ); + return $this->doCreateInternal( $params ); + } + + protected function doCopyInternal( array $params ) { + $this->debug( serialize( $params ) ); + $src = $params['src']; + $dst = $params['dst']; + $this->mocked[$dst] = $this->mocked[$src]; + return Status::newGood(); + } + + protected function doDeleteInternal( array $params ) { + $this->debug( serialize( $params ) ); + $src = $params['src']; + unset( $this->mocked[$src] ); + return Status::newGood(); + } + + protected function doGetFileStat( array $params ) { + $src = $params['src']; + if ( array_key_exists( $src, $this->mocked ) ) { + $this->debug( "('$src') found" ); + return array( + 'mtime' => wfTimestamp( TS_MW ), + 'size' => strlen( $this->mocked[$src] ), + # No sha1, stat does not need it. + ); + } else { + $this->debug( "('$src') not found" ); + return false; + } + } + + protected function doGetLocalCopyMulti( array $params ) { + $tmpFiles = array(); // (path => MockFSFile) + + $this->debug( '(' . serialize( $params ) . ')' ); + foreach ( $params['srcs'] as $src ) { + $tmpFiles[$src] = new MockFSFile( + wfTempDir() . '/' . wfRandomString( 32 ) + ); + } + return $tmpFiles; + } + + protected function doDirectoryExists( $container, $dir, array $params ) { + $this->debug(); + return true; + } + + public function getDirectoryListInternal( $container, $dir, array $params ) { + $this->debug(); + return array(); + } + + public function getFileListInternal( $container, $dir, array $params ) { + $this->debug(); + return array(); + } + + protected function directoriesAreVirtual() { + $this->debug(); + return true; + } +} diff --git a/tests/phpunit/mocks/media/MockBitmapHandler.php b/tests/phpunit/mocks/media/MockBitmapHandler.php new file mode 100644 index 00000000..742b41e4 --- /dev/null +++ b/tests/phpunit/mocks/media/MockBitmapHandler.php @@ -0,0 +1,92 @@ +<?php +/** + * Fake handler for images. + * + * 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 + * + * @file + * @ingroup Media + */ + +class MockBitmapHandler extends BitmapHandler { + function doTransform( $image, $dstPath, $dstUrl, $params, $flags = 0 ) { + return MockImageHandler::doFakeTransform( $this, $image, $dstPath, $dstUrl, $params, $flags ); + } + function doClientImage( $image, $scalerParams ) { + return $this->getClientScalingThumbnailImage( $image, $scalerParams ); + } +} +class MockSvgHandler extends SvgHandler { + function doTransform( $image, $dstPath, $dstUrl, $params, $flags = 0 ) { + return MockImageHandler::doFakeTransform( $this, $image, $dstPath, $dstUrl, $params, $flags ); + } +} +/** + * Mock handler for images. + * + * This is really intended for unit testing. + * + * @ingroup Media + */ +class MockImageHandler { + + /** + * Override BitmapHandler::doTransform() making sure we do not generate + * a thumbnail at all. That is merely returning a ThumbnailImage that + * will be consumed by the unit test. There is no need to create a real + * thumbnail on the filesystem. + */ + static function doFakeTransform( $that, $image, $dstPath, $dstUrl, $params, $flags = 0 ) { + # Example of what we receive: + # $image: LocalFile + # $dstPath: /tmp/transform_7d0a7a2f1a09-1.jpg + # $dstUrl : http://example.com/images/thumb/0/09/Bad.jpg/320px-Bad.jpg + # $params: width: 320, descriptionUrl http://trunk.dev/wiki/File:Bad.jpg + + $that->normaliseParams( $image, $params ); + + $scalerParams = array( + # The size to which the image will be resized + 'physicalWidth' => $params['physicalWidth'], + 'physicalHeight' => $params['physicalHeight'], + 'physicalDimensions' => "{$params['physicalWidth']}x{$params['physicalHeight']}", + # The size of the image on the page + 'clientWidth' => $params['width'], + 'clientHeight' => $params['height'], + # Comment as will be added to the EXIF of the thumbnail + 'comment' => isset( $params['descriptionUrl'] ) ? + "File source: {$params['descriptionUrl']}" : '', + # Properties of the original image + 'srcWidth' => $image->getWidth(), + 'srcHeight' => $image->getHeight(), + 'mimeType' => $image->getMimeType(), + 'dstPath' => $dstPath, + 'dstUrl' => $dstUrl, + ); + + # In some cases, we do not bother generating a thumbnail. + if ( !$image->mustRender() && + $scalerParams['physicalWidth'] == $scalerParams['srcWidth'] + && $scalerParams['physicalHeight'] == $scalerParams['srcHeight'] + ) { + wfDebug( __METHOD__ . ": returning unscaled image\n" ); + // getClientScalingThumbnailImage is protected + return $that->doClientImage( $image, $scalerParams ); + } + + return new ThumbnailImage( $image, $dstUrl, false, $params ); + } +} diff --git a/tests/phpunit/phpunit.php b/tests/phpunit/phpunit.php index bcbf4ec1..1d65e521 100644 --- a/tests/phpunit/phpunit.php +++ b/tests/phpunit/phpunit.php @@ -8,23 +8,20 @@ /* Configuration */ -// Evaluate the include path relative to this file -$IP = dirname( dirname( __DIR__ ) ); - // Set a flag which can be used to detect when other scripts have been entered through this entry point or not define( 'MW_PHPUNIT_TEST', true ); // Start up MediaWiki in command-line mode -require_once( "$IP/maintenance/Maintenance.php" ); +require_once dirname( dirname( __DIR__ ) ) . "/maintenance/Maintenance.php"; class PHPUnitMaintClass extends Maintenance { function __construct() { parent::__construct(); - $this->addOption( 'with-phpunitdir' - , 'Directory to include PHPUnit from, for example when using a git fetchout from upstream. Path will be prepended to PHP `include_path`.' - , false # not required - , true # need arg + $this->addOption( 'with-phpunitdir', + 'Directory to include PHPUnit from, for example when using a git fetchout from upstream. Path will be prepended to PHP `include_path`.', + false, # not required + true # need arg ); } @@ -36,6 +33,9 @@ class PHPUnitMaintClass extends Maintenance { global $wgLocaltimezone, $wgLocalisationCacheConf; global $wgDevelopmentWarnings; + // Inject test autoloader + require_once __DIR__ . '/../TestsAutoLoader.php'; + // wfWarn should cause tests to fail $wgDevelopmentWarnings = true; @@ -49,26 +49,35 @@ class PHPUnitMaintClass extends Maintenance { // Assume UTC for testing purposes $wgLocaltimezone = 'UTC'; - $wgLocalisationCacheConf['storeClass'] = 'LCStore_Null'; + $wgLocalisationCacheConf['storeClass'] = 'LCStore_Null'; + + // Bug 44192 Do not attempt to send a real e-mail + Hooks::clear( 'AlternateUserMailer' ); + Hooks::register( + 'AlternateUserMailer', + function () { + return false; + } + ); } public function execute() { global $IP; # Make sure we have --configuration or PHPUnit might complain - if( !in_array( '--configuration', $_SERVER['argv'] ) ) { + if ( !in_array( '--configuration', $_SERVER['argv'] ) ) { //Hack to eliminate the need to use the Makefile (which sucks ATM) array_splice( $_SERVER['argv'], 1, 0, array( '--configuration', $IP . '/tests/phpunit/suite.xml' ) ); } # --with-phpunitdir let us override the default PHPUnit version - if( $phpunitDir = $this->getOption( 'with-phpunitdir' ) ) { + if ( $phpunitDir = $this->getOption( 'with-phpunitdir' ) ) { # Sanity checks - if( !is_dir($phpunitDir) ) { + if ( !is_dir( $phpunitDir ) ) { $this->error( "--with-phpunitdir should be set to an existing directory", 1 ); } - if( !is_readable( $phpunitDir."/PHPUnit/Runner/Version.php" ) ) { + if ( !is_readable( $phpunitDir . "/PHPUnit/Runner/Version.php" ) ) { $this->error( "No usable PHPUnit installation in $phpunitDir.\nAborting.\n", 1 ); } @@ -80,10 +89,9 @@ class PHPUnitMaintClass extends Maintenance { # Cleanup $args array so the option and its value do not # pollute PHPUnit $key = array_search( '--with-phpunitdir', $_SERVER['argv'] ); - unset( $_SERVER['argv'][$key] ); // the option - unset( $_SERVER['argv'][$key+1] ); // its value + unset( $_SERVER['argv'][$key] ); // the option + unset( $_SERVER['argv'][$key + 1] ); // its value $_SERVER['argv'] = array_values( $_SERVER['argv'] ); - } } @@ -93,15 +101,19 @@ class PHPUnitMaintClass extends Maintenance { } $maintClass = 'PHPUnitMaintClass'; -require( RUN_MAINTENANCE_IF_MAIN ); +require RUN_MAINTENANCE_IF_MAIN; -require_once( 'PHPUnit/Runner/Version.php' ); +if ( !class_exists( 'PHPUnit_Runner_Version' ) ) { + require_once 'PHPUnit/Runner/Version.php'; +} -if( PHPUnit_Runner_Version::id() !== '@package_version@' - && version_compare( PHPUnit_Runner_Version::id(), '3.6.7', '<' ) ) { +if ( PHPUnit_Runner_Version::id() !== '@package_version@' + && version_compare( PHPUnit_Runner_Version::id(), '3.6.7', '<' ) +) { die( 'PHPUnit 3.6.7 or later required, you have ' . PHPUnit_Runner_Version::id() . ".\n" ); } -require_once( 'PHPUnit/Autoload.php' ); -require_once( "$IP/tests/TestsAutoLoader.php" ); +if ( !class_exists( 'PHPUnit_TextUI_Command' ) ) { + require_once 'PHPUnit/Autoload.php'; +} MediaWikiPHPUnitCommand::main(); diff --git a/tests/phpunit/skins/SideBarTest.php b/tests/phpunit/skins/SideBarTest.php index 912d7602..a385320f 100644 --- a/tests/phpunit/skins/SideBarTest.php +++ b/tests/phpunit/skins/SideBarTest.php @@ -5,15 +5,14 @@ */ class SideBarTest extends MediaWikiLangTestCase { - /** A skin template, reinitialized before each test */ + /** + * A skin template, reinitialized before each test + * @var SkinTemplate + */ private $skin; /** Local cache for sidebar messages */ private $messages; - function __construct() { - parent::__construct(); - } - /** Build $this->messages array */ private function initMessagesHref() { # List of default messages for the sidebar: @@ -26,29 +25,26 @@ class SideBarTest extends MediaWikiLangTestCase { 'helppage', ); - foreach( $URL_messages as $m ) { - $titleName = MessageCache::singleton()->get($m); + foreach ( $URL_messages as $m ) { + $titleName = MessageCache::singleton()->get( $m ); $title = Title::newFromText( $titleName ); $this->messages[$m]['href'] = $title->getLocalURL(); } } - function setUp() { + protected function setUp() { parent::setUp(); $this->initMessagesHref(); $this->skin = new SkinTemplate(); $this->skin->getContext()->setLanguage( Language::factory( 'en' ) ); } - function tearDown() { - parent::tearDown(); - $this->skin = null; - } /** * Internal helper to test the sidebar * @param $expected * @param $text * @param $message (Default: '') + * @todo this assert method to should be converted to a test using a dataprovider.. */ private function assertSideBar( $expected, $text, $message = '' ) { $bar = array(); @@ -56,93 +52,101 @@ class SideBarTest extends MediaWikiLangTestCase { $this->assertEquals( $expected, $bar, $message ); } - function testSidebarWithOnlyTwoTitles() { + /** + * @covers SkinTemplate::addToSidebarPlain + */ + public function testSidebarWithOnlyTwoTitles() { $this->assertSideBar( - array( - 'Title1' => array(), - 'Title2' => array(), - ), -'* Title1 + array( + 'Title1' => array(), + 'Title2' => array(), + ), + '* Title1 * Title2 ' ); } - function testExpandMessages() { + /** + * @covers SkinTemplate::addToSidebarPlain + */ + public function testExpandMessages() { $this->assertSidebar( - array( 'Title' => array( - array( - 'text' => 'Help', - 'href' => $this->messages['helppage']['href'], - 'id' => 'n-help', - 'active' => null - ) - )), -'* Title + array( 'Title' => array( + array( + 'text' => 'Help', + 'href' => $this->messages['helppage']['href'], + 'id' => 'n-help', + 'active' => null + ) + ) ), + '* Title ** helppage|help ' ); } - function testExternalUrlsRequireADescription() { + /** + * @covers SkinTemplate::addToSidebarPlain + */ + public function testExternalUrlsRequireADescription() { $this->assertSidebar( - array( 'Title' => array( - # ** http://www.mediawiki.org/| Home - array( - 'text' => 'Home', - 'href' => 'http://www.mediawiki.org/', - 'id' => 'n-Home', - 'active' => null, - 'rel' => 'nofollow', - ), - # ** http://valid.no.desc.org/ - # ... skipped since it is missing a pipe with a description - )), -'* Title + array( 'Title' => array( + # ** http://www.mediawiki.org/| Home + array( + 'text' => 'Home', + 'href' => 'http://www.mediawiki.org/', + 'id' => 'n-Home', + 'active' => null, + 'rel' => 'nofollow', + ), + # ** http://valid.no.desc.org/ + # ... skipped since it is missing a pipe with a description + ) ), + '* Title ** http://www.mediawiki.org/| Home ** http://valid.no.desc.org/ ' - ); - } + /** * bug 33321 - Make sure there's a | after transforming. * @group Database + * @covers SkinTemplate::addToSidebarPlain */ - function testTrickyPipe() { + public function testTrickyPipe() { $this->assertSidebar( - array( 'Title' => array( - # The first 2 are skipped - # Doesn't really test the url properly - # because it will vary with $wgArticlePath et al. - # ** Baz|Fred - array( - 'text' => 'Fred', - 'href' => Title::newFromText( 'Baz' )->getLocalUrl(), - 'id' => 'n-Fred', - 'active' => null, - ), - array( - 'text' => 'title-to-display', - 'href' => Title::newFromText( 'page-to-go-to' )->getLocalUrl(), - 'id' => 'n-title-to-display', - 'active' => null, - ), - )), -'* Title + array( 'Title' => array( + # The first 2 are skipped + # Doesn't really test the url properly + # because it will vary with $wgArticlePath et al. + # ** Baz|Fred + array( + 'text' => 'Fred', + 'href' => Title::newFromText( 'Baz' )->getLocalURL(), + 'id' => 'n-Fred', + 'active' => null, + ), + array( + 'text' => 'title-to-display', + 'href' => Title::newFromText( 'page-to-go-to' )->getLocalURL(), + 'id' => 'n-title-to-display', + 'active' => null, + ), + ) ), + '* Title ** {{PAGENAME|Foo}} ** Bar ** Baz|Fred ** {{PLURAL:1|page-to-go-to{{int:pipe-separator/en}}title-to-display|branch not taken}} ' ); - } #### Attributes for external links ########################## - private function getAttribs( ) { + private function getAttribs() { # Sidebar text we will use everytime $text = '* Title ** http://www.mediawiki.org/| Home'; @@ -155,11 +159,12 @@ class SideBarTest extends MediaWikiLangTestCase { /** * Simple test to verify our helper assertAttribs() is functional - * Please note this assume MediaWiki default settings: - * $wgNoFollowLinks = true - * $wgExternalLinkTarget = false */ - function testTestAttributesAssertionHelper() { + public function testTestAttributesAssertionHelper() { + $this->setMwGlobals( array( + 'wgNoFollowLinks' => true, + 'wgExternalLinkTarget' => false, + ) ); $attribs = $this->getAttribs(); $this->assertArrayHasKey( 'rel', $attribs ); @@ -171,39 +176,31 @@ class SideBarTest extends MediaWikiLangTestCase { /** * Test $wgNoFollowLinks in sidebar */ - function testRespectWgnofollowlinks() { - global $wgNoFollowLinks; - $saved = $wgNoFollowLinks; - $wgNoFollowLinks = false; + public function testRespectWgnofollowlinks() { + $this->setMwGlobals( 'wgNoFollowLinks', false ); $attribs = $this->getAttribs(); $this->assertArrayNotHasKey( 'rel', $attribs, 'External URL in sidebar do not have rel=nofollow when $wgNoFollowLinks = false' ); - - // Restore global - $wgNoFollowLinks = $saved; } /** * Test $wgExternaLinkTarget in sidebar + * @dataProvider dataRespectExternallinktarget */ - function testRespectExternallinktarget() { - global $wgExternalLinkTarget; - $saved = $wgExternalLinkTarget; - - $wgExternalLinkTarget = '_blank'; - $attribs = $this->getAttribs(); - $this->assertArrayHasKey( 'target', $attribs ); - $this->assertEquals( $attribs['target'], '_blank' ); + public function testRespectExternallinktarget( $externalLinkTarget ) { + $this->setMwGlobals( 'wgExternalLinkTarget', $externalLinkTarget ); - $wgExternalLinkTarget = '_self'; $attribs = $this->getAttribs(); $this->assertArrayHasKey( 'target', $attribs ); - $this->assertEquals( $attribs['target'], '_self' ); - - // Restore global - $wgExternalLinkTarget = $saved; + $this->assertEquals( $attribs['target'], $externalLinkTarget ); } + public static function dataRespectExternallinktarget() { + return array( + array( '_blank' ), + array( '_self' ), + ); + } } diff --git a/tests/phpunit/structure/AutoLoaderTest.php b/tests/phpunit/structure/AutoLoaderTest.php new file mode 100644 index 00000000..205ea360 --- /dev/null +++ b/tests/phpunit/structure/AutoLoaderTest.php @@ -0,0 +1,56 @@ +<?php +class AutoLoaderTest extends MediaWikiTestCase { + + /** + * Assert that there were no classes loaded that are not registered with the AutoLoader. + * + * For example foo.php having class Foo and class Bar but only registering Foo. + * This is important because we should not be relying on Foo being used before Bar. + */ + public function testAutoLoadConfig() { + $results = self::checkAutoLoadConf(); + + $this->assertEquals( + $results['expected'], + $results['actual'] + ); + } + + protected static function checkAutoLoadConf() { + global $wgAutoloadLocalClasses, $wgAutoloadClasses, $IP; + $supportsParsekit = function_exists( 'parsekit_compile_file' ); + + // wgAutoloadLocalClasses has precedence, just like in includes/AutoLoader.php + $expected = $wgAutoloadLocalClasses + $wgAutoloadClasses; + $actual = array(); + + $files = array_unique( $expected ); + + foreach ( $files as $file ) { + // Only prefix $IP if it doesn't have it already. + // Generally local classes don't have it, and those from extensions and test suites do. + if ( substr( $file, 0, 1 ) != '/' && substr( $file, 1, 1 ) != ':' ) { + $filePath = "$IP/$file"; + } else { + $filePath = $file; + } + if ( $supportsParsekit ) { + $parseInfo = parsekit_compile_file( "$filePath" ); + $classes = array_keys( $parseInfo['class_table'] ); + } else { + $contents = file_get_contents( "$filePath" ); + $m = array(); + preg_match_all( '/\n\s*(?:final)?\s*(?:abstract)?\s*(?:class|interface)\s+([a-zA-Z0-9_]+)/', $contents, $m, PREG_PATTERN_ORDER ); + $classes = $m[1]; + } + foreach ( $classes as $class ) { + $actual[$class] = $file; + } + } + + return array( + 'expected' => $expected, + 'actual' => $actual, + ); + } +} diff --git a/tests/phpunit/structure/ResourcesTest.php b/tests/phpunit/structure/ResourcesTest.php new file mode 100644 index 00000000..fe823fa4 --- /dev/null +++ b/tests/phpunit/structure/ResourcesTest.php @@ -0,0 +1,131 @@ +<?php +/** + * Sanity checks for making sure registered resources are sane. + * + * @file + * @author Antoine Musso + * @author Niklas Laxström + * @author Santhosh Thottingal + * @author Timo Tijhof + * @copyright © 2012, Antoine Musso + * @copyright © 2012, Niklas Laxström + * @copyright © 2012, Santhosh Thottingal + * @copyright © 2012, Timo Tijhof + * + * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License 2.0 or later + */ +class ResourcesTest extends MediaWikiTestCase { + + /** + * @dataProvider provideResourceFiles + */ + public function testFileExistence( $filename, $module, $resource ) { + $this->assertFileExists( $filename, + "File '$resource' referenced by '$module' must exist." + ); + } + + /** + * This ask the ResouceLoader for all registered files from modules + * created by ResourceLoaderFileModule (or one of its descendants). + * + * + * Since the raw data is stored in protected properties, we have to + * overrride this through ReflectionObject methods. + */ + public static function provideResourceFiles() { + global $wgEnableJavaScriptTest; + + // Test existance of test suite files as well + // (can't use setUp or setMwGlobals because providers are static) + $live_wgEnableJavaScriptTest = $wgEnableJavaScriptTest; + $wgEnableJavaScriptTest = true; + + // Array with arguments for the test function + $cases = array(); + + // Initialize ResourceLoader + $rl = new ResourceLoader(); + + // See also ResourceLoaderFileModule::__construct + $filePathProps = array( + // Lists of file paths + 'lists' => array( + 'scripts', + 'debugScripts', + 'loaderScripts', + 'styles', + ), + + // Collated lists of file paths + 'nested-lists' => array( + 'languageScripts', + 'skinScripts', + 'skinStyles', + ), + ); + + foreach ( $rl->getModuleNames() as $moduleName ) { + $module = $rl->getModule( $moduleName ); + if ( !$module instanceof ResourceLoaderFileModule ) { + continue; + } + + $reflectedModule = new ReflectionObject( $module ); + + $files = array(); + + foreach ( $filePathProps['lists'] as $propName ) { + $property = $reflectedModule->getProperty( $propName ); + $property->setAccessible( true ); + $list = $property->getValue( $module ); + foreach ( $list as $key => $value ) { + // 'scripts' are numeral arrays. + // 'styles' can be numeral or associative. + // In case of associative the key is the file path + // and the value is the 'media' attribute. + if ( is_int( $key ) ) { + $files[] = $value; + } else { + $files[] = $key; + } + } + } + + foreach ( $filePathProps['nested-lists'] as $propName ) { + $property = $reflectedModule->getProperty( $propName ); + $property->setAccessible( true ); + $lists = $property->getValue( $module ); + foreach ( $lists as $list ) { + foreach ( $list as $key => $value ) { + // We need the same filter as for 'lists', + // due to 'skinStyles'. + if ( is_int( $key ) ) { + $files[] = $value; + } else { + $files[] = $key; + } + } + } + } + + // Get method for resolving the paths to full paths + $method = $reflectedModule->getMethod( 'getLocalPath' ); + $method->setAccessible( true ); + + // Populate cases + foreach ( $files as $file ) { + $cases[] = array( + $method->invoke( $module, $file ), + $module->getName(), + $file, + ); + } + } + + // Restore settings + $wgEnableJavaScriptTest = $live_wgEnableJavaScriptTest; + + return $cases; + } +} diff --git a/tests/phpunit/StructureTest.php b/tests/phpunit/structure/StructureTest.php index 17ea06c4..df00d4df 100644 --- a/tests/phpunit/StructureTest.php +++ b/tests/phpunit/structure/StructureTest.php @@ -8,15 +8,18 @@ class StructureTest extends MediaWikiTestCase { /** * Verify all files that appear to be tests have file names ending in * Test. If the file names do not end in Test, they will not be run. + * @group medium */ public function testUnitTestFileNamesEndWithTest() { if ( wfIsWindows() ) { $this->markTestSkipped( 'This test does not work on Windows' ); } - $rootPath = escapeshellarg( __DIR__ ); + $rootPath = escapeshellarg( __DIR__ . '/..' ); $testClassRegex = implode( '|', array( 'ApiFormatTestBase', 'ApiTestCase', + 'ApiQueryTestBase', + 'ApiQueryContinueTestBase', 'MediaWikiLangTestCase', 'MediaWikiTestCase', 'PHPUnit_Framework_TestCase', @@ -28,7 +31,7 @@ class StructureTest extends MediaWikiTestCase { $results = null; $exitCode = null; - exec($finder, $results, $exitCode); + exec( $finder, $results, $exitCode ); $this->assertEquals( 0, @@ -41,7 +44,7 @@ class StructureTest extends MediaWikiTestCase { array( $this, 'filterSuites' ) ); $strip = strlen( $rootPath ) - 1; - foreach( $results as $k => $v) { + foreach ( $results as $k => $v ) { $results[$k] = substr( $v, $strip ); } $this->assertEquals( @@ -55,6 +58,6 @@ class StructureTest extends MediaWikiTestCase { * Filter to remove testUnitTestFileNamesEndWithTest false positives. */ public function filterSuites( $filename ) { - return strpos( $filename, __DIR__ . '/suites/' ) !== 0; + return strpos( $filename, __DIR__ . '/../suites/' ) !== 0; } } diff --git a/tests/phpunit/suite.xml b/tests/phpunit/suite.xml index 56f64477..7a9122fa 100644 --- a/tests/phpunit/suite.xml +++ b/tests/phpunit/suite.xml @@ -2,17 +2,18 @@ <!-- colors don't work on Windows! --> <phpunit bootstrap="./bootstrap.php" - colors="true" - backupGlobals="false" - convertErrorsToExceptions="true" - convertNoticesToExceptions="true" - convertWarningsToExceptions="true" - stopOnFailure="false" - timeoutForSmallTests="10" - timeoutForMediumTests="30" - timeoutForLargeTests="60" - strict="true" - verbose="true"> + colors="true" + backupGlobals="false" + convertErrorsToExceptions="true" + convertNoticesToExceptions="true" + convertWarningsToExceptions="true" + forceCoversAnnotation="true" + stopOnFailure="false" + timeoutForSmallTests="10" + timeoutForMediumTests="30" + timeoutForLargeTests="60" + strict="true" + verbose="true"> <testsuites> <testsuite name="includes"> <directory>includes</directory> @@ -29,13 +30,14 @@ <directory>maintenance</directory> </testsuite> <testsuite name="structure"> - <file>StructureTest.php</file> + <directory>structure</directory> </testsuite> <testsuite name="uploadfromurl"> <file>suites/UploadFromUrlTestSuite.php</file> </testsuite> <testsuite name="extensions"> <file>suites/ExtensionsTestSuite.php</file> + <file>suites/ExtensionsParserTestSuite.php</file> </testsuite> </testsuites> <groups> diff --git a/tests/phpunit/suites/ExtensionsParserTestSuite.php b/tests/phpunit/suites/ExtensionsParserTestSuite.php new file mode 100644 index 00000000..3d68b241 --- /dev/null +++ b/tests/phpunit/suites/ExtensionsParserTestSuite.php @@ -0,0 +1,8 @@ +<?php +class ExtensionsParserTestSuite extends PHPUnit_Framework_TestSuite { + + public static function suite() { + return MediaWikiParserTest::suite( MediaWikiParserTest::NO_CORE ); + } + +} diff --git a/tests/phpunit/suites/ExtensionsTestSuite.php b/tests/phpunit/suites/ExtensionsTestSuite.php index d728807f..eec773db 100644 --- a/tests/phpunit/suites/ExtensionsTestSuite.php +++ b/tests/phpunit/suites/ExtensionsTestSuite.php @@ -1,4 +1,4 @@ -<?php +<?php /** * This test suite runs unit tests registered by extensions. * See http://www.mediawiki.org/wiki/Manual:Hooks/UnitTestsList for details of how to register your tests. diff --git a/tests/phpunit/suites/UploadFromUrlTestSuite.php b/tests/phpunit/suites/UploadFromUrlTestSuite.php index f2638111..7eb599e3 100644 --- a/tests/phpunit/suites/UploadFromUrlTestSuite.php +++ b/tests/phpunit/suites/UploadFromUrlTestSuite.php @@ -1,6 +1,6 @@ <?php -require_once( dirname( __DIR__ ) . '/includes/upload/UploadFromUrlTest.php' ); +require_once dirname( __DIR__ ) . '/includes/upload/UploadFromUrlTest.php'; class UploadFromUrlTestSuite extends PHPUnit_Framework_TestSuite { public $savedGlobals = array(); @@ -15,32 +15,32 @@ class UploadFromUrlTestSuite extends PHPUnit_Framework_TestSuite { return true; } - function setUp() { - global $wgParser, $wgParserConf, $IP, $messageMemc, $wgMemc, - $wgUser, $wgLang, $wgOut, $wgRequest, $wgStyleDirectory, $wgEnableParserCache, - $wgNamespaceAliases, $wgNamespaceProtection, $parserMemc; + protected function setUp() { + global $wgParser, $wgParserConf, $IP, $messageMemc, $wgMemc, $wgUser, + $wgLang, $wgOut, $wgRequest, $wgStyleDirectory, + $wgEnableParserCache, $wgNamespaceAliases, $wgNamespaceProtection, + $parserMemc; $tmpGlobals = array(); $tmpGlobals['wgScript'] = '/index.php'; $tmpGlobals['wgScriptPath'] = '/'; $tmpGlobals['wgArticlePath'] = '/wiki/$1'; - $tmpGlobals['wgStyleSheetPath'] = '/skins'; $tmpGlobals['wgStylePath'] = '/skins'; $tmpGlobals['wgThumbnailScriptPath'] = false; $tmpGlobals['wgLocalFileRepo'] = array( - 'class' => 'LocalRepo', - 'name' => 'local', - 'url' => 'http://example.com/images', - 'hashLevels' => 2, + 'class' => 'LocalRepo', + 'name' => 'local', + 'url' => 'http://example.com/images', + 'hashLevels' => 2, 'transformVia404' => false, - 'backend' => new FSFileBackend( array( - 'name' => 'local-backend', + 'backend' => new FSFileBackend( array( + 'name' => 'local-backend', 'lockManager' => 'fsLockManager', 'containerPaths' => array( - 'local-public' => wfTempDir() . '/test-repo/public', - 'local-thumb' => wfTempDir() . '/test-repo/thumb', - 'local-temp' => wfTempDir() . '/test-repo/temp', + 'local-public' => wfTempDir() . '/test-repo/public', + 'local-thumb' => wfTempDir() . '/test-repo/thumb', + 'local-temp' => wfTempDir() . '/test-repo/temp', 'local-deleted' => wfTempDir() . '/test-repo/delete', ) ) ), @@ -56,7 +56,6 @@ class UploadFromUrlTestSuite extends PHPUnit_Framework_TestSuite { $wgNamespaceAliases['Image'] = NS_FILE; $wgNamespaceAliases['Image_talk'] = NS_FILE_TALK; - $wgEnableParserCache = false; DeferredUpdates::clearPendingUpdates(); $wgMemc = wfGetMainCache(); @@ -72,14 +71,14 @@ class UploadFromUrlTestSuite extends PHPUnit_Framework_TestSuite { $wgRequest = $context->getRequest(); if ( $wgStyleDirectory === false ) { - $wgStyleDirectory = "$IP/skins"; + $wgStyleDirectory = "$IP/skins"; } RepoGroup::destroySingleton(); FileBackendGroup::destroySingleton(); } - public function tearDown() { + protected function tearDown() { foreach ( $this->savedGlobals as $var => $val ) { $GLOBALS[$var] = $val; } @@ -88,6 +87,8 @@ class UploadFromUrlTestSuite extends PHPUnit_Framework_TestSuite { FileBackendGroup::destroySingleton(); $this->teardownUploadDir( $this->uploadDir ); + + parent::tearDown(); } private $uploadDir; @@ -103,7 +104,7 @@ class UploadFromUrlTestSuite extends PHPUnit_Framework_TestSuite { // delete the files first, then the dirs. self::deleteFiles( - array ( + array( "$dir/3/3a/Foobar.jpg", "$dir/thumb/3/3a/Foobar.jpg/180px-Foobar.jpg", "$dir/thumb/3/3a/Foobar.jpg/200px-Foobar.jpg", @@ -115,7 +116,7 @@ class UploadFromUrlTestSuite extends PHPUnit_Framework_TestSuite { ); self::deleteDirs( - array ( + array( "$dir/3/3a", "$dir/3", "$dir/thumb/6/65", @@ -182,6 +183,7 @@ class UploadFromUrlTestSuite extends PHPUnit_Framework_TestSuite { if ( file_exists( $dir ) ) { wfDebug( "Already exists!\n" ); + return $dir; } @@ -199,6 +201,7 @@ class UploadFromUrlTestSuite extends PHPUnit_Framework_TestSuite { // the UploadFromUrlTest class class_exists( 'UploadFromUrlTest' ); $suite = new UploadFromUrlTestSuite( 'UploadFromUrlTest' ); + return $suite; } } diff --git a/tests/qunit/QUnitTestResources.php b/tests/qunit/QUnitTestResources.php index 59ae73cd..c8743750 100644 --- a/tests/qunit/QUnitTestResources.php +++ b/tests/qunit/QUnitTestResources.php @@ -6,6 +6,7 @@ return array( 'mediawiki.tests.qunit.suites' => array( 'scripts' => array( + 'tests/qunit/suites/resources/startup.test.js', 'tests/qunit/suites/resources/jquery/jquery.autoEllipsis.test.js', 'tests/qunit/suites/resources/jquery/jquery.byteLength.test.js', 'tests/qunit/suites/resources/jquery/jquery.byteLimit.test.js', @@ -13,12 +14,15 @@ return array( 'tests/qunit/suites/resources/jquery/jquery.colorUtil.test.js', 'tests/qunit/suites/resources/jquery/jquery.delayedBind.test.js', 'tests/qunit/suites/resources/jquery/jquery.getAttrs.test.js', + 'tests/qunit/suites/resources/jquery/jquery.hidpi.test.js', 'tests/qunit/suites/resources/jquery/jquery.highlightText.test.js', 'tests/qunit/suites/resources/jquery/jquery.localize.test.js', + 'tests/qunit/suites/resources/jquery/jquery.makeCollapsible.test.js', 'tests/qunit/suites/resources/jquery/jquery.mwExtension.test.js', 'tests/qunit/suites/resources/jquery/jquery.tabIndex.test.js', 'tests/qunit/suites/resources/jquery/jquery.tablesorter.test.js', 'tests/qunit/suites/resources/jquery/jquery.textSelection.test.js', + 'tests/qunit/data/mediawiki.jqueryMsg.data.js', 'tests/qunit/suites/resources/mediawiki/mediawiki.jqueryMsg.test.js', 'tests/qunit/suites/resources/mediawiki/mediawiki.jscompat.test.js', 'tests/qunit/suites/resources/mediawiki/mediawiki.test.js', @@ -40,8 +44,10 @@ return array( 'jquery.colorUtil', 'jquery.delayedBind', 'jquery.getAttrs', + 'jquery.hidpi', 'jquery.highlightText', 'jquery.localize', + 'jquery.makeCollapsible', 'jquery.mwExtension', 'jquery.tabIndex', 'jquery.tablesorter', @@ -58,6 +64,5 @@ return array( 'mediawiki.language', 'mediawiki.cldr', ), - 'position' => 'top', ) ); diff --git a/tests/qunit/data/callMwLoaderTestCallback.js b/tests/qunit/data/callMwLoaderTestCallback.js index 3f2ee92f..dd034115 100644 --- a/tests/qunit/data/callMwLoaderTestCallback.js +++ b/tests/qunit/data/callMwLoaderTestCallback.js @@ -1 +1 @@ -mw.loader.testCallback(); +mediaWiki.loader.testCallback(); diff --git a/tests/qunit/data/generateJqueryMsgData.php b/tests/qunit/data/generateJqueryMsgData.php new file mode 100644 index 00000000..12e5a2dc --- /dev/null +++ b/tests/qunit/data/generateJqueryMsgData.php @@ -0,0 +1,150 @@ +<?php +/** + * This PHP script defines the spec that the mediawiki.jqueryMsg module should conform to. + * + * It does this by looking up the results of various kinds of string parsing, with various + * languages, in the current installation of MediaWiki. It then outputs a static specification, + * mapping expected inputs to outputs, which can be used fed into a unit test framework. + * (QUnit, Jasmine, anything, it just outputs an object with key/value pairs). + * + * This is similar to Michael Dale (mdale@mediawiki.org)'s parser tests, except that it doesn't + * look up the API results while doing the test, so the test run is much faster (at the cost + * of being out of date in rare circumstances. But mostly the parsing that we are doing in + * Javascript doesn't change much). + */ + +/* + * @example QUnit + * <code> + QUnit.test( 'Output matches PHP parser', mw.libs.phpParserData.tests.length, function ( assert ) { + mw.messages.set( mw.libs.phpParserData.messages ); + $.each( mw.libs.phpParserData.tests, function ( i, test ) { + QUnit.stop(); + getMwLanguage( test.lang, function ( langClass ) { + var parser = new mw.jqueryMsg.parser( { language: langClass } ); + assert.equal( + parser.parse( test.key, test.args ).html(), + test.result, + test.name + ); + QUnit.start(); + } ); + } ); + }); + * </code> + * + * @example Jasmine + * <code> + describe( 'match output to output from PHP parser', function () { + mw.messages.set( mw.libs.phpParserData.messages ); + $.each( mw.libs.phpParserData.tests, function ( i, test ) { + it( 'should parse ' + test.name, function () { + var langClass; + runs( function () { + getMwLanguage( test.lang, function ( gotIt ) { + langClass = gotIt; + }); + }); + waitsFor( function () { + return langClass !== undefined; + }, 'Language class should be loaded', 1000 ); + runs( function () { + console.log( test.lang, 'running tests' ); + var parser = new mw.jqueryMsg.parser( { language: langClass } ); + expect( + parser.parse( test.key, test.args ).html() + ).toEqual( test.result ); + } ); + } ); + } ); + } ); + * </code> + */ + +require __DIR__ . '/../../../maintenance/Maintenance.php'; + +class GenerateJqueryMsgData extends Maintenance { + + static $keyToTestArgs = array( + 'undelete_short' => array( + array( 0 ), + array( 1 ), + array( 2 ), + array( 5 ), + array( 21 ), + array( 101 ) + ), + 'category-subcat-count' => array( + array( 0, 10 ), + array( 1, 1 ), + array( 1, 2 ), + array( 3, 30 ) + ) + ); + + public function __construct() { + parent::__construct(); + $this->mDescription = 'Create a specification for message parsing ini JSON format'; + // add any other options here + } + + public function execute() { + list( $messages, $tests ) = $this->getMessagesAndTests(); + $this->writeJavascriptFile( $messages, $tests, __DIR__ . '/mediawiki.jqueryMsg.data.js' ); + } + + private function getMessagesAndTests() { + $messages = array(); + $tests = array(); + foreach ( array( 'en', 'fr', 'ar', 'jp', 'zh' ) as $languageCode ) { + foreach ( self::$keyToTestArgs as $key => $testArgs ) { + foreach ( $testArgs as $args ) { + // Get the raw message, without any transformations. + $template = wfMessage( $key )->inLanguage( $languageCode )->plain(); + + // Get the magic-parsed version with args. + $result = wfMessage( $key, $args )->inLanguage( $languageCode )->text(); + + // Record the template, args, language, and expected result + // fake multiple languages by flattening them together. + $langKey = $languageCode . '_' . $key; + $messages[$langKey] = $template; + $tests[] = array( + 'name' => $languageCode . ' ' . $key . ' ' . join( ',', $args ), + 'key' => $langKey, + 'args' => $args, + 'result' => $result, + 'lang' => $languageCode + ); + } + } + } + return array( $messages, $tests ); + } + + private function writeJavascriptFile( $messages, $tests, $dataSpecFile ) { + $phpParserData = array( + 'messages' => $messages, + 'tests' => $tests, + ); + + $output = + "// This file stores the output from the PHP parser for various messages, arguments,\n" + . "// languages, and parser modes. Intended for use by a unit test framework by looping\n" + . "// through the object and comparing its parser return value with the 'result' property.\n" + . '// Last generated with ' . basename( __FILE__ ) . ' at ' . gmdate( 'r' ) . "\n" + // This file will contain unquoted JSON strings as javascript native object literals, + // flip the quotemark convention for this file. + . "/*jshint quotmark: double */\n" + . "\n" + . 'mediaWiki.libs.phpParserData = ' . FormatJson::encode( $phpParserData, true ) . ";\n"; + + $fp = file_put_contents( $dataSpecFile, $output ); + if ( $fp === false ) { + die( "Couldn't write to $dataSpecFile." ); + } + } +} + +$maintClass = "GenerateJqueryMsgData"; +require_once RUN_MAINTENANCE_IF_MAIN; diff --git a/tests/qunit/data/load.mock.php b/tests/qunit/data/load.mock.php index 1c189703..f6eff77a 100644 --- a/tests/qunit/data/load.mock.php +++ b/tests/qunit/data/load.mock.php @@ -24,7 +24,8 @@ */ header( 'Content-Type: text/javascript; charset=utf-8' ); -require_once '../../../includes/Xml.php'; +require_once __DIR__ . '/../../../includes/json/FormatJson.php'; +require_once __DIR__ . '/../../../includes/Xml.php'; $moduleImplementations = array( 'testUsesMissing' => " @@ -50,7 +51,7 @@ if ( isset( $_GET['modules'] ) ) { if ( isset( $moduleImplementations[$module] ) ) { $response .= $moduleImplementations[$module]; } else { - $response .= Xml::encodeJsCall( 'mw.loader.state', array( $module, 'missing' ) ); + $response .= Xml::encodeJsCall( 'mw.loader.state', array( $module, 'missing' ), true ); } } } diff --git a/tests/qunit/data/mediawiki.jqueryMsg.data.js b/tests/qunit/data/mediawiki.jqueryMsg.data.js new file mode 100644 index 00000000..776ee24f --- /dev/null +++ b/tests/qunit/data/mediawiki.jqueryMsg.data.js @@ -0,0 +1,492 @@ +// This file stores the output from the PHP parser for various messages, arguments, +// languages, and parser modes. Intended for use by a unit test framework by looping +// through the object and comparing its parser return value with the 'result' property. +// Last generated with generateJqueryMsgData.php at Sat, 03 Nov 2012 21:32:01 +0000 +/*jshint quotmark: double */ + +mediaWiki.libs.phpParserData = { + "messages": { + "en_undelete_short": "Undelete {{PLURAL:$1|one edit|$1 edits}}", + "en_category-subcat-count": "{{PLURAL:$2|This category has only the following subcategory.|This category has the following {{PLURAL:$1|subcategory|$1 subcategories}}, out of $2 total.}}", + "fr_undelete_short": "Restaurer $1 modification{{PLURAL:$1||s}}", + "fr_category-subcat-count": "Cette cat\u00e9gorie comprend {{PLURAL:$2|la sous-cat\u00e9gorie|$2 sous-cat\u00e9gories, dont {{PLURAL:$1|celle|les $1}}}} ci-dessous.", + "ar_undelete_short": "\u0627\u0633\u062a\u0631\u062c\u0627\u0639 {{PLURAL:$1|\u062a\u0639\u062f\u064a\u0644 \u0648\u0627\u062d\u062f|\u062a\u0639\u062f\u064a\u0644\u064a\u0646|$1 \u062a\u0639\u062f\u064a\u0644\u0627\u062a|$1 \u062a\u0639\u062f\u064a\u0644|$1 \u062a\u0639\u062f\u064a\u0644\u0627}}", + "ar_category-subcat-count": "{{PLURAL:$2|\u0644\u0627 \u062a\u0635\u0627\u0646\u064a\u0641 \u0641\u0631\u0639\u064a\u0629 \u0641\u064a \u0647\u0630\u0627 \u0627\u0644\u062a\u0635\u0646\u064a\u0641|\u0647\u0630\u0627 \u0627\u0644\u062a\u0635\u0646\u064a\u0641 \u0641\u064a\u0647 \u0627\u0644\u062a\u0635\u0646\u064a\u0641 \u0627\u0644\u0641\u0631\u0639\u064a \u0627\u0644\u062a\u0627\u0644\u064a \u0641\u0642\u0637.|\u0647\u0630\u0627 \u0627\u0644\u062a\u0635\u0646\u064a\u0641 \u0641\u064a\u0647 {{PLURAL:$1||\u0647\u0630\u0627 \u0627\u0644\u062a\u0635\u0646\u064a\u0641 \u0627\u0644\u0641\u0631\u0639\u064a|\u0647\u0630\u064a\u0646 \u0627\u0644\u062a\u0635\u0646\u064a\u0641\u064a\u0646 \u0627\u0644\u0641\u0631\u0639\u064a\u064a\u0646|\u0647\u0630\u0647 \u0627\u0644$1 \u062a\u0635\u0627\u0646\u064a\u0641 \u0627\u0644\u0641\u0631\u0639\u064a\u0629|\u0647\u0630\u0647 \u0627\u0644$1 \u062a\u0635\u0646\u064a\u0641\u0627 \u0641\u0631\u0639\u064a\u0627|\u0647\u0630\u0647 \u0627\u0644$1 \u062a\u0635\u0646\u064a\u0641 \u0641\u0631\u0639\u064a}}\u060c \u0645\u0646 \u0625\u062c\u0645\u0627\u0644\u064a $2.}}", + "jp_undelete_short": "Undelete {{PLURAL:$1|one edit|$1 edits}}", + "jp_category-subcat-count": "{{PLURAL:$2|This category has only the following subcategory.|This category has the following {{PLURAL:$1|subcategory|$1 subcategories}}, out of $2 total.}}", + "zh_undelete_short": "\u6062\u590d$1\u4e2a\u88ab\u5220\u9664\u7684\u7f16\u8f91", + "zh_category-subcat-count": "{{PLURAL:$2|\u672c\u5206\u7c7b\u53ea\u6709\u4e0b\u5217\u4e00\u4e2a\u5b50\u5206\u7c7b\u3002|\u672c\u5206\u7c7b\u5305\u542b\u4e0b\u5217$1\u4e2a\u5b50\u5206\u7c7b\uff0c\u5171$2\u4e2a\u5b50\u5206\u7c7b\u3002}}" + }, + "tests": [ + { + "name": "en undelete_short 0", + "key": "en_undelete_short", + "args": [ + 0 + ], + "result": "Undelete 0 edits", + "lang": "en" + }, + { + "name": "en undelete_short 1", + "key": "en_undelete_short", + "args": [ + 1 + ], + "result": "Undelete one edit", + "lang": "en" + }, + { + "name": "en undelete_short 2", + "key": "en_undelete_short", + "args": [ + 2 + ], + "result": "Undelete 2 edits", + "lang": "en" + }, + { + "name": "en undelete_short 5", + "key": "en_undelete_short", + "args": [ + 5 + ], + "result": "Undelete 5 edits", + "lang": "en" + }, + { + "name": "en undelete_short 21", + "key": "en_undelete_short", + "args": [ + 21 + ], + "result": "Undelete 21 edits", + "lang": "en" + }, + { + "name": "en undelete_short 101", + "key": "en_undelete_short", + "args": [ + 101 + ], + "result": "Undelete 101 edits", + "lang": "en" + }, + { + "name": "en category-subcat-count 0,10", + "key": "en_category-subcat-count", + "args": [ + 0, + 10 + ], + "result": "This category has the following 0 subcategories, out of 10 total.", + "lang": "en" + }, + { + "name": "en category-subcat-count 1,1", + "key": "en_category-subcat-count", + "args": [ + 1, + 1 + ], + "result": "This category has only the following subcategory.", + "lang": "en" + }, + { + "name": "en category-subcat-count 1,2", + "key": "en_category-subcat-count", + "args": [ + 1, + 2 + ], + "result": "This category has the following subcategory, out of 2 total.", + "lang": "en" + }, + { + "name": "en category-subcat-count 3,30", + "key": "en_category-subcat-count", + "args": [ + 3, + 30 + ], + "result": "This category has the following 3 subcategories, out of 30 total.", + "lang": "en" + }, + { + "name": "fr undelete_short 0", + "key": "fr_undelete_short", + "args": [ + 0 + ], + "result": "Restaurer 0 modification", + "lang": "fr" + }, + { + "name": "fr undelete_short 1", + "key": "fr_undelete_short", + "args": [ + 1 + ], + "result": "Restaurer 1 modification", + "lang": "fr" + }, + { + "name": "fr undelete_short 2", + "key": "fr_undelete_short", + "args": [ + 2 + ], + "result": "Restaurer 2 modifications", + "lang": "fr" + }, + { + "name": "fr undelete_short 5", + "key": "fr_undelete_short", + "args": [ + 5 + ], + "result": "Restaurer 5 modifications", + "lang": "fr" + }, + { + "name": "fr undelete_short 21", + "key": "fr_undelete_short", + "args": [ + 21 + ], + "result": "Restaurer 21 modifications", + "lang": "fr" + }, + { + "name": "fr undelete_short 101", + "key": "fr_undelete_short", + "args": [ + 101 + ], + "result": "Restaurer 101 modifications", + "lang": "fr" + }, + { + "name": "fr category-subcat-count 0,10", + "key": "fr_category-subcat-count", + "args": [ + 0, + 10 + ], + "result": "Cette cat\u00e9gorie comprend 10 sous-cat\u00e9gories, dont celle ci-dessous.", + "lang": "fr" + }, + { + "name": "fr category-subcat-count 1,1", + "key": "fr_category-subcat-count", + "args": [ + 1, + 1 + ], + "result": "Cette cat\u00e9gorie comprend la sous-cat\u00e9gorie ci-dessous.", + "lang": "fr" + }, + { + "name": "fr category-subcat-count 1,2", + "key": "fr_category-subcat-count", + "args": [ + 1, + 2 + ], + "result": "Cette cat\u00e9gorie comprend 2 sous-cat\u00e9gories, dont celle ci-dessous.", + "lang": "fr" + }, + { + "name": "fr category-subcat-count 3,30", + "key": "fr_category-subcat-count", + "args": [ + 3, + 30 + ], + "result": "Cette cat\u00e9gorie comprend 30 sous-cat\u00e9gories, dont les 3 ci-dessous.", + "lang": "fr" + }, + { + "name": "ar undelete_short 0", + "key": "ar_undelete_short", + "args": [ + 0 + ], + "result": "\u0627\u0633\u062a\u0631\u062c\u0627\u0639 \u062a\u0639\u062f\u064a\u0644 \u0648\u0627\u062d\u062f", + "lang": "ar" + }, + { + "name": "ar undelete_short 1", + "key": "ar_undelete_short", + "args": [ + 1 + ], + "result": "\u0627\u0633\u062a\u0631\u062c\u0627\u0639 \u062a\u0639\u062f\u064a\u0644\u064a\u0646", + "lang": "ar" + }, + { + "name": "ar undelete_short 2", + "key": "ar_undelete_short", + "args": [ + 2 + ], + "result": "\u0627\u0633\u062a\u0631\u062c\u0627\u0639 2 \u062a\u0639\u062f\u064a\u0644\u0627\u062a", + "lang": "ar" + }, + { + "name": "ar undelete_short 5", + "key": "ar_undelete_short", + "args": [ + 5 + ], + "result": "\u0627\u0633\u062a\u0631\u062c\u0627\u0639 5 \u062a\u0639\u062f\u064a\u0644", + "lang": "ar" + }, + { + "name": "ar undelete_short 21", + "key": "ar_undelete_short", + "args": [ + 21 + ], + "result": "\u0627\u0633\u062a\u0631\u062c\u0627\u0639 21 \u062a\u0639\u062f\u064a\u0644\u0627", + "lang": "ar" + }, + { + "name": "ar undelete_short 101", + "key": "ar_undelete_short", + "args": [ + 101 + ], + "result": "\u0627\u0633\u062a\u0631\u062c\u0627\u0639 101 \u062a\u0639\u062f\u064a\u0644\u0627", + "lang": "ar" + }, + { + "name": "ar category-subcat-count 0,10", + "key": "ar_category-subcat-count", + "args": [ + 0, + 10 + ], + "result": "\u0647\u0630\u0627 \u0627\u0644\u062a\u0635\u0646\u064a\u0641 \u0641\u064a\u0647 \u060c \u0645\u0646 \u0625\u062c\u0645\u0627\u0644\u064a 10.", + "lang": "ar" + }, + { + "name": "ar category-subcat-count 1,1", + "key": "ar_category-subcat-count", + "args": [ + 1, + 1 + ], + "result": "\u0647\u0630\u0627 \u0627\u0644\u062a\u0635\u0646\u064a\u0641 \u0641\u064a\u0647 \u0627\u0644\u062a\u0635\u0646\u064a\u0641 \u0627\u0644\u0641\u0631\u0639\u064a \u0627\u0644\u062a\u0627\u0644\u064a \u0641\u0642\u0637.", + "lang": "ar" + }, + { + "name": "ar category-subcat-count 1,2", + "key": "ar_category-subcat-count", + "args": [ + 1, + 2 + ], + "result": "\u0647\u0630\u0627 \u0627\u0644\u062a\u0635\u0646\u064a\u0641 \u0641\u064a\u0647 \u0647\u0630\u0627 \u0627\u0644\u062a\u0635\u0646\u064a\u0641 \u0627\u0644\u0641\u0631\u0639\u064a\u060c \u0645\u0646 \u0625\u062c\u0645\u0627\u0644\u064a 2.", + "lang": "ar" + }, + { + "name": "ar category-subcat-count 3,30", + "key": "ar_category-subcat-count", + "args": [ + 3, + 30 + ], + "result": "\u0647\u0630\u0627 \u0627\u0644\u062a\u0635\u0646\u064a\u0641 \u0641\u064a\u0647 \u0647\u0630\u0647 \u0627\u06443 \u062a\u0635\u0627\u0646\u064a\u0641 \u0627\u0644\u0641\u0631\u0639\u064a\u0629\u060c \u0645\u0646 \u0625\u062c\u0645\u0627\u0644\u064a 30.", + "lang": "ar" + }, + { + "name": "jp undelete_short 0", + "key": "jp_undelete_short", + "args": [ + 0 + ], + "result": "Undelete 0 edits", + "lang": "jp" + }, + { + "name": "jp undelete_short 1", + "key": "jp_undelete_short", + "args": [ + 1 + ], + "result": "Undelete one edit", + "lang": "jp" + }, + { + "name": "jp undelete_short 2", + "key": "jp_undelete_short", + "args": [ + 2 + ], + "result": "Undelete 2 edits", + "lang": "jp" + }, + { + "name": "jp undelete_short 5", + "key": "jp_undelete_short", + "args": [ + 5 + ], + "result": "Undelete 5 edits", + "lang": "jp" + }, + { + "name": "jp undelete_short 21", + "key": "jp_undelete_short", + "args": [ + 21 + ], + "result": "Undelete 21 edits", + "lang": "jp" + }, + { + "name": "jp undelete_short 101", + "key": "jp_undelete_short", + "args": [ + 101 + ], + "result": "Undelete 101 edits", + "lang": "jp" + }, + { + "name": "jp category-subcat-count 0,10", + "key": "jp_category-subcat-count", + "args": [ + 0, + 10 + ], + "result": "This category has the following 0 subcategories, out of 10 total.", + "lang": "jp" + }, + { + "name": "jp category-subcat-count 1,1", + "key": "jp_category-subcat-count", + "args": [ + 1, + 1 + ], + "result": "This category has only the following subcategory.", + "lang": "jp" + }, + { + "name": "jp category-subcat-count 1,2", + "key": "jp_category-subcat-count", + "args": [ + 1, + 2 + ], + "result": "This category has the following subcategory, out of 2 total.", + "lang": "jp" + }, + { + "name": "jp category-subcat-count 3,30", + "key": "jp_category-subcat-count", + "args": [ + 3, + 30 + ], + "result": "This category has the following 3 subcategories, out of 30 total.", + "lang": "jp" + }, + { + "name": "zh undelete_short 0", + "key": "zh_undelete_short", + "args": [ + 0 + ], + "result": "\u6062\u590d0\u4e2a\u88ab\u5220\u9664\u7684\u7f16\u8f91", + "lang": "zh" + }, + { + "name": "zh undelete_short 1", + "key": "zh_undelete_short", + "args": [ + 1 + ], + "result": "\u6062\u590d1\u4e2a\u88ab\u5220\u9664\u7684\u7f16\u8f91", + "lang": "zh" + }, + { + "name": "zh undelete_short 2", + "key": "zh_undelete_short", + "args": [ + 2 + ], + "result": "\u6062\u590d2\u4e2a\u88ab\u5220\u9664\u7684\u7f16\u8f91", + "lang": "zh" + }, + { + "name": "zh undelete_short 5", + "key": "zh_undelete_short", + "args": [ + 5 + ], + "result": "\u6062\u590d5\u4e2a\u88ab\u5220\u9664\u7684\u7f16\u8f91", + "lang": "zh" + }, + { + "name": "zh undelete_short 21", + "key": "zh_undelete_short", + "args": [ + 21 + ], + "result": "\u6062\u590d21\u4e2a\u88ab\u5220\u9664\u7684\u7f16\u8f91", + "lang": "zh" + }, + { + "name": "zh undelete_short 101", + "key": "zh_undelete_short", + "args": [ + 101 + ], + "result": "\u6062\u590d101\u4e2a\u88ab\u5220\u9664\u7684\u7f16\u8f91", + "lang": "zh" + }, + { + "name": "zh category-subcat-count 0,10", + "key": "zh_category-subcat-count", + "args": [ + 0, + 10 + ], + "result": "\u672c\u5206\u7c7b\u5305\u542b\u4e0b\u52170\u4e2a\u5b50\u5206\u7c7b\uff0c\u517110\u4e2a\u5b50\u5206\u7c7b\u3002", + "lang": "zh" + }, + { + "name": "zh category-subcat-count 1,1", + "key": "zh_category-subcat-count", + "args": [ + 1, + 1 + ], + "result": "\u672c\u5206\u7c7b\u53ea\u6709\u4e0b\u5217\u4e00\u4e2a\u5b50\u5206\u7c7b\u3002", + "lang": "zh" + }, + { + "name": "zh category-subcat-count 1,2", + "key": "zh_category-subcat-count", + "args": [ + 1, + 2 + ], + "result": "\u672c\u5206\u7c7b\u5305\u542b\u4e0b\u52171\u4e2a\u5b50\u5206\u7c7b\uff0c\u51712\u4e2a\u5b50\u5206\u7c7b\u3002", + "lang": "zh" + }, + { + "name": "zh category-subcat-count 3,30", + "key": "zh_category-subcat-count", + "args": [ + 3, + 30 + ], + "result": "\u672c\u5206\u7c7b\u5305\u542b\u4e0b\u52173\u4e2a\u5b50\u5206\u7c7b\uff0c\u517130\u4e2a\u5b50\u5206\u7c7b\u3002", + "lang": "zh" + } + ] +}; diff --git a/tests/qunit/data/qunitOkCall.js b/tests/qunit/data/qunitOkCall.js index 25c42d6a..3ed5514e 100644 --- a/tests/qunit/data/qunitOkCall.js +++ b/tests/qunit/data/qunitOkCall.js @@ -1,2 +1,2 @@ QUnit.start(); -QUnit.assert.ok( true, 'Successfully loaded!'); +QUnit.assert.ok( true, 'Successfully loaded!' ); diff --git a/tests/qunit/data/styleTest.css.php b/tests/qunit/data/styleTest.css.php index 1870d5a3..0e845811 100644 --- a/tests/qunit/data/styleTest.css.php +++ b/tests/qunit/data/styleTest.css.php @@ -47,9 +47,9 @@ $wait = isset( $params['wait'] ) ? (int)$params['wait'] : 0; // seconds sleep( $wait ); -$css = " +$css = " /** - * Generated " . gmdate( 'r' ) . ". + * Generated " . gmdate( 'r' ) . ". * Waited {$wait}s. */ diff --git a/tests/qunit/data/testrunner.js b/tests/qunit/data/testrunner.js index efa65493..1a2bfa10 100644 --- a/tests/qunit/data/testrunner.js +++ b/tests/qunit/data/testrunner.js @@ -1,266 +1,431 @@ -( function ( $, mw, QUnit, undefined ) { /*global CompletenessTest */ -/*jshint evil:true */ -'use strict'; - -var mwTestIgnore, mwTester, addons; - -/** - * Add bogus to url to prevent IE crazy caching - * - * @param value {String} a relative path (eg. 'data/foo.js' - * or 'data/test.php?foo=bar'). - * @return {String} Such as 'data/foo.js?131031765087663960' - */ -QUnit.fixurl = function ( value ) { - return value + (/\?/.test( value ) ? '&' : '?') - + String( new Date().getTime() ) - + String( parseInt( Math.random() * 100000, 10 ) ); -}; - -/** - * Configuration - */ - -// When a test() indicates asynchronicity with stop(), -// allow 10 seconds to pass before killing the test(), -// and assuming failure. -QUnit.config.testTimeout = 10 * 1000; - -// Add a checkbox to QUnit header to toggle MediaWiki ResourceLoader debug mode. -QUnit.config.urlConfig.push( { - id: 'debug', - label: 'Enable ResourceLoaderDebug', - tooltip: 'Enable debug mode in ResourceLoader' -} ); - -/** - * Load TestSwarm agent - */ -// Only if the current url indicates that there is a TestSwarm instance watching us -// (TestSwarm appends swarmURL to the test suites url it loads in iframes). -// Otherwise this is just a simple view of Special:JavaScriptTest/qunit directly, -// no point in loading inject.js in that case. Also, make sure that this instance -// of MediaWiki has actually been configured with the required url to that inject.js -// script. By default it is false. -if ( QUnit.urlParams.swarmURL && mw.config.get( 'QUnitTestSwarmInjectJSPath' ) ) { - document.write( "<scr" + "ipt src='" + QUnit.fixurl( mw.config.get( 'QUnitTestSwarmInjectJSPath' ) ) + "'></scr" + "ipt>" ); -} - -/** - * CompletenessTest - */ -// Adds toggle checkbox to header -QUnit.config.urlConfig.push( { - id: 'completenesstest', - label: 'Run CompletenessTest', - tooltip: 'Run the completeness test' -} ); - -// Initiate when enabled -if ( QUnit.urlParams.completenesstest ) { - - // Return true to ignore - mwTestIgnore = function ( val, tester, funcPath ) { - - // Don't record methods of the properties of constructors, - // to avoid getting into a loop (prototype.constructor.prototype..). - // Since we're therefor skipping any injection for - // "new mw.Foo()", manually set it to true here. - if ( val instanceof mw.Map ) { - tester.methodCallTracker.Map = true; - return true; - } - if ( val instanceof mw.Title ) { - tester.methodCallTracker.Title = true; - return true; - } - - // Don't record methods of the properties of a jQuery object - if ( val instanceof $ ) { - return true; - } - - return false; +/*jshint evil: true */ +( function ( $, mw, QUnit, undefined ) { + 'use strict'; + + var mwTestIgnore, mwTester, + addons, + envExecCount, + ELEMENT_NODE = 1, + TEXT_NODE = 3; + + /** + * Add bogus to url to prevent IE crazy caching + * + * @param value {String} a relative path (eg. 'data/foo.js' + * or 'data/test.php?foo=bar'). + * @return {String} Such as 'data/foo.js?131031765087663960' + */ + QUnit.fixurl = function ( value ) { + return value + (/\?/.test( value ) ? '&' : '?') + + String( new Date().getTime() ) + + String( parseInt( Math.random() * 100000, 10 ) ); }; - mwTester = new CompletenessTest( mw, mwTestIgnore ); -} - -/** - * Test environment recommended for all QUnit test modules - */ -// Whether to log environment changes to the console -QUnit.config.urlConfig.push( 'mwlogenv' ); - -/** - * Reset mw.config and others to a fresh copy of the live config for each test(), - * and restore it back to the live one afterwards. - * @param localEnv {Object} [optional] - * @example (see test suite at the bottom of this file) - * </code> - */ -QUnit.newMwEnvironment = ( function () { - var log, liveConfig, liveMessages; - - liveConfig = mw.config.values; - liveMessages = mw.messages.values; - - function freshConfigCopy( custom ) { - // "deep=true" is important here. - // Otherwise we just create a new object with values referring to live config. - // e.g. mw.config.set( 'wgFileExtensions', [] ) would not effect liveConfig, - // but mw.config.get( 'wgFileExtensions' ).push( 'png' ) would as the array - // was passed by reference in $.extend's loop. - return $.extend( {}, liveConfig, custom, /*deep=*/true ); + /** + * Configuration + */ + + // When a test() indicates asynchronicity with stop(), + // allow 10 seconds to pass before killing the test(), + // and assuming failure. + QUnit.config.testTimeout = 10 * 1000; + + // Add a checkbox to QUnit header to toggle MediaWiki ResourceLoader debug mode. + QUnit.config.urlConfig.push( { + id: 'debug', + label: 'Enable ResourceLoaderDebug', + tooltip: 'Enable debug mode in ResourceLoader' + } ); + + QUnit.config.requireExpects = true; + + /** + * Load TestSwarm agent + */ + // Only if the current url indicates that there is a TestSwarm instance watching us + // (TestSwarm appends swarmURL to the test suites url it loads in iframes). + // Otherwise this is just a simple view of Special:JavaScriptTest/qunit directly, + // no point in loading inject.js in that case. Also, make sure that this instance + // of MediaWiki has actually been configured with the required url to that inject.js + // script. By default it is false. + if ( QUnit.urlParams.swarmURL && mw.config.get( 'QUnitTestSwarmInjectJSPath' ) ) { + jQuery.getScript( QUnit.fixurl( mw.config.get( 'QUnitTestSwarmInjectJSPath' ) ) ); } - function freshMessagesCopy( custom ) { - return $.extend( {}, liveMessages, custom, /*deep=*/true ); + /** + * CompletenessTest + * + * Adds toggle checkbox to header + */ + QUnit.config.urlConfig.push( { + id: 'completenesstest', + label: 'Run CompletenessTest', + tooltip: 'Run the completeness test' + } ); + + // Initiate when enabled + if ( QUnit.urlParams.completenesstest ) { + + // Return true to ignore + mwTestIgnore = function ( val, tester ) { + + // Don't record methods of the properties of constructors, + // to avoid getting into a loop (prototype.constructor.prototype..). + // Since we're therefor skipping any injection for + // "new mw.Foo()", manually set it to true here. + if ( val instanceof mw.Map ) { + tester.methodCallTracker.Map = true; + return true; + } + if ( val instanceof mw.Title ) { + tester.methodCallTracker.Title = true; + return true; + } + + // Don't record methods of the properties of a jQuery object + if ( val instanceof $ ) { + return true; + } + + return false; + }; + + mwTester = new CompletenessTest( mw, mwTestIgnore ); } - log = QUnit.urlParams.mwlogenv ? mw.log : function () {}; + /** + * Test environment recommended for all QUnit test modules + * + * Whether to log environment changes to the console + */ + QUnit.config.urlConfig.push( 'mwlogenv' ); + + /** + * Reset mw.config and others to a fresh copy of the live config for each test(), + * and restore it back to the live one afterwards. + * @param localEnv {Object} [optional] + * @example (see test suite at the bottom of this file) + * </code> + */ + QUnit.newMwEnvironment = ( function () { + var log, liveConfig, liveMessages; + + liveConfig = mw.config.values; + liveMessages = mw.messages.values; + + function freshConfigCopy( custom ) { + // Tests should mock all factors that directly influence the tested code. + // For backwards compatibility though we set mw.config to a copy of the live config + // and extend it with the (optionally) given custom settings for this test + // (instead of starting blank with only the given custmo settings). + // This is a shallow copy, so we don't end up with settings taking an array value + // extended with the custom settings - setting a config property means you override it, + // not extend it. + return $.extend( {}, liveConfig, custom ); + } - return function ( localEnv ) { - localEnv = $.extend( { - // QUnit - setup: $.noop, - teardown: $.noop, - // MediaWiki - config: {}, - messages: {} - }, localEnv ); + function freshMessagesCopy( custom ) { + return $.extend( /*deep=*/true, {}, liveMessages, custom ); + } - return { - setup: function () { - log( 'MwEnvironment> SETUP for "' + QUnit.config.current.module - + ': ' + QUnit.config.current.testName + '"' ); + log = QUnit.urlParams.mwlogenv ? mw.log : function () {}; + + return function ( localEnv ) { + localEnv = $.extend( { + // QUnit + setup: $.noop, + teardown: $.noop, + // MediaWiki + config: {}, + messages: {} + }, localEnv ); + + return { + setup: function () { + log( 'MwEnvironment> SETUP for "' + QUnit.config.current.module + + ': ' + QUnit.config.current.testName + '"' ); + + // Greetings, mock environment! + mw.config.values = freshConfigCopy( localEnv.config ); + mw.messages.values = freshMessagesCopy( localEnv.messages ); + + localEnv.setup(); + }, + + teardown: function () { + log( 'MwEnvironment> TEARDOWN for "' + QUnit.config.current.module + + ': ' + QUnit.config.current.testName + '"' ); + + localEnv.teardown(); + + // Farewell, mock environment! + mw.config.values = liveConfig; + mw.messages.values = liveMessages; + } + }; + }; + }() ); - // Greetings, mock environment! - mw.config.values = freshConfigCopy( localEnv.config ); - mw.messages.values = freshMessagesCopy( localEnv.messages ); + // $.when stops as soon as one fails, which makes sense in most + // practical scenarios, but not in a unit test where we really do + // need to wait until all of them are finished. + QUnit.whenPromisesComplete = function () { + var altPromises = []; - localEnv.setup(); - }, + $.each( arguments, function ( i, arg ) { + var alt = $.Deferred(); + altPromises.push( alt ); - teardown: function () { - log( 'MwEnvironment> TEARDOWN for "' + QUnit.config.current.module - + ': ' + QUnit.config.current.testName + '"' ); + // Whether this one fails or not, forwards it to + // the 'done' (resolve) callback of the alternative promise. + arg.always( alt.resolve ); + } ); - localEnv.teardown(); + return $.when.apply( $, altPromises ); + }; - // Farewell, mock environment! - mw.config.values = liveConfig; - mw.messages.values = liveMessages; + /** + * Recursively convert a node to a plain object representing its structure. + * Only considers attributes and contents (elements and text nodes). + * Attribute values are compared strictly and not normalised. + * + * @param {Node} node + * @return {Object|string} Plain JavaScript value representing the node. + */ + function getDomStructure( node ) { + var $node, children, processedChildren, i, len, el; + $node = $( node ); + if ( node.nodeType === ELEMENT_NODE ) { + children = $node.contents(); + processedChildren = []; + for ( i = 0, len = children.length; i < len; i++ ) { + el = children[i]; + if ( el.nodeType === ELEMENT_NODE || el.nodeType === TEXT_NODE ) { + processedChildren.push( getDomStructure( el ) ); + } } - }; - }; -}() ); - -// $.when stops as soon as one fails, which makes sense in most -// practical scenarios, but not in a unit test where we really do -// need to wait until all of them are finished. -QUnit.whenPromisesComplete = function () { - var altPromises = []; - - $.each( arguments, function ( i, arg ) { - var alt = $.Deferred(); - altPromises.push( alt ); - - // Whether this one fails or not, forwards it to - // the 'done' (resolve) callback of the alternative promise. - arg.always( alt.resolve ); - }); - - return $.when.apply( $, altPromises ); -}; - -/** - * Add-on assertion helpers - */ -// Define the add-ons -addons = { - - // Expect boolean true - assertTrue: function ( actual, message ) { - QUnit.push( actual === true, actual, true, message ); - }, - - // Expect boolean false - assertFalse: function ( actual, message ) { - QUnit.push( actual === false, actual, false, message ); - }, - - // Expect numerical value less than X - lt: function ( actual, expected, message ) { - QUnit.push( actual < expected, actual, 'less than ' + expected, message ); - }, - - // Expect numerical value less than or equal to X - ltOrEq: function ( actual, expected, message ) { - QUnit.push( actual <= expected, actual, 'less than or equal to ' + expected, message ); - }, - - // Expect numerical value greater than X - gt: function ( actual, expected, message ) { - QUnit.push( actual > expected, actual, 'greater than ' + expected, message ); - }, - - // Expect numerical value greater than or equal to X - gtOrEq: function ( actual, expected, message ) { - QUnit.push( actual >= expected, actual, 'greater than or equal to ' + expected, message ); + + return { + tagName: node.tagName, + attributes: $node.getAttrs(), + contents: processedChildren + }; + } else { + // Should be text node + return $node.text(); + } } -}; - -$.extend( QUnit.assert, addons ); - -/** - * Small test suite to confirm proper functionality of the utilities and - * initializations in this file. - */ -var envExecCount = 0; -QUnit.module( 'mediawiki.tests.qunit.testrunner', QUnit.newMwEnvironment({ - setup: function () { - envExecCount += 1; - this.mwHtmlLive = mw.html; - mw.html = { - escape: function () { - return 'mocked-' + envExecCount; - } - }; - }, - teardown: function () { - mw.html = this.mwHtmlLive; - }, - config: { - testVar: 'foo' - }, - messages: { - testMsg: 'Foo.' + + /** + * Gets structure of node for this HTML. + * + * @param {string} html HTML markup for one or more nodes. + */ + function getHtmlStructure( html ) { + var el = $( '<div>' ).append( html )[0]; + return getDomStructure( el ); } -}) ); - -QUnit.test( 'Setup', 3, function ( assert ) { - assert.equal( mw.html.escape( 'foo' ), 'mocked-1', 'extra setup() callback was ran.' ); - assert.equal( mw.config.get( 'testVar' ), 'foo', 'config object applied' ); - assert.equal( mw.messages.get( 'testMsg' ), 'Foo.', 'messages object applied' ); - - mw.config.set( 'testVar', 'bar' ); - mw.messages.set( 'testMsg', 'Bar.' ); -}); - -QUnit.test( 'Teardown', 3, function ( assert ) { - assert.equal( mw.html.escape( 'foo' ), 'mocked-2', 'extra setup() callback was re-ran.' ); - assert.equal( mw.config.get( 'testVar' ), 'foo', 'config object restored and re-applied after test()' ); - assert.equal( mw.messages.get( 'testMsg' ), 'Foo.', 'messages object restored and re-applied after test()' ); -}); - -QUnit.module( 'mediawiki.tests.qunit.testrunner-after', QUnit.newMwEnvironment() ); - -QUnit.test( 'Teardown', 3, function ( assert ) { - assert.equal( mw.html.escape( '<' ), '<', 'extra teardown() callback was ran.' ); - assert.equal( mw.config.get( 'testVar' ), null, 'config object restored to live in next module()' ); - assert.equal( mw.messages.get( 'testMsg' ), null, 'messages object restored to live in next module()' ); -}); + + /** + * Add-on assertion helpers + */ + // Define the add-ons + addons = { + + // Expect boolean true + assertTrue: function ( actual, message ) { + QUnit.push( actual === true, actual, true, message ); + }, + + // Expect boolean false + assertFalse: function ( actual, message ) { + QUnit.push( actual === false, actual, false, message ); + }, + + // Expect numerical value less than X + lt: function ( actual, expected, message ) { + QUnit.push( actual < expected, actual, 'less than ' + expected, message ); + }, + + // Expect numerical value less than or equal to X + ltOrEq: function ( actual, expected, message ) { + QUnit.push( actual <= expected, actual, 'less than or equal to ' + expected, message ); + }, + + // Expect numerical value greater than X + gt: function ( actual, expected, message ) { + QUnit.push( actual > expected, actual, 'greater than ' + expected, message ); + }, + + // Expect numerical value greater than or equal to X + gtOrEq: function ( actual, expected, message ) { + QUnit.push( actual >= expected, actual, 'greater than or equal to ' + expected, message ); + }, + + /** + * Asserts that two HTML strings are structurally equivalent. + * + * @param {string} actualHtml Actual HTML markup. + * @param {string} expectedHtml Expected HTML markup + * @param {string} message Assertion message. + */ + htmlEqual: function ( actualHtml, expectedHtml, message ) { + var actual = getHtmlStructure( actualHtml ), + expected = getHtmlStructure( expectedHtml ); + + QUnit.push( + QUnit.equiv( + actual, + expected + ), + actual, + expected, + message + ); + }, + + /** + * Asserts that two HTML strings are not structurally equivalent. + * + * @param {string} actualHtml Actual HTML markup. + * @param {string} expectedHtml Expected HTML markup. + * @param {string} message Assertion message. + */ + notHtmlEqual: function ( actualHtml, expectedHtml, message ) { + var actual = getHtmlStructure( actualHtml ), + expected = getHtmlStructure( expectedHtml ); + + QUnit.push( + !QUnit.equiv( + actual, + expected + ), + actual, + expected, + message + ); + } + }; + + $.extend( QUnit.assert, addons ); + + /** + * Small test suite to confirm proper functionality of the utilities and + * initializations defined above in this file. + */ + envExecCount = 0; + QUnit.module( 'mediawiki.tests.qunit.testrunner', QUnit.newMwEnvironment( { + setup: function () { + envExecCount += 1; + this.mwHtmlLive = mw.html; + mw.html = { + escape: function () { + return 'mocked-' + envExecCount; + } + }; + }, + teardown: function () { + mw.html = this.mwHtmlLive; + }, + config: { + testVar: 'foo' + }, + messages: { + testMsg: 'Foo.' + } + } ) ); + + QUnit.test( 'Setup', 3, function ( assert ) { + assert.equal( mw.html.escape( 'foo' ), 'mocked-1', 'extra setup() callback was ran.' ); + assert.equal( mw.config.get( 'testVar' ), 'foo', 'config object applied' ); + assert.equal( mw.messages.get( 'testMsg' ), 'Foo.', 'messages object applied' ); + + mw.config.set( 'testVar', 'bar' ); + mw.messages.set( 'testMsg', 'Bar.' ); + } ); + + QUnit.test( 'Teardown', 3, function ( assert ) { + assert.equal( mw.html.escape( 'foo' ), 'mocked-2', 'extra setup() callback was re-ran.' ); + assert.equal( mw.config.get( 'testVar' ), 'foo', 'config object restored and re-applied after test()' ); + assert.equal( mw.messages.get( 'testMsg' ), 'Foo.', 'messages object restored and re-applied after test()' ); + } ); + + QUnit.test( 'Loader status', 2, function ( assert ) { + var i, len, state, + modules = mw.loader.getModuleNames(), + error = [], + missing = []; + + for ( i = 0, len = modules.length; i < len; i++ ) { + state = mw.loader.getState( modules[i] ); + if ( state === 'error' ) { + error.push( modules[i] ); + } else if ( state === 'missing' ) { + missing.push( modules[i] ); + } + } + + assert.deepEqual( error, [], 'Modules in error state' ); + assert.deepEqual( missing, [], 'Modules in missing state' ); + } ); + + QUnit.test( 'htmlEqual', 8, function ( assert ) { + assert.htmlEqual( + '<div><p class="some classes" data-length="10">Child paragraph with <a href="http://example.com">A link</a></p>Regular text<span>A span</span></div>', + '<div><p data-length=\'10\' class=\'some classes\'>Child paragraph with <a href=\'http://example.com\' >A link</a></p>Regular text<span>A span</span></div>', + 'Attribute order, spacing and quotation marks (equal)' + ); + + assert.notHtmlEqual( + '<div><p class="some classes" data-length="10">Child paragraph with <a href="http://example.com">A link</a></p>Regular text<span>A span</span></div>', + '<div><p data-length=\'10\' class=\'some more classes\'>Child paragraph with <a href=\'http://example.com\' >A link</a></p>Regular text<span>A span</span></div>', + 'Attribute order, spacing and quotation marks (not equal)' + ); + + assert.htmlEqual( + '<label for="firstname" accesskey="f" class="important">First</label><input id="firstname" /><label for="lastname" accesskey="l" class="minor">Last</label><input id="lastname" />', + '<label for="firstname" accesskey="f" class="important">First</label><input id="firstname" /><label for="lastname" accesskey="l" class="minor">Last</label><input id="lastname" />', + 'Multiple root nodes (equal)' + ); + + assert.notHtmlEqual( + '<label for="firstname" accesskey="f" class="important">First</label><input id="firstname" /><label for="lastname" accesskey="l" class="minor">Last</label><input id="lastname" />', + '<label for="firstname" accesskey="f" class="important">First</label><input id="firstname" /><label for="lastname" accesskey="l" class="important" >Last</label><input id="lastname" />', + 'Multiple root nodes (not equal, last label node is different)' + ); + + assert.htmlEqual( + 'fo"o<br/>b>ar', + 'fo"o<br/>b>ar', + 'Extra escaping is equal' + ); + assert.notHtmlEqual( + 'foo<br/>bar', + 'foo<br/>bar', + 'Text escaping (not equal)' + ); + + assert.htmlEqual( + 'foo<a href="http://example.com">example</a>bar', + 'foo<a href="http://example.com">example</a>bar', + 'Outer text nodes are compared (equal)' + ); + + assert.notHtmlEqual( + 'foo<a href="http://example.com">example</a>bar', + 'foo<a href="http://example.com">example</a>quux', + 'Outer text nodes are compared (last text node different)' + ); + + } ); + + QUnit.module( 'mediawiki.tests.qunit.testrunner-after', QUnit.newMwEnvironment() ); + + QUnit.test( 'Teardown', 3, function ( assert ) { + assert.equal( mw.html.escape( '<' ), '<', 'extra teardown() callback was ran.' ); + assert.equal( mw.config.get( 'testVar' ), null, 'config object restored to live in next module()' ); + assert.equal( mw.messages.get( 'testMsg' ), null, 'messages object restored to live in next module()' ); + } ); }( jQuery, mediaWiki, QUnit ) ); diff --git a/tests/qunit/suites/resources/jquery/jquery.autoEllipsis.test.js b/tests/qunit/suites/resources/jquery/jquery.autoEllipsis.test.js index 0dee2ef0..e1895248 100644 --- a/tests/qunit/suites/resources/jquery/jquery.autoEllipsis.test.js +++ b/tests/qunit/suites/resources/jquery/jquery.autoEllipsis.test.js @@ -1,52 +1,58 @@ -( function ( mw, $ ) { +( function ( $ ) { -QUnit.module( 'jquery.autoEllipsis', QUnit.newMwEnvironment() ); + QUnit.module( 'jquery.autoEllipsis', QUnit.newMwEnvironment() ); -function createWrappedDiv( text, width ) { - var $wrapper = $( '<div>' ).css( 'width', width ); - var $div = $( '<div>' ).text( text ); - $wrapper.append( $div ); - return $wrapper; -} - -function findDivergenceIndex( a, b ) { - var i = 0; - while ( i < a.length && i < b.length && a[i] === b[i] ) { - i++; + function createWrappedDiv( text, width ) { + var $wrapper = $( '<div>' ).css( 'width', width ), + $div = $( '<div>' ).text( text ); + $wrapper.append( $div ); + return $wrapper; } - return i; -} - -QUnit.test( 'Position right', 4, function ( assert ) { - // We need this thing to be visible, so append it to the DOM - var origText = 'This is a really long random string and there is no way it fits in 100 pixels.'; - var $wrapper = createWrappedDiv( origText, '100px' ); - $( '#qunit-fixture' ).append( $wrapper ); - $wrapper.autoEllipsis( { position: 'right' } ); - - // Verify that, and only one, span element was created - var $span = $wrapper.find( '> span' ); - assert.strictEqual( $span.length, 1, 'autoEllipsis wrapped the contents in a span element' ); - - // Check that the text fits by turning on word wrapping - $span.css( 'whiteSpace', 'nowrap' ); - assert.ltOrEq( $span.width(), $span.parent().width(), "Text fits (making the span 'white-space:nowrap' does not make it wider than its parent)" ); - - // Add two characters using scary black magic - var spanText = $span.text(); - var d = findDivergenceIndex( origText, spanText ); - var spanTextNew = spanText.substr( 0, d ) + origText[d] + origText[d] + '...'; - - assert.gt( spanTextNew.length, spanText.length, 'Verify that the new span-length is indeed greater' ); - - // Put this text in the span and verify it doesn't fit - $span.text( spanTextNew ); - // In IE6 width works like min-width, allow IE6's width to be "equal to" - if ( $.browser.msie && Number( $.browser.version ) === 6 ) { - assert.gtOrEq( $span.width(), $span.parent().width(), 'Fit is maximal (adding two characters makes it not fit any more) - IE6: Maybe equal to as well due to width behaving like min-width in IE6' ); - } else { - assert.gt( $span.width(), $span.parent().width(), 'Fit is maximal (adding two characters makes it not fit any more)' ); + + function findDivergenceIndex( a, b ) { + var i = 0; + while ( i < a.length && i < b.length && a[i] === b[i] ) { + i++; + } + return i; } -}); -}( mediaWiki, jQuery ) ); + QUnit.test( 'Position right', 4, function ( assert ) { + // We need this thing to be visible, so append it to the DOM + var $span, spanText, d, spanTextNew, + origText = 'This is a really long random string and there is no way it fits in 100 pixels.', + $wrapper = createWrappedDiv( origText, '100px' ); + + $( '#qunit-fixture' ).append( $wrapper ); + $wrapper.autoEllipsis( { position: 'right' } ); + + // Verify that, and only one, span element was created + $span = $wrapper.find( '> span' ); + assert.strictEqual( $span.length, 1, 'autoEllipsis wrapped the contents in a span element' ); + + // Check that the text fits by turning on word wrapping + $span.css( 'whiteSpace', 'nowrap' ); + assert.ltOrEq( + $span.width(), + $span.parent().width(), + 'Text fits (making the span "white-space: nowrap" does not make it wider than its parent)' + ); + + // Add two characters using scary black magic + spanText = $span.text(); + d = findDivergenceIndex( origText, spanText ); + spanTextNew = spanText.substr( 0, d ) + origText[d] + origText[d] + '...'; + + assert.gt( spanTextNew.length, spanText.length, 'Verify that the new span-length is indeed greater' ); + + // Put this text in the span and verify it doesn't fit + $span.text( spanTextNew ); + // In IE6 width works like min-width, allow IE6's width to be "equal to" + if ( $.browser.msie && Number( $.browser.version ) === 6 ) { + assert.gtOrEq( $span.width(), $span.parent().width(), 'Fit is maximal (adding two characters makes it not fit any more) - IE6: Maybe equal to as well due to width behaving like min-width in IE6' ); + } else { + assert.gt( $span.width(), $span.parent().width(), 'Fit is maximal (adding two characters makes it not fit any more)' ); + } + } ); + +}( jQuery ) ); diff --git a/tests/qunit/suites/resources/jquery/jquery.byteLength.test.js b/tests/qunit/suites/resources/jquery/jquery.byteLength.test.js index a6ddfca6..e6aa3aa8 100644 --- a/tests/qunit/suites/resources/jquery/jquery.byteLength.test.js +++ b/tests/qunit/suites/resources/jquery/jquery.byteLength.test.js @@ -1,33 +1,37 @@ -QUnit.module( 'jquery.byteLength', QUnit.newMwEnvironment() ); +( function ( $ ) { + QUnit.module( 'jquery.byteLength', QUnit.newMwEnvironment() ); -QUnit.test( 'Simple text', 5, function ( assert ) { - var azLc = 'abcdefghijklmnopqrstuvwxyz', - azUc = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', - num = '0123456789', - x = '*', - space = ' '; + QUnit.test( 'Simple text', 5, function ( assert ) { + var azLc = 'abcdefghijklmnopqrstuvwxyz', + azUc = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', + num = '0123456789', + x = '*', + space = ' '; - assert.equal( $.byteLength( azLc ), 26, 'Lowercase a-z' ); - assert.equal( $.byteLength( azUc ), 26, 'Uppercase A-Z' ); - assert.equal( $.byteLength( num ), 10, 'Numbers 0-9' ); - assert.equal( $.byteLength( x ), 1, 'An asterisk' ); - assert.equal( $.byteLength( space ), 3, '3 spaces' ); + assert.equal( $.byteLength( azLc ), 26, 'Lowercase a-z' ); + assert.equal( $.byteLength( azUc ), 26, 'Uppercase A-Z' ); + assert.equal( $.byteLength( num ), 10, 'Numbers 0-9' ); + assert.equal( $.byteLength( x ), 1, 'An asterisk' ); + assert.equal( $.byteLength( space ), 3, '3 spaces' ); -} ); + } ); -QUnit.test( 'Special text', 5, function ( assert ) { - // http://en.wikipedia.org/wiki/UTF-8 - var U_0024 = '$', - U_00A2 = '\u00A2', - U_20AC = '\u20AC', - U_024B62 = '\u024B62', - // The normal one doesn't display properly, try the below which is the same - // according to http://www.fileformat.info/info/unicode/char/24B62/index.htm - U_024B62_alt = '\uD852\uDF62'; + QUnit.test( 'Special text', 4, function ( assert ) { + // https://en.wikipedia.org/wiki/UTF-8 + var u0024 = '$', + // Cent symbol + u00A2 = '\u00A2', + // Euro symbol + u20AC = '\u20AC', + // Character \U00024B62 (Han script) can't be represented in javascript as a single + // code point, instead it is composed as a surrogate pair of two separate code units. + // http://codepoints.net/U+24B62 + // http://www.fileformat.info/info/unicode/char/24B62/index.htm + u024B62 = '\uD852\uDF62'; - assert.strictEqual( $.byteLength( U_0024 ), 1, 'U+0024: 1 byte. $ (dollar sign)' ); - assert.strictEqual( $.byteLength( U_00A2 ), 2, 'U+00A2: 2 bytes. \u00A2 (cent sign)' ); - assert.strictEqual( $.byteLength( U_20AC ), 3, 'U+20AC: 3 bytes. \u20AC (euro sign)' ); - assert.strictEqual( $.byteLength( U_024B62 ), 4, 'U+024B62: 4 bytes. \uD852\uDF62 (a Han character)' ); - assert.strictEqual( $.byteLength( U_024B62_alt ), 4, 'U+024B62: 4 bytes. \uD852\uDF62 (a Han character) - alternative method' ); -} ); + assert.strictEqual( $.byteLength( u0024 ), 1, 'U+0024' ); + assert.strictEqual( $.byteLength( u00A2 ), 2, 'U+00A2' ); + assert.strictEqual( $.byteLength( u20AC ), 3, 'U+20AC' ); + assert.strictEqual( $.byteLength( u024B62 ), 4, 'U+024B62 (surrogate pair: \\uD852\\uDF62)' ); + } ); +}( jQuery ) ); diff --git a/tests/qunit/suites/resources/jquery/jquery.byteLimit.test.js b/tests/qunit/suites/resources/jquery/jquery.byteLimit.test.js index 4f86eb96..22d2af19 100644 --- a/tests/qunit/suites/resources/jquery/jquery.byteLimit.test.js +++ b/tests/qunit/suites/resources/jquery/jquery.byteLimit.test.js @@ -15,12 +15,15 @@ // Basic sendkey-implementation function addChars( $input, charstr ) { var c, len; + + function x( $input, i ) { + // Add character to the value + return $input.val() + charstr.charAt( i ); + } + for ( c = 0, len = charstr.length; c < len; c += 1 ) { $input - .val( function ( i, val ) { - // Add character to the value - return val + charstr.charAt( c ); - } ) + .val( x( $input, c ) ) .trigger( 'change' ); } } @@ -28,165 +31,159 @@ /** * Test factory for $.fn.byteLimit * - * @param $input {jQuery} jQuery object in an input element - * @param hasLimit {Boolean} Wether a limit should apply at all - * @param limit {Number} Limit (if used) otherwise undefined - * The limit should be less than 20 (the sample data's length) + * @param {Object} options + * @param {string} options.description Test name + * @param {jQuery} options.$input jQuery object in an input element + * @param {string} options.sample Sequence of characters to simulate being + * added one by one + * @param {string} options.expected Expected final value of `$input` */ function byteLimitTest( options ) { - var opt = $.extend({ + var opt = $.extend( { description: '', $input: null, sample: '', - hasLimit: false, - expected: '', - limit: null - }, options); - - QUnit.asyncTest( opt.description, opt.hasLimit ? 3 : 2, function ( assert ) { - setTimeout( function () { - var rawVal, fn, effectiveVal; - - opt.$input.appendTo( '#qunit-fixture' ); + expected: '' + }, options ); - // Simulate pressing keys for each of the sample characters - addChars( opt.$input, opt.sample ); + QUnit.asyncTest( opt.description, 1, function ( assert ) { + setTimeout( function () { + opt.$input.appendTo( '#qunit-fixture' ); - rawVal = opt.$input.val(); - fn = opt.$input.data( 'byteLimit.callback' ); - effectiveVal = fn ? fn( rawVal ) : rawVal; + // Simulate pressing keys for each of the sample characters + addChars( opt.$input, opt.sample ); - if ( opt.hasLimit ) { - assert.ltOrEq( - $.byteLength( effectiveVal ), - opt.limit, - 'Prevent keypresses after byteLimit was reached, length never exceeded the limit' - ); assert.equal( - $.byteLength( rawVal ), - $.byteLength( opt.expected ), - 'Not preventing keypresses too early, length has reached the expected length' + opt.$input.val(), + opt.expected, + 'New value matches the expected string' ); - assert.equal( rawVal, opt.expected, 'New value matches the expected string' ); - } else { - assert.equal( - $.byteLength( effectiveVal ), - $.byteLength( opt.expected ), - 'Unlimited scenarios are not affected, expected length reached' - ); - assert.equal( rawVal, opt.expected, 'New value matches the expected string' ); - } - QUnit.start(); - }, 10 ); + QUnit.start(); + }, 10 ); } ); } - byteLimitTest({ + byteLimitTest( { description: 'Plain text input', $input: $( '<input type="text"/>' ), sample: simpleSample, - hasLimit: false, expected: simpleSample - }); + } ); - byteLimitTest({ + byteLimitTest( { description: 'Plain text input. Calling byteLimit with no parameters and no maxlength attribute (bug 36310)', $input: $( '<input type="text"/>' ) .byteLimit(), sample: simpleSample, - hasLimit: false, expected: simpleSample - }); + } ); - byteLimitTest({ + byteLimitTest( { description: 'Limit using the maxlength attribute', $input: $( '<input type="text"/>' ) .attr( 'maxlength', '10' ) .byteLimit(), sample: simpleSample, - hasLimit: true, - limit: 10, expected: '1234567890' - }); + } ); - byteLimitTest({ + byteLimitTest( { description: 'Limit using a custom value', $input: $( '<input type="text"/>' ) .byteLimit( 10 ), sample: simpleSample, - hasLimit: true, - limit: 10, expected: '1234567890' - }); + } ); - byteLimitTest({ + byteLimitTest( { description: 'Limit using a custom value, overriding maxlength attribute', $input: $( '<input type="text"/>' ) .attr( 'maxlength', '10' ) .byteLimit( 15 ), sample: simpleSample, - hasLimit: true, - limit: 15, expected: '123456789012345' - }); + } ); - byteLimitTest({ + byteLimitTest( { description: 'Limit using a custom value (multibyte)', $input: $( '<input type="text"/>' ) .byteLimit( 14 ), sample: mbSample, - hasLimit: true, - limit: 14, expected: '1234567890' + U_20AC + '1' - }); + } ); - byteLimitTest({ + byteLimitTest( { description: 'Limit using a custom value (multibyte) overlapping a byte', $input: $( '<input type="text"/>' ) .byteLimit( 12 ), sample: mbSample, - hasLimit: true, - limit: 12, expected: '1234567890' + '12' - }); + } ); - byteLimitTest({ + byteLimitTest( { description: 'Pass the limit and a callback as input filter', $input: $( '<input type="text"/>' ) .byteLimit( 6, function ( val ) { - // Invalid title - if ( val === '' ) { - return ''; - } - + var title = mw.Title.newFromText( String( val ) ); // Return without namespace prefix - return new mw.Title( String( val ) ).getMain(); + return title ? title.getMain() : ''; } ), sample: 'User:Sample', - hasLimit: true, - limit: 6, // 'Sample' length expected: 'User:Sample' - }); + } ); - byteLimitTest({ + byteLimitTest( { description: 'Limit using the maxlength attribute and pass a callback as input filter', $input: $( '<input type="text"/>' ) .attr( 'maxlength', '6' ) .byteLimit( function ( val ) { - // Invalid title - if ( val === '' ) { - return ''; - } - + var title = mw.Title.newFromText( String( val ) ); // Return without namespace prefix - return new mw.Title( String( val ) ).getMain(); + return title ? title.getMain() : ''; } ), sample: 'User:Sample', - hasLimit: true, - limit: 6, // 'Sample' length expected: 'User:Sample' - }); + } ); + + byteLimitTest( { + description: 'Pass the limit and a callback as input filter', + $input: $( '<input type="text"/>' ) + .byteLimit( 6, function ( val ) { + var title = mw.Title.newFromText( String( val ) ); + // Return without namespace prefix + return title ? title.getMain() : ''; + } ), + sample: 'User:Example', + // The callback alters the value to be used to calculeate + // the length. The altered value is "Exampl" which has + // a length of 6, the "e" would exceed the limit. + expected: 'User:Exampl' + } ); + + byteLimitTest( { + description: 'Input filter that increases the length', + $input: $( '<input type="text"/>' ) + .byteLimit( 10, function ( text ) { + return 'prefix' + text; + } ), + sample: simpleSample, + // Prefix adds 6 characters, limit is reached after 4 + expected: '1234' + } ); + + // Regression tests for bug 41450 + byteLimitTest( { + description: 'Input filter of which the base exceeds the limit', + $input: $( '<input type="text"/>' ) + .byteLimit( 3, function ( text ) { + return 'prefix' + text; + } ), + sample: simpleSample, + hasLimit: true, + limit: 6, // 'prefix' length + expected: '' + } ); QUnit.test( 'Confirm properties and attributes set', 4, function ( assert ) { var $el, $elA, $elB; @@ -229,6 +226,27 @@ assert.strictEqual( $el.length, 2, 'Verify that there are no other elements clashing with this test suite' ); $el.byteLimit(); - }); + } ); + + QUnit.test( 'Trim from insertion when limit exceeded', 2, function ( assert ) { + var $el; + + // Use a new <input /> because the bug only occurs on the first time + // the limit it reached (bug 40850) + $el = $( '<input type="text"/>' ) + .appendTo( '#qunit-fixture' ) + .byteLimit( 3 ) + .val( 'abc' ).trigger( 'change' ) + .val( 'zabc' ).trigger( 'change' ); + + assert.strictEqual( $el.val(), 'abc', 'Trim from the insertion point (at 0), not the end' ); + + $el = $( '<input type="text"/>' ) + .appendTo( '#qunit-fixture' ) + .byteLimit( 3 ) + .val( 'abc' ).trigger( 'change' ) + .val( 'azbc' ).trigger( 'change' ); + assert.strictEqual( $el.val(), 'abc', 'Trim from the insertion point (at 1), not the end' ); + } ); }( jQuery, mediaWiki ) ); diff --git a/tests/qunit/suites/resources/jquery/jquery.client.test.js b/tests/qunit/suites/resources/jquery/jquery.client.test.js index bf62b39a..4c7c3022 100644 --- a/tests/qunit/suites/resources/jquery/jquery.client.test.js +++ b/tests/qunit/suites/resources/jquery/jquery.client.test.js @@ -1,317 +1,542 @@ -QUnit.module( 'jquery.client', QUnit.newMwEnvironment() ); +( function ( $ ) { -/** Number of user-agent defined */ -var uacount = 0; + QUnit.module( 'jquery.client', QUnit.newMwEnvironment() ); -var uas = (function () { - - // Object keyed by userAgent. Value is an array (human-readable name, client-profile object, navigator.platform value) - // Info based on results from http://toolserver.org/~krinkle/testswarm/job/174/ - var uas = { - // Internet Explorer 6 - // Internet Explorer 7 - 'Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1)': { - title: 'Internet Explorer 7', - platform: 'Win32', - profile: { - "name": "msie", - "layout": "trident", - "layoutVersion": "unknown", - "platform": "win", - "version": "7.0", - "versionBase": "7", - "versionNumber": 7 + var uacount = 0, + // Object keyed by userAgent. Value is an array (human-readable name, client-profile object, navigator.platform value) + // Info based on results from http://toolserver.org/~krinkle/testswarm/job/174/ + uas = { + // Internet Explorer 6 + // Internet Explorer 7 + 'Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1)': { + title: 'Internet Explorer 7', + platform: 'Win32', + profile: { + name: 'msie', + layout: 'trident', + layoutVersion: 'unknown', + platform: 'win', + version: '7.0', + versionBase: '7', + versionNumber: 7 + }, + wikiEditor: { + ltr: true, + rtl: false + } }, - wikiEditor: { - ltr: true, - rtl: false - } - }, - // Internet Explorer 8 - // Internet Explorer 9 - // Internet Explorer 10 - 'Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; Trident/6.0)': { - title: 'Internet Explorer 10', - platform: 'Win32', - profile: { - "name": "msie", - "layout": "trident", - "layoutVersion": "unknown", // should be able to report 6? - "platform": "win", - "version": "10.0", - "versionBase": "10", - "versionNumber": 10 + // Internet Explorer 8 + // Internet Explorer 9 + // Internet Explorer 10 + 'Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; Trident/6.0)': { + title: 'Internet Explorer 10', + platform: 'Win32', + profile: { + name: 'msie', + layout: 'trident', + layoutVersion: 6, + platform: 'win', + version: '10.0', + versionBase: '10', + versionNumber: 10 + }, + wikiEditor: { + ltr: true, + rtl: true + } }, - wikiEditor: { - ltr: true, - rtl: true - } - }, - // Firefox 2 - // Firefox 3.5 - 'Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.1.19) Gecko/20110420 Firefox/3.5.19': { - title: 'Firefox 3.5', - platform: 'MacIntel', - profile: { - "name": "firefox", - "layout": "gecko", - "layoutVersion": 20110420, - "platform": "mac", - "version": "3.5.19", - "versionBase": "3", - "versionNumber": 3.5 + // Internet Explorer 11 + 'Mozilla/5.0 (Windows NT 6.3; Trident/7.0; rv 11.0) like Gecko': { + title: 'Internet Explorer 11', + platform: 'Win32', + profile: { + name: 'msie', + layout: 'trident', + layoutVersion: 7, + platform: 'win', + version: '11.0', + versionBase: '11', + versionNumber: 11 + }, + wikiEditor: { + ltr: true, + rtl: true + } }, - wikiEditor: { - ltr: true, - rtl: true - } - }, - // Firefox 3.6 - 'Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.2.17) Gecko/20110422 Ubuntu/10.10 (maverick) Firefox/3.6.17': { - title: 'Firefox 3.6', - platform: 'Linux i686', - profile: { - "name": "firefox", - "layout": "gecko", - "layoutVersion": 20110422, - "platform": "linux", - "version": "3.6.17", - "versionBase": "3", - "versionNumber": 3.6 + // Internet Explorer 11 - Windows 8.1 x64 Modern UI + 'Mozilla/5.0 (Windows NT 6.3; Win64; x64; Trident/7.0; rv:11.0) like Gecko': { + title: 'Internet Explorer 11', + platform: 'Win64', + profile: { + name: 'msie', + layout: 'trident', + layoutVersion: 7, + platform: 'win', + version: '11.0', + versionBase: '11', + versionNumber: 11 + }, + wikiEditor: { + ltr: true, + rtl: true + } }, - wikiEditor: { - ltr: true, - rtl: true - } - }, - // Firefox 4 - 'Mozilla/5.0 (Windows NT 6.0; rv:2.0.1) Gecko/20100101 Firefox/4.0.1': { - title: 'Firefox 4', - platform: 'Win32', - profile: { - "name": "firefox", - "layout": "gecko", - "layoutVersion": 20100101, - "platform": "win", - "version": "4.0.1", - "versionBase": "4", - "versionNumber": 4 + // Internet Explorer 11 - Windows 8.1 x64 desktop UI + 'Mozilla/5.0 (Windows NT 6.3; WOW64; Trident/7.0; rv:11.0) like Gecko': { + title: 'Internet Explorer 11', + platform: 'WOW64', + profile: { + name: 'msie', + layout: 'trident', + layoutVersion: 7, + platform: 'win', + version: '11.0', + versionBase: '11', + versionNumber: 11 + }, + wikiEditor: { + ltr: true, + rtl: true + } }, - wikiEditor: { - ltr: true, - rtl: true - } - }, - // Firefox 10 nightly build - 'Mozilla/5.0 (X11; Linux x86_64; rv:10.0a1) Gecko/20111103 Firefox/10.0a1': { - title: 'Firefox 10 nightly', - platform: 'Linux', - profile: { - "name": "firefox", - "layout": "gecko", - "layoutVersion": 20111103, - "platform": "linux", - "version": "10.0a1", - "versionBase": "10", - "versionNumber": 10 + // Firefox 2 + // Firefox 3.5 + 'Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.1.19) Gecko/20110420 Firefox/3.5.19': { + title: 'Firefox 3.5', + platform: 'MacIntel', + profile: { + name: 'firefox', + layout: 'gecko', + layoutVersion: 20110420, + platform: 'mac', + version: '3.5.19', + versionBase: '3', + versionNumber: 3.5 + }, + wikiEditor: { + ltr: true, + rtl: true + } }, - wikiEditor: { - ltr: true, - rtl: true - } - }, - // Firefox 5 - // Safari 3 - // Safari 4 - 'Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_7; nl-nl) AppleWebKit/531.22.7 (KHTML, like Gecko) Version/4.0.5 Safari/531.22.7': { - title: 'Safari 4', - platform: 'MacIntel', - profile: { - "name": "safari", - "layout": "webkit", - "layoutVersion": 531, - "platform": "mac", - "version": "4.0.5", - "versionBase": "4", - "versionNumber": 4 + // Firefox 3.6 + 'Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.2.17) Gecko/20110422 Ubuntu/10.10 (maverick) Firefox/3.6.17': { + title: 'Firefox 3.6', + platform: 'Linux i686', + profile: { + name: 'firefox', + layout: 'gecko', + layoutVersion: 20110422, + platform: 'linux', + version: '3.6.17', + versionBase: '3', + versionNumber: 3.6 + }, + wikiEditor: { + ltr: true, + rtl: true + } }, - wikiEditor: { - ltr: true, - rtl: true - } - }, - 'Mozilla/5.0 (Windows; U; Windows NT 6.0; cs-CZ) AppleWebKit/533.21.1 (KHTML, like Gecko) Version/4.0.5 Safari/531.22.7': { - title: 'Safari 4', - platform: 'Win32', - profile: { - "name": "safari", - "layout": "webkit", - "layoutVersion": 533, - "platform": "win", - "version": "4.0.5", - "versionBase": "4", - "versionNumber": 4 + // Firefox 4 + 'Mozilla/5.0 (Windows NT 6.0; rv:2.0.1) Gecko/20100101 Firefox/4.0.1': { + title: 'Firefox 4', + platform: 'Win32', + profile: { + name: 'firefox', + layout: 'gecko', + layoutVersion: 20100101, + platform: 'win', + version: '4.0.1', + versionBase: '4', + versionNumber: 4 + }, + wikiEditor: { + ltr: true, + rtl: true + } }, - wikiEditor: { - ltr: true, - rtl: true - } - }, - // Safari 5 - // Opera 10 - // Chrome 5 - // Chrome 6 - // Chrome 7 - // Chrome 8 - // Chrome 9 - // Chrome 10 - // Chrome 11 - // Chrome 12 - 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_5_8) AppleWebKit/534.30 (KHTML, like Gecko) Chrome/12.0.742.112 Safari/534.30': { - title: 'Chrome 12', - platform: 'MacIntel', - profile: { - "name": "chrome", - "layout": "webkit", - "layoutVersion": 534, - "platform": "mac", - "version": "12.0.742.112", - "versionBase": "12", - "versionNumber": 12 + // Firefox 10 nightly build + 'Mozilla/5.0 (X11; Linux x86_64; rv:10.0a1) Gecko/20111103 Firefox/10.0a1': { + title: 'Firefox 10 nightly', + platform: 'Linux', + profile: { + name: 'firefox', + layout: 'gecko', + layoutVersion: 20111103, + platform: 'linux', + version: '10.0a1', + versionBase: '10', + versionNumber: 10 + }, + wikiEditor: { + ltr: true, + rtl: true + } }, - wikiEditor: { - ltr: true, - rtl: true - } - }, - 'Mozilla/5.0 (X11; Linux i686) AppleWebKit/534.30 (KHTML, like Gecko) Chrome/12.0.742.68 Safari/534.30': { - title: 'Chrome 12', - platform: 'Linux i686', - profile: { - "name": "chrome", - "layout": "webkit", - "layoutVersion": 534, - "platform": "linux", - "version": "12.0.742.68", - "versionBase": "12", - "versionNumber": 12 + // Iceweasel 10.0.6 + 'Mozilla/5.0 (X11; Linux i686; rv:10.0.6) Gecko/20100101 Iceweasel/10.0.6': { + title: 'Iceweasel 10.0.6', + platform: 'Linux', + profile: { + name: 'iceweasel', + layout: 'gecko', + layoutVersion: 20100101, + platform: 'linux', + version: '10.0.6', + versionBase: '10', + versionNumber: 10 + }, + wikiEditor: { + ltr: true, + rtl: true + } + }, + // Iceweasel 15.0.1 + 'Mozilla/5.0 (X11; Linux x86_64; rv:15.0) Gecko/20100101 Firefox/15.0.1 Iceweasel/15.0.1': { + title: 'Iceweasel 15.0.1', + platform: 'Linux', + profile: { + name: 'iceweasel', + layout: 'gecko', + layoutVersion: 20100101, + platform: 'linux', + version: '15.0.1', + versionBase: '15', + versionNumber: 15 + }, + wikiEditor: { + ltr: true, + rtl: true + } + }, + // Firefox 5 + // Safari 3 + // Safari 4 + 'Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_7; nl-nl) AppleWebKit/531.22.7 (KHTML, like Gecko) Version/4.0.5 Safari/531.22.7': { + title: 'Safari 4', + platform: 'MacIntel', + profile: { + name: 'safari', + layout: 'webkit', + layoutVersion: 531, + platform: 'mac', + version: '4.0.5', + versionBase: '4', + versionNumber: 4 + }, + wikiEditor: { + ltr: true, + rtl: true + } + }, + 'Mozilla/5.0 (Windows; U; Windows NT 6.0; cs-CZ) AppleWebKit/533.21.1 (KHTML, like Gecko) Version/4.0.5 Safari/531.22.7': { + title: 'Safari 4', + platform: 'Win32', + profile: { + name: 'safari', + layout: 'webkit', + layoutVersion: 533, + platform: 'win', + version: '4.0.5', + versionBase: '4', + versionNumber: 4 + }, + wikiEditor: { + ltr: true, + rtl: true + } }, - wikiEditor: { - ltr: true, - rtl: true + // Safari 5 + // Safari 6 + 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_3) AppleWebKit/536.29.13 (KHTML, like Gecko) Version/6.0.4 Safari/536.29.13': { + title: 'Safari 6', + platform: 'MacIntel', + profile: { + name: 'safari', + layout: 'webkit', + layoutVersion: 536, + platform: 'mac', + version: '6.0.4', + versionBase: '6', + versionNumber: 6 + }, + wikiEditor: { + ltr: true, + rtl: true + } + }, + // Safari 6.0.5+ (doesn't have the comma in "KHTML, like Gecko") + 'Mozilla/5.0 (Macintosh; Intel Mac OS X 1084) AppleWebKit/536.30.1 (KHTML like Gecko) Version/6.0.5 Safari/536.30.1': { + title: 'Safari 6', + platform: 'MacIntel', + profile: { + name: 'safari', + layout: 'webkit', + layoutVersion: 536, + platform: 'mac', + version: '6.0.5', + versionBase: '6', + versionNumber: 6 + }, + wikiEditor: { + ltr: true, + rtl: true + } + }, + // Opera 10+ + 'Opera/9.80 (Windows NT 5.1)': { + title: 'Opera 10+ (exact version unspecified)', + platform: 'Win32', + profile: { + name: 'opera', + layout: 'presto', + layoutVersion: 'unknown', + platform: 'win', + version: '10', + versionBase: '10', + versionNumber: 10 + }, + wikiEditor: { + ltr: true, + rtl: true + } + }, + // Opera 12 + 'Opera/9.80 (Windows NT 5.1) Presto/2.12.388 Version/12.11': { + title: 'Opera 12', + platform: 'Win32', + profile: { + name: 'opera', + layout: 'presto', + layoutVersion: 'unknown', + platform: 'win', + version: '12.11', + versionBase: '12', + versionNumber: 12.11 + }, + wikiEditor: { + ltr: true, + rtl: true + } + }, + // Opera 15 (WebKit-based) + 'Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/28.0.1500.52 Safari/537.36 OPR/15.0.1147.130': { + title: 'Opera 15', + platform: 'Win32', + profile: { + name: 'opera', + layout: 'webkit', + layoutVersion: 537, + platform: 'win', + version: '15.0.1147.130', + versionBase: '15', + versionNumber: 15 + }, + wikiEditor: { + ltr: true, + rtl: true + } + }, + // Chrome 5 + // Chrome 6 + // Chrome 7 + // Chrome 8 + // Chrome 9 + // Chrome 10 + // Chrome 11 + // Chrome 12 + 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_5_8) AppleWebKit/534.30 (KHTML, like Gecko) Chrome/12.0.742.112 Safari/534.30': { + title: 'Chrome 12', + platform: 'MacIntel', + profile: { + name: 'chrome', + layout: 'webkit', + layoutVersion: 534, + platform: 'mac', + version: '12.0.742.112', + versionBase: '12', + versionNumber: 12 + }, + wikiEditor: { + ltr: true, + rtl: true + } + }, + 'Mozilla/5.0 (X11; Linux i686) AppleWebKit/534.30 (KHTML, like Gecko) Chrome/12.0.742.68 Safari/534.30': { + title: 'Chrome 12', + platform: 'Linux i686', + profile: { + name: 'chrome', + layout: 'webkit', + layoutVersion: 534, + platform: 'linux', + version: '12.0.742.68', + versionBase: '12', + versionNumber: 12 + }, + wikiEditor: { + ltr: true, + rtl: true + } + }, + // Android WebKit Browser 2.3 + 'Mozilla/5.0 (Linux; U; Android 2.3.5; en-us; HTC Vision Build/GRI40) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1': { + title: 'Android WebKit Browser 2.3', + platform: 'Linux armv7l', + profile: { + name: 'android', + layout: 'webkit', + layoutVersion: 533, + platform: 'linux', + version: '2.3.5', + versionBase: '2', + versionNumber: 2.3 + }, + wikiEditor: { + ltr: true, + rtl: true + } + }, + // Bug #34924 + 'Mozilla/5.0 (X11; Linux i686) AppleWebKit/534.34 (KHTML, like Gecko) rekonq Safari/534.34': { + title: 'Rekonq', + platform: 'Linux i686', + profile: { + name: 'rekonq', + layout: 'webkit', + layoutVersion: 534, + platform: 'linux', + version: '534.34', + versionBase: '534', + versionNumber: 534.34 + }, + wikiEditor: { + ltr: true, + rtl: true + } } }, - // Bug #34924 - 'Mozilla/5.0 (X11; Linux i686) AppleWebKit/534.34 (KHTML, like Gecko) rekonq Safari/534.34': { - title: 'Rekonq', - platform: 'Linux i686', - profile: { - "name": "rekonq", - "layout": "webkit", - "layoutVersion": 534, - "platform": "linux", - "version": "534.34", - "versionBase": "534", - "versionNumber": 534.34 + testMap = { + // Example from WikiEditor + // Make sure to use raw numbers, a string like "7.0" would fail on a + // version 10 browser since in string comparaison "10" is before "7.0" :) + 'ltr': { + 'msie': [['>=', 7.0]], + 'firefox': [['>=', 2]], + 'opera': [['>=', 9.6]], + 'safari': [['>=', 3]], + 'chrome': [['>=', 3]], + 'netscape': [['>=', 9]], + 'blackberry': false, + 'ipod': false, + 'iphone': false }, - wikiEditor: { - ltr: true, - rtl: true + 'rtl': { + 'msie': [['>=', 8]], + 'firefox': [['>=', 2]], + 'opera': [['>=', 9.6]], + 'safari': [['>=', 3]], + 'chrome': [['>=', 3]], + 'netscape': [['>=', 9]], + 'blackberry': false, + 'ipod': false, + 'iphone': false } } - }; + ; + + // Count test cases $.each( uas, function () { uacount++; - }); - return uas; -}()); + } ); + + QUnit.test( 'profile( navObject )', 7, function ( assert ) { + var p = $.client.profile(); + + function unknownOrType( val, type, summary ) { + assert.ok( typeof val === type || val === 'unknown', summary ); + } + + assert.equal( typeof p, 'object', 'profile returns an object' ); + unknownOrType( p.layout, 'string', 'p.layout is a string (or "unknown")' ); + unknownOrType( p.layoutVersion, 'number', 'p.layoutVersion is a number (or "unknown")' ); + unknownOrType( p.platform, 'string', 'p.platform is a string (or "unknown")' ); + unknownOrType( p.version, 'string', 'p.version is a string (or "unknown")' ); + unknownOrType( p.versionBase, 'string', 'p.versionBase is a string (or "unknown")' ); + assert.equal( typeof p.versionNumber, 'number', 'p.versionNumber is a number' ); + } ); -QUnit.test( 'profile userAgent support', uacount, function ( assert ) { - // Generate a client profile object and compare recursively - var uaTest = function( rawUserAgent, data ) { - var ret = $.client.profile( { - userAgent: rawUserAgent, - platform: data.platform + QUnit.test( 'profile( navObject ) - samples', uacount, function ( assert ) { + // Loop through and run tests + $.each( uas, function ( rawUserAgent, data ) { + // Generate a client profile object and compare recursively + var ret = $.client.profile( { + userAgent: rawUserAgent, + platform: data.platform + } ); + assert.deepEqual( ret, data.profile, 'Client profile support check for ' + data.title + ' (' + data.platform + '): ' + rawUserAgent ); } ); - assert.deepEqual( ret, data.profile, 'Client profile support check for ' + data.title + ' (' + data.platform + '): ' + rawUserAgent ); - }; + } ); - // Loop through and run tests - $.each( uas, uaTest ); -} ); + QUnit.test( 'test( testMap )', 4, function ( assert ) { + // .test() uses eval, make sure no exceptions are thrown + // then do a basic return value type check + var testMatch = $.client.test( testMap ), + ie7Profile = $.client.profile( { + 'userAgent': 'Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1)', + 'platform': '' + } ); -QUnit.test( 'profile return validation for current user agent', 7, function ( assert ) { - var p = $.client.profile(); - function unknownOrType( val, type, summary ) { - assert.ok( typeof val === type || val === 'unknown', summary ); - } + assert.equal( typeof testMatch, 'boolean', 'map with ltr/rtl split returns a boolean value' ); - assert.equal( typeof p, 'object', 'profile returns an object' ); - unknownOrType( p.layout, 'string', 'p.layout is a string (or "unknown")' ); - unknownOrType( p.layoutVersion, 'number', 'p.layoutVersion is a number (or "unknown")' ); - unknownOrType( p.platform, 'string', 'p.platform is a string (or "unknown")' ); - unknownOrType( p.version, 'string', 'p.version is a string (or "unknown")' ); - unknownOrType( p.versionBase, 'string', 'p.versionBase is a string (or "unknown")' ); - assert.equal( typeof p.versionNumber, 'number', 'p.versionNumber is a number' ); -}); + testMatch = $.client.test( testMap.ltr ); -// Example from WikiEditor -// Make sure to use raw numbers, a string like "7.0" would fail on a -// version 10 browser since in string comparaison "10" is before "7.0" :) -var testMap = { - 'ltr': { - 'msie': [['>=', 7.0]], - 'firefox': [['>=', 2]], - 'opera': [['>=', 9.6]], - 'safari': [['>=', 3]], - 'chrome': [['>=', 3]], - 'netscape': [['>=', 9]], - 'blackberry': false, - 'ipod': false, - 'iphone': false - }, - 'rtl': { - 'msie': [['>=', 8]], - 'firefox': [['>=', 2]], - 'opera': [['>=', 9.6]], - 'safari': [['>=', 3]], - 'chrome': [['>=', 3]], - 'netscape': [['>=', 9]], - 'blackberry': false, - 'ipod': false, - 'iphone': false - } -}; + assert.equal( typeof testMatch, 'boolean', 'simple map (without ltr/rtl split) returns a boolean value' ); -QUnit.test( 'test', 1, function ( assert ) { - // .test() uses eval, make sure no exceptions are thrown - // then do a basic return value type check - var testMatch = $.client.test( testMap ); + assert.equal( $.client.test( { + 'msie': null + }, ie7Profile ), true, 'returns true if any version of a browser are allowed (null)' ); - assert.equal( typeof testMatch, 'boolean', 'test returns a boolean value' ); + assert.equal( $.client.test( { + 'msie': false + }, ie7Profile ), false, 'returns false if all versions of a browser are not allowed (false)' ); + } ); -}); + QUnit.test( 'test( testMap, exactMatchOnly )', 2, function ( assert ) { + var ie7Profile = $.client.profile( { + 'userAgent': 'Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1)', + 'platform': '' + } ); -QUnit.test( 'User-agent matches against WikiEditor\'s compatibility map', uacount * 2, function ( assert ) { - var $body = $( 'body' ), - bodyClasses = $body.attr( 'class' ); + assert.equal( $.client.test( { + 'firefox': [['>=', 2]] + }, ie7Profile, false ), true, 'returns true if browser not found and exactMatchOnly not set' ); - // Loop through and run tests - $.each( uas, function ( agent, data ) { - $.each( ['ltr', 'rtl'], function ( i, dir ) { - $body.removeClass( 'ltr rtl' ).addClass( dir ); - var profile = $.client.profile( { - userAgent: agent, - platform: data.platform - } ); - var testMatch = $.client.test( testMap, profile ); - $body.removeClass( dir ); + assert.equal( $.client.test( { + 'firefox': [['>=', 2]] + }, ie7Profile, true ), false, 'returns false if browser not found and exactMatchOnly is set' ); + } ); + + QUnit.test( 'test( testMap) - WikiEditor sample', uacount * 2, function ( assert ) { + var $body = $( 'body' ), + bodyClasses = $body.attr( 'class' ); - assert.equal( testMatch, data.wikiEditor[dir], 'testing comparison based on ' + dir + ', ' + agent ); - }); - }); + // Loop through and run tests + $.each( uas, function ( agent, data ) { + $.each( ['ltr', 'rtl'], function ( i, dir ) { + var profile, testMatch; + $body.removeClass( 'ltr rtl' ).addClass( dir ); + profile = $.client.profile( { + userAgent: agent, + platform: data.platform + } ); + testMatch = $.client.test( testMap, profile ); + $body.removeClass( dir ); - // Restore body classes - $body.attr( 'class', bodyClasses ); -}); + assert.equal( testMatch, data.wikiEditor[dir], 'testing comparison based on ' + dir + ', ' + agent ); + } ); + } ); + // Restore body classes + $body.attr( 'class', bodyClasses ); + } ); +}( jQuery ) ); diff --git a/tests/qunit/suites/resources/jquery/jquery.colorUtil.test.js b/tests/qunit/suites/resources/jquery/jquery.colorUtil.test.js index 7b37f5a0..39ae363c 100644 --- a/tests/qunit/suites/resources/jquery/jquery.colorUtil.test.js +++ b/tests/qunit/suites/resources/jquery/jquery.colorUtil.test.js @@ -1,58 +1,63 @@ -QUnit.module( 'jquery.colorUtil', QUnit.newMwEnvironment() ); - -QUnit.test( 'getRGB', 18, function ( assert ) { - assert.strictEqual( $.colorUtil.getRGB(), undefined, 'No arguments' ); - assert.strictEqual( $.colorUtil.getRGB( '' ), undefined, 'Empty string' ); - assert.deepEqual( $.colorUtil.getRGB( [0, 100, 255] ), [0, 100, 255], 'Parse array of rgb values' ); - assert.deepEqual( $.colorUtil.getRGB( 'rgb(0,100,255)' ), [0, 100, 255], 'Parse simple rgb string' ); - assert.deepEqual( $.colorUtil.getRGB( 'rgb(0, 100, 255)' ), [0, 100, 255], 'Parse simple rgb string with spaces' ); - assert.deepEqual( $.colorUtil.getRGB( 'rgb(0%,20%,40%)' ), [0, 51, 102], 'Parse rgb string with percentages' ); - assert.deepEqual( $.colorUtil.getRGB( 'rgb(0%, 20%, 40%)' ), [0, 51, 102], 'Parse rgb string with percentages and spaces' ); - assert.deepEqual( $.colorUtil.getRGB( '#f2ddee' ), [242, 221, 238], 'Hex string: 6 char lowercase' ); - assert.deepEqual( $.colorUtil.getRGB( '#f2DDEE' ), [242, 221, 238], 'Hex string: 6 char uppercase' ); - assert.deepEqual( $.colorUtil.getRGB( '#f2DdEe' ), [242, 221, 238], 'Hex string: 6 char mixed' ); - assert.deepEqual( $.colorUtil.getRGB( '#eee' ), [238, 238, 238], 'Hex string: 3 char lowercase' ); - assert.deepEqual( $.colorUtil.getRGB( '#EEE' ), [238, 238, 238], 'Hex string: 3 char uppercase' ); - assert.deepEqual( $.colorUtil.getRGB( '#eEe' ), [238, 238, 238], 'Hex string: 3 char mixed' ); - assert.deepEqual( $.colorUtil.getRGB( 'rgba(0, 0, 0, 0)' ), [255, 255, 255], 'Zero rgba for Safari 3; Transparent (whitespace)' ); - - // Perhaps this is a bug in colorUtil, but it is the current behaviour so, let's keep - // track of it, so we will know in case it would ever change. - assert.strictEqual( $.colorUtil.getRGB( 'rgba(0,0,0,0)' ), undefined, 'Zero rgba without whitespace' ); - - assert.deepEqual( $.colorUtil.getRGB( 'lightGreen' ), [144, 238, 144], 'Color names (lightGreen)' ); - assert.deepEqual( $.colorUtil.getRGB( 'transparent' ), [255, 255, 255], 'Color names (transparent)' ); - assert.strictEqual( $.colorUtil.getRGB( 'mediaWiki' ), undefined, 'Inexisting color name' ); -}); - -QUnit.test( 'rgbToHsl', 1, function ( assert ) { - var hsl = $.colorUtil.rgbToHsl( 144, 238, 144 ); - - // Cross-browser differences in decimals... - // Round to two decimals so they can be more reliably checked. - var dualDecimals = function(a,b){ - return Math.round(a*100)/100; - }; - // Re-create the rgbToHsl return array items, limited to two decimals. - var ret = [dualDecimals(hsl[0]), dualDecimals(hsl[1]), dualDecimals(hsl[2])]; - - assert.deepEqual( ret, [0.33, 0.73, 0.75], 'rgb(144, 238, 144): hsl(0.33, 0.73, 0.75)' ); -}); - -QUnit.test( 'hslToRgb', 1, function ( assert ) { - var rgb = $.colorUtil.hslToRgb( 0.3, 0.7, 0.8 ); - - // Cross-browser differences in decimals... - // Re-create the hslToRgb return array items, rounded to whole numbers. - var ret = [Math.round(rgb[0]), Math.round(rgb[1]), Math.round(rgb[2])]; - - assert.deepEqual( ret ,[183, 240, 168], 'hsl(0.3, 0.7, 0.8): rgb(183, 240, 168)' ); -}); - -QUnit.test( 'getColorBrightness', 2, function ( assert ) { - var a = $.colorUtil.getColorBrightness( 'red', +0.1 ); - assert.equal( a, 'rgb(255,50,50)', 'Start with named color "red", brighten 10%' ); - - var b = $.colorUtil.getColorBrightness( 'rgb(200,50,50)', -0.2 ); - assert.equal( b, 'rgb(118,29,29)', 'Start with rgb string "rgb(200,50,50)", darken 20%' ); -}); +( function ( $ ) { + QUnit.module( 'jquery.colorUtil', QUnit.newMwEnvironment() ); + + QUnit.test( 'getRGB', 18, function ( assert ) { + assert.strictEqual( $.colorUtil.getRGB(), undefined, 'No arguments' ); + assert.strictEqual( $.colorUtil.getRGB( '' ), undefined, 'Empty string' ); + assert.deepEqual( $.colorUtil.getRGB( [0, 100, 255] ), [0, 100, 255], 'Parse array of rgb values' ); + assert.deepEqual( $.colorUtil.getRGB( 'rgb(0,100,255)' ), [0, 100, 255], 'Parse simple rgb string' ); + assert.deepEqual( $.colorUtil.getRGB( 'rgb(0, 100, 255)' ), [0, 100, 255], 'Parse simple rgb string with spaces' ); + assert.deepEqual( $.colorUtil.getRGB( 'rgb(0%,20%,40%)' ), [0, 51, 102], 'Parse rgb string with percentages' ); + assert.deepEqual( $.colorUtil.getRGB( 'rgb(0%, 20%, 40%)' ), [0, 51, 102], 'Parse rgb string with percentages and spaces' ); + assert.deepEqual( $.colorUtil.getRGB( '#f2ddee' ), [242, 221, 238], 'Hex string: 6 char lowercase' ); + assert.deepEqual( $.colorUtil.getRGB( '#f2DDEE' ), [242, 221, 238], 'Hex string: 6 char uppercase' ); + assert.deepEqual( $.colorUtil.getRGB( '#f2DdEe' ), [242, 221, 238], 'Hex string: 6 char mixed' ); + assert.deepEqual( $.colorUtil.getRGB( '#eee' ), [238, 238, 238], 'Hex string: 3 char lowercase' ); + assert.deepEqual( $.colorUtil.getRGB( '#EEE' ), [238, 238, 238], 'Hex string: 3 char uppercase' ); + assert.deepEqual( $.colorUtil.getRGB( '#eEe' ), [238, 238, 238], 'Hex string: 3 char mixed' ); + assert.deepEqual( $.colorUtil.getRGB( 'rgba(0, 0, 0, 0)' ), [255, 255, 255], 'Zero rgba for Safari 3; Transparent (whitespace)' ); + + // Perhaps this is a bug in colorUtil, but it is the current behavior so, let's keep + // track of it, so we will know in case it would ever change. + assert.strictEqual( $.colorUtil.getRGB( 'rgba(0,0,0,0)' ), undefined, 'Zero rgba without whitespace' ); + + assert.deepEqual( $.colorUtil.getRGB( 'lightGreen' ), [144, 238, 144], 'Color names (lightGreen)' ); + assert.deepEqual( $.colorUtil.getRGB( 'transparent' ), [255, 255, 255], 'Color names (transparent)' ); + assert.strictEqual( $.colorUtil.getRGB( 'mediaWiki' ), undefined, 'Inexisting color name' ); + } ); + + QUnit.test( 'rgbToHsl', 1, function ( assert ) { + var hsl, ret; + + // Cross-browser differences in decimals... + // Round to two decimals so they can be more reliably checked. + function dualDecimals( a ) { + return Math.round( a * 100 ) / 100; + } + + // Re-create the rgbToHsl return array items, limited to two decimals. + hsl = $.colorUtil.rgbToHsl( 144, 238, 144 ); + ret = [ dualDecimals( hsl[0] ), dualDecimals( hsl[1] ), dualDecimals( hsl[2] ) ]; + + assert.deepEqual( ret, [0.33, 0.73, 0.75], 'rgb(144, 238, 144): hsl(0.33, 0.73, 0.75)' ); + } ); + + QUnit.test( 'hslToRgb', 1, function ( assert ) { + var rgb, ret; + rgb = $.colorUtil.hslToRgb( 0.3, 0.7, 0.8 ); + + // Re-create the hslToRgb return array items, rounded to whole numbers. + ret = [ Math.round( rgb[0] ), Math.round( rgb[1] ), Math.round( rgb[2] ) ]; + + assert.deepEqual( ret, [183, 240, 168], 'hsl(0.3, 0.7, 0.8): rgb(183, 240, 168)' ); + } ); + + QUnit.test( 'getColorBrightness', 2, function ( assert ) { + var a, b; + a = $.colorUtil.getColorBrightness( 'red', +0.1 ); + assert.equal( a, 'rgb(255,50,50)', 'Start with named color "red", brighten 10%' ); + + b = $.colorUtil.getColorBrightness( 'rgb(200,50,50)', -0.2 ); + assert.equal( b, 'rgb(118,29,29)', 'Start with rgb string "rgb(200,50,50)", darken 20%' ); + } ); +}( jQuery ) ); diff --git a/tests/qunit/suites/resources/jquery/jquery.delayedBind.test.js b/tests/qunit/suites/resources/jquery/jquery.delayedBind.test.js index a3079835..234b19cb 100644 --- a/tests/qunit/suites/resources/jquery/jquery.delayedBind.test.js +++ b/tests/qunit/suites/resources/jquery/jquery.delayedBind.test.js @@ -1,35 +1,37 @@ -QUnit.asyncTest('jquery.delayedBind with data option', 2, function ( assert ) { - var $fixture = $('<div>').appendTo('#qunit-fixture'), - data = { magic: "beeswax" }, - delay = 50; +( function ( $ ) { + QUnit.asyncTest( 'jquery.delayedBind with data option', 2, function ( assert ) { + var $fixture = $( '<div>' ).appendTo( '#qunit-fixture' ), + data = { + magic: 'beeswax' + }, + delay = 50; - $fixture.delayedBind(delay, 'testevent', data, function ( e ) { - QUnit.start(); // continue! - assert.ok( true, 'testevent fired'); - assert.ok( e.data === data, 'data is passed through delayedBind'); - }); + $fixture.delayedBind( delay, 'testevent', data, function ( e ) { + assert.ok( true, 'testevent fired' ); + assert.ok( e.data === data, 'data is passed through delayedBind' ); + QUnit.start(); + } ); - // We'll trigger it thrice, but it should only happen once. - $fixture.trigger( 'testevent', {} ); - $fixture.trigger( 'testevent', {} ); - $fixture.trigger( 'testevent', {} ); - $fixture.trigger( 'testevent', {} ); -}); + // We'll trigger it thrice, but it should only happen once. + $fixture.trigger( 'testevent', {} ); + $fixture.trigger( 'testevent', {} ); + $fixture.trigger( 'testevent', {} ); + $fixture.trigger( 'testevent', {} ); + } ); -QUnit.asyncTest('jquery.delayedBind without data option', 1, function ( assert ) { - var $fixture = $('<div>').appendTo('#qunit-fixture'), - data = { magic: "beeswax" }, - delay = 50; + QUnit.asyncTest( 'jquery.delayedBind without data option', 1, function ( assert ) { + var $fixture = $( '<div>' ).appendTo( '#qunit-fixture' ), + delay = 50; - $fixture.delayedBind(delay, 'testevent', function ( e ) { - QUnit.start(); // continue! - assert.ok(true, 'testevent fired'); - }); - - // We'll trigger it thrice, but it should only happen once. - $fixture.trigger( 'testevent', {} ); - $fixture.trigger( 'testevent', {} ); - $fixture.trigger( 'testevent', {} ); - $fixture.trigger( 'testevent', {} ); -}); + $fixture.delayedBind( delay, 'testevent', function () { + assert.ok( true, 'testevent fired' ); + QUnit.start(); + } ); + // We'll trigger it thrice, but it should only happen once. + $fixture.trigger( 'testevent', {} ); + $fixture.trigger( 'testevent', {} ); + $fixture.trigger( 'testevent', {} ); + $fixture.trigger( 'testevent', {} ); + } ); +}( jQuery ) ); diff --git a/tests/qunit/suites/resources/jquery/jquery.getAttrs.test.js b/tests/qunit/suites/resources/jquery/jquery.getAttrs.test.js index 6eef1abb..0b7e87ee 100644 --- a/tests/qunit/suites/resources/jquery/jquery.getAttrs.test.js +++ b/tests/qunit/suites/resources/jquery/jquery.getAttrs.test.js @@ -1,11 +1,13 @@ -QUnit.module( 'jquery.getAttrs', QUnit.newMwEnvironment() ); +( function ( $ ) { + QUnit.module( 'jquery.getAttrs', QUnit.newMwEnvironment() ); -QUnit.test( 'Check', 1, function ( assert ) { - var attrs = { - foo: 'bar', - 'class': 'lorem' - }, - $el = jQuery( '<div>', attrs ); + QUnit.test( 'Check', 1, function ( assert ) { + var attrs = { + foo: 'bar', + 'class': 'lorem' + }, + $el = $( '<div>' ).attr( attrs ); - assert.deepEqual( $el.getAttrs(), attrs, 'getAttrs() return object should match the attributes set, no more, no less' ); -} ); + assert.deepEqual( $el.getAttrs(), attrs, 'getAttrs() return object should match the attributes set, no more, no less' ); + } ); +}( jQuery ) ); diff --git a/tests/qunit/suites/resources/jquery/jquery.hidpi.test.js b/tests/qunit/suites/resources/jquery/jquery.hidpi.test.js new file mode 100644 index 00000000..906369ee --- /dev/null +++ b/tests/qunit/suites/resources/jquery/jquery.hidpi.test.js @@ -0,0 +1,22 @@ +( function ( $ ) { + QUnit.module( 'jquery.hidpi', QUnit.newMwEnvironment() ); + + QUnit.test( 'devicePixelRatio', 1, function ( assert ) { + var devicePixelRatio = $.devicePixelRatio(); + assert.equal( typeof devicePixelRatio, 'number', '$.devicePixelRatio() returns a number' ); + } ); + + QUnit.test( 'matchSrcSet', 6, function ( assert ) { + var srcset = 'onefive.png 1.5x, two.png 2x'; + + // Nice exact matches + assert.equal( $.matchSrcSet( 1, srcset ), null, '1.0 gives no match' ); + assert.equal( $.matchSrcSet( 1.5, srcset ), 'onefive.png', '1.5 gives match' ); + assert.equal( $.matchSrcSet( 2, srcset ), 'two.png', '2 gives match' ); + + // Non-exact matches; should return the next-biggest specified + assert.equal( $.matchSrcSet( 1.25, srcset ), null, '1.25 gives no match' ); + assert.equal( $.matchSrcSet( 1.75, srcset ), 'onefive.png', '1.75 gives match to 1.5' ); + assert.equal( $.matchSrcSet( 2.25, srcset ), 'two.png', '2.25 gives match to 2' ); + } ); +}( jQuery ) ); diff --git a/tests/qunit/suites/resources/jquery/jquery.highlightText.test.js b/tests/qunit/suites/resources/jquery/jquery.highlightText.test.js index a94dca31..e1fb96dc 100644 --- a/tests/qunit/suites/resources/jquery/jquery.highlightText.test.js +++ b/tests/qunit/suites/resources/jquery/jquery.highlightText.test.js @@ -1,232 +1,235 @@ -QUnit.module( 'jquery.highlightText', QUnit.newMwEnvironment() ); +( function ( $ ) { + QUnit.module( 'jquery.highlightText', QUnit.newMwEnvironment() ); -QUnit.test( 'Check', function ( assert ) { - var $fixture, cases = [ - { - desc: 'Test 001', - text: 'Blue Öyster Cult', - highlight: 'Blue', - expected: '<span class="highlight">Blue</span> Öyster Cult' - }, - { - desc: 'Test 002', - text: 'Blue Öyster Cult', - highlight: 'Blue ', - expected: '<span class="highlight">Blue</span> Öyster Cult' - }, - { - desc: 'Test 003', - text: 'Blue Öyster Cult', - highlight: 'Blue Ö', - expected: '<span class="highlight">Blue</span> <span class="highlight">Ö</span>yster Cult' - }, - { - desc: 'Test 004', - text: 'Blue Öyster Cult', - highlight: 'Blue Öy', - expected: '<span class="highlight">Blue</span> <span class="highlight">Öy</span>ster Cult' - }, - { - desc: 'Test 005', - text: 'Blue Öyster Cult', - highlight: ' Blue', - expected: '<span class="highlight">Blue</span> Öyster Cult' - }, - { - desc: 'Test 006', - text: 'Blue Öyster Cult', - highlight: ' Blue ', - expected: '<span class="highlight">Blue</span> Öyster Cult' - }, - { - desc: 'Test 007', - text: 'Blue Öyster Cult', - highlight: ' Blue Ö', - expected: '<span class="highlight">Blue</span> <span class="highlight">Ö</span>yster Cult' - }, - { - desc: 'Test 008', - text: 'Blue Öyster Cult', - highlight: ' Blue Öy', - expected: '<span class="highlight">Blue</span> <span class="highlight">Öy</span>ster Cult' - }, - { - desc: 'Test 009: Highlighter broken on starting Umlaut?', - text: 'Österreich', - highlight: 'Österreich', - expected: '<span class="highlight">Österreich</span>' - }, - { - desc: 'Test 010: Highlighter broken on starting Umlaut?', - text: 'Österreich', - highlight: 'Ö', - expected: '<span class="highlight">Ö</span>sterreich' - }, - { - desc: 'Test 011: Highlighter broken on starting Umlaut?', - text: 'Österreich', - highlight: 'Öst', - expected: '<span class="highlight">Öst</span>erreich' - }, - { - desc: 'Test 012: Highlighter broken on starting Umlaut?', - text: 'Österreich', - highlight: 'Oe', - expected: 'Österreich' - }, - { - desc: 'Test 013: Highlighter broken on punctuation mark?', - text: 'So good. To be there', - highlight: 'good', - expected: 'So <span class="highlight">good</span>. To be there' - }, - { - desc: 'Test 014: Highlighter broken on space?', - text: 'So good. To be there', - highlight: 'be', - expected: 'So good. To <span class="highlight">be</span> there' - }, - { - desc: 'Test 015: Highlighter broken on space?', - text: 'So good. To be there', - highlight: ' be', - expected: 'So good. To <span class="highlight">be</span> there' - }, - { - desc: 'Test 016: Highlighter broken on space?', - text: 'So good. To be there', - highlight: 'be ', - expected: 'So good. To <span class="highlight">be</span> there' - }, - { - desc: 'Test 017: Highlighter broken on space?', - text: 'So good. To be there', - highlight: ' be ', - expected: 'So good. To <span class="highlight">be</span> there' - }, - { - desc: 'Test 018: en de Highlighter broken on special character at the end?', - text: 'So good. xbß', - highlight: 'xbß', - expected: 'So good. <span class="highlight">xbß</span>' - }, - { - desc: 'Test 019: en de Highlighter broken on special character at the end?', - text: 'So good. xbß.', - highlight: 'xbß.', - expected: 'So good. <span class="highlight">xbß.</span>' - }, - { - desc: 'Test 020: RTL he Hebrew', - text: 'חסיד אומות העולם', - highlight: 'חסיד אומות העולם', - expected: '<span class="highlight">חסיד</span> <span class="highlight">אומות</span> <span class="highlight">העולם</span>' - }, - { - desc: 'Test 021: RTL he Hebrew', - text: 'חסיד אומות העולם', - highlight: 'חסי', - expected: '<span class="highlight">חסי</span>ד אומות העולם' - }, - { - desc: 'Test 022: ja Japanese', - text: '諸国民の中の正義の人', - highlight: '諸国民の中の正義の人', - expected: '<span class="highlight">諸国民の中の正義の人</span>' - }, - { - desc: 'Test 023: ja Japanese', - text: '諸国民の中の正義の人', - highlight: '諸国', - expected: '<span class="highlight">諸国</span>民の中の正義の人' - }, - { - desc: 'Test 024: fr French text and « french quotes » (guillemets)', - text: "« L'oiseau est sur l’île »", - highlight: "« L'oiseau est sur l’île »", - expected: '<span class="highlight">«</span> <span class="highlight">L\'oiseau</span> <span class="highlight">est</span> <span class="highlight">sur</span> <span class="highlight">l’île</span> <span class="highlight">»</span>' - }, - { - desc: 'Test 025: fr French text and « french quotes » (guillemets)', - text: "« L'oiseau est sur l’île »", - highlight: "« L'oise", - expected: '<span class="highlight">«</span> <span class="highlight">L\'oise</span>au est sur l’île »' - }, - { - desc: 'Test 025a: fr French text and « french quotes » (guillemets) - does it match the single strings "«" and "L" separately?', - text: "« L'oiseau est sur l’île »", - highlight: "« L", - expected: '<span class="highlight">«</span> <span class="highlight">L</span>\'oiseau est sur <span class="highlight">l</span>’île »' - }, - { - desc: 'Test 026: ru Russian', - text: 'Праведники мира', - highlight: 'Праведники мира', - expected: '<span class="highlight">Праведники</span> <span class="highlight">мира</span>' - }, - { - desc: 'Test 027: ru Russian', - text: 'Праведники мира', - highlight: 'Праве', - expected: '<span class="highlight">Праве</span>дники мира' - }, - { - desc: 'Test 028 ka Georgian', - text: 'მთავარი გვერდი', - highlight: 'მთავარი გვერდი', - expected: '<span class="highlight">მთავარი</span> <span class="highlight">გვერდი</span>' - }, - { - desc: 'Test 029 ka Georgian', - text: 'მთავარი გვერდი', - highlight: 'მთა', - expected: '<span class="highlight">მთა</span>ვარი გვერდი' - }, - { - desc: 'Test 030 hy Armenian', - text: 'Նոնա Գափրինդաշվիլի', - highlight: 'Նոնա Գափրինդաշվիլի', - expected: '<span class="highlight">Նոնա</span> <span class="highlight">Գափրինդաշվիլի</span>' - }, - { - desc: 'Test 031 hy Armenian', - text: 'Նոնա Գափրինդաշվիլի', - highlight: 'Նոն', - expected: '<span class="highlight">Նոն</span>ա Գափրինդաշվիլի' - }, - { - desc: 'Test 032: th Thai', - text: 'พอล แอร์ดิช', - highlight: 'พอล แอร์ดิช', - expected: '<span class="highlight">พอล</span> <span class="highlight">แอร์ดิช</span>' - }, - { - desc: 'Test 033: th Thai', - text: 'พอล แอร์ดิช', - highlight: 'พอ', - expected: '<span class="highlight">พอ</span>ล แอร์ดิช' - }, - { - desc: 'Test 034: RTL ar Arabic', - text: 'بول إيردوس', - highlight: 'بول إيردوس', - expected: '<span class="highlight">بول</span> <span class="highlight">إيردوس</span>' - }, - { - desc: 'Test 035: RTL ar Arabic', - text: 'بول إيردوس', - highlight: 'بو', - expected: '<span class="highlight">بو</span>ل إيردوس' - } - ]; - QUnit.expect( cases.length ); + QUnit.test( 'Check', function ( assert ) { + var $fixture, cases = [ + { + desc: 'Test 001', + text: 'Blue Öyster Cult', + highlight: 'Blue', + expected: '<span class="highlight">Blue</span> Öyster Cult' + }, + { + desc: 'Test 002', + text: 'Blue Öyster Cult', + highlight: 'Blue ', + expected: '<span class="highlight">Blue</span> Öyster Cult' + }, + { + desc: 'Test 003', + text: 'Blue Öyster Cult', + highlight: 'Blue Ö', + expected: '<span class="highlight">Blue</span> <span class="highlight">Ö</span>yster Cult' + }, + { + desc: 'Test 004', + text: 'Blue Öyster Cult', + highlight: 'Blue Öy', + expected: '<span class="highlight">Blue</span> <span class="highlight">Öy</span>ster Cult' + }, + { + desc: 'Test 005', + text: 'Blue Öyster Cult', + highlight: ' Blue', + expected: '<span class="highlight">Blue</span> Öyster Cult' + }, + { + desc: 'Test 006', + text: 'Blue Öyster Cult', + highlight: ' Blue ', + expected: '<span class="highlight">Blue</span> Öyster Cult' + }, + { + desc: 'Test 007', + text: 'Blue Öyster Cult', + highlight: ' Blue Ö', + expected: '<span class="highlight">Blue</span> <span class="highlight">Ö</span>yster Cult' + }, + { + desc: 'Test 008', + text: 'Blue Öyster Cult', + highlight: ' Blue Öy', + expected: '<span class="highlight">Blue</span> <span class="highlight">Öy</span>ster Cult' + }, + { + desc: 'Test 009: Highlighter broken on starting Umlaut?', + text: 'Österreich', + highlight: 'Österreich', + expected: '<span class="highlight">Österreich</span>' + }, + { + desc: 'Test 010: Highlighter broken on starting Umlaut?', + text: 'Österreich', + highlight: 'Ö', + expected: '<span class="highlight">Ö</span>sterreich' + }, + { + desc: 'Test 011: Highlighter broken on starting Umlaut?', + text: 'Österreich', + highlight: 'Öst', + expected: '<span class="highlight">Öst</span>erreich' + }, + { + desc: 'Test 012: Highlighter broken on starting Umlaut?', + text: 'Österreich', + highlight: 'Oe', + expected: 'Österreich' + }, + { + desc: 'Test 013: Highlighter broken on punctuation mark?', + text: 'So good. To be there', + highlight: 'good', + expected: 'So <span class="highlight">good</span>. To be there' + }, + { + desc: 'Test 014: Highlighter broken on space?', + text: 'So good. To be there', + highlight: 'be', + expected: 'So good. To <span class="highlight">be</span> there' + }, + { + desc: 'Test 015: Highlighter broken on space?', + text: 'So good. To be there', + highlight: ' be', + expected: 'So good. To <span class="highlight">be</span> there' + }, + { + desc: 'Test 016: Highlighter broken on space?', + text: 'So good. To be there', + highlight: 'be ', + expected: 'So good. To <span class="highlight">be</span> there' + }, + { + desc: 'Test 017: Highlighter broken on space?', + text: 'So good. To be there', + highlight: ' be ', + expected: 'So good. To <span class="highlight">be</span> there' + }, + { + desc: 'Test 018: en de Highlighter broken on special character at the end?', + text: 'So good. xbß', + highlight: 'xbß', + expected: 'So good. <span class="highlight">xbß</span>' + }, + { + desc: 'Test 019: en de Highlighter broken on special character at the end?', + text: 'So good. xbß.', + highlight: 'xbß.', + expected: 'So good. <span class="highlight">xbß.</span>' + }, + { + desc: 'Test 020: RTL he Hebrew', + text: 'חסיד אומות העולם', + highlight: 'חסיד אומות העולם', + expected: '<span class="highlight">חסיד</span> <span class="highlight">אומות</span> <span class="highlight">העולם</span>' + }, + { + desc: 'Test 021: RTL he Hebrew', + text: 'חסיד אומות העולם', + highlight: 'חסי', + expected: '<span class="highlight">חסי</span>ד אומות העולם' + }, + { + desc: 'Test 022: ja Japanese', + text: '諸国民の中の正義の人', + highlight: '諸国民の中の正義の人', + expected: '<span class="highlight">諸国民の中の正義の人</span>' + }, + { + desc: 'Test 023: ja Japanese', + text: '諸国民の中の正義の人', + highlight: '諸国', + expected: '<span class="highlight">諸国</span>民の中の正義の人' + }, + { + desc: 'Test 024: fr French text and « french quotes » (guillemets)', + text: '« L\'oiseau est sur l’île »', + highlight: '« L\'oiseau est sur l’île »', + expected: '<span class="highlight">«</span> <span class="highlight">L\'oiseau</span> <span class="highlight">est</span> <span class="highlight">sur</span> <span class="highlight">l’île</span> <span class="highlight">»</span>' + }, + { + desc: 'Test 025: fr French text and « french quotes » (guillemets)', + text: '« L\'oiseau est sur l’île »', + highlight: '« L\'oise', + expected: '<span class="highlight">«</span> <span class="highlight">L\'oise</span>au est sur l’île »' + }, + { + desc: 'Test 025a: fr French text and « french quotes » (guillemets) - does it match the single strings "«" and "L" separately?', + text: '« L\'oiseau est sur l’île »', + highlight: '« L', + expected: '<span class="highlight">«</span> <span class="highlight">L</span>\'oiseau est sur <span class="highlight">l</span>’île »' + }, + { + desc: 'Test 026: ru Russian', + text: 'Праведники мира', + highlight: 'Праведники мира', + expected: '<span class="highlight">Праведники</span> <span class="highlight">мира</span>' + }, + { + desc: 'Test 027: ru Russian', + text: 'Праведники мира', + highlight: 'Праве', + expected: '<span class="highlight">Праве</span>дники мира' + }, + { + desc: 'Test 028 ka Georgian', + text: 'მთავარი გვერდი', + highlight: 'მთავარი გვერდი', + expected: '<span class="highlight">მთავარი</span> <span class="highlight">გვერდი</span>' + }, + { + desc: 'Test 029 ka Georgian', + text: 'მთავარი გვერდი', + highlight: 'მთა', + expected: '<span class="highlight">მთა</span>ვარი გვერდი' + }, + { + desc: 'Test 030 hy Armenian', + text: 'Նոնա Գափրինդաշվիլի', + highlight: 'Նոնա Գափրինդաշվիլի', + expected: '<span class="highlight">Նոնա</span> <span class="highlight">Գափրինդաշվիլի</span>' + }, + { + desc: 'Test 031 hy Armenian', + text: 'Նոնա Գափրինդաշվիլի', + highlight: 'Նոն', + expected: '<span class="highlight">Նոն</span>ա Գափրինդաշվիլի' + }, + { + desc: 'Test 032: th Thai', + text: 'พอล แอร์ดิช', + highlight: 'พอล แอร์ดิช', + expected: '<span class="highlight">พอล</span> <span class="highlight">แอร์ดิช</span>' + }, + { + desc: 'Test 033: th Thai', + text: 'พอล แอร์ดิช', + highlight: 'พอ', + expected: '<span class="highlight">พอ</span>ล แอร์ดิช' + }, + { + desc: 'Test 034: RTL ar Arabic', + text: 'بول إيردوس', + highlight: 'بول إيردوس', + expected: '<span class="highlight">بول</span> <span class="highlight">إيردوس</span>' + }, + { + desc: 'Test 035: RTL ar Arabic', + text: 'بول إيردوس', + highlight: 'بو', + expected: '<span class="highlight">بو</span>ل إيردوس' + } + ]; + QUnit.expect( cases.length ); - $.each( cases, function ( i, item ) { - $fixture = $( '<p>' ).text( item.text ).highlightText( item.highlight ); - assert.equal( - $fixture.html(), - $( '<p>' ).html( item.expected ).html(), // re-parse to normalize! - item.desc || undefined - ); + $.each( cases, function ( i, item ) { + $fixture = $( '<p>' ).text( item.text ).highlightText( item.highlight ); + assert.equal( + $fixture.html(), + // Re-parse to normalize + $( '<p>' ).html( item.expected ).html(), + item.desc || undefined + ); + } ); } ); -} ); +}( jQuery ) ); diff --git a/tests/qunit/suites/resources/jquery/jquery.localize.test.js b/tests/qunit/suites/resources/jquery/jquery.localize.test.js index c8e1d9f9..3ef27903 100644 --- a/tests/qunit/suites/resources/jquery/jquery.localize.test.js +++ b/tests/qunit/suites/resources/jquery/jquery.localize.test.js @@ -1,133 +1,135 @@ -QUnit.module( 'jquery.localize', QUnit.newMwEnvironment() ); +( function ( $, mw ) { + QUnit.module( 'jquery.localize', QUnit.newMwEnvironment() ); -QUnit.test( 'Handle basic replacements', 4, function ( assert ) { - var html, $lc; - mw.messages.set( 'basic', 'Basic stuff' ); + QUnit.test( 'Handle basic replacements', 4, function ( assert ) { + var html, $lc; + mw.messages.set( 'basic', 'Basic stuff' ); - // Tag: html:msg - html = '<div><span><html:msg key="basic" /></span></div>'; - $lc = $( html ).localize().find( 'span' ); + // Tag: html:msg + html = '<div><span><html:msg key="basic" /></span></div>'; + $lc = $( html ).localize().find( 'span' ); - assert.strictEqual( $lc.text(), 'Basic stuff', 'Tag: html:msg' ); + assert.strictEqual( $lc.text(), 'Basic stuff', 'Tag: html:msg' ); - // Attribute: title-msg - html = '<div><span title-msg="basic"></span></div>'; - $lc = $( html ).localize().find( 'span' ); + // Attribute: title-msg + html = '<div><span title-msg="basic"></span></div>'; + $lc = $( html ).localize().find( 'span' ); - assert.strictEqual( $lc.attr( 'title' ), 'Basic stuff', 'Attribute: title-msg' ); + assert.strictEqual( $lc.attr( 'title' ), 'Basic stuff', 'Attribute: title-msg' ); - // Attribute: alt-msg - html = '<div><span alt-msg="basic"></span></div>'; - $lc = $( html ).localize().find( 'span' ); + // Attribute: alt-msg + html = '<div><span alt-msg="basic"></span></div>'; + $lc = $( html ).localize().find( 'span' ); - assert.strictEqual( $lc.attr( 'alt' ), 'Basic stuff', 'Attribute: alt-msg' ); + assert.strictEqual( $lc.attr( 'alt' ), 'Basic stuff', 'Attribute: alt-msg' ); - // Attribute: placeholder-msg - html = '<div><input placeholder-msg="basic" /></div>'; - $lc = $( html ).localize().find( 'input' ); + // Attribute: placeholder-msg + html = '<div><input placeholder-msg="basic" /></div>'; + $lc = $( html ).localize().find( 'input' ); - assert.strictEqual( $lc.attr( 'placeholder' ), 'Basic stuff', 'Attribute: placeholder-msg' ); -} ); + assert.strictEqual( $lc.attr( 'placeholder' ), 'Basic stuff', 'Attribute: placeholder-msg' ); + } ); + + QUnit.test( 'Proper escaping', 2, function ( assert ) { + var html, $lc; + mw.messages.set( 'properfoo', '<proper esc="test">' ); + + // This is handled by jQuery inside $.fn.localize, just a simple sanity checked + // making sure it is actually using text() and attr() (or something with the same effect) -QUnit.test( 'Proper escaping', 2, function ( assert ) { - var html, $lc; - mw.messages.set( 'properfoo', '<proper esc="test">' ); + // Text escaping + html = '<div><span><html:msg key="properfoo" /></span></div>'; + $lc = $( html ).localize().find( 'span' ); - // This is handled by jQuery inside $.fn.localize, just a simple sanity checked - // making sure it is actually using text() and attr() (or something with the same effect) + assert.strictEqual( $lc.text(), mw.msg( 'properfoo' ), 'Content is inserted as text, not as html.' ); - // Text escaping - html = '<div><span><html:msg key="properfoo"></span></div>'; - $lc = $( html ).localize().find( 'span' ); + // Attribute escaping + html = '<div><span title-msg="properfoo"></span></div>'; + $lc = $( html ).localize().find( 'span' ); - assert.strictEqual( $lc.text(), mw.msg( 'properfoo' ), 'Content is inserted as text, not as html.' ); + assert.strictEqual( $lc.attr( 'title' ), mw.msg( 'properfoo' ), 'Attributes are not inserted raw.' ); + } ); - // Attribute escaping - html = '<div><span title-msg="properfoo"></span></div>'; - $lc = $( html ).localize().find( 'span' ); + QUnit.test( 'Options', 7, function ( assert ) { + mw.messages.set( { + 'foo-lorem': 'Lorem', + 'foo-ipsum': 'Ipsum', + 'foo-bar-title': 'Read more about bars', + 'foo-bar-label': 'The Bars', + 'foo-bazz-title': 'Read more about bazz at $1 (last modified: $2)', + 'foo-bazz-label': 'The Bazz ($1)', + 'foo-welcome': 'Welcome to $1! (last visit: $2)' + } ); + var html, $lc, x, sitename = 'Wikipedia'; + + // Message key prefix + html = '<div><span title-msg="lorem"><html:msg key="ipsum" /></span></div>'; + $lc = $( html ).localize( { + prefix: 'foo-' + } ).find( 'span' ); + + assert.strictEqual( $lc.attr( 'title' ), 'Lorem', 'Message key prefix - attr' ); + assert.strictEqual( $lc.text(), 'Ipsum', 'Message key prefix - text' ); + + // Variable keys mapping + x = 'bar'; + html = '<div><span title-msg="title"><html:msg key="label" /></span></div>'; + $lc = $( html ).localize( { + keys: { + 'title': 'foo-' + x + '-title', + 'label': 'foo-' + x + '-label' + } + } ).find( 'span' ); + + assert.strictEqual( $lc.attr( 'title' ), 'Read more about bars', 'Variable keys mapping - attr' ); + assert.strictEqual( $lc.text(), 'The Bars', 'Variable keys mapping - text' ); + + // Passing parameteters to mw.msg + html = '<div><span><html:msg key="foo-welcome" /></span></div>'; + $lc = $( html ).localize( { + params: { + 'foo-welcome': [sitename, 'yesterday'] + } + } ).find( 'span' ); + + assert.strictEqual( $lc.text(), 'Welcome to Wikipedia! (last visit: yesterday)', 'Passing parameteters to mw.msg' ); + + // Combination of options prefix, params and keys + x = 'bazz'; + html = '<div><span title-msg="title"><html:msg key="label" /></span></div>'; + $lc = $( html ).localize( { + prefix: 'foo-', + keys: { + 'title': x + '-title', + 'label': x + '-label' + }, + params: { + 'title': [sitename, '3 minutes ago'], + 'label': [sitename, '3 minutes ago'] + + } + } ).find( 'span' ); + + assert.strictEqual( $lc.text(), 'The Bazz (Wikipedia)', 'Combination of options prefix, params and keys - text' ); + assert.strictEqual( $lc.attr( 'title' ), 'Read more about bazz at Wikipedia (last modified: 3 minutes ago)', 'Combination of options prefix, params and keys - attr' ); + } ); - assert.strictEqual( $lc.attr( 'title' ), mw.msg( 'properfoo' ), 'Attributes are not inserted raw.' ); -} ); + QUnit.test( 'Handle data text', 2, function ( assert ) { + var html, $lc; + mw.messages.set( 'option-one', 'Item 1' ); + mw.messages.set( 'option-two', 'Item 2' ); + html = '<select><option data-msg-text="option-one"></option><option data-msg-text="option-two"></option></select>'; + $lc = $( html ).localize().find( 'option' ); + assert.strictEqual( $lc.eq( 0 ).text(), mw.msg( 'option-one' ), 'data-msg-text becomes text of options' ); + assert.strictEqual( $lc.eq( 1 ).text(), mw.msg( 'option-two' ), 'data-msg-text becomes text of options' ); + } ); -QUnit.test( 'Options', 7, function ( assert ) { - mw.messages.set( { - 'foo-lorem': 'Lorem', - 'foo-ipsum': 'Ipsum', - 'foo-bar-title': 'Read more about bars', - 'foo-bar-label': 'The Bars', - 'foo-bazz-title': 'Read more about bazz at $1 (last modified: $2)', - 'foo-bazz-label': 'The Bazz ($1)', - 'foo-welcome': 'Welcome to $1! (last visit: $2)' + QUnit.test( 'Handle data html', 2, function ( assert ) { + var html, $lc; + mw.messages.set( 'html', 'behold... there is a <a>link</a> here!!' ); + html = '<div><div data-msg-html="html"></div></div>'; + $lc = $( html ).localize().find( 'a' ); + assert.strictEqual( $lc.length, 1, 'link is created' ); + assert.strictEqual( $lc.text(), 'link', 'the link text got added' ); } ); - var html, $lc, attrs, x, sitename = 'Wikipedia'; - - // Message key prefix - html = '<div><span title-msg="lorem"><html:msg key="ipsum"></span></div>'; - $lc = $( html ).localize( { - prefix: 'foo-' - } ).find( 'span' ); - - assert.strictEqual( $lc.attr( 'title' ), 'Lorem', 'Message key prefix - attr' ); - assert.strictEqual( $lc.text(), 'Ipsum', 'Message key prefix - text' ); - - // Variable keys mapping - x = 'bar'; - html = '<div><span title-msg="title"><html:msg key="label"></span></div>'; - $lc = $( html ).localize( { - keys: { - 'title': 'foo-' + x + '-title', - 'label': 'foo-' + x + '-label' - } - } ).find( 'span' ); - - assert.strictEqual( $lc.attr( 'title' ), 'Read more about bars', 'Variable keys mapping - attr' ); - assert.strictEqual( $lc.text(), 'The Bars', 'Variable keys mapping - text' ); - - // Passing parameteters to mw.msg - html = '<div><span><html:msg key="foo-welcome"></span></div>'; - $lc = $( html ).localize( { - params: { - 'foo-welcome': [sitename, 'yesterday'] - } - } ).find( 'span' ); - - assert.strictEqual( $lc.text(), 'Welcome to Wikipedia! (last visit: yesterday)', 'Passing parameteters to mw.msg' ); - - // Combination of options prefix, params and keys - x = 'bazz'; - html = '<div><span title-msg="title"><html:msg key="label"></span></div>'; - $lc = $( html ).localize( { - prefix: 'foo-', - keys: { - 'title': x + '-title', - 'label': x + '-label' - }, - params: { - 'title': [sitename, '3 minutes ago'], - 'label': [sitename, '3 minutes ago'] - - } - } ).find( 'span' ); - - assert.strictEqual( $lc.text(), 'The Bazz (Wikipedia)', 'Combination of options prefix, params and keys - text' ); - assert.strictEqual( $lc.attr( 'title' ), 'Read more about bazz at Wikipedia (last modified: 3 minutes ago)', 'Combination of options prefix, params and keys - attr' ); -} ); - -QUnit.test( 'Handle data text', 2, function ( assert ) { - var html, $lc; - mw.messages.set( 'option-one', 'Item 1' ); - mw.messages.set( 'option-two', 'Item 2' ); - html = '<select><option data-msg-text="option-one"></option><option data-msg-text="option-two"></option></select>'; - $lc = $( html ).localize().find( 'option' ); - assert.strictEqual( $lc.eq( 0 ).text(), mw.msg( 'option-one' ), 'data-msg-text becomes text of options' ); - assert.strictEqual( $lc.eq( 1 ).text(), mw.msg( 'option-two' ), 'data-msg-text becomes text of options' ); -} ); - -QUnit.test( 'Handle data html', 2, function ( assert ) { - var html, $lc; - mw.messages.set( 'html', 'behold... there is a <a>link</a> here!!' ); - html = '<div><div data-msg-html="html"></div></div>'; - $lc = $( html ).localize().find( 'a' ); - assert.strictEqual( $lc.length, 1, 'link is created' ); - assert.strictEqual( $lc.text(), 'link', 'the link text got added' ); -} ); +}( jQuery, mediaWiki ) ); diff --git a/tests/qunit/suites/resources/jquery/jquery.makeCollapsible.test.js b/tests/qunit/suites/resources/jquery/jquery.makeCollapsible.test.js new file mode 100644 index 00000000..6da56ed2 --- /dev/null +++ b/tests/qunit/suites/resources/jquery/jquery.makeCollapsible.test.js @@ -0,0 +1,287 @@ +( function ( mw, $ ) { + var loremIpsum = 'Lorem ipsum dolor sit amet, consectetuer adipiscing elit.'; + + QUnit.module( 'jquery.makeCollapsible', QUnit.newMwEnvironment() ); + + function prepareCollapsible( html, options ) { + return $( $.parseHTML( html ) ) + .appendTo( '#qunit-fixture' ) + // options might be undefined here - this is okay + .makeCollapsible( options ); + } + + // This test is first because if it fails, then almost all of the latter tests are meaningless. + QUnit.asyncTest( 'testing hooks/triggers', 4, function ( assert ) { + var $collapsible, $content, $toggle; + $collapsible = prepareCollapsible( + '<div class="mw-collapsible">' + loremIpsum + '</div>' + ); + $content = $collapsible.find( '.mw-collapsible-content' ); + $toggle = $collapsible.find( '.mw-collapsible-toggle' ); + + // In one full collapse-expand cycle, each event will be fired once + + // On collapse... + $collapsible.on( 'beforeCollapse.mw-collapsible', function () { + assert.assertTrue( $content.is( ':visible' ), 'first beforeCollapseExpand: content is visible' ); + } ); + $collapsible.on( 'afterCollapse.mw-collapsible', function () { + assert.assertTrue( $content.is( ':hidden' ), 'first afterCollapseExpand: content is hidden' ); + + // On expand... + $collapsible.on( 'beforeExpand.mw-collapsible', function () { + assert.assertTrue( $content.is( ':hidden' ), 'second beforeCollapseExpand: content is hidden' ); + } ); + $collapsible.on( 'afterExpand.mw-collapsible', function () { + assert.assertTrue( $content.is( ':visible' ), 'second afterCollapseExpand: content is visible' ); + + QUnit.start(); + } ); + + // ...expanding happens here + $toggle.trigger( 'click' ); + } ); + + // ...collapsing happens here + $toggle.trigger( 'click' ); + } ); + + QUnit.asyncTest( 'basic operation (<div>)', 5, function ( assert ) { + var $collapsible, $content, $toggle; + $collapsible = prepareCollapsible( + '<div class="mw-collapsible">' + loremIpsum + '</div>' + ); + $content = $collapsible.find( '.mw-collapsible-content' ); + $toggle = $collapsible.find( '.mw-collapsible-toggle' ); + + assert.equal( $content.length, 1, 'content is present' ); + assert.equal( $content.find( $toggle ).length, 0, 'toggle is not a descendant of content' ); + + assert.assertTrue( $content.is( ':visible' ), 'content is visible' ); + + $collapsible.on( 'afterCollapse.mw-collapsible', function () { + assert.assertTrue( $content.is( ':hidden' ), 'after collapsing: content is hidden' ); + + $collapsible.on( 'afterExpand.mw-collapsible', function () { + assert.assertTrue( $content.is( ':visible' ), 'after expanding: content is visible' ); + QUnit.start(); + } ); + + $toggle.trigger( 'click' ); + } ); + + $toggle.trigger( 'click' ); + } ); + + QUnit.asyncTest( 'basic operation (<table>)', 7, function ( assert ) { + var $collapsible, $headerRow, $contentRow, $toggle; + $collapsible = prepareCollapsible( + '<table class="mw-collapsible">' + + '<tr><td>' + loremIpsum + '</td><td>' + loremIpsum + '</td></tr>' + + '<tr><td>' + loremIpsum + '</td><td>' + loremIpsum + '</td></tr>' + + '<tr><td>' + loremIpsum + '</td><td>' + loremIpsum + '</td></tr>' + + '</table>' + ); + $headerRow = $collapsible.find( 'tr:first' ); + $contentRow = $collapsible.find( 'tr:last' ); + + $toggle = $headerRow.find( 'td:last .mw-collapsible-toggle' ); + assert.equal( $toggle.length, 1, 'toggle is added to last cell of first row' ); + + assert.assertTrue( $headerRow.is( ':visible' ), 'headerRow is visible' ); + assert.assertTrue( $contentRow.is( ':visible' ), 'contentRow is visible' ); + + $collapsible.on( 'afterCollapse.mw-collapsible', function () { + assert.assertTrue( $headerRow.is( ':visible' ), 'after collapsing: headerRow is still visible' ); + assert.assertTrue( $contentRow.is( ':hidden' ), 'after collapsing: contentRow is hidden' ); + + $collapsible.on( 'afterExpand.mw-collapsible', function () { + assert.assertTrue( $headerRow.is( ':visible' ), 'after expanding: headerRow is still visible' ); + assert.assertTrue( $contentRow.is( ':visible' ), 'after expanding: contentRow is visible' ); + QUnit.start(); + } ); + + $toggle.trigger( 'click' ); + } ); + + $toggle.trigger( 'click' ); + } ); + + function listTest( listType, assert ) { + var $collapsible, $toggleItem, $contentItem, $toggle; + $collapsible = prepareCollapsible( + '<' + listType + ' class="mw-collapsible">' + + '<li>' + loremIpsum + '</li>' + + '<li>' + loremIpsum + '</li>' + + '</' + listType + '>' + ); + $toggleItem = $collapsible.find( 'li.mw-collapsible-toggle-li:first-child' ); + $contentItem = $collapsible.find( 'li:last' ); + + $toggle = $toggleItem.find( '.mw-collapsible-toggle' ); + assert.equal( $toggle.length, 1, 'toggle is present, added inside new zeroth list item' ); + + assert.assertTrue( $toggleItem.is( ':visible' ), 'toggleItem is visible' ); + assert.assertTrue( $contentItem.is( ':visible' ), 'contentItem is visible' ); + + $collapsible.on( 'afterCollapse.mw-collapsible', function () { + assert.assertTrue( $toggleItem.is( ':visible' ), 'after collapsing: toggleItem is still visible' ); + assert.assertTrue( $contentItem.is( ':hidden' ), 'after collapsing: contentItem is hidden' ); + + $collapsible.on( 'afterExpand.mw-collapsible', function () { + assert.assertTrue( $toggleItem.is( ':visible' ), 'after expanding: toggleItem is still visible' ); + assert.assertTrue( $contentItem.is( ':visible' ), 'after expanding: contentItem is visible' ); + QUnit.start(); + } ); + + $toggle.trigger( 'click' ); + } ); + + $toggle.trigger( 'click' ); + } + + QUnit.asyncTest( 'basic operation (<ul>)', 7, function ( assert ) { + listTest( 'ul', assert ); + } ); + + QUnit.asyncTest( 'basic operation (<ol>)', 7, function ( assert ) { + listTest( 'ol', assert ); + } ); + + QUnit.test( 'basic operation when synchronous (options.instantHide)', 2, function ( assert ) { + var $collapsible, $content; + $collapsible = prepareCollapsible( + '<div class="mw-collapsible">' + loremIpsum + '</div>', + { instantHide: true } + ); + $content = $collapsible.find( '.mw-collapsible-content' ); + + assert.assertTrue( $content.is( ':visible' ), 'content is visible' ); + + $collapsible.find( '.mw-collapsible-toggle' ).trigger( 'click' ); + + assert.assertTrue( $content.is( ':hidden' ), 'after collapsing: content is hidden' ); + } ); + + QUnit.test( 'mw-made-collapsible data added', 1, function ( assert ) { + var $collapsible; + $collapsible = prepareCollapsible( + '<div>' + loremIpsum + '</div>' + ); + assert.equal( $collapsible.data( 'mw-made-collapsible' ), true, 'mw-made-collapsible data present' ); + } ); + + QUnit.test( 'mw-collapsible added when missing', 1, function ( assert ) { + var $collapsible; + $collapsible = prepareCollapsible( + '<div>' + loremIpsum + '</div>' + ); + assert.assertTrue( $collapsible.hasClass( 'mw-collapsible' ), 'mw-collapsible class present' ); + } ); + + QUnit.test( 'mw-collapsed added when missing', 1, function ( assert ) { + var $collapsible; + $collapsible = prepareCollapsible( + '<div>' + loremIpsum + '</div>', + { collapsed: true } + ); + assert.assertTrue( $collapsible.hasClass( 'mw-collapsed' ), 'mw-collapsed class present' ); + } ); + + QUnit.asyncTest( 'initial collapse (mw-collapsed class)', 2, function ( assert ) { + var $collapsible, $content; + $collapsible = prepareCollapsible( + '<div class="mw-collapsible mw-collapsed">' + loremIpsum + '</div>' + ); + $content = $collapsible.find( '.mw-collapsible-content' ); + + // Synchronous - mw-collapsed should cause instantHide: true to be used on initial collapsing + assert.assertTrue( $content.is( ':hidden' ), 'content is hidden' ); + + $collapsible.on( 'afterExpand.mw-collapsible', function () { + assert.assertTrue( $content.is( ':visible' ), 'after expanding: content is visible' ); + QUnit.start(); + } ); + + $collapsible.find( '.mw-collapsible-toggle' ).trigger( 'click' ); + } ); + + QUnit.asyncTest( 'initial collapse (options.collapsed)', 2, function ( assert ) { + var $collapsible, $content; + $collapsible = prepareCollapsible( + '<div class="mw-collapsible">' + loremIpsum + '</div>', + { collapsed: true } + ); + $content = $collapsible.find( '.mw-collapsible-content' ); + + // Synchronous - collapsed: true should cause instantHide: true to be used on initial collapsing + assert.assertTrue( $content.is( ':hidden' ), 'content is hidden' ); + + $collapsible.on( 'afterExpand.mw-collapsible', function () { + assert.assertTrue( $content.is( ':visible' ), 'after expanding: content is visible' ); + QUnit.start(); + } ); + + $collapsible.find( '.mw-collapsible-toggle' ).trigger( 'click' ); + } ); + + QUnit.test( 'clicks on links inside toggler pass through (options.linksPassthru)' , 2, function ( assert ) { + var $collapsible, $content; + + $collapsible = prepareCollapsible( + '<div class="mw-collapsible">' + + '<div class="mw-collapsible-toggle">' + + 'Toggle <a href="#top">toggle</a> toggle <b>toggle</b>' + + '</div>' + + '<div class="mw-collapsible-content">' + loremIpsum + '</div>' + + '</div>', + // Can't do asynchronous because we're testing that the event *doesn't* happen + { instantHide: true } + ); + $content = $collapsible.find( '.mw-collapsible-content' ); + + $collapsible.find( '.mw-collapsible-toggle a' ).trigger( 'click' ); + assert.assertTrue( $content.is( ':visible' ), 'click event on link inside toggle passes through (content not toggled)' ); + + $collapsible.find( '.mw-collapsible-toggle b' ).trigger( 'click' ); + assert.assertTrue( $content.is( ':hidden' ), 'click event on non-link inside toggle toggles content' ); + } ); + + QUnit.asyncTest( 'collapse/expand text (data-collapsetext, data-expandtext)', 2, function ( assert ) { + var $collapsible, $toggleLink; + $collapsible = prepareCollapsible( + '<div class="mw-collapsible" data-collapsetext="Collapse me!" data-expandtext="Expand me!">' + + loremIpsum + + '</div>' + ); + $toggleLink = $collapsible.find( '.mw-collapsible-toggle a' ); + + assert.equal( $toggleLink.text(), 'Collapse me!', 'data-collapsetext is respected' ); + + $collapsible.on( 'afterCollapse.mw-collapsible', function () { + assert.equal( $toggleLink.text(), 'Expand me!', 'data-expandtext is respected' ); + QUnit.start(); + } ); + + $collapsible.find( '.mw-collapsible-toggle' ).trigger( 'click' ); + } ); + + QUnit.asyncTest( 'collapse/expand text (options.collapseText, options.expandText)', 2, function ( assert ) { + var $collapsible, $toggleLink; + $collapsible = prepareCollapsible( + '<div class="mw-collapsible">' + loremIpsum + '</div>', + { collapseText: 'Collapse me!', expandText: 'Expand me!' } + ); + $toggleLink = $collapsible.find( '.mw-collapsible-toggle a' ); + + assert.equal( $toggleLink.text(), 'Collapse me!', 'options.collapseText is respected' ); + + $collapsible.on( 'afterCollapse.mw-collapsible', function () { + assert.equal( $toggleLink.text(), 'Expand me!', 'options.expandText is respected' ); + QUnit.start(); + } ); + + $collapsible.find( '.mw-collapsible-toggle' ).trigger( 'click' ); + } ); + +}( mediaWiki, jQuery ) ); diff --git a/tests/qunit/suites/resources/jquery/jquery.mwExtension.test.js b/tests/qunit/suites/resources/jquery/jquery.mwExtension.test.js index 5b566ae0..7571b929 100644 --- a/tests/qunit/suites/resources/jquery/jquery.mwExtension.test.js +++ b/tests/qunit/suites/resources/jquery/jquery.mwExtension.test.js @@ -1,58 +1,57 @@ -QUnit.module( 'jquery.mwExtension', QUnit.newMwEnvironment() ); - -QUnit.test( 'String functions', function ( assert ) { - - assert.equal( $.trimLeft( ' foo bar ' ), 'foo bar ', 'trimLeft' ); - assert.equal( $.trimRight( ' foo bar ' ), ' foo bar', 'trimRight' ); - assert.equal( $.ucFirst( 'foo' ), 'Foo', 'ucFirst' ); - - assert.equal( $.escapeRE( '<!-- ([{+mW+}]) $^|?>' ), - '<!\\-\\- \\(\\[\\{\\+mW\\+\\}\\]\\) \\$\\^\\|\\?>', 'escapeRE - Escape specials' ); - assert.equal( $.escapeRE( 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' ), - 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'escapeRE - Leave uppercase alone' ); - assert.equal( $.escapeRE( 'abcdefghijklmnopqrstuvwxyz' ), - 'abcdefghijklmnopqrstuvwxyz', 'escapeRE - Leave lowercase alone' ); - assert.equal( $.escapeRE( '0123456789' ), '0123456789', 'escapeRE - Leave numbers alone' ); -}); - -QUnit.test( 'Is functions', function ( assert ) { - - assert.strictEqual( $.isDomElement( document.getElementById( 'qunit-header' ) ), true, - 'isDomElement: #qunit-header Node' ); - assert.strictEqual( $.isDomElement( document.getElementById( 'random-name' ) ), false, - 'isDomElement: #random-name (null)' ); - assert.strictEqual( $.isDomElement( document.getElementsByTagName( 'div' ) ), false, - 'isDomElement: getElementsByTagName Array' ); - assert.strictEqual( $.isDomElement( document.getElementsByTagName( 'div' )[0] ), true, - 'isDomElement: getElementsByTagName(..)[0] Node' ); - assert.strictEqual( $.isDomElement( $( 'div' ) ), false, - 'isDomElement: jQuery object' ); - assert.strictEqual( $.isDomElement( $( 'div' ).get(0) ), true, - 'isDomElement: jQuery object > Get node' ); - assert.strictEqual( $.isDomElement( document.createElement( 'div' ) ), true, - 'isDomElement: createElement' ); - assert.strictEqual( $.isDomElement( { foo: 1 } ), false, - 'isDomElement: Object' ); - - assert.strictEqual( $.isEmpty( 'string' ), false, 'isEmptry: "string"' ); - assert.strictEqual( $.isEmpty( '0' ), true, 'isEmptry: "0"' ); - assert.strictEqual( $.isEmpty( '' ), true, 'isEmptry: ""' ); - assert.strictEqual( $.isEmpty( 1 ), false, 'isEmptry: 1' ); - assert.strictEqual( $.isEmpty( [] ), true, 'isEmptry: []' ); - assert.strictEqual( $.isEmpty( {} ), true, 'isEmptry: {}' ); - - // Documented behaviour - assert.strictEqual( $.isEmpty( { length: 0 } ), true, 'isEmptry: { length: 0 }' ); -}); - -QUnit.test( 'Comparison functions', function ( assert ) { - - assert.ok( $.compareArray( [0, 'a', [], [2, 'b'] ], [0, "a", [], [2, "b"] ] ), - 'compareArray: Two deep arrays that are excactly the same' ); - assert.ok( !$.compareArray( [1], [2] ), 'compareArray: Two different arrays (false)' ); - - assert.ok( $.compareObject( {}, {} ), 'compareObject: Two empty objects' ); - assert.ok( $.compareObject( { foo: 1 }, { foo: 1 } ), 'compareObject: Two the same objects' ); - assert.ok( !$.compareObject( { bar: true }, { baz: false } ), - 'compareObject: Two different objects (false)' ); -}); +( function ( $ ) { + QUnit.module( 'jquery.mwExtension', QUnit.newMwEnvironment() ); + + QUnit.test( 'String functions', 7, function ( assert ) { + assert.equal( $.trimLeft( ' foo bar ' ), 'foo bar ', 'trimLeft' ); + assert.equal( $.trimRight( ' foo bar ' ), ' foo bar', 'trimRight' ); + assert.equal( $.ucFirst( 'foo' ), 'Foo', 'ucFirst' ); + + assert.equal( $.escapeRE( '<!-- ([{+mW+}]) $^|?>' ), + '<!\\-\\- \\(\\[\\{\\+mW\\+\\}\\]\\) \\$\\^\\|\\?>', 'escapeRE - Escape specials' ); + assert.equal( $.escapeRE( 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' ), + 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'escapeRE - Leave uppercase alone' ); + assert.equal( $.escapeRE( 'abcdefghijklmnopqrstuvwxyz' ), + 'abcdefghijklmnopqrstuvwxyz', 'escapeRE - Leave lowercase alone' ); + assert.equal( $.escapeRE( '0123456789' ), '0123456789', 'escapeRE - Leave numbers alone' ); + } ); + + QUnit.test( 'Is functions', 15, function ( assert ) { + assert.strictEqual( $.isDomElement( document.getElementById( 'qunit-header' ) ), true, + 'isDomElement: #qunit-header Node' ); + assert.strictEqual( $.isDomElement( document.getElementById( 'random-name' ) ), false, + 'isDomElement: #random-name (null)' ); + assert.strictEqual( $.isDomElement( document.getElementsByTagName( 'div' ) ), false, + 'isDomElement: getElementsByTagName Array' ); + assert.strictEqual( $.isDomElement( document.getElementsByTagName( 'div' )[0] ), true, + 'isDomElement: getElementsByTagName(..)[0] Node' ); + assert.strictEqual( $.isDomElement( $( 'div' ) ), false, + 'isDomElement: jQuery object' ); + assert.strictEqual( $.isDomElement( $( 'div' ).get( 0 ) ), true, + 'isDomElement: jQuery object > Get node' ); + assert.strictEqual( $.isDomElement( document.createElement( 'div' ) ), true, + 'isDomElement: createElement' ); + assert.strictEqual( $.isDomElement( { foo: 1 } ), false, + 'isDomElement: Object' ); + + assert.strictEqual( $.isEmpty( 'string' ), false, 'isEmpty: "string"' ); + assert.strictEqual( $.isEmpty( '0' ), true, 'isEmpty: "0"' ); + assert.strictEqual( $.isEmpty( '' ), true, 'isEmpty: ""' ); + assert.strictEqual( $.isEmpty( 1 ), false, 'isEmpty: 1' ); + assert.strictEqual( $.isEmpty( [] ), true, 'isEmpty: []' ); + assert.strictEqual( $.isEmpty( {} ), true, 'isEmpty: {}' ); + + // Documented behavior + assert.strictEqual( $.isEmpty( { length: 0 } ), true, 'isEmpty: { length: 0 }' ); + } ); + + QUnit.test( 'Comparison functions', 5, function ( assert ) { + assert.ok( $.compareArray( [0, 'a', [], [2, 'b'] ], [0, 'a', [], [2, 'b'] ] ), + 'compareArray: Two deep arrays that are excactly the same' ); + assert.ok( !$.compareArray( [1], [2] ), 'compareArray: Two different arrays (false)' ); + + assert.ok( $.compareObject( {}, {} ), 'compareObject: Two empty objects' ); + assert.ok( $.compareObject( { foo: 1 }, { foo: 1 } ), 'compareObject: Two the same objects' ); + assert.ok( !$.compareObject( { bar: true }, { baz: false } ), + 'compareObject: Two different objects (false)' ); + } ); +}( jQuery ) ); diff --git a/tests/qunit/suites/resources/jquery/jquery.tabIndex.test.js b/tests/qunit/suites/resources/jquery/jquery.tabIndex.test.js index 161f0cd1..12137931 100644 --- a/tests/qunit/suites/resources/jquery/jquery.tabIndex.test.js +++ b/tests/qunit/suites/resources/jquery/jquery.tabIndex.test.js @@ -1,33 +1,35 @@ -QUnit.module( 'jquery.tabIndex', QUnit.newMwEnvironment() ); +( function ( $ ) { + QUnit.module( 'jquery.tabIndex', QUnit.newMwEnvironment() ); -QUnit.test( 'firstTabIndex', 2, function ( assert ) { - var testEnvironment = -'<form>' + - '<input tabindex="7" />' + - '<input tabindex="9" />' + - '<textarea tabindex="2">Foobar</textarea>' + - '<textarea tabindex="5">Foobar</textarea>' + -'</form>'; + QUnit.test( 'firstTabIndex', 2, function ( assert ) { + var html, $testA, $testB; + html = '<form>' + + '<input tabindex="7" />' + + '<input tabindex="9" />' + + '<textarea tabindex="2">Foobar</textarea>' + + '<textarea tabindex="5">Foobar</textarea>' + + '</form>'; - var $testA = $( '<div>' ).html( testEnvironment ).appendTo( '#qunit-fixture' ); - assert.strictEqual( $testA.firstTabIndex(), 2, 'First tabindex should be 2 within this context.' ); + $testA = $( '<div>' ).html( html ).appendTo( '#qunit-fixture' ); + assert.strictEqual( $testA.firstTabIndex(), 2, 'First tabindex should be 2 within this context.' ); - var $testB = $( '<div>' ); - assert.strictEqual( $testB.firstTabIndex(), null, 'Return null if none available.' ); -}); + $testB = $( '<div>' ); + assert.strictEqual( $testB.firstTabIndex(), null, 'Return null if none available.' ); + } ); -QUnit.test( 'lastTabIndex', 2, function ( assert ) { - var testEnvironment = -'<form>' + - '<input tabindex="7" />' + - '<input tabindex="9" />' + - '<textarea tabindex="2">Foobar</textarea>' + - '<textarea tabindex="5">Foobar</textarea>' + -'</form>'; + QUnit.test( 'lastTabIndex', 2, function ( assert ) { + var html, $testA, $testB; + html = '<form>' + + '<input tabindex="7" />' + + '<input tabindex="9" />' + + '<textarea tabindex="2">Foobar</textarea>' + + '<textarea tabindex="5">Foobar</textarea>' + + '</form>'; - var $testA = $( '<div>' ).html( testEnvironment ).appendTo( '#qunit-fixture' ); - assert.strictEqual( $testA.lastTabIndex(), 9, 'Last tabindex should be 9 within this context.' ); + $testA = $( '<div>' ).html( html ).appendTo( '#qunit-fixture' ); + assert.strictEqual( $testA.lastTabIndex(), 9, 'Last tabindex should be 9 within this context.' ); - var $testB = $( '<div>' ); - assert.strictEqual( $testB.lastTabIndex(), null, 'Return null if none available.' ); -}); + $testB = $( '<div>' ); + assert.strictEqual( $testB.lastTabIndex(), null, 'Return null if none available.' ); + } ); +}( jQuery ) ); diff --git a/tests/qunit/suites/resources/jquery/jquery.tablesorter.test.js b/tests/qunit/suites/resources/jquery/jquery.tablesorter.test.js index 16d81707..f73fd7bf 100644 --- a/tests/qunit/suites/resources/jquery/jquery.tablesorter.test.js +++ b/tests/qunit/suites/resources/jquery/jquery.tablesorter.test.js @@ -1,697 +1,1268 @@ ( function ( $, mw ) { - -var config = { - wgMonthNames: ['', 'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'], - wgMonthNamesShort: ['', 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'], - wgDefaultDateFormat: 'dmy', - wgContentLanguage: 'en' -}; - -QUnit.module( 'jquery.tablesorter', QUnit.newMwEnvironment({ config: config }) ); - -/** - * Create an HTML table from an array of row arrays containing text strings. - * First row will be header row. No fancy rowspan/colspan stuff. - * - * @param {String[]} header - * @param {String[][]} data - * @return jQuery - */ -function tableCreate( header, data ) { - var i, - $table = $( '<table class="sortable"><thead></thead><tbody></tbody></table>' ), - $thead = $table.find( 'thead' ), - $tbody = $table.find( 'tbody' ), - $tr = $( '<tr>' ); - - $.each( header, function ( i, str ) { - var $th = $( '<th>' ); - $th.text( str ).appendTo( $tr ); - }); - $tr.appendTo( $thead ); - - for ( i = 0; i < data.length; i++ ) { - $tr = $( '<tr>' ); - $.each( data[i], function ( j, str ) { - var $td = $( '<td>' ); - $td.text( str ).appendTo( $tr ); - }); - $tr.appendTo( $tbody ); - } - return $table; -} - -/** - * Extract text from table. - * - * @param {jQuery} $table - * @return String[][] - */ -function tableExtract( $table ) { - var data = []; - - $table.find( 'tbody' ).find( 'tr' ).each( function( i, tr ) { - var row = []; - $( tr ).find( 'td,th' ).each( function( i, td ) { - row.push( $( td ).text() ); - }); - data.push( row ); - }); - return data; -} - -/** - * Run a table test by building a table with the given data, - * running some callback on it, then checking the results. - * - * @param {String} msg text to pass on to qunit for the comparison - * @param {String[]} header cols to make the table - * @param {String[][]} data rows/cols to make the table - * @param {String[][]} expected rows/cols to compare against at end - * @param {function($table)} callback something to do with the table before we compare - */ -function tableTest( msg, header, data, expected, callback ) { - QUnit.test( msg, 1, function ( assert ) { - var $table = tableCreate( header, data ); - - // Give caller a chance to set up sorting and manipulate the table. - callback( $table ); - - // Table sorting is done synchronously; if it ever needs to change back - // to asynchronous, we'll need a timeout or a callback here. - var extracted = tableExtract( $table ); - assert.deepEqual( extracted, expected, msg ); - }); -} - -function reversed(arr) { - // Clone array - var arr2 = arr.slice(0); - - arr2.reverse(); - - return arr2; -} - -// Sample data set using planets named and their radius -var header = [ 'Planet' , 'Radius (km)'], - mercury = [ 'Mercury', '2439.7' ], - venus = [ 'Venus' , '6051.8' ], - earth = [ 'Earth' , '6371.0' ], - mars = [ 'Mars' , '3390.0' ], - jupiter = [ 'Jupiter', '69911' ], - saturn = [ 'Saturn' , '58232' ]; - -// Initial data set -var planets = [mercury, venus, earth, mars, jupiter, saturn]; -var ascendingName = [earth, jupiter, mars, mercury, saturn, venus]; -var ascendingRadius = [mercury, mars, venus, earth, saturn, jupiter]; - -tableTest( - 'Basic planet table: ascending by name', - header, - planets, - ascendingName, - function ( $table ) { - $table.tablesorter(); - $table.find( '.headerSort:eq(0)' ).click(); - } -); -tableTest( - 'Basic planet table: ascending by name a second time', - header, - planets, - ascendingName, - function ( $table ) { - $table.tablesorter(); - $table.find( '.headerSort:eq(0)' ).click(); - } -); -tableTest( - 'Basic planet table: descending by name', - header, - planets, - reversed(ascendingName), - function ( $table ) { - $table.tablesorter(); - $table.find( '.headerSort:eq(0)' ).click().click(); - } -); -tableTest( - 'Basic planet table: ascending radius', - header, - planets, - ascendingRadius, - function ( $table ) { - $table.tablesorter(); - $table.find( '.headerSort:eq(1)' ).click(); - } -); -tableTest( - 'Basic planet table: descending radius', - header, - planets, - reversed(ascendingRadius), - function ( $table ) { - $table.tablesorter(); - $table.find( '.headerSort:eq(1)' ).click().click(); + /*jshint onevar: false */ + + var config = { + wgMonthNames: ['', 'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'], + wgMonthNamesShort: ['', 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'], + wgDefaultDateFormat: 'dmy', + wgSeparatorTransformTable: ['', ''], + wgDigitTransformTable: ['', ''], + wgContentLanguage: 'en' + }; + + QUnit.module( 'jquery.tablesorter', QUnit.newMwEnvironment( { config: config } ) ); + + /** + * Create an HTML table from an array of row arrays containing text strings. + * First row will be header row. No fancy rowspan/colspan stuff. + * + * @param {String[]} header + * @param {String[][]} data + * @return jQuery + */ + function tableCreate( header, data ) { + var i, + $table = $( '<table class="sortable"><thead></thead><tbody></tbody></table>' ), + $thead = $table.find( 'thead' ), + $tbody = $table.find( 'tbody' ), + $tr = $( '<tr>' ); + + $.each( header, function ( i, str ) { + var $th = $( '<th>' ); + $th.text( str ).appendTo( $tr ); + } ); + $tr.appendTo( $thead ); + + for ( i = 0; i < data.length; i++ ) { + /*jshint loopfunc: true */ + $tr = $( '<tr>' ); + $.each( data[i], function ( j, str ) { + var $td = $( '<td>' ); + $td.text( str ).appendTo( $tr ); + } ); + $tr.appendTo( $tbody ); + } + return $table; } -); - - -// Regression tests! -tableTest( - 'Bug 28775: German-style (dmy) short numeric dates', - ['Date'], - [ // German-style dates are day-month-year - ['11.11.2011'], - ['01.11.2011'], - ['02.10.2011'], - ['03.08.2011'], - ['09.11.2011'] - ], - [ // Sorted by ascending date - ['03.08.2011'], - ['02.10.2011'], - ['01.11.2011'], - ['09.11.2011'], - ['11.11.2011'] - ], - function ( $table ) { - mw.config.set( 'wgDefaultDateFormat', 'dmy' ); - mw.config.set( 'wgContentLanguage', 'de' ); - $table.tablesorter(); - $table.find( '.headerSort:eq(0)' ).click(); + /** + * Extract text from table. + * + * @param {jQuery} $table + * @return String[][] + */ + function tableExtract( $table ) { + var data = []; + + $table.find( 'tbody' ).find( 'tr' ).each( function ( i, tr ) { + var row = []; + $( tr ).find( 'td,th' ).each( function ( i, td ) { + row.push( $( td ).text() ); + } ); + data.push( row ); + } ); + return data; } -); - -tableTest( - 'Bug 28775: American-style (mdy) short numeric dates', - ['Date'], - [ // American-style dates are month-day-year - ['11.11.2011'], - ['01.11.2011'], - ['02.10.2011'], - ['03.08.2011'], - ['09.11.2011'] - ], - [ // Sorted by ascending date - ['01.11.2011'], - ['02.10.2011'], - ['03.08.2011'], - ['09.11.2011'], - ['11.11.2011'] - ], - function ( $table ) { - mw.config.set( 'wgDefaultDateFormat', 'mdy' ); - $table.tablesorter(); - $table.find( '.headerSort:eq(0)' ).click(); - } -); - -var ipv4 = [ - // Some randomly generated fake IPs - ['45.238.27.109'], - ['44.172.9.22'], - ['247.240.82.209'], - ['204.204.132.158'], - ['170.38.91.162'], - ['197.219.164.9'], - ['45.68.154.72'], - ['182.195.149.80'] -]; -var ipv4Sorted = [ - // Sort order should go octet by octet - ['44.172.9.22'], - ['45.68.154.72'], - ['45.238.27.109'], - ['170.38.91.162'], - ['182.195.149.80'], - ['197.219.164.9'], - ['204.204.132.158'], - ['247.240.82.209'] -]; - -tableTest( - 'Bug 17141: IPv4 address sorting', - ['IP'], - ipv4, - ipv4Sorted, - function ( $table ) { - $table.tablesorter(); - $table.find( '.headerSort:eq(0)' ).click(); - } -); -tableTest( - 'Bug 17141: IPv4 address sorting (reverse)', - ['IP'], - ipv4, - reversed(ipv4Sorted), - function ( $table ) { - $table.tablesorter(); - $table.find( '.headerSort:eq(0)' ).click().click(); + /** + * Run a table test by building a table with the given data, + * running some callback on it, then checking the results. + * + * @param {String} msg text to pass on to qunit for the comparison + * @param {String[]} header cols to make the table + * @param {String[][]} data rows/cols to make the table + * @param {String[][]} expected rows/cols to compare against at end + * @param {function($table)} callback something to do with the table before we compare + */ + function tableTest( msg, header, data, expected, callback ) { + QUnit.test( msg, 1, function ( assert ) { + var $table = tableCreate( header, data ); + + // Give caller a chance to set up sorting and manipulate the table. + callback( $table ); + + // Table sorting is done synchronously; if it ever needs to change back + // to asynchronous, we'll need a timeout or a callback here. + var extracted = tableExtract( $table ); + assert.deepEqual( extracted, expected, msg ); + } ); } -); - -var umlautWords = [ - // Some words with Umlauts - ['Günther'], - ['Peter'], - ['Björn'], - ['Bjorn'], - ['Apfel'], - ['Äpfel'], - ['Strasse'], - ['Sträßschen'] -]; - -var umlautWordsSorted = [ - // Some words with Umlauts - ['Äpfel'], - ['Apfel'], - ['Björn'], - ['Bjorn'], - ['Günther'], - ['Peter'], - ['Sträßschen'], - ['Strasse'] -]; - -tableTest( - 'Accented Characters with custom collation', - ['Name'], - umlautWords, - umlautWordsSorted, - function ( $table ) { - mw.config.set( 'tableSorterCollation', { - 'ä': 'ae', - 'ö': 'oe', - 'ß': 'ss', - 'ü':'ue' + + /** + * Run a table test by building a table with the given HTML, + * running some callback on it, then checking the results. + * + * @param {String} msg text to pass on to qunit for the comparison + * @param {String} HTML to make the table + * @param {String[][]} expected rows/cols to compare against at end + * @param {function($table)} callback something to do with the table before we compare + */ + function tableTestHTML( msg, html, expected, callback ) { + QUnit.test( msg, 1, function ( assert ) { + var $table = $( html ); + + // Give caller a chance to set up sorting and manipulate the table. + if ( callback ) { + callback( $table ); + } else { + $table.tablesorter(); + $table.find( '#sortme' ).click(); + } + + // Table sorting is done synchronously; if it ever needs to change back + // to asynchronous, we'll need a timeout or a callback here. + var extracted = tableExtract( $table ); + assert.deepEqual( extracted, expected, msg ); } ); + } - $table.tablesorter(); - $table.find( '.headerSort:eq(0)' ).click(); + function reversed( arr ) { + // Clone array + var arr2 = arr.slice( 0 ); + + arr2.reverse(); + + return arr2; } -); - -var planetsRowspan = [["Earth","6051.8"], jupiter, ["Mars","6051.8"], mercury, saturn, venus]; -var planetsRowspanII = [jupiter, mercury, saturn, venus, ['Venus', '6371.0'], ['Venus', '3390.0']]; - -tableTest( - 'Basic planet table: same value for multiple rows via rowspan', - header, - planets, - planetsRowspan, - function ( $table ) { - // Modify the table to have a multiuple-row-spanning cell: + + // Sample data set using planets named and their radius + var header = [ 'Planet' , 'Radius (km)'], + mercury = [ 'Mercury', '2439.7' ], + venus = [ 'Venus' , '6051.8' ], + earth = [ 'Earth' , '6371.0' ], + mars = [ 'Mars' , '3390.0' ], + jupiter = [ 'Jupiter', '69911' ], + saturn = [ 'Saturn' , '58232' ]; + + // Initial data set + var planets = [mercury, venus, earth, mars, jupiter, saturn]; + var ascendingName = [earth, jupiter, mars, mercury, saturn, venus]; + var ascendingRadius = [mercury, mars, venus, earth, saturn, jupiter]; + + tableTest( + 'Basic planet table: sorting initially - ascending by name', + header, + planets, + ascendingName, + function ( $table ) { + $table.tablesorter( { sortList: [ + { 0: 'asc' } + ] } ); + } + ); + tableTest( + 'Basic planet table: sorting initially - descending by radius', + header, + planets, + reversed( ascendingRadius ), + function ( $table ) { + $table.tablesorter( { sortList: [ + { 1: 'desc' } + ] } ); + } + ); + tableTest( + 'Basic planet table: ascending by name', + header, + planets, + ascendingName, + function ( $table ) { + $table.tablesorter(); + $table.find( '.headerSort:eq(0)' ).click(); + } + ); + tableTest( + 'Basic planet table: ascending by name a second time', + header, + planets, + ascendingName, + function ( $table ) { + $table.tablesorter(); + $table.find( '.headerSort:eq(0)' ).click(); + } + ); + tableTest( + 'Basic planet table: ascending by name (multiple clicks)', + header, + planets, + ascendingName, + function ( $table ) { + $table.tablesorter(); + $table.find( '.headerSort:eq(0)' ).click(); + $table.find( '.headerSort:eq(1)' ).click(); + $table.find( '.headerSort:eq(0)' ).click(); + } + ); + tableTest( + 'Basic planet table: descending by name', + header, + planets, + reversed( ascendingName ), + function ( $table ) { + $table.tablesorter(); + $table.find( '.headerSort:eq(0)' ).click().click(); + } + ); + tableTest( + 'Basic planet table: ascending radius', + header, + planets, + ascendingRadius, + function ( $table ) { + $table.tablesorter(); + $table.find( '.headerSort:eq(1)' ).click(); + } + ); + tableTest( + 'Basic planet table: descending radius', + header, + planets, + reversed( ascendingRadius ), + function ( $table ) { + $table.tablesorter(); + $table.find( '.headerSort:eq(1)' ).click().click(); + } + ); + + // Sample data set to test multiple column sorting + header = [ 'column1' , 'column2']; + var + a1 = [ 'A', '1' ], + a2 = [ 'A', '2' ], + a3 = [ 'A', '3' ], + b1 = [ 'B', '1' ], + b2 = [ 'B', '2' ], + b3 = [ 'B', '3' ]; + var initial = [a2, b3, a1, a3, b2, b1]; + var asc = [a1, a2, a3, b1, b2, b3]; + var descasc = [b1, b2, b3, a1, a2, a3]; + + tableTest( + 'Sorting multiple columns by passing sort list', + header, + initial, + asc, + function ( $table ) { + $table.tablesorter( + { sortList: [ + { 0: 'asc' }, + { 1: 'asc' } + ] } + ); + } + ); + tableTest( + 'Sorting multiple columns by programmatically triggering sort()', + header, + initial, + descasc, + function ( $table ) { + $table.tablesorter(); + $table.data( 'tablesorter' ).sort( + [ + { 0: 'desc' }, + { 1: 'asc' } + ] + ); + } + ); + tableTest( + 'Reset to initial sorting by triggering sort() without any parameters', + header, + initial, + asc, + function ( $table ) { + $table.tablesorter( + { sortList: [ + { 0: 'asc' }, + { 1: 'asc' } + ] } + ); + $table.data( 'tablesorter' ).sort( + [ + { 0: 'desc' }, + { 1: 'asc' } + ] + ); + $table.data( 'tablesorter' ).sort(); + } + ); + tableTest( + 'Sort via click event after having initialized the tablesorter with initial sorting', + header, + initial, + descasc, + function ( $table ) { + $table.tablesorter( + { sortList: [ { 0: 'asc' }, { 1: 'asc' } ] } + ); + $table.find( '.headerSort:eq(0)' ).click(); + } + ); + tableTest( + 'Multi-sort via click event after having initialized the tablesorter with initial sorting', + header, + initial, + asc, + function ( $table ) { + $table.tablesorter( + { sortList: [ { 0: 'desc' }, { 1: 'desc' } ] } + ); + $table.find( '.headerSort:eq(0)' ).click(); + + // Pretend to click while pressing the multi-sort key + var event = $.Event( 'click' ); + event[$table.data( 'tablesorter' ).config.sortMultiSortKey] = true; + $table.find( '.headerSort:eq(1)' ).trigger( event ); + } + ); + QUnit.test( 'Reset sorting making table appear unsorted', 3, function ( assert ) { + var $table = tableCreate( header, initial ); + $table.tablesorter( + { sortList: [ + { 0: 'desc' }, + { 1: 'asc' } + ] } + ); + $table.data( 'tablesorter' ).sort( [] ); + + assert.equal( + $table.find( 'th.headerSortUp' ).length + $table.find( 'th.headerSortDown' ).length, + 0, + 'No sort specific sort classes addign to header cells' + ); + + assert.equal( + $table.find( 'th' ).first().attr( 'title' ), + mw.msg( 'sort-ascending' ), + 'First header cell has default title' + ); + + assert.equal( + $table.find( 'th' ).first().attr( 'title' ), + $table.find( 'th' ).last().attr( 'title' ), + 'Both header cells\' titles match' + ); + } ); + + // Sorting with colspans + header = [ 'column1a' , 'column1b', 'column1c', 'column2' ]; + var + aaa1 = [ 'A', 'A', 'A', '1' ], + aab5 = [ 'A', 'A', 'B', '5' ], + abc3 = [ 'A', 'B', 'C', '3' ], + bbc2 = [ 'B', 'B', 'C', '2' ], + caa4 = [ 'C', 'A', 'A', '4' ]; + // initial is already declared above + initial = [ aab5, aaa1, abc3, bbc2, caa4 ]; + tableTest( 'Sorting with colspanned headers: spanned column', + header, + initial, + [ aaa1, aab5, abc3, bbc2, caa4 ], + function ( $table ) { + // Make colspanned header for test + $table.find( 'tr:eq(0) th:eq(1), tr:eq(0) th:eq(2)' ).remove(); + $table.find( 'tr:eq(0) th:eq(0)' ).attr( 'colspan', '3' ); + + $table.tablesorter(); + $table.find( '.headerSort:eq(0)' ).click(); + } + ); + tableTest( 'Sorting with colspanned headers: sort spanned column twice', + header, + initial, + [ caa4, bbc2, abc3, aab5, aaa1 ], + function ( $table ) { + // Make colspanned header for test + $table.find( 'tr:eq(0) th:eq(1), tr:eq(0) th:eq(2)' ).remove(); + $table.find( 'tr:eq(0) th:eq(0)' ).attr( 'colspan', '3' ); + + $table.tablesorter(); + $table.find( '.headerSort:eq(0)' ).click(); + $table.find( '.headerSort:eq(0)' ).click(); + } + ); + tableTest( 'Sorting with colspanned headers: subsequent column', + header, + initial, + [ aaa1, bbc2, abc3, caa4, aab5 ], + function ( $table ) { + // Make colspanned header for test + $table.find( 'tr:eq(0) th:eq(1), tr:eq(0) th:eq(2)' ).remove(); + $table.find( 'tr:eq(0) th:eq(0)' ).attr( 'colspan', '3' ); + + $table.tablesorter(); + $table.find( '.headerSort:eq(1)' ).click(); + } + ); + tableTest( 'Sorting with colspanned headers: sort subsequent column twice', + header, + initial, + [ aab5, caa4, abc3, bbc2, aaa1 ], + function ( $table ) { + // Make colspanned header for test + $table.find( 'tr:eq(0) th:eq(1), tr:eq(0) th:eq(2)' ).remove(); + $table.find( 'tr:eq(0) th:eq(0)' ).attr( 'colspan', '3' ); + + $table.tablesorter(); + $table.find( '.headerSort:eq(1)' ).click(); + $table.find( '.headerSort:eq(1)' ).click(); + } + ); + + + tableTest( + 'Basic planet table: one unsortable column', + header, + planets, + planets, + function ( $table ) { + $table.find( 'tr:eq(0) > th:eq(0)' ).addClass( 'unsortable' ); + + $table.tablesorter(); + $table.find( 'tr:eq(0) > th:eq(0)' ).click(); + } + ); + + // Regression tests! + tableTest( + 'Bug 28775: German-style (dmy) short numeric dates', + ['Date'], + [ + // German-style dates are day-month-year + ['11.11.2011'], + ['01.11.2011'], + ['02.10.2011'], + ['03.08.2011'], + ['09.11.2011'] + ], + [ + // Sorted by ascending date + ['03.08.2011'], + ['02.10.2011'], + ['01.11.2011'], + ['09.11.2011'], + ['11.11.2011'] + ], + function ( $table ) { + mw.config.set( 'wgDefaultDateFormat', 'dmy' ); + mw.config.set( 'wgContentLanguage', 'de' ); + + $table.tablesorter(); + $table.find( '.headerSort:eq(0)' ).click(); + } + ); + + tableTest( + 'Bug 28775: American-style (mdy) short numeric dates', + ['Date'], + [ + // American-style dates are month-day-year + ['11.11.2011'], + ['01.11.2011'], + ['02.10.2011'], + ['03.08.2011'], + ['09.11.2011'] + ], + [ + // Sorted by ascending date + ['01.11.2011'], + ['02.10.2011'], + ['03.08.2011'], + ['09.11.2011'], + ['11.11.2011'] + ], + function ( $table ) { + mw.config.set( 'wgDefaultDateFormat', 'mdy' ); + + $table.tablesorter(); + $table.find( '.headerSort:eq(0)' ).click(); + } + ); + + var ipv4 = [ + // Some randomly generated fake IPs + ['45.238.27.109'], + ['44.172.9.22'], + ['247.240.82.209'], + ['204.204.132.158'], + ['170.38.91.162'], + ['197.219.164.9'], + ['45.68.154.72'], + ['182.195.149.80'] + ]; + var ipv4Sorted = [ + // Sort order should go octet by octet + ['44.172.9.22'], + ['45.68.154.72'], + ['45.238.27.109'], + ['170.38.91.162'], + ['182.195.149.80'], + ['197.219.164.9'], + ['204.204.132.158'], + ['247.240.82.209'] + ]; + + tableTest( + 'Bug 17141: IPv4 address sorting', + ['IP'], + ipv4, + ipv4Sorted, + function ( $table ) { + $table.tablesorter(); + $table.find( '.headerSort:eq(0)' ).click(); + } + ); + tableTest( + 'Bug 17141: IPv4 address sorting (reverse)', + ['IP'], + ipv4, + reversed( ipv4Sorted ), + function ( $table ) { + $table.tablesorter(); + $table.find( '.headerSort:eq(0)' ).click().click(); + } + ); + + var umlautWords = [ + // Some words with Umlauts + ['Günther'], + ['Peter'], + ['Björn'], + ['Bjorn'], + ['Apfel'], + ['Äpfel'], + ['Strasse'], + ['Sträßschen'] + ]; + + var umlautWordsSorted = [ + // Some words with Umlauts + ['Äpfel'], + ['Apfel'], + ['Björn'], + ['Bjorn'], + ['Günther'], + ['Peter'], + ['Sträßschen'], + ['Strasse'] + ]; + + tableTest( + 'Accented Characters with custom collation', + ['Name'], + umlautWords, + umlautWordsSorted, + function ( $table ) { + mw.config.set( 'tableSorterCollation', { + 'ä': 'ae', + 'ö': 'oe', + 'ß': 'ss', + 'ü': 'ue' + } ); + + $table.tablesorter(); + $table.find( '.headerSort:eq(0)' ).click(); + } + ); + + QUnit.test( 'Rowspan not exploded on init', 1, function ( assert ) { + var $table = tableCreate( header, planets ); + + // Modify the table to have a multiple-row-spanning cell: // - Remove 2nd cell of 4th row, and, 2nd cell or 5th row. $table.find( 'tr:eq(3) td:eq(1), tr:eq(4) td:eq(1)' ).remove(); // - Set rowspan for 2nd cell of 3rd row to 3. // This covers the removed cell in the 4th and 5th row. - $table.find( 'tr:eq(2) td:eq(1)' ).prop( 'rowspan', '3' ); + $table.find( 'tr:eq(2) td:eq(1)' ).attr( 'rowspan', '3' ); $table.tablesorter(); - $table.find( '.headerSort:eq(0)' ).click(); - } -); -tableTest( - 'Basic planet table: Same value for multiple rows via rowspan II', - header, - planets, - planetsRowspanII, - function ( $table ) { - // Modify the table to have a multiuple-row-spanning cell: - // - Remove 1st cell of 4th row, and, 1st cell or 5th row. - $table.find( 'tr:eq(3) td:eq(0), tr:eq(4) td:eq(0)' ).remove(); - // - Set rowspan for 1st cell of 3rd row to 3. - // This covers the removed cell in the 4th and 5th row. - $table.find( 'tr:eq(2) td:eq(0)' ).prop( 'rowspan', '3' ); - $table.tablesorter(); - $table.find( '.headerSort:eq(0)' ).click(); - } -); - -var complexMDYDates = [ - // Some words with Umlauts - ['January, 19 2010'], - ['April 21 1991'], - ['04 22 1991'], - ['5.12.1990'], - ['December 12 \'10'] -]; - -var complexMDYSorted = [ - ['5.12.1990'], - ['April 21 1991'], - ['04 22 1991'], - ['January, 19 2010'], - ['December 12 \'10'] -]; - -tableTest( - 'Complex date parsing I', - ['date'], - complexMDYDates, - complexMDYSorted, - function ( $table ) { - mw.config.set( 'wgDefaultDateFormat', 'mdy' ); + assert.equal( + $table.find( 'tr:eq(2) td:eq(1)' ).prop( 'rowSpan' ), + 3, + 'Rowspan not exploded' + ); + } ); + + var planetsRowspan = [ + [ 'Earth', '6051.8' ], + jupiter, + [ 'Mars', '6051.8' ], + mercury, + saturn, + venus + ]; + var planetsRowspanII = [ jupiter, mercury, saturn, venus, [ 'Venus', '6371.0' ], [ 'Venus', '3390.0' ] ]; + + tableTest( + 'Basic planet table: same value for multiple rows via rowspan', + header, + planets, + planetsRowspan, + function ( $table ) { + // Modify the table to have a multiple-row-spanning cell: + // - Remove 2nd cell of 4th row, and, 2nd cell or 5th row. + $table.find( 'tr:eq(3) td:eq(1), tr:eq(4) td:eq(1)' ).remove(); + // - Set rowspan for 2nd cell of 3rd row to 3. + // This covers the removed cell in the 4th and 5th row. + $table.find( 'tr:eq(2) td:eq(1)' ).attr( 'rowspan', '3' ); + + $table.tablesorter(); + $table.find( '.headerSort:eq(0)' ).click(); + } + ); + tableTest( + 'Basic planet table: same value for multiple rows via rowspan (sorting initially)', + header, + planets, + planetsRowspan, + function ( $table ) { + // Modify the table to have a multiple-row-spanning cell: + // - Remove 2nd cell of 4th row, and, 2nd cell or 5th row. + $table.find( 'tr:eq(3) td:eq(1), tr:eq(4) td:eq(1)' ).remove(); + // - Set rowspan for 2nd cell of 3rd row to 3. + // This covers the removed cell in the 4th and 5th row. + $table.find( 'tr:eq(2) td:eq(1)' ).attr( 'rowspan', '3' ); + + $table.tablesorter( { sortList: [ + { 0: 'asc' } + ] } ); + } + ); + tableTest( + 'Basic planet table: Same value for multiple rows via rowspan II', + header, + planets, + planetsRowspanII, + function ( $table ) { + // Modify the table to have a multiple-row-spanning cell: + // - Remove 1st cell of 4th row, and, 1st cell or 5th row. + $table.find( 'tr:eq(3) td:eq(0), tr:eq(4) td:eq(0)' ).remove(); + // - Set rowspan for 1st cell of 3rd row to 3. + // This covers the removed cell in the 4th and 5th row. + $table.find( 'tr:eq(2) td:eq(0)' ).attr( 'rowspan', '3' ); + + $table.tablesorter(); + $table.find( '.headerSort:eq(0)' ).click(); + } + ); + + var complexMDYDates = [ + // Some words with Umlauts + ['January, 19 2010'], + ['April 21 1991'], + ['04 22 1991'], + ['5.12.1990'], + ['December 12 \'10'] + ]; + + var complexMDYSorted = [ + ['5.12.1990'], + ['April 21 1991'], + ['04 22 1991'], + ['January, 19 2010'], + ['December 12 \'10'] + ]; + + tableTest( + 'Complex date parsing I', + ['date'], + complexMDYDates, + complexMDYSorted, + function ( $table ) { + mw.config.set( 'wgDefaultDateFormat', 'mdy' ); + + $table.tablesorter(); + $table.find( '.headerSort:eq(0)' ).click(); + } + ); + + var currencyUnsorted = [ + ['1.02 $'], + ['$ 3.00'], + ['€ 2,99'], + ['$ 1.00'], + ['$3.50'], + ['$ 1.50'], + ['€ 0.99'] + ]; + + var currencySorted = [ + ['€ 0.99'], + ['$ 1.00'], + ['1.02 $'], + ['$ 1.50'], + ['$ 3.00'], + ['$3.50'], + // Comma's sort after dots + // Not intentional but test to detect changes + ['€ 2,99'] + ]; + + tableTest( + 'Currency parsing I', + ['currency'], + currencyUnsorted, + currencySorted, + function ( $table ) { + $table.tablesorter(); + $table.find( '.headerSort:eq(0)' ).click(); + } + ); + var ascendingNameLegacy = ascendingName.slice( 0 ); + ascendingNameLegacy[4] = ascendingNameLegacy[5]; + ascendingNameLegacy.pop(); + + tableTest( + 'Legacy compat with .sortbottom', + header, + planets, + ascendingNameLegacy, + function ( $table ) { + $table.find( 'tr:last' ).addClass( 'sortbottom' ); + $table.tablesorter(); + $table.find( '.headerSort:eq(0)' ).click(); + } + ); + + QUnit.test( 'Test detection routine', 1, function ( assert ) { + var $table; + $table = $( + '<table class="sortable">' + + '<caption>CAPTION</caption>' + + '<tr><th>THEAD</th></tr>' + + '<tr><td>1</td></tr>' + + '<tr class="sortbottom"><td>text</td></tr>' + + '</table>' + ); $table.tablesorter(); $table.find( '.headerSort:eq(0)' ).click(); - } -); - -var currencyUnsorted = [ - ['1.02 $'], - ['$ 3.00'], - ['€ 2,99'], - ['$ 1.00'], - ['$3.50'], - ['$ 1.50'], - ['€ 0.99'] -]; - -var currencySorted = [ - ['€ 0.99'], - ['$ 1.00'], - ['1.02 $'], - ['$ 1.50'], - ['$ 3.00'], - ['$3.50'], - // Comma's sort after dots - // Not intentional but test to detect changes - ['€ 2,99'] -]; - -tableTest( - 'Currency parsing I', - ['currency'], - currencyUnsorted, - currencySorted, - function ( $table ) { + + assert.equal( + $table.data( 'tablesorter' ).config.parsers[0].id, + 'number', + 'Correctly detected column content skipping sortbottom' + ); + } ); + + /** FIXME: the diff output is not very readeable. */ + QUnit.test( 'bug 32047 - caption must be before thead', 1, function ( assert ) { + var $table; + $table = $( + '<table class="sortable">' + + '<caption>CAPTION</caption>' + + '<tr><th>THEAD</th></tr>' + + '<tr><td>A</td></tr>' + + '<tr><td>B</td></tr>' + + '<tr class="sortbottom"><td>TFOOT</td></tr>' + + '</table>' + ); $table.tablesorter(); + + assert.equal( + $table.children().get( 0 ).nodeName, + 'CAPTION', + 'First element after <thead> must be <caption> (bug 32047)' + ); + } ); + + QUnit.test( 'data-sort-value attribute, when available, should override sorting position', 3, function ( assert ) { + var $table, data; + + // Example 1: All cells except one cell without data-sort-value, + // which should be sorted at it's text content value. + $table = $( + '<table class="sortable"><thead><tr><th>Data</th></tr></thead>' + + '<tbody>' + + '<tr><td>Cheetah</td></tr>' + + '<tr><td data-sort-value="Apple">Bird</td></tr>' + + '<tr><td data-sort-value="Bananna">Ferret</td></tr>' + + '<tr><td data-sort-value="Drupe">Elephant</td></tr>' + + '<tr><td data-sort-value="Cherry">Dolphin</td></tr>' + + '</tbody></table>' + ); + $table.tablesorter().find( '.headerSort:eq(0)' ).click(); + + data = []; + $table.find( 'tbody > tr' ).each( function ( i, tr ) { + $( tr ).find( 'td' ).each( function ( i, td ) { + data.push( { + data: $( td ).data( 'sortValue' ), + text: $( td ).text() + } ); + } ); + } ); + + assert.deepEqual( data, [ + { + data: 'Apple', + text: 'Bird' + }, + { + data: 'Bananna', + text: 'Ferret' + }, + { + data: undefined, + text: 'Cheetah' + }, + { + data: 'Cherry', + text: 'Dolphin' + }, + { + data: 'Drupe', + text: 'Elephant' + } + ], 'Order matches expected order (based on data-sort-value attribute values)' ); + + // Example 2 + $table = $( + '<table class="sortable"><thead><tr><th>Data</th></tr></thead>' + + '<tbody>' + + '<tr><td>D</td></tr>' + + '<tr><td data-sort-value="E">A</td></tr>' + + '<tr><td>B</td></tr>' + + '<tr><td>G</td></tr>' + + '<tr><td data-sort-value="F">C</td></tr>' + + '</tbody></table>' + ); + $table.tablesorter().find( '.headerSort:eq(0)' ).click(); + + data = []; + $table.find( 'tbody > tr' ).each( function ( i, tr ) { + $( tr ).find( 'td' ).each( function ( i, td ) { + data.push( { + data: $( td ).data( 'sortValue' ), + text: $( td ).text() + } ); + } ); + } ); + + assert.deepEqual( data, [ + { + data: undefined, + text: 'B' + }, + { + data: undefined, + text: 'D' + }, + { + data: 'E', + text: 'A' + }, + { + data: 'F', + text: 'C' + }, + { + data: undefined, + text: 'G' + } + ], 'Order matches expected order (based on data-sort-value attribute values)' ); + + // Example 3: Test that live changes are used from data-sort-value, + // even if they change after the tablesorter is constructed (bug 38152). + $table = $( + '<table class="sortable"><thead><tr><th>Data</th></tr></thead>' + + '<tbody>' + + '<tr><td>D</td></tr>' + + '<tr><td data-sort-value="1">A</td></tr>' + + '<tr><td>B</td></tr>' + + '<tr><td data-sort-value="2">G</td></tr>' + + '<tr><td>C</td></tr>' + + '</tbody></table>' + ); + // initialize table sorter and sort once + $table + .tablesorter() + .find( '.headerSort:eq(0)' ).click(); + + // Change the sortValue data properties (bug 38152) + // - change data + $table.find( 'td:contains(A)' ).data( 'sortValue', 3 ); + // - add data + $table.find( 'td:contains(B)' ).data( 'sortValue', 1 ); + // - remove data, bring back attribute: 2 + $table.find( 'td:contains(G)' ).removeData( 'sortValue' ); + + // Now sort again (twice, so it is back at Ascending) $table.find( '.headerSort:eq(0)' ).click(); - } -); - -var ascendingNameLegacy = ascendingName.slice(0); -ascendingNameLegacy[4] = ascendingNameLegacy[5]; -ascendingNameLegacy.pop(); - -tableTest( - 'Legacy compat with .sortbottom', - header, - planets, - ascendingNameLegacy, - function( $table ) { - $table.find( 'tr:last' ).addClass( 'sortbottom' ); - $table.tablesorter(); $table.find( '.headerSort:eq(0)' ).click(); - } -); + data = []; + $table.find( 'tbody > tr' ).each( function ( i, tr ) { + $( tr ).find( 'td' ).each( function ( i, td ) { + data.push( { + data: $( td ).data( 'sortValue' ), + text: $( td ).text() + } ); + } ); + } ); + + assert.deepEqual( data, [ + { + data: 1, + text: 'B' + }, + { + data: 2, + text: 'G' + }, + { + data: 3, + text: 'A' + }, + { + data: undefined, + text: 'C' + }, + { + data: undefined, + text: 'D' + } + ], 'Order matches expected order, using the current sortValue in $.data()' ); + + } ); + + var numbers = [ + [ '12' ], + [ '7' ], + [ '13,000'], + [ '9' ], + [ '14' ], + [ '8.0' ] + ]; + var numbersAsc = [ + [ '7' ], + [ '8.0' ], + [ '9' ], + [ '12' ], + [ '14' ], + [ '13,000'] + ]; + + tableTest( 'bug 8115: sort numbers with commas (ascending)', + ['Numbers'], numbers, numbersAsc, + function ( $table ) { + $table.tablesorter(); + $table.find( '.headerSort:eq(0)' ).click(); + } + ); -/** FIXME: the diff output is not very readeable. */ -QUnit.test( 'bug 32047 - caption must be before thead', function ( assert ) { - var $table; - $table = $( - '<table class="sortable">' + - '<caption>CAPTION</caption>' + - '<tr><th>THEAD</th></tr>' + - '<tr><td>A</td></tr>' + - '<tr><td>B</td></tr>' + - '<tr class="sortbottom"><td>TFOOT</td></tr>' + - '</table>' + tableTest( 'bug 8115: sort numbers with commas (descending)', + ['Numbers'], numbers, reversed( numbersAsc ), + function ( $table ) { + $table.tablesorter(); + $table.find( '.headerSort:eq(0)' ).click().click(); + } + ); + // TODO add numbers sorting tests for bug 8115 with a different language + + QUnit.test( 'bug 32888 - Tables inside a tableheader cell', 2, function ( assert ) { + var $table; + $table = $( + '<table class="sortable" id="mw-bug-32888">' + + '<tr><th>header<table id="mw-bug-32888-2">' + + '<tr><th>1</th><th>2</th></tr>' + + '</table></th></tr>' + + '<tr><td>A</td></tr>' + + '<tr><td>B</td></tr>' + + '</table>' ); - $table.tablesorter(); + $table.tablesorter(); - assert.equal( - $table.children( ).get( 0 ).nodeName, - 'CAPTION', - 'First element after <thead> must be <caption> (bug 32047)' + assert.equal( + $table.find( '> thead:eq(0) > tr > th.headerSort' ).length, + 1, + 'Child tables inside a headercell should not interfere with sortable headers (bug 32888)' + ); + assert.equal( + $( '#mw-bug-32888-2' ).find( 'th.headerSort' ).length, + 0, + 'The headers of child tables inside a headercell should not be sortable themselves (bug 32888)' + ); + } ); + + + var correctDateSorting1 = [ + ['01 January 2010'], + ['05 February 2010'], + ['16 January 2010'] + ]; + + var correctDateSortingSorted1 = [ + ['01 January 2010'], + ['16 January 2010'], + ['05 February 2010'] + ]; + + tableTest( + 'Correct date sorting I', + ['date'], + correctDateSorting1, + correctDateSortingSorted1, + function ( $table ) { + mw.config.set( 'wgDefaultDateFormat', 'mdy' ); + + $table.tablesorter(); + $table.find( '.headerSort:eq(0)' ).click(); + } ); -}); -QUnit.test( 'data-sort-value attribute, when available, should override sorting position', function ( assert ) { - var $table, data; + var correctDateSorting2 = [ + ['January 01 2010'], + ['February 05 2010'], + ['January 16 2010'] + ]; + + var correctDateSortingSorted2 = [ + ['January 01 2010'], + ['January 16 2010'], + ['February 05 2010'] + ]; + + tableTest( + 'Correct date sorting II', + ['date'], + correctDateSorting2, + correctDateSortingSorted2, + function ( $table ) { + mw.config.set( 'wgDefaultDateFormat', 'dmy' ); + + $table.tablesorter(); + $table.find( '.headerSort:eq(0)' ).click(); + } + ); - // Example 1: All cells except one cell without data-sort-value, - // which should be sorted at it's text content value. - $table = $( - '<table class="sortable"><thead><tr><th>Data</th></tr></thead>' + - '<tbody>' + - '<tr><td>Cheetah</td></tr>' + - '<tr><td data-sort-value="Apple">Bird</td></tr>' + - '<tr><td data-sort-value="Bananna">Ferret</td></tr>' + - '<tr><td data-sort-value="Drupe">Elephant</td></tr>' + - '<tr><td data-sort-value="Cherry">Dolphin</td></tr>' + - '</tbody></table>' - ); - $table.tablesorter().find( '.headerSort:eq(0)' ).click(); - - data = []; - $table.find( 'tbody > tr' ).each( function( i, tr ) { - $( tr ).find( 'td' ).each( function( i, td ) { - data.push( { - data: $( td ).data( 'sortValue' ), - text: $( td ).text() - } ); - }); - }); - - assert.deepEqual( data, [ - { - data: 'Apple', - text: 'Bird' - }, { - data: 'Bananna', - text: 'Ferret' - }, { - data: undefined, - text: 'Cheetah' - }, { - data: 'Cherry', - text: 'Dolphin' - }, { - data: 'Drupe', - text: 'Elephant' - } - ], 'Order matches expected order (based on data-sort-value attribute values)' ); - - // Example 2 - $table = $( - '<table class="sortable"><thead><tr><th>Data</th></tr></thead>' + - '<tbody>' + - '<tr><td>D</td></tr>' + - '<tr><td data-sort-value="E">A</td></tr>' + - '<tr><td>B</td></tr>' + - '<tr><td>G</td></tr>' + - '<tr><td data-sort-value="F">C</td></tr>' + - '</tbody></table>' - ); - $table.tablesorter().find( '.headerSort:eq(0)' ).click(); - - data = []; - $table.find( 'tbody > tr' ).each( function ( i, tr ) { - $( tr ).find( 'td' ).each( function ( i, td ) { - data.push( { - data: $( td ).data( 'sortValue' ), - text: $( td ).text() - } ); - }); - }); - - assert.deepEqual( data, [ - { - data: undefined, - text: 'B' - }, { - data: undefined, - text: 'D' - }, { - data: 'E', - text: 'A' - }, { - data: 'F', - text: 'C' - }, { - data: undefined, - text: 'G' - } - ], 'Order matches expected order (based on data-sort-value attribute values)' ); - - // Example 3: Test that live changes are used from data-sort-value, - // even if they change after the tablesorter is constructed (bug 38152). - $table = $( - '<table class="sortable"><thead><tr><th>Data</th></tr></thead>' + - '<tbody>' + - '<tr><td>D</td></tr>' + - '<tr><td data-sort-value="1">A</td></tr>' + - '<tr><td>B</td></tr>' + - '<tr><td data-sort-value="2">G</td></tr>' + - '<tr><td>C</td></tr>' + - '</tbody></table>' - ); - // initialize table sorter and sort once - $table - .tablesorter() - .find( '.headerSort:eq(0)' ).click(); - - // Change the sortValue data properties (bug 38152) - // - change data - $table.find( 'td:contains(A)' ).data( 'sortValue', 3 ); - // - add data - $table.find( 'td:contains(B)' ).data( 'sortValue', 1 ); - // - remove data, bring back attribute: 2 - $table.find( 'td:contains(G)' ).removeData( 'sortValue' ); - - // Now sort again (twice, so it is back at Ascending) - $table.find( '.headerSort:eq(0)' ).click(); - $table.find( '.headerSort:eq(0)' ).click(); - - data = []; - $table.find( 'tbody > tr' ).each( function( i, tr ) { - $( tr ).find( 'td' ).each( function( i, td ) { - data.push( { - data: $( td ).data( 'sortValue' ), - text: $( td ).text() - } ); - }); - }); - - assert.deepEqual( data, [ - { - data: 1, - text: "B" - }, { - data: 2, - text: "G" - }, { - data: 3, - text: "A" - }, { - data: undefined, - text: "C" - }, { - data: undefined, - text: "D" - } - ], 'Order matches expected order, using the current sortValue in $.data()' ); - -}); - -var numbers = [ - [ '12' ], - [ '7' ], - [ '13,000'], - [ '9' ], - [ '14' ], - [ '8.0' ] -]; -var numbersAsc = [ - [ '7' ], - [ '8.0' ], - [ '9' ], - [ '12' ], - [ '14' ], - [ '13,000'] -]; - -tableTest( 'bug 8115: sort numbers with commas (ascending)', - ['Numbers'], numbers, numbersAsc, - function( $table ) { + QUnit.test( 'Sorting images using alt text', 1, function ( assert ) { + var $table = $( + '<table class="sortable">' + + '<tr><th>THEAD</th></tr>' + + '<tr><td><img alt="2"/></td></tr>' + + '<tr><td>1</td></tr>' + + '</table>' + ); + $table.tablesorter().find( '.headerSort:eq(0)' ).click(); + + assert.equal( + $table.find( 'td' ).first().text(), + '1', + 'Applied correct sorting order' + ); + } ); + + QUnit.test( 'Sorting images using alt text (complex)', 1, function ( assert ) { + var $table = $( + '<table class="sortable">' + + '<tr><th>THEAD</th></tr>' + + '<tr><td><img alt="D" />A</td></tr>' + + '<tr><td>CC</td></tr>' + + '<tr><td><a><img alt="A" /></a>F</tr>' + + '<tr><td><img alt="A" /><strong>E</strong></tr>' + + '<tr><td><strong><img alt="A" />D</strong></tr>' + + '<tr><td><img alt="A" />C</tr>' + + '</table>' + ); + $table.tablesorter().find( '.headerSort:eq(0)' ).click(); + + assert.equal( + $table.find( 'td' ).text(), + 'CDEFCCA', + 'Applied correct sorting order' + ); + } ); + + QUnit.test( 'Sorting images using alt text (with format autodetection)', 1, function ( assert ) { + var $table = $( + '<table class="sortable">' + + '<tr><th>THEAD</th></tr>' + + '<tr><td><img alt="1" />7</td></tr>' + + '<tr><td>1<img alt="6" /></td></tr>' + + '<tr><td>5</td></tr>' + + '<tr><td>4</td></tr>' + + '</table>' + ); + $table.tablesorter().find( '.headerSort:eq(0)' ).click(); + + assert.equal( + $table.find( 'td' ).text(), + '4517', + 'Applied correct sorting order' + ); + } ); + + QUnit.test( 'bug 38911 - The row with the largest amount of columns should receive the sort indicators', 3, function ( assert ) { + var $table = $( + '<table class="sortable">' + + '<thead>' + + '<tr><th rowspan="2" id="A1">A1</th><th colspan="2">B2a</th></tr>' + + '<tr><th id="B2b">B2b</th><th id="C2b">C2b</th></tr>' + + '</thead>' + + '<tr><td>A</td><td>Aa</td><td>Ab</td></tr>' + + '<tr><td>B</td><td>Ba</td><td>Bb</td></tr>' + + '</table>' + ); $table.tablesorter(); - $table.find( '.headerSort:eq(0)' ).click(); - } -); -tableTest( 'bug 8115: sort numbers with commas (descending)', - ['Numbers'], numbers, reversed(numbersAsc), - function( $table ) { + assert.equal( + $table.find( '#A1' ).attr( 'class' ), + 'headerSort', + 'The first column of the first row should be sortable' + ); + assert.equal( + $table.find( '#B2b' ).attr( 'class' ), + 'headerSort', + 'The th element of the 2nd row of the 2nd column should be sortable' + ); + assert.equal( + $table.find( '#C2b' ).attr( 'class' ), + 'headerSort', + 'The th element of the 2nd row of the 3rd column should be sortable' + ); + } ); + + QUnit.test( 'rowspans in table headers should prefer the last row when rows are equal in length', 2, function ( assert ) { + var $table = $( + '<table class="sortable">' + + '<thead>' + + '<tr><th rowspan="2" id="A1">A1</th><th>B2a</th></tr>' + + '<tr><th id="B2b">B2b</th></tr>' + + '</thead>' + + '<tr><td>A</td><td>Aa</td></tr>' + + '<tr><td>B</td><td>Ba</td></tr>' + + '</table>' + ); $table.tablesorter(); - $table.find( '.headerSort:eq(0)' ).click().click(); - } -); -// TODO add numbers sorting tests for bug 8115 with a different language - -QUnit.test( 'bug 32888 - Tables inside a tableheader cell', 2, function ( assert ) { - var $table; - $table = $( - '<table class="sortable" id="mw-bug-32888">' + - '<tr><th>header<table id="mw-bug-32888-2">'+ - '<tr><th>1</th><th>2</th></tr>' + - '</table></th></tr>' + - '<tr><td>A</td></tr>' + - '<tr><td>B</td></tr>' + - '</table>' + + assert.equal( + $table.find( '#A1' ).attr( 'class' ), + 'headerSort', + 'The first column of the first row should be sortable' ); - $table.tablesorter(); + assert.equal( + $table.find( '#B2b' ).attr( 'class' ), + 'headerSort', + 'The th element of the 2nd row of the 2nd column should be sortable' + ); + } ); - assert.equal( - $table.find('> thead:eq(0) > tr > th.headerSort').length, - 1, - 'Child tables inside a headercell should not interfere with sortable headers (bug 32888)' - ); - assert.equal( - $( '#mw-bug-32888-2' ).find('th.headerSort').length, - 0, - 'The headers of child tables inside a headercell should not be sortable themselves (bug 32888)' + // bug 41889 - exploding rowspans in more complex cases + tableTestHTML( + 'Rowspan exploding with row headers', + '<table class="sortable">' + + '<thead><tr><th id="sortme">n</th><th>foo</th><th>bar</th><th>baz</th></tr></thead>' + + '<tbody>' + + '<tr><td>1</td><th rowspan="2">foo</th><td rowspan="2">bar</td><td>baz</td></tr>' + + '<tr><td>2</td><td>baz</td></tr>' + + '</tbody></table>', + [ + [ '1', 'foo', 'bar', 'baz' ], + [ '2', 'foo', 'bar', 'baz' ] + ] ); -}); + tableTestHTML( + 'Rowspan exploding with colspanned cells', + '<table class="sortable">' + + '<thead><tr><th id="sortme">n</th><th>foo</th><th>bar</th><th>baz</th></tr></thead>' + + '<tbody>' + + '<tr><td>1</td><td>foo</td><td>bar</td><td rowspan="2">baz</td></tr>' + + '<tr><td>2</td><td colspan="2">foobar</td></tr>' + + '</tbody></table>', + [ + [ '1', 'foo', 'bar', 'baz' ], + [ '2', 'foobar', 'baz' ] + ] + ); -var correctDateSorting1 = [ - ['01 January 2010'], - ['05 February 2010'], - ['16 January 2010'] -]; + tableTestHTML( + 'Rowspan exploding with colspanned cells (2)', + '<table class="sortable">' + + '<thead><tr><th id="sortme">n</th><th>foo</th><th>bar</th><th>baz</th><th>quux</th></tr></thead>' + + '<tbody>' + + '<tr><td>1</td><td>foo</td><td>bar</td><td rowspan="2">baz</td><td>quux</td></tr>' + + '<tr><td>2</td><td colspan="2">foobar</td><td>quux</td></tr>' + + '</tbody></table>', + [ + [ '1', 'foo', 'bar', 'baz', 'quux' ], + [ '2', 'foobar', 'baz', 'quux' ] + ] + ); -var correctDateSortingSorted1 = [ - ['01 January 2010'], - ['16 January 2010'], - ['05 February 2010'] -]; + tableTestHTML( + 'Rowspan exploding with rightmost rows spanning most', + '<table class="sortable">' + + '<thead><tr><th id="sortme">n</th><th>foo</th><th>bar</th></tr></thead>' + + '<tbody>' + + '<tr><td>1</td><td rowspan="2">foo</td><td rowspan="4">bar</td></tr>' + + '<tr><td>2</td></tr>' + + '<tr><td>3</td><td rowspan="2">foo</td></tr>' + + '<tr><td>4</td></tr>' + + '</tbody></table>', + [ + [ '1', 'foo', 'bar' ], + [ '2', 'foo', 'bar' ], + [ '3', 'foo', 'bar' ], + [ '4', 'foo', 'bar' ] + ] + ); -tableTest( - 'Correct date sorting I', - ['date'], - correctDateSorting1, - correctDateSortingSorted1, - function ( $table ) { - mw.config.set( 'wgDefaultDateFormat', 'mdy' ); + tableTestHTML( + 'Rowspan exploding with rightmost rows spanning most (2)', + '<table class="sortable">' + + '<thead><tr><th id="sortme">n</th><th>foo</th><th>bar</th><th>baz</th></tr></thead>' + + '<tbody>' + + '<tr><td>1</td><td rowspan="2">foo</td><td rowspan="4">bar</td><td>baz</td></tr>' + + '<tr><td>2</td><td>baz</td></tr>' + + '<tr><td>3</td><td rowspan="2">foo</td><td>baz</td></tr>' + + '<tr><td>4</td><td>baz</td></tr>' + + '</tbody></table>', + [ + [ '1', 'foo', 'bar', 'baz' ], + [ '2', 'foo', 'bar', 'baz' ], + [ '3', 'foo', 'bar', 'baz' ], + [ '4', 'foo', 'bar', 'baz' ] + ] + ); - $table.tablesorter(); - $table.find( '.headerSort:eq(0)' ).click(); - } -); - -var correctDateSorting2 = [ - ['January 01 2010'], - ['February 05 2010'], - ['January 16 2010'] -]; - -var correctDateSortingSorted2 = [ - ['January 01 2010'], - ['January 16 2010'], - ['February 05 2010'] -]; - -tableTest( - 'Correct date sorting II', - ['date'], - correctDateSorting2, - correctDateSortingSorted2, - function ( $table ) { - mw.config.set( 'wgDefaultDateFormat', 'dmy' ); + tableTestHTML( + 'Rowspan exploding with row-and-colspanned cells', + '<table class="sortable">' + + '<thead><tr><th id="sortme">n</th><th>foo1</th><th>foo2</th><th>bar</th><th>baz</th></tr></thead>' + + '<tbody>' + + '<tr><td>1</td><td rowspan="2">foo1</td><td rowspan="2">foo2</td><td rowspan="4">bar</td><td>baz</td></tr>' + + '<tr><td>2</td><td>baz</td></tr>' + + '<tr><td>3</td><td colspan="2" rowspan="2">foo</td><td>baz</td></tr>' + + '<tr><td>4</td><td>baz</td></tr>' + + '</tbody></table>', + [ + [ '1', 'foo1', 'foo2', 'bar', 'baz' ], + [ '2', 'foo1', 'foo2', 'bar', 'baz' ], + [ '3', 'foo', 'bar', 'baz' ], + [ '4', 'foo', 'bar', 'baz' ] + ] + ); - $table.tablesorter(); - $table.find( '.headerSort:eq(0)' ).click(); - } -); + tableTestHTML( + 'Rowspan exploding with uneven rowspan layout', + '<table class="sortable">' + + '<thead><tr><th id="sortme">n</th><th>foo1</th><th>foo2</th><th>foo3</th><th>bar</th><th>baz</th></tr></thead>' + + '<tbody>' + + '<tr><td>1</td><td rowspan="2">foo1</td><td rowspan="2">foo2</td><td rowspan="2">foo3</td><td>bar</td><td>baz</td></tr>' + + '<tr><td>2</td><td rowspan="3">bar</td><td>baz</td></tr>' + + '<tr><td>3</td><td rowspan="2">foo1</td><td rowspan="2">foo2</td><td rowspan="2">foo3</td><td>baz</td></tr>' + + '<tr><td>4</td><td>baz</td></tr>' + + '</tbody></table>', + [ + [ '1', 'foo1', 'foo2', 'foo3', 'bar', 'baz' ], + [ '2', 'foo1', 'foo2', 'foo3', 'bar', 'baz' ], + [ '3', 'foo1', 'foo2', 'foo3', 'bar', 'baz' ], + [ '4', 'foo1', 'foo2', 'foo3', 'bar', 'baz' ] + ] + ); }( jQuery, mediaWiki ) ); diff --git a/tests/qunit/suites/resources/jquery/jquery.textSelection.test.js b/tests/qunit/suites/resources/jquery/jquery.textSelection.test.js index f0a210f5..5fe23944 100644 --- a/tests/qunit/suites/resources/jquery/jquery.textSelection.test.js +++ b/tests/qunit/suites/resources/jquery/jquery.textSelection.test.js @@ -1,275 +1,275 @@ -QUnit.module( 'jquery.textSelection', QUnit.newMwEnvironment() ); - -/** - * Test factory for $.fn.textSelection( 'encapsulateText' ) - * - * @param options {object} associative array containing: - * description {string} - * input {string} - * output {string} - * start {int} starting char for selection - * end {int} ending char for selection - * params {object} add'l parameters for $().textSelection( 'encapsulateText' ) - */ -function encapsulateTest( options ) { - var opt = $.extend({ - description: '', - before: {}, - after: {}, - replace: {} - }, options); - - opt.before = $.extend({ - text: '', - start: 0, - end: 0 - }, opt.before); - opt.after = $.extend({ - text: '', - selected: null - }, opt.after); - - QUnit.test( opt.description, function ( assert ) { - var tests = 1; - if ( opt.after.selected !== null ) { - tests++; - } - QUnit.expect( tests ); - - var $textarea = $( '<textarea>' ); - - $( '#qunit-fixture' ).append( $textarea ); - - //$textarea.textSelection( 'setContents', opt.before.text); // this method is actually missing atm... - $textarea.val( opt.before.text ); // won't work with the WikiEditor iframe? - - var start = opt.before.start, - end = opt.before.end; - if ( window.opera ) { - // Compensate for Opera's craziness converting "\n" to "\r\n" and counting that as two chars - var newLinesBefore = opt.before.text.substring( 0, start ).split( "\n" ).length - 1, - newLinesInside = opt.before.text.substring( start, end ).split( "\n" ).length - 1; - start += newLinesBefore; - end += newLinesBefore + newLinesInside; - } - - var options = $.extend( {}, opt.replace ); // Clone opt.replace - options.selectionStart = start; - options.selectionEnd = end; - $textarea.textSelection( 'encapsulateSelection', options ); - - var text = $textarea.textSelection( 'getContents' ).replace( /\r\n/g, "\n" ); - - assert.equal( text, opt.after.text, 'Checking full text after encapsulation' ); - - if (opt.after.selected !== null) { - var selected = $textarea.textSelection( 'getSelection' ); - assert.equal( selected, opt.after.selected, 'Checking selected text after encapsulation.' ); - } +( function ( $ ) { + + QUnit.module( 'jquery.textSelection', QUnit.newMwEnvironment() ); + + /** + * Test factory for $.fn.textSelection( 'encapsulateText' ) + * + * @param options {object} associative array containing: + * description {string} + * input {string} + * output {string} + * start {int} starting char for selection + * end {int} ending char for selection + * params {object} add'l parameters for $().textSelection( 'encapsulateText' ) + */ + function encapsulateTest( options ) { + var opt = $.extend( { + description: '', + before: {}, + after: {}, + replace: {} + }, options ); + + opt.before = $.extend( { + text: '', + start: 0, + end: 0 + }, opt.before ); + opt.after = $.extend( { + text: '', + selected: null + }, opt.after ); + + QUnit.test( opt.description, function ( assert ) { + /*jshint onevar: false */ + var tests = 1; + if ( opt.after.selected !== null ) { + tests++; + } + QUnit.expect( tests ); + + var $textarea = $( '<textarea>' ); + + $( '#qunit-fixture' ).append( $textarea ); + + //$textarea.textSelection( 'setContents', opt.before.text); // this method is actually missing atm... + $textarea.val( opt.before.text ); // won't work with the WikiEditor iframe? + + var start = opt.before.start, + end = opt.before.end; + + var options = $.extend( {}, opt.replace ); // Clone opt.replace + options.selectionStart = start; + options.selectionEnd = end; + $textarea.textSelection( 'encapsulateSelection', options ); + var text = $textarea.textSelection( 'getContents' ).replace( /\r\n/g, '\n' ); + + assert.equal( text, opt.after.text, 'Checking full text after encapsulation' ); + + if ( opt.after.selected !== null ) { + var selected = $textarea.textSelection( 'getSelection' ); + assert.equal( selected, opt.after.selected, 'Checking selected text after encapsulation.' ); + } + + } ); + } + + var caretSample, + sig = { + pre: '--~~~~' + }, + bold = { + pre: '\'\'\'', + peri: 'Bold text', + post: '\'\'\'' + }, + h2 = { + pre: '== ', + peri: 'Heading 2', + post: ' ==', + regex: /^(\s*)(={1,6})(.*?)\2(\s*)$/, + regexReplace: '$1==$3==$4', + ownline: true + }, + ulist = { + pre: '* ', + peri: 'Bulleted list item', + post: '', + ownline: true, + splitlines: true + }; + + encapsulateTest( { + description: 'Adding sig to end of text', + before: { + text: 'Wikilove dude! ', + start: 15, + end: 15 + }, + after: { + text: 'Wikilove dude! --~~~~', + selected: '' + }, + replace: sig } ); -} - -var sig = { - 'pre': "--~~~~" -}, bold = { - pre: "'''", - peri: 'Bold text', - post: "'''" -}, h2 = { - 'pre': '== ', - 'peri': 'Heading 2', - 'post': ' ==', - 'regex': /^(\s*)(={1,6})(.*?)\2(\s*)$/, - 'regexReplace': "$1==$3==$4", - 'ownline': true -}, ulist = { - 'pre': "* ", - 'peri': 'Bulleted list item', - 'post': "", - 'ownline': true, - 'splitlines': true -}; - -encapsulateTest({ - description: "Adding sig to end of text", - before: { - text: "Wikilove dude! ", - start: 15, - end: 15 - }, - after: { - text: "Wikilove dude! --~~~~", - selected: "" - }, - replace: sig -}); - -encapsulateTest({ - description: "Adding bold to empty", - before: { - text: "", - start: 0, - end: 0 - }, - after: { - text: "'''Bold text'''", - selected: "Bold text" // selected because it's the default - }, - replace: bold -}); - -encapsulateTest({ - description: "Adding bold to existing text", - before: { - text: "Now is the time for all good men to come to the aid of their country", - start: 20, - end: 32 - }, - after: { - text: "Now is the time for '''all good men''' to come to the aid of their country", - selected: "" // empty because it's not the default' - }, - replace: bold -}); - -encapsulateTest({ - description: "ownline option: adding new h2", - before: { - text:"Before\nAfter", - start: 7, - end: 7 - }, - after: { - text: "Before\n== Heading 2 ==\nAfter", - selected: "Heading 2" - }, - replace: h2 -}); - -encapsulateTest({ - description: "ownline option: turn a whole line into new h2", - before: { - text:"Before\nMy heading\nAfter", - start: 7, - end: 17 - }, - after: { - text: "Before\n== My heading ==\nAfter", - selected: "" - }, - replace: h2 -}); - - -encapsulateTest({ - description: "ownline option: turn a partial line into new h2", - before: { - text:"BeforeMy headingAfter", - start: 6, - end: 16 - }, - after: { - text: "Before\n== My heading ==\nAfter", - selected: "" - }, - replace: h2 -}); - - -encapsulateTest({ - description: "splitlines option: no selection, insert new list item", - before: { - text: "Before\nAfter", - start: 7, - end: 7 - }, - after: { - text: "Before\n* Bulleted list item\nAfter" - }, - replace: ulist -}); - -encapsulateTest({ - description: "splitlines option: single partial line selection, insert new list item", - before: { - text: "BeforeMy List ItemAfter", - start: 6, - end: 18 - }, - after: { - text: "Before\n* My List Item\nAfter" - }, - replace: ulist -}); - -encapsulateTest({ - description: "splitlines option: multiple lines", - before: { - text: "Before\nFirst\nSecond\nThird\nAfter", - start: 7, - end: 25 - }, - after: { - text: "Before\n* First\n* Second\n* Third\nAfter" - }, - replace: ulist -}); - - -function caretTest( options ) { - QUnit.test( options.description, 2, function ( assert ) { - var $textarea = $( '<textarea>' ).text( options.text ); - - $( '#qunit-fixture' ).append( $textarea ); - - if ( options.mode === 'set' ) { - $textarea.textSelection('setSelection', { - start: options.start, - end: options.end - }); - } - - function among( actual, expected, message ) { - if ( $.isArray( expected ) ) { - assert.ok( $.inArray( actual, expected ) !== -1 , message + ' (got ' + actual + '; expected one of ' + expected.join(', ') + ')' ); - } else { - assert.equal( actual, expected, message ); + + encapsulateTest( { + description: 'Adding bold to empty', + before: { + text: '', + start: 0, + end: 0 + }, + after: { + text: '\'\'\'Bold text\'\'\'', + selected: 'Bold text' // selected because it's the default + }, + replace: bold + } ); + + encapsulateTest( { + description: 'Adding bold to existing text', + before: { + text: 'Now is the time for all good men to come to the aid of their country', + start: 20, + end: 32 + }, + after: { + text: 'Now is the time for \'\'\'all good men\'\'\' to come to the aid of their country', + selected: '' // empty because it's not the default' + }, + replace: bold + } ); + + encapsulateTest( { + description: 'ownline option: adding new h2', + before: { + text: 'Before\nAfter', + start: 7, + end: 7 + }, + after: { + text: 'Before\n== Heading 2 ==\nAfter', + selected: 'Heading 2' + }, + replace: h2 + } ); + + encapsulateTest( { + description: 'ownline option: turn a whole line into new h2', + before: { + text: 'Before\nMy heading\nAfter', + start: 7, + end: 17 + }, + after: { + text: 'Before\n== My heading ==\nAfter', + selected: '' + }, + replace: h2 + } ); + + + encapsulateTest( { + description: 'ownline option: turn a partial line into new h2', + before: { + text: 'BeforeMy headingAfter', + start: 6, + end: 16 + }, + after: { + text: 'Before\n== My heading ==\nAfter', + selected: '' + }, + replace: h2 + } ); + + + encapsulateTest( { + description: 'splitlines option: no selection, insert new list item', + before: { + text: 'Before\nAfter', + start: 7, + end: 7 + }, + after: { + text: 'Before\n* Bulleted list item\nAfter' + }, + replace: ulist + } ); + + encapsulateTest( { + description: 'splitlines option: single partial line selection, insert new list item', + before: { + text: 'BeforeMy List ItemAfter', + start: 6, + end: 18 + }, + after: { + text: 'Before\n* My List Item\nAfter' + }, + replace: ulist + } ); + + encapsulateTest( { + description: 'splitlines option: multiple lines', + before: { + text: 'Before\nFirst\nSecond\nThird\nAfter', + start: 7, + end: 25 + }, + after: { + text: 'Before\n* First\n* Second\n* Third\nAfter' + }, + replace: ulist + } ); + + + function caretTest( options ) { + QUnit.test( options.description, 2, function ( assert ) { + var pos, $textarea = $( '<textarea>' ).text( options.text ); + + $( '#qunit-fixture' ).append( $textarea ); + + if ( options.mode === 'set' ) { + $textarea.textSelection( 'setSelection', { + start: options.start, + end: options.end + } ); + } + + function among( actual, expected, message ) { + if ( $.isArray( expected ) ) { + assert.ok( $.inArray( actual, expected ) !== -1, message + ' (got ' + actual + '; expected one of ' + expected.join( ', ' ) + ')' ); + } else { + assert.equal( actual, expected, message ); + } } - } - - var pos = $textarea.textSelection('getCaretPosition', {startAndEnd: true}); - among(pos[0], options.start, 'Caret start should be where we set it.'); - among(pos[1], options.end, 'Caret end should be where we set it.'); - }); -} - -var caretSample = "Some big text that we like to work with. Nothing fancy... you know what I mean?"; - -/* - // @broken: Disabled per bug 34820 -caretTest({ - description: 'getCaretPosition with original/empty selection - bug 31847 with IE 6/7/8', - text: caretSample, - start: [0, caretSample.length], // Opera and Firefox (prior to FF 6.0) default caret to the end of the box (caretSample.length) - end: [0, caretSample.length], // Other browsers default it to the beginning (0), so check both. - mode: 'get' -}); -*/ - -caretTest({ - description: 'set/getCaretPosition with forced empty selection', - text: caretSample, - start: 7, - end: 7, - mode: 'set' -}); - -caretTest({ - description: 'set/getCaretPosition with small selection', - text: caretSample, - start: 6, - end: 11, - mode: 'set' -}); + pos = $textarea.textSelection( 'getCaretPosition', { startAndEnd: true } ); + among( pos[0], options.start, 'Caret start should be where we set it.' ); + among( pos[1], options.end, 'Caret end should be where we set it.' ); + } ); + } + + caretSample = 'Some big text that we like to work with. Nothing fancy... you know what I mean?'; + + /* + // @broken: Disabled per bug 34820 + caretTest({ + description: 'getCaretPosition with original/empty selection - bug 31847 with IE 6/7/8', + text: caretSample, + start: [0, caretSample.length], // Opera and Firefox (prior to FF 6.0) default caret to the end of the box (caretSample.length) + end: [0, caretSample.length], // Other browsers default it to the beginning (0), so check both. + mode: 'get' + }); + */ + + caretTest( { + description: 'set/getCaretPosition with forced empty selection', + text: caretSample, + start: 7, + end: 7, + mode: 'set' + } ); + + caretTest( { + description: 'set/getCaretPosition with small selection', + text: caretSample, + start: 6, + end: 11, + mode: 'set' + } ); +}( jQuery ) ); diff --git a/tests/qunit/suites/resources/mediawiki.api/mediawiki.api.parse.test.js b/tests/qunit/suites/resources/mediawiki.api/mediawiki.api.parse.test.js index 3d3f630f..2bed9b9f 100644 --- a/tests/qunit/suites/resources/mediawiki.api/mediawiki.api.parse.test.js +++ b/tests/qunit/suites/resources/mediawiki.api/mediawiki.api.parse.test.js @@ -1,26 +1,28 @@ -QUnit.module( 'mediawiki.api.parse', QUnit.newMwEnvironment() ); +( function ( mw, $ ) { + QUnit.module( 'mediawiki.api.parse', QUnit.newMwEnvironment() ); -QUnit.asyncTest( 'Hello world', function ( assert ) { - var api; - QUnit.expect( 6 ); + QUnit.asyncTest( 'Hello world', function ( assert ) { + var api; + QUnit.expect( 6 ); - api = new mw.Api(); + api = new mw.Api(); - api.parse( "'''Hello world'''" ) - .done( function ( html ) { - // Parse into a document fragment instead of comparing HTML, due to - // presence of Tidy influencing whitespace. - // Html also contains "NewPP report" comment. - var $res = $( '<div>' ).html( html ).children(), - res = $res.get( 0 ); - assert.equal( $res.length, 1, 'Response contains 1 element' ); - assert.equal( res.nodeName.toLowerCase(), 'p', 'Response is a paragraph' ); - assert.equal( $res.children().length, 1, 'Response has 1 child element' ); - assert.equal( $res.children().get( 0 ).nodeName.toLowerCase(), 'b', 'Child element is a bold tag' ); - // Trim since Tidy may or may not mess with the spacing here - assert.equal( $.trim( $res.text() ), 'Hello world', 'Response contains given text' ); - assert.equal( $res.find( 'b' ).text(), 'Hello world', 'Bold tag wraps the entire, same, text' ); + api.parse( '\'\'\'Hello world\'\'\'' ) + .done( function ( html ) { + // Parse into a document fragment instead of comparing HTML, due to + // presence of Tidy influencing whitespace. + // Html also contains "NewPP report" comment. + var $res = $( '<div>' ).html( html ).children(), + res = $res.get( 0 ); + assert.equal( $res.length, 1, 'Response contains 1 element' ); + assert.equal( res.nodeName.toLowerCase(), 'p', 'Response is a paragraph' ); + assert.equal( $res.children().length, 1, 'Response has 1 child element' ); + assert.equal( $res.children().get( 0 ).nodeName.toLowerCase(), 'b', 'Child element is a bold tag' ); + // Trim since Tidy may or may not mess with the spacing here + assert.equal( $.trim( $res.text() ), 'Hello world', 'Response contains given text' ); + assert.equal( $res.find( 'b' ).text(), 'Hello world', 'Bold tag wraps the entire, same, text' ); - QUnit.start(); - }); -}); + QUnit.start(); + } ); + } ); +}( mediaWiki, jQuery ) ); diff --git a/tests/qunit/suites/resources/mediawiki.api/mediawiki.api.test.js b/tests/qunit/suites/resources/mediawiki.api/mediawiki.api.test.js index 79bd7306..9eda75ce 100644 --- a/tests/qunit/suites/resources/mediawiki.api/mediawiki.api.test.js +++ b/tests/qunit/suites/resources/mediawiki.api/mediawiki.api.test.js @@ -1,59 +1,61 @@ -QUnit.module( 'mediawiki.api', QUnit.newMwEnvironment() ); +( function ( mw ) { + QUnit.module( 'mediawiki.api', QUnit.newMwEnvironment() ); -QUnit.asyncTest( 'Basic functionality', function ( assert ) { - var api, d1, d2, d3; - QUnit.expect( 3 ); + QUnit.asyncTest( 'Basic functionality', function ( assert ) { + var api, d1, d2, d3; + QUnit.expect( 3 ); - api = new mw.Api(); + api = new mw.Api(); - d1 = api.get( {} ) - .done( function ( data ) { - assert.deepEqual( data, [], 'If request succeeds without errors, resolve deferred' ); - }); + d1 = api.get( {} ) + .done( function ( data ) { + assert.deepEqual( data, [], 'If request succeeds without errors, resolve deferred' ); + } ); - d2 = api.get({ + d2 = api.get( { action: 'doesntexist' - }) - .fail( function ( errorCode, details ) { - assert.equal( errorCode, 'unknown_action', 'API error (e.g. "unknown_action") should reject the deferred' ); - }); - - d3 = api.post( {} ) - .done( function ( data ) { - assert.deepEqual( data, [], 'Simple POST request' ); - }); - - // After all are completed, continue the test suite. - QUnit.whenPromisesComplete( d1, d2, d3 ).always( function () { - QUnit.start(); - }); -}); - -QUnit.asyncTest( 'Deprecated callback methods', function ( assert ) { - var api, d1, d2, d3; - QUnit.expect( 3 ); - - api = new mw.Api(); - - d1 = api.get( {}, function () { - assert.ok( true, 'Function argument treated as success callback.' ); - }); - - d2 = api.get( {}, { - ok: function ( data ) { - assert.ok( true, '"ok" property treated as success callback.' ); - } - }); - - d3 = api.get({ + } ) + .fail( function ( errorCode ) { + assert.equal( errorCode, 'unknown_action', 'API error (e.g. "unknown_action") should reject the deferred' ); + } ); + + d3 = api.post( {} ) + .done( function ( data ) { + assert.deepEqual( data, [], 'Simple POST request' ); + } ); + + // After all are completed, continue the test suite. + QUnit.whenPromisesComplete( d1, d2, d3 ).always( function () { + QUnit.start(); + } ); + } ); + + QUnit.asyncTest( 'Deprecated callback methods', function ( assert ) { + var api, d1, d2, d3; + QUnit.expect( 3 ); + + api = new mw.Api(); + + d1 = api.get( {}, function () { + assert.ok( true, 'Function argument treated as success callback.' ); + } ); + + d2 = api.get( {}, { + ok: function () { + assert.ok( true, '"ok" property treated as success callback.' ); + } + } ); + + d3 = api.get( { action: 'doesntexist' }, { - err: function ( data ) { - assert.ok( true, '"err" property treated as error callback.' ); - } - }); - - QUnit.whenPromisesComplete( d1, d2, d3 ).always( function () { - QUnit.start(); - }); -}); + err: function () { + assert.ok( true, '"err" property treated as error callback.' ); + } + } ); + + QUnit.whenPromisesComplete( d1, d2, d3 ).always( function () { + QUnit.start(); + } ); + } ); +}( mediaWiki ) ); diff --git a/tests/qunit/suites/resources/mediawiki.special/mediawiki.special.recentchanges.test.js b/tests/qunit/suites/resources/mediawiki.special/mediawiki.special.recentchanges.test.js index 7fe7baf8..ee854aef 100644 --- a/tests/qunit/suites/resources/mediawiki.special/mediawiki.special.recentchanges.test.js +++ b/tests/qunit/suites/resources/mediawiki.special/mediawiki.special.recentchanges.test.js @@ -1,62 +1,63 @@ -QUnit.module( 'mediawiki.special.recentchanges', QUnit.newMwEnvironment() ); - -// TODO: verify checkboxes == [ 'nsassociated', 'nsinvert' ] - -QUnit.test( '"all" namespace disable checkboxes', function ( assert ) { - - // from Special:Recentchanges - var select = - '<select id="namespace" name="namespace" class="namespaceselector">' - + '<option value="" selected="selected">all</option>' - + '<option value="0">(Main)</option>' - + '<option value="1">Talk</option>' - + '<option value="2">User</option>' - + '<option value="3">User talk</option>' - + '<option value="4">ProjectName</option>' - + '<option value="5">ProjectName talk</option>' - + '</select>' - + '<input name="invert" type="checkbox" value="1" id="nsinvert" title="no title" />' - + '<label for="nsinvert" title="no title">Invert selection</label>' - + '<input name="associated" type="checkbox" value="1" id="nsassociated" title="no title" />' - + '<label for="nsassociated" title="no title">Associated namespace</label>' - + '<input type="submit" value="Go" />' - + '<input type="hidden" value="Special:RecentChanges" name="title" />' - ; - - var $env = $( '<div>' ).html( select ).appendTo( 'body' ); - - // TODO abstract the double strictEquals - - // At first checkboxes are enabled - assert.strictEqual( $( '#nsinvert' ).prop( 'disabled' ), false ); - assert.strictEqual( $( '#nsassociated' ).prop( 'disabled' ), false ); - - // Initiate the recentchanges module - mw.special.recentchanges.init(); - - // By default - assert.strictEqual( $( '#nsinvert' ).prop( 'disabled' ), true ); - assert.strictEqual( $( '#nsassociated' ).prop( 'disabled' ), true ); - - // select second option... - var $options = $( '#namespace' ).find( 'option' ); - $options.eq(0).removeProp( 'selected' ); - $options.eq(1).prop( 'selected', true ); - $( '#namespace' ).change(); - - // ... and checkboxes should be enabled again - assert.strictEqual( $( '#nsinvert' ).prop( 'disabled' ), false ); - assert.strictEqual( $( '#nsassociated' ).prop( 'disabled' ), false ); - - // select first option ( 'all' namespace)... - $options.eq(1).removeProp( 'selected' ); - $options.eq(0).prop( 'selected', true ); - $( '#namespace' ).change(); - - // ... and checkboxes should now be disabled - assert.strictEqual( $( '#nsinvert' ).prop( 'disabled' ), true ); - assert.strictEqual( $( '#nsassociated' ).prop( 'disabled' ), true ); - - // DOM cleanup - $env.remove(); -}); +( function ( mw, $ ) { + QUnit.module( 'mediawiki.special.recentchanges', QUnit.newMwEnvironment() ); + + // TODO: verify checkboxes == [ 'nsassociated', 'nsinvert' ] + + QUnit.test( '"all" namespace disable checkboxes', 8, function ( assert ) { + var selectHtml, $env, $options; + + // from Special:Recentchanges + selectHtml = '<select id="namespace" name="namespace" class="namespaceselector">' + + '<option value="" selected="selected">all</option>' + + '<option value="0">(Main)</option>' + + '<option value="1">Talk</option>' + + '<option value="2">User</option>' + + '<option value="3">User talk</option>' + + '<option value="4">ProjectName</option>' + + '<option value="5">ProjectName talk</option>' + + '</select>' + + '<input name="invert" type="checkbox" value="1" id="nsinvert" title="no title" />' + + '<label for="nsinvert" title="no title">Invert selection</label>' + + '<input name="associated" type="checkbox" value="1" id="nsassociated" title="no title" />' + + '<label for="nsassociated" title="no title">Associated namespace</label>' + + '<input type="submit" value="Go" />' + + '<input type="hidden" value="Special:RecentChanges" name="title" />'; + + $env = $( '<div>' ).html( selectHtml ).appendTo( 'body' ); + + // TODO abstract the double strictEquals + + // At first checkboxes are enabled + assert.strictEqual( $( '#nsinvert' ).prop( 'disabled' ), false ); + assert.strictEqual( $( '#nsassociated' ).prop( 'disabled' ), false ); + + // Initiate the recentchanges module + mw.special.recentchanges.init(); + + // By default + assert.strictEqual( $( '#nsinvert' ).prop( 'disabled' ), true ); + assert.strictEqual( $( '#nsassociated' ).prop( 'disabled' ), true ); + + // select second option... + $options = $( '#namespace' ).find( 'option' ); + $options.eq( 0 ).removeProp( 'selected' ); + $options.eq( 1 ).prop( 'selected', true ); + $( '#namespace' ).change(); + + // ... and checkboxes should be enabled again + assert.strictEqual( $( '#nsinvert' ).prop( 'disabled' ), false ); + assert.strictEqual( $( '#nsassociated' ).prop( 'disabled' ), false ); + + // select first option ( 'all' namespace)... + $options.eq( 1 ).removeProp( 'selected' ); + $options.eq( 0 ).prop( 'selected', true ); + $( '#namespace' ).change(); + + // ... and checkboxes should now be disabled + assert.strictEqual( $( '#nsinvert' ).prop( 'disabled' ), true ); + assert.strictEqual( $( '#nsassociated' ).prop( 'disabled' ), true ); + + // DOM cleanup + $env.remove(); + } ); +}( mediaWiki, jQuery ) ); diff --git a/tests/qunit/suites/resources/mediawiki/mediawiki.Title.test.js b/tests/qunit/suites/resources/mediawiki/mediawiki.Title.test.js index a736e121..ab96f753 100644 --- a/tests/qunit/suites/resources/mediawiki/mediawiki.Title.test.js +++ b/tests/qunit/suites/resources/mediawiki/mediawiki.Title.test.js @@ -1,200 +1,416 @@ -( function () { - -// mw.Title relies on these three config vars -// Restore them after each test run -var config = { - "wgFormattedNamespaces": { - "-2": "Media", - "-1": "Special", - "0": "", - "1": "Talk", - "2": "User", - "3": "User talk", - "4": "Wikipedia", - "5": "Wikipedia talk", - "6": "File", - "7": "File talk", - "8": "MediaWiki", - "9": "MediaWiki talk", - "10": "Template", - "11": "Template talk", - "12": "Help", - "13": "Help talk", - "14": "Category", - "15": "Category talk", - // testing custom / localized namespace - "100": "Penguins" +( function ( mw, $ ) { + // mw.Title relies on these three config vars + // Restore them after each test run + var config = { + wgFormattedNamespaces: { + '-2': 'Media', + '-1': 'Special', + 0: '', + 1: 'Talk', + 2: 'User', + 3: 'User talk', + 4: 'Wikipedia', + 5: 'Wikipedia talk', + 6: 'File', + 7: 'File talk', + 8: 'MediaWiki', + 9: 'MediaWiki talk', + 10: 'Template', + 11: 'Template talk', + 12: 'Help', + 13: 'Help talk', + 14: 'Category', + 15: 'Category talk', + // testing custom / localized namespace + 100: 'Penguins' + }, + wgNamespaceIds: { + /*jshint camelcase: false */ + media: -2, + special: -1, + '': 0, + talk: 1, + user: 2, + user_talk: 3, + wikipedia: 4, + wikipedia_talk: 5, + file: 6, + file_talk: 7, + mediawiki: 8, + mediawiki_talk: 9, + template: 10, + template_talk: 11, + help: 12, + help_talk: 13, + category: 14, + category_talk: 15, + image: 6, + image_talk: 7, + project: 4, + project_talk: 5, + /* testing custom / alias */ + penguins: 100, + antarctic_waterfowl: 100 + }, + wgCaseSensitiveNamespaces: [] }, - "wgNamespaceIds": { - "media": -2, - "special": -1, - "": 0, - "talk": 1, - "user": 2, - "user_talk": 3, - "wikipedia": 4, - "wikipedia_talk": 5, - "file": 6, - "file_talk": 7, - "mediawiki": 8, - "mediawiki_talk": 9, - "template": 10, - "template_talk": 11, - "help": 12, - "help_talk": 13, - "category": 14, - "category_talk": 15, - "image": 6, - "image_talk": 7, - "project": 4, - "project_talk": 5, - /* testing custom / alias */ - "penguins": 100, - "antarctic_waterfowl": 100 + repeat = function ( input, multiplier ) { + return new Array( multiplier + 1 ).join( input ); }, - "wgCaseSensitiveNamespaces": [] -}; + cases = { + // See also TitleTest.php#testSecureAndSplit + valid: [ + 'Sandbox', + 'A "B"', + 'A \'B\'', + '.com', + '~', + '"', + '\'', + 'Talk:Sandbox', + 'Talk:Foo:Sandbox', + 'File:Example.svg', + 'File_talk:Example.svg', + 'Foo/.../Sandbox', + 'Sandbox/...', + 'A~~', + // Length is 256 total, but only title part matters + 'Category:' + repeat( 'x', 248 ), + repeat( 'x', 252 ) + ], + invalid: [ + '', + '__ __', + ' __ ', + // Bad characters forbidden regardless of wgLegalTitleChars + 'A [ B', + 'A ] B', + 'A { B', + 'A } B', + 'A < B', + 'A > B', + 'A | B', + // URL encoding + 'A%20B', + 'A%23B', + 'A%2523B', + // XML/HTML character entity references + // Note: The ones with # are commented out as those are interpreted as fragment and + // as such end up being valid. + 'A é B', + //'A é B', + //'A é B', + // Subject of NS_TALK does not roundtrip to NS_MAIN + 'Talk:File:Example.svg', + // Directory navigation + '.', + '..', + './Sandbox', + '../Sandbox', + 'Foo/./Sandbox', + 'Foo/../Sandbox', + 'Sandbox/.', + 'Sandbox/..', + // Tilde + 'A ~~~ Name', + 'A ~~~~ Signature', + 'A ~~~~~ Timestamp', + repeat( 'x', 256 ), + // Extension separation is a js invention, for length + // purposes it is part of the title + repeat( 'x', 252 ) + '.json', + // Namespace prefix without actual title + // ':', // bug 54044 + 'Talk:', + 'Category: ', + 'Category: #bar' + ] + }; + + QUnit.module( 'mediawiki.Title', QUnit.newMwEnvironment( { config: config } ) ); + + QUnit.test( 'constructor', cases.invalid.length, function ( assert ) { + var i, title; + for ( i = 0; i < cases.valid.length; i++ ) { + title = new mw.Title( cases.valid[i] ); + } + for ( i = 0; i < cases.invalid.length; i++ ) { + /*jshint loopfunc:true */ + title = cases.invalid[i]; + assert.throws( function () { + return new mw.Title( title ); + }, cases.invalid[i] ); + } + } ); + + QUnit.test( 'newFromText', cases.valid.length + cases.invalid.length, function ( assert ) { + var i; + for ( i = 0; i < cases.valid.length; i++ ) { + assert.equal( + $.type( mw.Title.newFromText( cases.valid[i] ) ), + 'object', + cases.valid[i] + ); + } + for ( i = 0; i < cases.invalid.length; i++ ) { + assert.equal( + $.type( mw.Title.newFromText( cases.invalid[i] ) ), + 'null', + cases.invalid[i] + ); + } + } ); + + QUnit.test( 'Basic parsing', 12, function ( assert ) { + var title; + title = new mw.Title( 'File:Foo_bar.JPG' ); + + assert.equal( title.getNamespaceId(), 6 ); + assert.equal( title.getNamespacePrefix(), 'File:' ); + assert.equal( title.getName(), 'Foo_bar' ); + assert.equal( title.getNameText(), 'Foo bar' ); + assert.equal( title.getExtension(), 'JPG' ); + assert.equal( title.getDotExtension(), '.JPG' ); + assert.equal( title.getMain(), 'Foo_bar.JPG' ); + assert.equal( title.getMainText(), 'Foo bar.JPG' ); + assert.equal( title.getPrefixedDb(), 'File:Foo_bar.JPG' ); + assert.equal( title.getPrefixedText(), 'File:Foo bar.JPG' ); + + title = new mw.Title( 'Foo#bar' ); + assert.equal( title.getPrefixedText(), 'Foo' ); + assert.equal( title.getFragment(), 'bar' ); + } ); + + QUnit.test( 'Transformation', 11, function ( assert ) { + var title; + + title = new mw.Title( 'File:quux pif.jpg' ); + assert.equal( title.getNameText(), 'Quux pif', 'First character of title' ); + + title = new mw.Title( 'File:Glarg_foo_glang.jpg' ); + assert.equal( title.getNameText(), 'Glarg foo glang', 'Underscores' ); + + title = new mw.Title( 'User:ABC.DEF' ); + assert.equal( title.toText(), 'User:ABC.DEF', 'Round trip text' ); + assert.equal( title.getNamespaceId(), 2, 'Parse canonical namespace prefix' ); + + title = new mw.Title( 'Image:quux pix.jpg' ); + assert.equal( title.getNamespacePrefix(), 'File:', 'Transform alias to canonical namespace' ); + + title = new mw.Title( 'uSEr:hAshAr' ); + assert.equal( title.toText(), 'User:HAshAr' ); + assert.equal( title.getNamespaceId(), 2, 'Case-insensitive namespace prefix' ); + + // Don't ask why, it's the way the backend works. One space is kept of each set. + title = new mw.Title( 'Foo __ \t __ bar' ); + assert.equal( title.getMain(), 'Foo_bar', 'Merge multiple types of whitespace/underscores into a single underscore' ); + + // Regression test: Previously it would only detect an extension if there is no space after it + title = new mw.Title( 'Example.js ' ); + assert.equal( title.getExtension(), 'js', 'Space after an extension is stripped' ); + + title = new mw.Title( 'Example#foo' ); + assert.equal( title.getFragment(), 'foo', 'Fragment' ); + + title = new mw.Title( 'Example#_foo_bar baz_' ); + assert.equal( title.getFragment(), ' foo bar baz', 'Fragment' ); + } ); + + QUnit.test( 'Namespace detection and conversion', 10, function ( assert ) { + var title; + + title = new mw.Title( 'File:User:Example' ); + assert.equal( title.getNamespaceId(), 6, 'Titles can contain namespace prefixes, which are otherwise ignored' ); + + title = new mw.Title( 'Example', 6 ); + assert.equal( title.getNamespaceId(), 6, 'Default namespace passed is used' ); + + title = new mw.Title( 'User:Example', 6 ); + assert.equal( title.getNamespaceId(), 2, 'Included namespace prefix overrides the given default' ); + + title = new mw.Title( ':Example', 6 ); + assert.equal( title.getNamespaceId(), 0, 'Colon forces main namespace' ); + + title = new mw.Title( 'something.PDF', 6 ); + assert.equal( title.toString(), 'File:Something.PDF' ); + + title = new mw.Title( 'NeilK', 3 ); + assert.equal( title.toString(), 'User_talk:NeilK' ); + assert.equal( title.toText(), 'User talk:NeilK' ); + + title = new mw.Title( 'Frobisher', 100 ); + assert.equal( title.toString(), 'Penguins:Frobisher' ); + + title = new mw.Title( 'antarctic_waterfowl:flightless_yet_cute.jpg' ); + assert.equal( title.toString(), 'Penguins:Flightless_yet_cute.jpg' ); + + title = new mw.Title( 'Penguins:flightless_yet_cute.jpg' ); + assert.equal( title.toString(), 'Penguins:Flightless_yet_cute.jpg' ); + } ); + + QUnit.test( 'Throw error on invalid title', 1, function ( assert ) { + assert.throws( function () { + return new mw.Title( '' ); + }, 'Throw error on empty string' ); + } ); + + QUnit.test( 'Case-sensivity', 3, function ( assert ) { + var title; + + // Default config + mw.config.set( 'wgCaseSensitiveNamespaces', [] ); + + title = new mw.Title( 'article' ); + assert.equal( title.toString(), 'Article', 'Default config: No sensitive namespaces by default. First-letter becomes uppercase' ); + + // $wgCapitalLinks = false; + mw.config.set( 'wgCaseSensitiveNamespaces', [0, -2, 1, 4, 5, 6, 7, 10, 11, 12, 13, 14, 15] ); + + title = new mw.Title( 'article' ); + assert.equal( title.toString(), 'article', '$wgCapitalLinks=false: Article namespace is sensitive, first-letter case stays lowercase' ); + + title = new mw.Title( 'john', 2 ); + assert.equal( title.toString(), 'User:John', '$wgCapitalLinks=false: User namespace is insensitive, first-letter becomes uppercase' ); + } ); + + QUnit.test( 'toString / toText', 2, function ( assert ) { + var title = new mw.Title( 'Some random page' ); -QUnit.module( 'mediawiki.Title', QUnit.newMwEnvironment({ config: config }) ); + assert.equal( title.toString(), title.getPrefixedDb() ); + assert.equal( title.toText(), title.getPrefixedText() ); + } ); - -QUnit.test( 'Transformation', 8, function ( assert ) { - var title; - - title = new mw.Title( 'File:quux pif.jpg' ); - assert.equal( title.getName(), 'Quux_pif' ); - - title = new mw.Title( 'File:Glarg_foo_glang.jpg' ); - assert.equal( title.getNameText(), 'Glarg foo glang' ); - - title = new mw.Title( 'User:ABC.DEF' ); - assert.equal( title.toText(), 'User:ABC.DEF' ); - assert.equal( title.getNamespaceId(), 2 ); - assert.equal( title.getNamespacePrefix(), 'User:' ); - - title = new mw.Title( 'uSEr:hAshAr' ); - assert.equal( title.toText(), 'User:HAshAr' ); - assert.equal( title.getNamespaceId(), 2 ); - - title = new mw.Title( ' MediaWiki: Foo bar .js ' ); - // Don't ask why, it's the way the backend works. One space is kept of each set - assert.equal( title.getName(), 'Foo_bar_.js', "Merge multiple spaces to a single space." ); -}); - -QUnit.test( 'Main text for filename', 8, function ( assert ) { - var title = new mw.Title( 'File:foo_bar.JPG' ); - - assert.equal( title.getNamespaceId(), 6 ); - assert.equal( title.getNamespacePrefix(), 'File:' ); - assert.equal( title.getName(), 'Foo_bar' ); - assert.equal( title.getNameText(), 'Foo bar' ); - assert.equal( title.getMain(), 'Foo_bar.JPG' ); - assert.equal( title.getMainText(), 'Foo bar.JPG' ); - assert.equal( title.getExtension(), 'JPG' ); - assert.equal( title.getDotExtension(), '.JPG' ); -}); - -QUnit.test( 'Namespace detection and conversion', 6, function ( assert ) { - var title; - - title = new mw.Title( 'something.PDF', 6 ); - assert.equal( title.toString(), 'File:Something.PDF' ); - - title = new mw.Title( 'NeilK', 3 ); - assert.equal( title.toString(), 'User_talk:NeilK' ); - assert.equal( title.toText(), 'User talk:NeilK' ); - - title = new mw.Title( 'Frobisher', 100 ); - assert.equal( title.toString(), 'Penguins:Frobisher' ); - - title = new mw.Title( 'antarctic_waterfowl:flightless_yet_cute.jpg' ); - assert.equal( title.toString(), 'Penguins:Flightless_yet_cute.jpg' ); - - title = new mw.Title( 'Penguins:flightless_yet_cute.jpg' ); - assert.equal( title.toString(), 'Penguins:Flightless_yet_cute.jpg' ); -}); - -QUnit.test( 'Throw error on invalid title', 1, function ( assert ) { - assert.throws(function () { - var title = new mw.Title( '' ); - }, 'Throw error on empty string' ); -}); - -QUnit.test( 'Case-sensivity', 3, function ( assert ) { - var title; - - // Default config - mw.config.set( 'wgCaseSensitiveNamespaces', [] ); - - title = new mw.Title( 'article' ); - assert.equal( title.toString(), 'Article', 'Default config: No sensitive namespaces by default. First-letter becomes uppercase' ); - - // $wgCapitalLinks = false; - mw.config.set( 'wgCaseSensitiveNamespaces', [0, -2, 1, 4, 5, 6, 7, 10, 11, 12, 13, 14, 15] ); - - title = new mw.Title( 'article' ); - assert.equal( title.toString(), 'article', '$wgCapitalLinks=false: Article namespace is sensitive, first-letter case stays lowercase' ); - - title = new mw.Title( 'john', 2 ); - assert.equal( title.toString(), 'User:John', '$wgCapitalLinks=false: User namespace is insensitive, first-letter becomes uppercase' ); -}); - -QUnit.test( 'toString / toText', 2, function ( assert ) { - var title = new mw.Title( 'Some random page' ); - - assert.equal( title.toString(), title.getPrefixedDb() ); - assert.equal( title.toText(), title.getPrefixedText() ); -}); - -QUnit.test( 'getExtension', 7, function ( assert ) { - - function extTest( pagename, ext, description ) { - var title = new mw.Title( pagename ); - assert.equal( title.getExtension(), ext, description || pagename ); - } - - extTest( 'MediaWiki:Vector.js', 'js' ); - extTest( 'User:Example/common.css', 'css' ); - extTest( 'File:Example.longextension', 'longextension', 'Extension parsing not limited (bug 36151)' ); - extTest( 'Example/information.json', 'json', 'Extension parsing not restricted from any namespace' ); - extTest( 'Foo.', null, 'Trailing dot is not an extension' ); - extTest( 'Foo..', null, 'Trailing dots are not an extension' ); - extTest( 'Foo.a.', null, 'Page name with dots and ending in a dot does not have an extension' ); - - // @broken: Throws an exception - // extTest( '.NET', null, 'Leading dot is (or is not?) an extension' ); -}); - -QUnit.test( 'exists', 3, function ( assert ) { - var title; - - // Empty registry, checks default to null - - title = new mw.Title( 'Some random page', 4 ); - assert.strictEqual( title.exists(), null, 'Return null with empty existance registry' ); - - // Basic registry, checks default to boolean - mw.Title.exist.set( ['Does_exist', 'User_talk:NeilK', 'Wikipedia:Sandbox_rules'], true ); - mw.Title.exist.set( ['Does_not_exist', 'User:John', 'Foobar'], false ); - - title = new mw.Title( 'Project:Sandbox rules' ); - assert.assertTrue( title.exists(), 'Return true for page titles marked as existing' ); - title = new mw.Title( 'Foobar' ); - assert.assertFalse( title.exists(), 'Return false for page titles marked as nonexistent' ); - -}); - -QUnit.test( 'getUrl', 2, function ( assert ) { - var title; - - // Config - mw.config.set( 'wgArticlePath', '/wiki/$1' ); - - title = new mw.Title( 'Foobar' ); - assert.equal( title.getUrl(), '/wiki/Foobar', 'Basic functionally, toString passing to wikiGetlink' ); - - title = new mw.Title( 'John Doe', 3 ); - assert.equal( title.getUrl(), '/wiki/User_talk:John_Doe', 'Escaping in title and namespace for urls' ); -}); - -}() );
\ No newline at end of file + QUnit.test( 'getExtension', 7, function ( assert ) { + function extTest( pagename, ext, description ) { + var title = new mw.Title( pagename ); + assert.equal( title.getExtension(), ext, description || pagename ); + } + + extTest( 'MediaWiki:Vector.js', 'js' ); + extTest( 'User:Example/common.css', 'css' ); + extTest( 'File:Example.longextension', 'longextension', 'Extension parsing not limited (bug 36151)' ); + extTest( 'Example/information.json', 'json', 'Extension parsing not restricted from any namespace' ); + extTest( 'Foo.', null, 'Trailing dot is not an extension' ); + extTest( 'Foo..', null, 'Trailing dots are not an extension' ); + extTest( 'Foo.a.', null, 'Page name with dots and ending in a dot does not have an extension' ); + + // @broken: Throws an exception + // extTest( '.NET', null, 'Leading dot is (or is not?) an extension' ); + } ); + + QUnit.test( 'exists', 3, function ( assert ) { + var title; + + // Empty registry, checks default to null + + title = new mw.Title( 'Some random page', 4 ); + assert.strictEqual( title.exists(), null, 'Return null with empty existance registry' ); + + // Basic registry, checks default to boolean + mw.Title.exist.set( ['Does_exist', 'User_talk:NeilK', 'Wikipedia:Sandbox_rules'], true ); + mw.Title.exist.set( ['Does_not_exist', 'User:John', 'Foobar'], false ); + + title = new mw.Title( 'Project:Sandbox rules' ); + assert.assertTrue( title.exists(), 'Return true for page titles marked as existing' ); + title = new mw.Title( 'Foobar' ); + assert.assertFalse( title.exists(), 'Return false for page titles marked as nonexistent' ); + + } ); + + QUnit.test( 'getUrl', 2, function ( assert ) { + var title; + + // Config + mw.config.set( 'wgArticlePath', '/wiki/$1' ); + + title = new mw.Title( 'Foobar' ); + assert.equal( title.getUrl(), '/wiki/Foobar', 'Basic functionally, getUrl uses mw.util.getUrl' ); + + title = new mw.Title( 'John Doe', 3 ); + assert.equal( title.getUrl(), '/wiki/User_talk:John_Doe', 'Escaping in title and namespace for urls' ); + } ); + + QUnit.test( 'newFromImg', 28, function ( assert ) { + var title, i, thisCase, prefix, + cases = [ + { + url: '/wiki/images/thumb/9/91/Anticlockwise_heliotrope%27s.jpg/99px-Anticlockwise_heliotrope%27s.jpg', + typeOfUrl: 'Normal hashed directory thumbnail', + nameText: 'Anticlockwise heliotrope\'s', + prefixedText: 'File:Anticlockwise heliotrope\'s.jpg' + }, + + { + url: '//upload.wikimedia.org/wikipedia/commons/thumb/8/80/Wikipedia-logo-v2.svg/150px-Wikipedia-logo-v2.svg.png', + typeOfUrl: 'Commons thumbnail', + nameText: 'Wikipedia-logo-v2', + prefixedText: 'File:Wikipedia-logo-v2.svg' + }, + + { + url: '/wiki/images/9/91/Anticlockwise_heliotrope%27s.jpg', + typeOfUrl: 'Full image', + nameText: 'Anticlockwise heliotrope\'s', + prefixedText: 'File:Anticlockwise heliotrope\'s.jpg' + }, + + { + url: 'http://localhost/thumb.php?f=Stuffless_Figaro%27s.jpg&width=180', + typeOfUrl: 'thumb.php-based thumbnail', + nameText: 'Stuffless Figaro\'s', + prefixedText: 'File:Stuffless Figaro\'s.jpg' + }, + + { + url: '/wikipedia/commons/thumb/Wikipedia-logo-v2.svg/150px-Wikipedia-logo-v2.svg.png', + typeOfUrl: 'Commons unhashed thumbnail', + nameText: 'Wikipedia-logo-v2', + prefixedText: 'File:Wikipedia-logo-v2.svg' + }, + + { + url: '/wiki/images/Anticlockwise_heliotrope%27s.jpg', + typeOfUrl: 'Unhashed local file', + nameText: 'Anticlockwise heliotrope\'s', + prefixedText: 'File:Anticlockwise heliotrope\'s.jpg' + }, + + { + url: '', + typeOfUrl: 'Empty string' + }, + + { + url: 'foo', + typeOfUrl: 'String with only alphabet characters' + }, + + { + url: 'foobar.foobar', + typeOfUrl: 'Not a file path' + }, + + { + url: '/a/a0/blah blah blah', + typeOfUrl: 'Space characters' + } + ]; + + for ( i = 0; i < cases.length; i++ ) { + thisCase = cases[i]; + title = mw.Title.newFromImg( { src: thisCase.url } ); + + if ( thisCase.nameText !== undefined ) { + prefix = '[' + thisCase.typeOfUrl + ' URL' + '] '; + + assert.notStrictEqual( title, null, prefix + 'Parses successfully' ); + assert.equal( title.getNameText(), thisCase.nameText, prefix + 'Filename matches original' ); + assert.equal( title.getPrefixedText(), thisCase.prefixedText, prefix + 'File page title matches original' ); + assert.equal( title.getNamespaceId(), 6, prefix + 'Namespace ID matches File namespace' ); + } else { + assert.strictEqual( title, null, thisCase.typeOfUrl + ', should not produce an mw.Title object' ); + } + } + } ); + +}( mediaWiki, jQuery ) ); diff --git a/tests/qunit/suites/resources/mediawiki/mediawiki.Uri.test.js b/tests/qunit/suites/resources/mediawiki/mediawiki.Uri.test.js index 68a9eafb..9913f5e6 100644 --- a/tests/qunit/suites/resources/mediawiki/mediawiki.Uri.test.js +++ b/tests/qunit/suites/resources/mediawiki/mediawiki.Uri.test.js @@ -1,388 +1,433 @@ -QUnit.module( 'mediawiki.Uri', QUnit.newMwEnvironment({ - setup: function () { - this.mwUriOrg = mw.Uri; - mw.Uri = mw.UriRelative( 'http://example.org/w/index.php' ); - }, - teardown: function () { - mw.Uri = this.mwUriOrg; - delete this.mwUriOrg; - } -}) ); - -$.each( [true, false], function ( i, strictMode ) { - QUnit.test( 'Basic mw.Uri object test in ' + ( strictMode ? '' : 'non-' ) + 'strict mode for a simple HTTP URI', 2, function ( assert ) { - var uriString, uri; - uriString = 'http://www.ietf.org/rfc/rfc2396.txt'; - uri = new mw.Uri( uriString, { - strictMode: strictMode - }); +( function ( mw, $ ) { + QUnit.module( 'mediawiki.Uri', QUnit.newMwEnvironment( { + setup: function () { + this.mwUriOrg = mw.Uri; + mw.Uri = mw.UriRelative( 'http://example.org/w/index.php' ); + }, + teardown: function () { + mw.Uri = this.mwUriOrg; + delete this.mwUriOrg; + } + } ) ); + + $.each( [true, false], function ( i, strictMode ) { + QUnit.test( 'Basic construction and properties (' + ( strictMode ? '' : 'non-' ) + 'strict mode)', 2, function ( assert ) { + var uriString, uri; + uriString = 'http://www.ietf.org/rfc/rfc2396.txt'; + uri = new mw.Uri( uriString, { + strictMode: strictMode + } ); + + assert.deepEqual( + { + protocol: uri.protocol, + host: uri.host, + port: uri.port, + path: uri.path, + query: uri.query, + fragment: uri.fragment + }, { + protocol: 'http', + host: 'www.ietf.org', + port: undefined, + path: '/rfc/rfc2396.txt', + query: {}, + fragment: undefined + }, + 'basic object properties' + ); + + assert.deepEqual( + { + userInfo: uri.getUserInfo(), + authority: uri.getAuthority(), + hostPort: uri.getHostPort(), + queryString: uri.getQueryString(), + relativePath: uri.getRelativePath(), + toString: uri.toString() + }, + { + userInfo: '', + authority: 'www.ietf.org', + hostPort: 'www.ietf.org', + queryString: '', + relativePath: '/rfc/rfc2396.txt', + toString: uriString + }, + 'construct composite components of URI on request' + ); + } ); + } ); + + QUnit.test( 'Constructor( String[, Object ] )', 10, function ( assert ) { + var uri; + + uri = new mw.Uri( 'http://www.example.com/dir/?m=foo&m=bar&n=1', { + overrideKeys: true + } ); + + // Strict comparison to assert that numerical values stay strings + assert.strictEqual( uri.query.n, '1', 'Simple parameter with overrideKeys:true' ); + assert.strictEqual( uri.query.m, 'bar', 'Last key overrides earlier keys with overrideKeys:true' ); + + uri = new mw.Uri( 'http://www.example.com/dir/?m=foo&m=bar&n=1', { + overrideKeys: false + } ); + + assert.strictEqual( uri.query.n, '1', 'Simple parameter with overrideKeys:false' ); + assert.strictEqual( uri.query.m[0], 'foo', 'Order of multi-value parameters with overrideKeys:true' ); + assert.strictEqual( uri.query.m[1], 'bar', 'Order of multi-value parameters with overrideKeys:true' ); + assert.strictEqual( uri.query.m.length, 2, 'Number of mult-value field is correct' ); + + uri = new mw.Uri( 'ftp://usr:pwd@192.0.2.16/' ); assert.deepEqual( { protocol: uri.protocol, + user: uri.user, + password: uri.password, host: uri.host, port: uri.port, path: uri.path, query: uri.query, fragment: uri.fragment - }, { - protocol: 'http', - host: 'www.ietf.org', + }, + { + protocol: 'ftp', + user: 'usr', + password: 'pwd', + host: '192.0.2.16', port: undefined, - path: '/rfc/rfc2396.txt', + path: '/', query: {}, fragment: undefined }, - 'basic object properties' + 'Parse an ftp URI correctly with user and password' ); - assert.deepEqual( - { - userInfo: uri.getUserInfo(), - authority: uri.getAuthority(), - hostPort: uri.getHostPort(), - queryString: uri.getQueryString(), - relativePath: uri.getRelativePath(), - toString: uri.toString() + assert.throws( + function () { + return new mw.Uri( 'glaswegian penguins' ); }, - { - userInfo: '', - authority: 'www.ietf.org', - hostPort: 'www.ietf.org', - queryString: '', - relativePath: '/rfc/rfc2396.txt', - toString: uriString + function ( e ) { + return e.message === 'Bad constructor arguments'; }, - 'construct composite components of URI on request' + 'throw error on non-URI as argument to constructor' ); - }); -}); - -QUnit.test( 'Parse an ftp URI correctly with user and password', 1, function ( assert ) { - var uri = new mw.Uri( 'ftp://usr:pwd@192.0.2.16/' ); - - assert.deepEqual( - { - protocol: uri.protocol, - user: uri.user, - password: uri.password, - host: uri.host, - port: uri.port, - path: uri.path, - query: uri.query, - fragment: uri.fragment - }, - { - protocol: 'ftp', - user: 'usr', - password: 'pwd', - host: '192.0.2.16', - port: undefined, - path: '/', - query: {}, - fragment: undefined - }, - 'basic object properties' - ); -} ); - -QUnit.test( 'Parse a uri with simple querystring', 1, function ( assert ) { - var uri = new mw.Uri( 'http://www.google.com/?q=uri' ); - - assert.deepEqual( - { - protocol: uri.protocol, - host: uri.host, - port: uri.port, - path: uri.path, - query: uri.query, - fragment: uri.fragment, - queryString: uri.getQueryString() - }, - { + assert.throws( + function () { + return new mw.Uri( 'foo.com/bar/baz', { + strictMode: true + } ); + }, + function ( e ) { + return e.message === 'Bad constructor arguments'; + }, + 'throw error on URI without protocol or // or leading / in strict mode' + ); + + uri = new mw.Uri( 'foo.com/bar/baz', { + strictMode: false + } ); + assert.equal( uri.toString(), 'http://foo.com/bar/baz', 'normalize URI without protocol or // in loose mode' ); + } ); + + QUnit.test( 'Constructor( Object )', 3, function ( assert ) { + var uri = new mw.Uri( { protocol: 'http', - host: 'www.google.com', - port: undefined, - path: '/', - query: { q: 'uri' }, - fragment: undefined, - queryString: 'q=uri' - }, - 'basic object properties' - ); -} ); - -QUnit.test( 'Handle multiple query parameter (overrideKeys on)', 5, function ( assert ) { - var uri = new mw.Uri( 'http://www.example.com/dir/?m=foo&m=bar&n=1', { - overrideKeys: true - }); - - assert.equal( uri.query.n, '1', 'multiple parameters are parsed' ); - assert.equal( uri.query.m, 'bar', 'last key overrides earlier keys' ); - - uri.query.n = [ 'x', 'y', 'z' ]; - - // Verify parts and total length instead of entire string because order - // of iteration can vary. - assert.ok( uri.toString().indexOf( 'm=bar' ), 'toString preserves other values' ); - assert.ok( uri.toString().indexOf( 'n=x&n=y&n=z' ), 'toString parameter includes all values of an array query parameter' ); - assert.equal( uri.toString().length, 'http://www.example.com/dir/?m=bar&n=x&n=y&n=z'.length, 'toString matches expected string' ); -} ); - -QUnit.test( 'Handle multiple query parameter (overrideKeys off)', 9, function ( assert ) { - var uri = new mw.Uri( 'http://www.example.com/dir/?m=foo&m=bar&n=1', { - overrideKeys: false - }); - - // Strict comparison so that types are also verified (n should be string '1') - assert.strictEqual( uri.query.m.length, 2, 'multi-value query should be an array with 2 items' ); - assert.strictEqual( uri.query.m[0], 'foo', 'order and value is correct' ); - assert.strictEqual( uri.query.m[1], 'bar', 'order and value is correct' ); - assert.strictEqual( uri.query.n, '1', 'n=1 is parsed with the correct value of the expected type' ); - - // Change query values - uri.query.n = [ 'x', 'y', 'z' ]; - - // Verify parts and total length instead of entire string because order - // of iteration can vary. - assert.ok( uri.toString().indexOf( 'm=foo&m=bar' ) >= 0, 'toString preserves other values' ); - assert.ok( uri.toString().indexOf( 'n=x&n=y&n=z' ) >= 0, 'toString parameter includes all values of an array query parameter' ); - assert.equal( uri.toString().length, 'http://www.example.com/dir/?m=foo&m=bar&n=x&n=y&n=z'.length, 'toString matches expected string' ); - - // Remove query values - uri.query.m.splice( 0, 1 ); - delete uri.query.n; - - assert.equal( uri.toString(), 'http://www.example.com/dir/?m=bar', 'deletion properties' ); - - // Remove more query values, leaving an empty array - uri.query.m.splice( 0, 1 ); - assert.equal( uri.toString(), 'http://www.example.com/dir/', 'empty array value is ommitted' ); -} ); - -QUnit.test( 'All-dressed URI with everything', 11, function ( assert ) { - var uri, queryString, relativePath; - - uri = new mw.Uri( 'http://auth@www.example.com:81/dir/dir.2/index.htm?q1=0&&test1&test2=value+%28escaped%29#top' ); - - assert.deepEqual( - { - protocol: uri.protocol, - user: uri.user, - password: uri.password, - host: uri.host, - port: uri.port, - path: uri.path, - query: uri.query, - fragment: uri.fragment - }, - { + host: 'www.foo.local', + path: '/this' + } ); + assert.equal( uri.toString(), 'http://www.foo.local/this', 'Basic properties' ); + + uri = new mw.Uri( { protocol: 'http', - user: 'auth', - password: undefined, - host: 'www.example.com', - port: '81', - path: '/dir/dir.2/index.htm', - query: { q1: '0', test1: null, test2: 'value (escaped)' }, - fragment: 'top' - }, - 'basic object properties' - ); - - assert.equal( uri.getUserInfo(), 'auth', 'user info' ); - - assert.equal( uri.getAuthority(), 'auth@www.example.com:81', 'authority equal to auth@hostport' ); - - assert.equal( uri.getHostPort(), 'www.example.com:81', 'hostport equal to host:port' ); - - queryString = uri.getQueryString(); - assert.ok( queryString.indexOf( 'q1=0' ) >= 0, 'query param with numbers' ); - assert.ok( queryString.indexOf( 'test1' ) >= 0, 'query param with null value is included' ); - assert.ok( queryString.indexOf( 'test1=' ) === -1, 'query param with null value does not generate equals sign' ); - assert.ok( queryString.indexOf( 'test2=value+%28escaped%29' ) >= 0, 'query param is url escaped' ); - - relativePath = uri.getRelativePath(); - assert.ok( relativePath.indexOf( uri.path ) >= 0, 'path in relative path' ); - assert.ok( relativePath.indexOf( uri.getQueryString() ) >= 0, 'query string in relative path' ); - assert.ok( relativePath.indexOf( uri.fragment ) >= 0, 'fragement in relative path' ); -} ); - -QUnit.test( 'Cloning', 6, function ( assert ) { - var original, clone; - - original = new mw.Uri( 'http://foo.example.org/index.php?one=1&two=2' ); - clone = original.clone(); - - assert.deepEqual( clone, original, 'clone has equivalent properties' ); - assert.equal( original.toString(), clone.toString(), 'toString matches original' ); - - assert.notStrictEqual( clone, original, 'clone is a different object when compared by reference' ); - - clone.host = 'bar.example.org'; - assert.notEqual( original.host, clone.host, 'manipulating clone did not effect original' ); - assert.notEqual( original.toString(), clone.toString(), 'Stringified url no longer matches original' ); - - clone.query.three = 3; - - assert.deepEqual( - original.query, - { 'one': '1', 'two': '2' }, - 'Properties is deep cloned (bug 37708)' - ); -} ); - -QUnit.test( 'Constructing mw.Uri from plain object', 3, function ( assert ) { - var uri = new mw.Uri({ - protocol: 'http', - host: 'www.foo.local', - path: '/this' - }); - assert.equal( uri.toString(), 'http://www.foo.local/this', 'Basic properties' ); - - uri = new mw.Uri({ - protocol: 'http', - host: 'www.foo.local', - path: '/this', - query: { hi: 'there' }, - fragment: 'blah' - }); - assert.equal( uri.toString(), 'http://www.foo.local/this?hi=there#blah', 'More complex properties' ); - - assert.throws( - function () { - var uri = new mw.Uri({ + host: 'www.foo.local', + path: '/this', + query: { hi: 'there' }, + fragment: 'blah' + } ); + assert.equal( uri.toString(), 'http://www.foo.local/this?hi=there#blah', 'More complex properties' ); + + assert.throws( + function () { + return new mw.Uri( { + protocol: 'http', + host: 'www.foo.local' + } ); + }, + function ( e ) { + return e.message === 'Bad constructor arguments'; + }, + 'Construction failed when missing required properties' + ); + } ); + + QUnit.test( 'Constructor( empty )', 4, function ( assert ) { + var testuri, MyUri, uri; + + testuri = 'http://example.org/w/index.php'; + MyUri = mw.UriRelative( testuri ); + + uri = new MyUri(); + assert.equal( uri.toString(), testuri, 'no arguments' ); + + uri = new MyUri( undefined ); + assert.equal( uri.toString(), testuri, 'undefined' ); + + uri = new MyUri( null ); + assert.equal( uri.toString(), testuri, 'null' ); + + uri = new MyUri( '' ); + assert.equal( uri.toString(), testuri, 'empty string' ); + } ); + + QUnit.test( 'Properties', 8, function ( assert ) { + var uriBase, uri; + + uriBase = new mw.Uri( 'http://en.wiki.local/w/api.php' ); + + uri = uriBase.clone(); + uri.fragment = 'frag'; + assert.equal( uri.toString(), 'http://en.wiki.local/w/api.php#frag', 'add a fragment' ); + + uri = uriBase.clone(); + uri.host = 'fr.wiki.local'; + uri.port = '8080'; + assert.equal( uri.toString(), 'http://fr.wiki.local:8080/w/api.php', 'change host and port' ); + + uri = uriBase.clone(); + uri.query.foo = 'bar'; + assert.equal( uri.toString(), 'http://en.wiki.local/w/api.php?foo=bar', 'add query arguments' ); + + delete uri.query.foo; + assert.equal( uri.toString(), 'http://en.wiki.local/w/api.php', 'delete query arguments' ); + + uri = uriBase.clone(); + uri.query.foo = 'bar'; + assert.equal( uri.toString(), 'http://en.wiki.local/w/api.php?foo=bar', 'extend query arguments' ); + uri.extend( { + foo: 'quux', + pif: 'paf' + } ); + assert.ok( uri.toString().indexOf( 'foo=quux' ) >= 0, 'extend query arguments' ); + assert.ok( uri.toString().indexOf( 'foo=bar' ) === -1, 'extend query arguments' ); + assert.ok( uri.toString().indexOf( 'pif=paf' ) >= 0, 'extend query arguments' ); + } ); + + QUnit.test( '.getQueryString()', 2, function ( assert ) { + var uri = new mw.Uri( 'http://www.google.com/?q=uri' ); + + assert.deepEqual( + { + protocol: uri.protocol, + host: uri.host, + port: uri.port, + path: uri.path, + query: uri.query, + fragment: uri.fragment, + queryString: uri.getQueryString() + }, + { protocol: 'http', - host: 'www.foo.local' - }); - }, - function ( e ) { - return e.message === 'Bad constructor arguments'; - }, - 'Construction failed when missing required properties' - ); -} ); + host: 'www.google.com', + port: undefined, + path: '/', + query: { q: 'uri' }, + fragment: undefined, + queryString: 'q=uri' + }, + 'basic object properties' + ); -QUnit.test( 'Manipulate properties', 8, function ( assert ) { - var uriBase, uri; + uri = new mw.Uri( 'https://example.org/mw/index.php?title=Sandbox/7&other=Sandbox/7&foo' ); + assert.equal( + uri.getQueryString(), + 'title=Sandbox/7&other=Sandbox%2F7&foo', + 'title parameter is escaped the wiki-way' + ); - uriBase = new mw.Uri( 'http://en.wiki.local/w/api.php' ); + } ); - uri = uriBase.clone(); - uri.fragment = 'frag'; - assert.equal( uri.toString(), 'http://en.wiki.local/w/api.php#frag', 'add a fragment' ); + QUnit.test( '.clone()', 6, function ( assert ) { + var original, clone; - uri = uriBase.clone(); - uri.host = 'fr.wiki.local'; - uri.port = '8080'; - assert.equal( uri.toString(), 'http://fr.wiki.local:8080/w/api.php', 'change host and port' ); + original = new mw.Uri( 'http://foo.example.org/index.php?one=1&two=2' ); + clone = original.clone(); - uri = uriBase.clone(); - uri.query.foo = 'bar'; - assert.equal( uri.toString(), 'http://en.wiki.local/w/api.php?foo=bar', 'add query arguments' ); + assert.deepEqual( clone, original, 'clone has equivalent properties' ); + assert.equal( original.toString(), clone.toString(), 'toString matches original' ); - delete uri.query.foo; - assert.equal( uri.toString(), 'http://en.wiki.local/w/api.php', 'delete query arguments' ); + assert.notStrictEqual( clone, original, 'clone is a different object when compared by reference' ); - uri = uriBase.clone(); - uri.query.foo = 'bar'; - assert.equal( uri.toString(), 'http://en.wiki.local/w/api.php?foo=bar', 'extend query arguments' ); - uri.extend({ - foo: 'quux', - pif: 'paf' - }); - assert.ok( uri.toString().indexOf( 'foo=quux' ) >= 0, 'extend query arguments' ); - assert.ok( uri.toString().indexOf( 'foo=bar' ) === -1, 'extend query arguments' ); - assert.ok( uri.toString().indexOf( 'pif=paf' ) >= 0 , 'extend query arguments' ); -} ); + clone.host = 'bar.example.org'; + assert.notEqual( original.host, clone.host, 'manipulating clone did not effect original' ); + assert.notEqual( original.toString(), clone.toString(), 'Stringified url no longer matches original' ); -QUnit.test( 'Handle protocol-relative URLs', 5, function ( assert ) { - var UriRel, uri; + clone.query.three = 3; - UriRel = mw.UriRelative( 'glork://en.wiki.local/foo.php' ); + assert.deepEqual( + original.query, + { 'one': '1', 'two': '2' }, + 'Properties is deep cloned (bug 37708)' + ); + } ); - uri = new UriRel( '//en.wiki.local/w/api.php' ); - assert.equal( uri.protocol, 'glork', 'create protocol-relative URLs with same protocol as document' ); + QUnit.test( '.toString() after query manipulation', 8, function ( assert ) { + var uri; - uri = new UriRel( '/foo.com' ); - assert.equal( uri.toString(), 'glork://en.wiki.local/foo.com', 'handle absolute paths by supplying protocol and host from document in loose mode' ); + uri = new mw.Uri( 'http://www.example.com/dir/?m=foo&m=bar&n=1', { + overrideKeys: true + } ); - uri = new UriRel( 'http:/foo.com' ); - assert.equal( uri.toString(), 'http://en.wiki.local/foo.com', 'handle absolute paths by supplying host from document in loose mode' ); + uri.query.n = [ 'x', 'y', 'z' ]; - uri = new UriRel( '/foo.com', true ); - assert.equal( uri.toString(), 'glork://en.wiki.local/foo.com', 'handle absolute paths by supplying protocol and host from document in strict mode' ); + // Verify parts and total length instead of entire string because order + // of iteration can vary. + assert.ok( uri.toString().indexOf( 'm=bar' ), 'toString preserves other values' ); + assert.ok( uri.toString().indexOf( 'n=x&n=y&n=z' ), 'toString parameter includes all values of an array query parameter' ); + assert.equal( uri.toString().length, 'http://www.example.com/dir/?m=bar&n=x&n=y&n=z'.length, 'toString matches expected string' ); - uri = new UriRel( 'http:/foo.com', true ); - assert.equal( uri.toString(), 'http://en.wiki.local/foo.com', 'handle absolute paths by supplying host from document in strict mode' ); -} ); + uri = new mw.Uri( 'http://www.example.com/dir/?m=foo&m=bar&n=1', { + overrideKeys: false + } ); -QUnit.test( 'Bad calls', 3, function ( assert ) { - var uri; + // Change query values + uri.query.n = [ 'x', 'y', 'z' ]; - assert.throws( - function () { - return new mw.Uri( 'glaswegian penguins' ); - }, - function ( e ) { - return e.message === 'Bad constructor arguments'; - }, - 'throw error on non-URI as argument to constructor' - ); - - assert.throws( - function () { - return new mw.Uri( 'foo.com/bar/baz', { - strictMode: true - }); - }, - function ( e ) { - return e.message === 'Bad constructor arguments'; - }, - 'throw error on URI without protocol or // or leading / in strict mode' - ); + // Verify parts and total length instead of entire string because order + // of iteration can vary. + assert.ok( uri.toString().indexOf( 'm=foo&m=bar' ) >= 0, 'toString preserves other values' ); + assert.ok( uri.toString().indexOf( 'n=x&n=y&n=z' ) >= 0, 'toString parameter includes all values of an array query parameter' ); + assert.equal( uri.toString().length, 'http://www.example.com/dir/?m=foo&m=bar&n=x&n=y&n=z'.length, 'toString matches expected string' ); + + // Remove query values + uri.query.m.splice( 0, 1 ); + delete uri.query.n; + + assert.equal( uri.toString(), 'http://www.example.com/dir/?m=bar', 'deletion properties' ); + + // Remove more query values, leaving an empty array + uri.query.m.splice( 0, 1 ); + assert.equal( uri.toString(), 'http://www.example.com/dir/', 'empty array value is ommitted' ); + } ); + + QUnit.test( 'Advanced URL', 11, function ( assert ) { + var uri, queryString, relativePath; + + uri = new mw.Uri( 'http://auth@www.example.com:81/dir/dir.2/index.htm?q1=0&&test1&test2=value+%28escaped%29#top' ); + + assert.deepEqual( + { + protocol: uri.protocol, + user: uri.user, + password: uri.password, + host: uri.host, + port: uri.port, + path: uri.path, + query: uri.query, + fragment: uri.fragment + }, + { + protocol: 'http', + user: 'auth', + password: undefined, + host: 'www.example.com', + port: '81', + path: '/dir/dir.2/index.htm', + query: { q1: '0', test1: null, test2: 'value (escaped)' }, + fragment: 'top' + }, + 'basic object properties' + ); + + assert.equal( uri.getUserInfo(), 'auth', 'user info' ); + + assert.equal( uri.getAuthority(), 'auth@www.example.com:81', 'authority equal to auth@hostport' ); + + assert.equal( uri.getHostPort(), 'www.example.com:81', 'hostport equal to host:port' ); + + queryString = uri.getQueryString(); + assert.ok( queryString.indexOf( 'q1=0' ) >= 0, 'query param with numbers' ); + assert.ok( queryString.indexOf( 'test1' ) >= 0, 'query param with null value is included' ); + assert.ok( queryString.indexOf( 'test1=' ) === -1, 'query param with null value does not generate equals sign' ); + assert.ok( queryString.indexOf( 'test2=value+%28escaped%29' ) >= 0, 'query param is url escaped' ); + + relativePath = uri.getRelativePath(); + assert.ok( relativePath.indexOf( uri.path ) >= 0, 'path in relative path' ); + assert.ok( relativePath.indexOf( uri.getQueryString() ) >= 0, 'query string in relative path' ); + assert.ok( relativePath.indexOf( uri.fragment ) >= 0, 'fragement in relative path' ); + } ); + + QUnit.test( 'Parse a uri with an @ symbol in the path and query', 1, function ( assert ) { + var uri = new mw.Uri( 'http://www.example.com/test@test?x=@uri&y@=uri&z@=@' ); + + assert.deepEqual( + { + protocol: uri.protocol, + user: uri.user, + password: uri.password, + host: uri.host, + port: uri.port, + path: uri.path, + query: uri.query, + fragment: uri.fragment, + queryString: uri.getQueryString() + }, + { + protocol: 'http', + user: undefined, + password: undefined, + host: 'www.example.com', + port: undefined, + path: '/test@test', + query: { x: '@uri', 'y@': 'uri', 'z@': '@' }, + fragment: undefined, + queryString: 'x=%40uri&y%40=uri&z%40=%40' + }, + 'basic object properties' + ); + } ); - uri = new mw.Uri( 'foo.com/bar/baz', { - strictMode: false - }); - assert.equal( uri.toString(), 'http://foo.com/bar/baz', 'normalize URI without protocol or // in loose mode' ); -}); + QUnit.test( 'Handle protocol-relative URLs', 5, function ( assert ) { + var UriRel, uri; -QUnit.test( 'bug 35658', 2, function ( assert ) { - var testProtocol, testServer, testPort, testPath, UriClass, uri, href; + UriRel = mw.UriRelative( 'glork://en.wiki.local/foo.php' ); - testProtocol = 'https://'; - testServer = 'foo.example.org'; - testPort = '3004'; - testPath = '/!1qy'; + uri = new UriRel( '//en.wiki.local/w/api.php' ); + assert.equal( uri.protocol, 'glork', 'create protocol-relative URLs with same protocol as document' ); - UriClass = mw.UriRelative( testProtocol + testServer + '/some/path/index.html' ); - uri = new UriClass( testPath ); - href = uri.toString(); - assert.equal( href, testProtocol + testServer + testPath, 'Root-relative URL gets host & protocol supplied' ); + uri = new UriRel( '/foo.com' ); + assert.equal( uri.toString(), 'glork://en.wiki.local/foo.com', 'handle absolute paths by supplying protocol and host from document in loose mode' ); - UriClass = mw.UriRelative( testProtocol + testServer + ':' + testPort + '/some/path.php' ); - uri = new UriClass( testPath ); - href = uri.toString(); - assert.equal( href, testProtocol + testServer + ':' + testPort + testPath, 'Root-relative URL gets host, protocol, and port supplied' ); + uri = new UriRel( 'http:/foo.com' ); + assert.equal( uri.toString(), 'http://en.wiki.local/foo.com', 'handle absolute paths by supplying host from document in loose mode' ); -} ); + uri = new UriRel( '/foo.com', true ); + assert.equal( uri.toString(), 'glork://en.wiki.local/foo.com', 'handle absolute paths by supplying protocol and host from document in strict mode' ); -QUnit.test( 'Constructor falls back to default location', 4, function ( assert ) { - var testuri, MyUri, uri; + uri = new UriRel( 'http:/foo.com', true ); + assert.equal( uri.toString(), 'http://en.wiki.local/foo.com', 'handle absolute paths by supplying host from document in strict mode' ); + } ); - testuri = 'http://example.org/w/index.php'; - MyUri = mw.UriRelative( testuri ); + QUnit.test( 'bug 35658', 2, function ( assert ) { + var testProtocol, testServer, testPort, testPath, UriClass, uri, href; - uri = new MyUri(); - assert.equal( uri.toString(), testuri, 'no arguments' ); + testProtocol = 'https://'; + testServer = 'foo.example.org'; + testPort = '3004'; + testPath = '/!1qy'; - uri = new MyUri( undefined ); - assert.equal( uri.toString(), testuri, 'undefined' ); + UriClass = mw.UriRelative( testProtocol + testServer + '/some/path/index.html' ); + uri = new UriClass( testPath ); + href = uri.toString(); + assert.equal( href, testProtocol + testServer + testPath, 'Root-relative URL gets host & protocol supplied' ); - uri = new MyUri( null ); - assert.equal( uri.toString(), testuri, 'null' ); + UriClass = mw.UriRelative( testProtocol + testServer + ':' + testPort + '/some/path.php' ); + uri = new UriClass( testPath ); + href = uri.toString(); + assert.equal( href, testProtocol + testServer + ':' + testPort + testPath, 'Root-relative URL gets host, protocol, and port supplied' ); - uri = new MyUri( '' ); - assert.equal( uri.toString(), testuri, 'empty string' ); -} ); + } ); +}( mediaWiki, jQuery ) ); diff --git a/tests/qunit/suites/resources/mediawiki/mediawiki.cldr.test.js b/tests/qunit/suites/resources/mediawiki/mediawiki.cldr.test.js index e2c66685..779a0ed4 100644 --- a/tests/qunit/suites/resources/mediawiki/mediawiki.cldr.test.js +++ b/tests/qunit/suites/resources/mediawiki/mediawiki.cldr.test.js @@ -1,74 +1,81 @@ -QUnit.module( 'mediawiki.cldr', QUnit.newMwEnvironment() ); +( function ( mw, $ ) { + QUnit.module( 'mediawiki.cldr', QUnit.newMwEnvironment() ); -var pluralTestcases = { - /* - * Sample: - * "languagecode" : [ - * [ number, [ "form1", "form2", ... ], "expected", "description" ] - * ]; - */ - "en": [ - [ 0, [ "one", "other" ], "other", "English plural test- 0 is other" ], - [ 1, [ "one", "other" ], "one", "English plural test- 1 is one" ] - ], - "fa": [ - [ 0, [ "one", "other" ], "other", "Persian plural test- 0 is other" ], - [ 1, [ "one", "other" ], "one", "Persian plural test- 1 is one" ], - [ 2, [ "one", "other" ], "other", "Persian plural test- 2 is other" ] - ], - "fr": [ - [ 0, [ "one", "other" ], "other", "French plural test- 0 is other" ], - [ 1, [ "one", "other" ], "one", "French plural test- 1 is one" ] - ], - "hi": [ - [ 0, [ "one", "other" ], "one", "Hindi plural test- 0 is one" ], - [ 1, [ "one", "other" ], "one", "Hindi plural test- 1 is one" ], - [ 2, [ "one", "other" ], "other", "Hindi plural test- 2 is other" ] - ], - "he": [ - [ 0, [ "one", "other" ], "other", "Hebrew plural test- 0 is other" ], - [ 1, [ "one", "other" ], "one", "Hebrew plural test- 1 is one" ], - [ 2, [ "one", "other" ], "other", "Hebrew plural test- 2 is other with 2 forms" ], - [ 2, [ "one", "dual", "other" ], "dual", "Hebrew plural test- 2 is dual with 3 forms" ] - ], - "hu": [ - [ 0, [ "one", "other" ], "other", "Hungarian plural test- 0 is other" ], - [ 1, [ "one", "other" ], "one", "Hungarian plural test- 1 is one" ], - [ 2, [ "one", "other" ], "other", "Hungarian plural test- 2 is other" ] - ], - "ar": [ - [ 0, [ "zero", "one", "two", "few", "many", "other" ], "zero", "Arabic plural test - 0 is zero" ], - [ 1, [ "zero", "one", "two", "few", "many", "other" ], "one", "Arabic plural test - 1 is one" ], - [ 2, [ "zero", "one", "two", "few", "many", "other" ], "two", "Arabic plural test - 2 is two" ], - [ 3, [ "zero", "one", "two", "few", "many", "other" ], "few", "Arabic plural test - 3 is few" ], - [ 9, [ "zero", "one", "two", "few", "many", "other" ], "few", "Arabic plural test - 9 is few" ], - [ "9", [ "zero", "one", "two", "few", "many", "other" ], "few", "Arabic plural test - 9 is few" ], - [ 110, [ "zero", "one", "two", "few", "many", "other" ], "few", "Arabic plural test - 110 is few" ], - [ 11, [ "zero", "one", "two", "few", "many", "other" ], "many", "Arabic plural test - 11 is many" ], - [ 15, [ "zero", "one", "two", "few", "many", "other" ], "many", "Arabic plural test - 15 is many" ], - [ 99, [ "zero", "one", "two", "few", "many", "other" ], "many", "Arabic plural test - 99 is many" ], - [ 9999, [ "zero", "one", "two", "few", "many", "other" ], "many", "Arabic plural test - 9999 is many" ], - [ 100, [ "zero", "one", "two", "few", "many", "other" ], "other", "Arabic plural test - 100 is other" ], - [ 102, [ "zero", "one", "two", "few", "many", "other" ], "other", "Arabic plural test - 102 is other" ], - [ 1000, [ "zero", "one", "two", "few", "many", "other" ], "other", "Arabic plural test - 1000 is other" ], - [ 1.7, [ "zero", "one", "two", "few", "many", "other" ], "other", "Arabic plural test - 1.7 is other" ] - ] -}; + var pluralTestcases = { + /* + * Sample: + * languagecode : [ + * [ number, [ 'form1', 'form2', ... ], 'expected', 'description' ] + * ]; + */ + en: [ + [ 0, [ 'one', 'other' ], 'other', 'English plural test- 0 is other' ], + [ 1, [ 'one', 'other' ], 'one', 'English plural test- 1 is one' ] + ], + fa: [ + [ 0, [ 'one', 'other' ], 'other', 'Persian plural test- 0 is other' ], + [ 1, [ 'one', 'other' ], 'one', 'Persian plural test- 1 is one' ], + [ 2, [ 'one', 'other' ], 'other', 'Persian plural test- 2 is other' ] + ], + fr: [ + [ 0, [ 'one', 'other' ], 'other', 'French plural test- 0 is other' ], + [ 1, [ 'one', 'other' ], 'one', 'French plural test- 1 is one' ] + ], + hi: [ + [ 0, [ 'one', 'other' ], 'one', 'Hindi plural test- 0 is one' ], + [ 1, [ 'one', 'other' ], 'one', 'Hindi plural test- 1 is one' ], + [ 2, [ 'one', 'other' ], 'other', 'Hindi plural test- 2 is other' ] + ], + he: [ + [ 0, [ 'one', 'other' ], 'other', 'Hebrew plural test- 0 is other' ], + [ 1, [ 'one', 'other' ], 'one', 'Hebrew plural test- 1 is one' ], + [ 2, [ 'one', 'other' ], 'other', 'Hebrew plural test- 2 is other with 2 forms' ], + [ 2, [ 'one', 'dual', 'other' ], 'dual', 'Hebrew plural test- 2 is dual with 3 forms' ] + ], + hu: [ + [ 0, [ 'one', 'other' ], 'other', 'Hungarian plural test- 0 is other' ], + [ 1, [ 'one', 'other' ], 'one', 'Hungarian plural test- 1 is one' ], + [ 2, [ 'one', 'other' ], 'other', 'Hungarian plural test- 2 is other' ] + ], + hy: [ + [ 0, [ 'one', 'other' ], 'other', 'Armenian plural test- 0 is other' ], + [ 1, [ 'one', 'other' ], 'one', 'Armenian plural test- 1 is one' ], + [ 2, [ 'one', 'other' ], 'other', 'Armenian plural test- 2 is other' ] + ], + ar: [ + [ 0, [ 'zero', 'one', 'two', 'few', 'many', 'other' ], 'zero', 'Arabic plural test - 0 is zero' ], + [ 1, [ 'zero', 'one', 'two', 'few', 'many', 'other' ], 'one', 'Arabic plural test - 1 is one' ], + [ 2, [ 'zero', 'one', 'two', 'few', 'many', 'other' ], 'two', 'Arabic plural test - 2 is two' ], + [ 3, [ 'zero', 'one', 'two', 'few', 'many', 'other' ], 'few', 'Arabic plural test - 3 is few' ], + [ 9, [ 'zero', 'one', 'two', 'few', 'many', 'other' ], 'few', 'Arabic plural test - 9 is few' ], + [ '9', [ 'zero', 'one', 'two', 'few', 'many', 'other' ], 'few', 'Arabic plural test - 9 is few' ], + [ 110, [ 'zero', 'one', 'two', 'few', 'many', 'other' ], 'few', 'Arabic plural test - 110 is few' ], + [ 11, [ 'zero', 'one', 'two', 'few', 'many', 'other' ], 'many', 'Arabic plural test - 11 is many' ], + [ 15, [ 'zero', 'one', 'two', 'few', 'many', 'other' ], 'many', 'Arabic plural test - 15 is many' ], + [ 99, [ 'zero', 'one', 'two', 'few', 'many', 'other' ], 'many', 'Arabic plural test - 99 is many' ], + [ 9999, [ 'zero', 'one', 'two', 'few', 'many', 'other' ], 'many', 'Arabic plural test - 9999 is many' ], + [ 100, [ 'zero', 'one', 'two', 'few', 'many', 'other' ], 'other', 'Arabic plural test - 100 is other' ], + [ 102, [ 'zero', 'one', 'two', 'few', 'many', 'other' ], 'other', 'Arabic plural test - 102 is other' ], + [ 1000, [ 'zero', 'one', 'two', 'few', 'many', 'other' ], 'other', 'Arabic plural test - 1000 is other' ], + [ 1.7, [ 'zero', 'one', 'two', 'few', 'many', 'other' ], 'other', 'Arabic plural test - 1.7 is other' ] + ] + }; -function pluralTest( langCode, tests ) { - QUnit.test( 'Plural Test for ' + langCode, tests.length, function ( assert ) { - for ( var i = 0; i < tests.length; i++ ) { - assert.equal( - mw.language.convertPlural( tests[i][0], tests[i][1] ), - tests[i][2], - tests[i][3] - ); + function pluralTest( langCode, tests ) { + QUnit.test( 'Plural Test for ' + langCode, tests.length, function ( assert ) { + for ( var i = 0; i < tests.length; i++ ) { + assert.equal( + mw.language.convertPlural( tests[i][0], tests[i][1] ), + tests[i][2], + tests[i][3] + ); + } + } ); + } + + $.each( pluralTestcases, function ( langCode, tests ) { + if ( langCode === mw.config.get( 'wgUserLanguage' ) ) { + pluralTest( langCode, tests ); } } ); -} - -$.each( pluralTestcases, function ( langCode, tests ) { - if ( langCode === mw.config.get( 'wgUserLanguage' ) ) { - pluralTest( langCode, tests ); - } -} ); +}( mediaWiki, jQuery ) ); diff --git a/tests/qunit/suites/resources/mediawiki/mediawiki.jqueryMsg.test.js b/tests/qunit/suites/resources/mediawiki/mediawiki.jqueryMsg.test.js index b8193a92..be362e22 100644 --- a/tests/qunit/suites/resources/mediawiki/mediawiki.jqueryMsg.test.js +++ b/tests/qunit/suites/resources/mediawiki/mediawiki.jqueryMsg.test.js @@ -1,97 +1,714 @@ -QUnit.module( 'mediawiki.jqueryMsg' ); +( function ( mw, $ ) { + var mwLanguageCache = {}, formatText, formatParse, formatnumTests, specialCharactersPageName, + expectedListUsers, expectedEntrypoints; -QUnit.test( 'mw.jqueryMsg Plural', 3, function ( assert ) { - var parser = mw.jqueryMsg.getMessageFunction(); + // When the expected result is the same in both modes + function assertBothModes( assert, parserArguments, expectedResult, assertMessage ) { + assert.equal( formatText.apply( null, parserArguments ), expectedResult, assertMessage + ' when format is \'text\'' ); + assert.equal( formatParse.apply( null, parserArguments ), expectedResult, assertMessage + ' when format is \'parse\'' ); + } - mw.messages.set( 'plural-msg', 'Found $1 {{PLURAL:$1|item|items}}' ); - assert.equal( parser( 'plural-msg', 0 ), 'Found 0 items', 'Plural test for english with zero as count' ); - assert.equal( parser( 'plural-msg', 1 ), 'Found 1 item', 'Singular test for english' ); - assert.equal( parser( 'plural-msg', 2 ), 'Found 2 items', 'Plural test for english' ); + QUnit.module( 'mediawiki.jqueryMsg', QUnit.newMwEnvironment( { + setup: function () { + this.orgMwLangauge = mw.language; + mw.language = $.extend( true, {}, this.orgMwLangauge ); + + // Messages that are reused in multiple tests + mw.messages.set( { + // The values for gender are not significant, + // what matters is which of the values is choosen by the parser + 'gender-msg': '$1: {{GENDER:$2|blue|pink|green}}', + + 'plural-msg': 'Found $1 {{PLURAL:$1|item|items}}', + + // Assume the grammar form grammar_case_foo is not valid in any language + 'grammar-msg': 'Przeszukaj {{GRAMMAR:grammar_case_foo|{{SITENAME}}}}', + + 'formatnum-msg': '{{formatnum:$1}}', + + 'portal-url': 'Project:Community portal', + 'see-portal-url': '{{Int:portal-url}} is an important community page.', + + 'jquerymsg-test-statistics-users': '注册[[Special:ListUsers|用户]]', + + 'jquerymsg-test-version-entrypoints-index-php': '[https://www.mediawiki.org/wiki/Manual:index.php index.php]', + + 'external-link-replace': 'Foo [$1 bar]' + } ); + + mw.config.set( { + wgArticlePath: '/wiki/$1' + } ); + + specialCharactersPageName = '"Who" wants to be a millionaire & live on \'Exotic Island\'?'; + + expectedListUsers = '注册<a title="Special:ListUsers" href="/wiki/Special:ListUsers">用户</a>'; + + expectedEntrypoints = '<a href="https://www.mediawiki.org/wiki/Manual:index.php">index.php</a>'; + + formatText = mw.jqueryMsg.getMessageFunction( { + format: 'text' + } ); + + formatParse = mw.jqueryMsg.getMessageFunction( { + format: 'parse' + } ); + }, + teardown: function () { + mw.language = this.orgMwLangauge; + } + } ) ); + + function getMwLanguage( langCode, cb ) { + if ( mwLanguageCache[langCode] !== undefined ) { + mwLanguageCache[langCode].add( cb ); + return; + } + mwLanguageCache[langCode] = $.Callbacks( 'once memory' ); + mwLanguageCache[langCode].add( cb ); + $.ajax( { + url: mw.util.wikiScript( 'load' ), + data: { + skin: mw.config.get( 'skin' ), + lang: langCode, + debug: mw.config.get( 'debug' ), + modules: [ + 'mediawiki.language.data', + 'mediawiki.language' + ].join( '|' ), + only: 'scripts' + }, + dataType: 'script' + } ).done(function () { + mwLanguageCache[langCode].fire( mw.language ); + } ).fail( function () { + mwLanguageCache[langCode].fire( false ); + } ); + } + + QUnit.test( 'Replace', 9, function ( assert ) { + mw.messages.set( 'simple', 'Foo $1 baz $2' ); + + assert.equal( formatParse( 'simple' ), 'Foo $1 baz $2', 'Replacements with no substitutes' ); + assert.equal( formatParse( 'simple', 'bar' ), 'Foo bar baz $2', 'Replacements with less substitutes' ); + assert.equal( formatParse( 'simple', 'bar', 'quux' ), 'Foo bar baz quux', 'Replacements with all substitutes' ); + + mw.messages.set( 'plain-input', '<foo foo="foo">x$1y<</foo>z' ); + + assert.equal( + formatParse( 'plain-input', 'bar' ), + '<foo foo="foo">xbary&lt;</foo>z', + 'Input is not considered html' + ); + + mw.messages.set( 'plain-replace', 'Foo $1' ); + + assert.equal( + formatParse( 'plain-replace', '<bar bar="bar">></bar>' ), + 'Foo <bar bar="bar">&gt;</bar>', + 'Replacement is not considered html' + ); + + mw.messages.set( 'object-replace', 'Foo $1' ); + + assert.equal( + formatParse( 'object-replace', $( '<div class="bar">></div>' ) ), + 'Foo <div class="bar">></div>', + 'jQuery objects are preserved as raw html' + ); + + assert.equal( + formatParse( 'object-replace', $( '<div class="bar">></div>' ).get( 0 ) ), + 'Foo <div class="bar">></div>', + 'HTMLElement objects are preserved as raw html' + ); + + assert.equal( + formatParse( 'object-replace', $( '<div class="bar">></div>' ).toArray() ), + 'Foo <div class="bar">></div>', + 'HTMLElement[] arrays are preserved as raw html' + ); + + assert.equal( + formatParse( 'external-link-replace', 'http://example.org/?x=y&z' ), + 'Foo <a href="http://example.org/?x=y&z">bar</a>', + 'Href is not double-escaped in wikilink function' + ); + } ); + + QUnit.test( 'Plural', 3, function ( assert ) { + assert.equal( formatParse( 'plural-msg', 0 ), 'Found 0 items', 'Plural test for english with zero as count' ); + assert.equal( formatParse( 'plural-msg', 1 ), 'Found 1 item', 'Singular test for english' ); + assert.equal( formatParse( 'plural-msg', 2 ), 'Found 2 items', 'Plural test for english' ); + } ); + + QUnit.test( 'Gender', 11, function ( assert ) { + // TODO: These tests should be for mw.msg once mw.msg integrated with mw.jqueryMsg + // TODO: English may not be the best language for these tests. Use a language like Arabic or Russian + var user = mw.user; + + user.options.set( 'gender', 'male' ); + assert.equal( + formatParse( 'gender-msg', 'Bob', 'male' ), + 'Bob: blue', + 'Masculine from string "male"' + ); + assert.equal( + formatParse( 'gender-msg', 'Bob', user ), + 'Bob: blue', + 'Masculine from mw.user object' + ); + + user.options.set( 'gender', 'unknown' ); + assert.equal( + formatParse( 'gender-msg', 'Foo', user ), + 'Foo: green', + 'Neutral from mw.user object' ); + assert.equal( + formatParse( 'gender-msg', 'Alice', 'female' ), + 'Alice: pink', + 'Feminine from string "female"' ); + assert.equal( + formatParse( 'gender-msg', 'User' ), + 'User: green', + 'Neutral when no parameter given' ); + assert.equal( + formatParse( 'gender-msg', 'User', 'unknown' ), + 'User: green', + 'Neutral from string "unknown"' + ); + + mw.messages.set( 'gender-msg-one-form', '{{GENDER:$1|User}}: $2 {{PLURAL:$2|edit|edits}}' ); + + assert.equal( + formatParse( 'gender-msg-one-form', 'male', 10 ), + 'User: 10 edits', + 'Gender neutral and plural form' + ); + assert.equal( + formatParse( 'gender-msg-one-form', 'female', 1 ), + 'User: 1 edit', + 'Gender neutral and singular form' + ); + + mw.messages.set( 'gender-msg-lowercase', '{{gender:$1|he|she}} is awesome' ); + assert.equal( + formatParse( 'gender-msg-lowercase', 'male' ), + 'he is awesome', + 'Gender masculine' + ); + assert.equal( + formatParse( 'gender-msg-lowercase', 'female' ), + 'she is awesome', + 'Gender feminine' + ); + + mw.messages.set( 'gender-msg-wrong', '{{gender}} test' ); + assert.equal( + formatParse( 'gender-msg-wrong', 'female' ), + ' test', + 'Invalid syntax should result in {{gender}} simply being stripped away' + ); + } ); + + QUnit.test( 'Grammar', 2, function ( assert ) { + assert.equal( formatParse( 'grammar-msg' ), 'Przeszukaj ' + mw.config.get( 'wgSiteName' ), 'Grammar Test with sitename' ); + + mw.messages.set( 'grammar-msg-wrong-syntax', 'Przeszukaj {{GRAMMAR:grammar_case_xyz}}' ); + assert.equal( formatParse( 'grammar-msg-wrong-syntax' ), 'Przeszukaj ', 'Grammar Test with wrong grammar template syntax' ); + } ); + + QUnit.test( 'Match PHP parser', mw.libs.phpParserData.tests.length, function ( assert ) { + mw.messages.set( mw.libs.phpParserData.messages ); + $.each( mw.libs.phpParserData.tests, function ( i, test ) { + QUnit.stop(); + getMwLanguage( test.lang, function ( langClass ) { + QUnit.start(); + if ( !langClass ) { + assert.ok( false, 'Language "' + test.lang + '" failed to load' ); + return; + } + mw.config.set( 'wgUserLanguage', test.lang ); + var parser = new mw.jqueryMsg.parser( { language: langClass } ); + assert.equal( + parser.parse( test.key, test.args ).html(), + test.result, + test.name + ); + } ); + } ); + } ); + + QUnit.test( 'Links', 6, function ( assert ) { + var expectedDisambiguationsText, + expectedMultipleBars, + expectedSpecialCharacters; + + /* + The below three are all identical to or based on real messages. For disambiguations-text, + the bold was removed because it is not yet implemented. + */ + + assert.htmlEqual( + formatParse( 'jquerymsg-test-statistics-users' ), + expectedListUsers, + 'Piped wikilink' + ); + + expectedDisambiguationsText = 'The following pages contain at least one link to a disambiguation page.\nThey may have to link to a more appropriate page instead.\nA page is treated as a disambiguation page if it uses a template that is linked from ' + + '<a title="MediaWiki:Disambiguationspage" href="/wiki/MediaWiki:Disambiguationspage">MediaWiki:Disambiguationspage</a>.'; + + mw.messages.set( 'disambiguations-text', 'The following pages contain at least one link to a disambiguation page.\nThey may have to link to a more appropriate page instead.\nA page is treated as a disambiguation page if it uses a template that is linked from [[MediaWiki:Disambiguationspage]].' ); + assert.htmlEqual( + formatParse( 'disambiguations-text' ), + expectedDisambiguationsText, + 'Wikilink without pipe' + ); + + assert.htmlEqual( + formatParse( 'jquerymsg-test-version-entrypoints-index-php' ), + expectedEntrypoints, + 'External link' + ); + + // Pipe trick is not supported currently, but should not parse as text either. + mw.messages.set( 'pipe-trick', '[[Tampa, Florida|]]' ); + assert.equal( + formatParse( 'pipe-trick' ), + 'pipe-trick: Parse error at position 0 in input: [[Tampa, Florida|]]', + 'Pipe trick should return error string.' + ); + + expectedMultipleBars = '<a title="Main Page" href="/wiki/Main_Page">Main|Page</a>'; + mw.messages.set( 'multiple-bars', '[[Main Page|Main|Page]]' ); + assert.htmlEqual( + formatParse( 'multiple-bars' ), + expectedMultipleBars, + 'Bar in anchor' + ); + + expectedSpecialCharacters = '<a title=""Who" wants to be a millionaire & live on 'Exotic Island'?" href="/wiki/%22Who%22_wants_to_be_a_millionaire_%26_live_on_%27Exotic_Island%27%3F">"Who" wants to be a millionaire & live on 'Exotic Island'?</a>'; + + mw.messages.set( 'special-characters', '[[' + specialCharactersPageName + ']]' ); + assert.htmlEqual( + formatParse( 'special-characters' ), + expectedSpecialCharacters, + 'Special characters' + ); + } ); + +// Tests that {{-transformation vs. general parsing are done as requested + QUnit.test( 'Curly brace transformation', 14, function ( assert ) { + var oldUserLang = mw.config.get( 'wgUserLanguage' ); + + assertBothModes( assert, ['gender-msg', 'Bob', 'male'], 'Bob: blue', 'gender is resolved' ); + + assertBothModes( assert, ['plural-msg', 5], 'Found 5 items', 'plural is resolved' ); + + assertBothModes( assert, ['grammar-msg'], 'Przeszukaj ' + mw.config.get( 'wgSiteName' ), 'grammar is resolved' ); + + mw.config.set( 'wgUserLanguage', 'en' ); + assertBothModes( assert, ['formatnum-msg', '987654321.654321'], '987,654,321.654', 'formatnum is resolved' ); + + // Test non-{{ wikitext, where behavior differs + + // Wikilink + assert.equal( + formatText( 'jquerymsg-test-statistics-users' ), + mw.messages.get( 'jquerymsg-test-statistics-users' ), + 'Internal link message unchanged when format is \'text\'' + ); + assert.htmlEqual( + formatParse( 'jquerymsg-test-statistics-users' ), + expectedListUsers, + 'Internal link message parsed when format is \'parse\'' + ); + + // External link + assert.equal( + formatText( 'jquerymsg-test-version-entrypoints-index-php' ), + mw.messages.get( 'jquerymsg-test-version-entrypoints-index-php' ), + 'External link message unchanged when format is \'text\'' + ); + assert.htmlEqual( + formatParse( 'jquerymsg-test-version-entrypoints-index-php' ), + expectedEntrypoints, + 'External link message processed when format is \'parse\'' + ); + + // External link with parameter + assert.equal( + formatText( 'external-link-replace', 'http://example.com' ), + 'Foo [http://example.com bar]', + 'External link message only substitutes parameter when format is \'text\'' + ); + assert.htmlEqual( + formatParse( 'external-link-replace', 'http://example.com' ), + 'Foo <a href="http://example.com">bar</a>', + 'External link message processed when format is \'parse\'' + ); + + mw.config.set( 'wgUserLanguage', oldUserLang ); + } ); + + QUnit.test( 'Int', 4, function ( assert ) { + var newarticletextSource = 'You have followed a link to a page that does not exist yet. To create the page, start typing in the box below (see the [[{{Int:Helppage}}|help page]] for more info). If you are here by mistake, click your browser\'s back button.', + expectedNewarticletext, + helpPageTitle = 'Help:Contents'; + + mw.messages.set( 'helppage', helpPageTitle ); + + expectedNewarticletext = 'You have followed a link to a page that does not exist yet. To create the page, start typing in the box below (see the ' + + '<a title="Help:Contents" href="/wiki/Help:Contents">help page</a> for more info). If you are here by mistake, click your browser\'s back button.'; + + mw.messages.set( 'newarticletext', newarticletextSource ); + + assert.htmlEqual( + formatParse( 'newarticletext' ), + expectedNewarticletext, + 'Link with nested message' + ); + + assert.equal( + formatParse( 'see-portal-url' ), + 'Project:Community portal is an important community page.', + 'Nested message' + ); + + mw.messages.set( 'newarticletext-lowercase', + newarticletextSource.replace( 'Int:Helppage', 'int:helppage' ) ); + + assert.htmlEqual( + formatParse( 'newarticletext-lowercase' ), + expectedNewarticletext, + 'Link with nested message, lowercase include' + ); + + mw.messages.set( 'uses-missing-int', '{{int:doesnt-exist}}' ); + + assert.equal( + formatParse( 'uses-missing-int' ), + '[doesnt-exist]', + 'int: where nested message does not exist' + ); + } ); + +// Tests that getMessageFunction is used for non-plain messages with curly braces or +// square brackets, but not otherwise. + QUnit.test( 'mw.Message.prototype.parser monkey-patch', 22, function ( assert ) { + var oldGMF, outerCalled, innerCalled; + + mw.messages.set( { + 'curly-brace': '{{int:message}}', + 'single-square-bracket': '[https://www.mediawiki.org/ MediaWiki]', + 'double-square-bracket': '[[Some page]]', + 'regular': 'Other message' + } ); + + oldGMF = mw.jqueryMsg.getMessageFunction; + + mw.jqueryMsg.getMessageFunction = function () { + outerCalled = true; + return function () { + innerCalled = true; + }; + }; + + function verifyGetMessageFunction( key, format, shouldCall ) { + var message; + outerCalled = false; + innerCalled = false; + message = mw.message( key ); + message[format](); + assert.strictEqual( outerCalled, shouldCall, 'Outer function called for ' + key ); + assert.strictEqual( innerCalled, shouldCall, 'Inner function called for ' + key ); + } + + verifyGetMessageFunction( 'curly-brace', 'parse', true ); + verifyGetMessageFunction( 'curly-brace', 'plain', false ); + + verifyGetMessageFunction( 'single-square-bracket', 'parse', true ); + verifyGetMessageFunction( 'single-square-bracket', 'plain', false ); + + verifyGetMessageFunction( 'double-square-bracket', 'parse', true ); + verifyGetMessageFunction( 'double-square-bracket', 'plain', false ); + + verifyGetMessageFunction( 'regular', 'parse', false ); + verifyGetMessageFunction( 'regular', 'plain', false ); + + verifyGetMessageFunction( 'jquerymsg-test-pagetriage-del-talk-page-notify-summary', 'plain', false ); + verifyGetMessageFunction( 'jquerymsg-test-categorytree-collapse-bullet', 'plain', false ); + verifyGetMessageFunction( 'jquerymsg-test-wikieditor-toolbar-help-content-signature-result', 'plain', false ); + + mw.jqueryMsg.getMessageFunction = oldGMF; + } ); + +formatnumTests = [ + { + lang: 'en', + number: 987654321.654321, + result: '987,654,321.654', + description: 'formatnum test for English, decimal seperator' + }, + { + lang: 'ar', + number: 987654321.654321, + result: '٩٨٧٬٦٥٤٬٣٢١٫٦٥٤', + description: 'formatnum test for Arabic, with decimal seperator' + }, + { + lang: 'ar', + number: '٩٨٧٦٥٤٣٢١٫٦٥٤٣٢١', + result: 987654321, + integer: true, + description: 'formatnum test for Arabic, with decimal seperator, reverse' + }, + { + lang: 'ar', + number: -12.89, + result: '-١٢٫٨٩', + description: 'formatnum test for Arabic, negative number' + }, + { + lang: 'ar', + number: '-١٢٫٨٩', + result: -12, + integer: true, + description: 'formatnum test for Arabic, negative number, reverse' + }, + { + lang: 'nl', + number: 987654321.654321, + result: '987.654.321,654', + description: 'formatnum test for Nederlands, decimal seperator' + }, + { + lang: 'nl', + number: -12.89, + result: '-12,89', + description: 'formatnum test for Nederlands, negative number' + }, + { + lang: 'nl', + number: '.89', + result: '0,89', + description: 'formatnum test for Nederlands' + }, + { + lang: 'nl', + number: 'invalidnumber', + result: 'invalidnumber', + description: 'formatnum test for Nederlands, invalid number' + }, + { + lang: 'ml', + number: '1000000000', + result: '1,00,00,00,000', + description: 'formatnum test for Malayalam' + }, + { + lang: 'ml', + number: '-1000000000', + result: '-1,00,00,00,000', + description: 'formatnum test for Malayalam, negative number' + }, + /* + * This will fail because of wrong pattern for ml in MW(different from CLDR) + { + lang: 'ml', + number: '1000000000.000', + result: '1,00,00,00,000.000', + description: 'formatnum test for Malayalam with decimal place' + }, + */ + { + lang: 'hi', + number: '123456789.123456789', + result: '१२,३४,५६,७८९', + description: 'formatnum test for Hindi' + }, + { + lang: 'hi', + number: '१२,३४,५६,७८९', + result: '१२,३४,५६,७८९', + description: 'formatnum test for Hindi, Devanagari digits passed' + }, + { + lang: 'hi', + number: '१२३४५६,७८९', + result: '123456', + integer: true, + description: 'formatnum test for Hindi, Devanagari digits passed to get integer value' + } +]; + +QUnit.test( 'formatnum', formatnumTests.length, function ( assert ) { + mw.messages.set( 'formatnum-msg', '{{formatnum:$1}}' ); + mw.messages.set( 'formatnum-msg-int', '{{formatnum:$1|R}}' ); + $.each( formatnumTests, function ( i, test ) { + QUnit.stop(); + getMwLanguage( test.lang, function ( langClass ) { + QUnit.start(); + if ( !langClass ) { + assert.ok( false, 'Language "' + test.lang + '" failed to load' ); + return; + } + mw.messages.set(test.message ); + mw.config.set( 'wgUserLanguage', test.lang ) ; + var parser = new mw.jqueryMsg.parser( { language: langClass } ); + assert.equal( + parser.parse( test.integer ? 'formatnum-msg-int' : 'formatnum-msg', + [ test.number ] ).html(), + test.result, + test.description + ); + } ); + } ); } ); +// HTML in wikitext +QUnit.test( 'HTML', 26, function ( assert ) { + mw.messages.set( 'jquerymsg-italics-msg', '<i>Very</i> important' ); -QUnit.test( 'mw.jqueryMsg Gender', 11, function ( assert ) { - // TODO: These tests should be for mw.msg once mw.msg integrated with mw.jqueryMsg - // TODO: English may not be the best language for these tests. Use a language like Arabic or Russian - var user = mw.user, - parser = mw.jqueryMsg.getMessageFunction(); + assertBothModes( assert, ['jquerymsg-italics-msg'], mw.messages.get( 'jquerymsg-italics-msg' ), 'Simple italics unchanged' ); - // The values here are not significant, - // what matters is which of the values is choosen by the parser - mw.messages.set( 'gender-msg', '$1: {{GENDER:$2|blue|pink|green}}' ); + mw.messages.set( 'jquerymsg-bold-msg', '<b>Strong</b> speaker' ); + assertBothModes( assert, ['jquerymsg-bold-msg'], mw.messages.get( 'jquerymsg-bold-msg' ), 'Simple bold unchanged' ); - user.options.set( 'gender', 'male' ); - assert.equal( - parser( 'gender-msg', 'Bob', 'male' ), - 'Bob: blue', - 'Masculine from string "male"' + mw.messages.set( 'jquerymsg-bold-italics-msg', 'It is <b><i>key</i></b>' ); + assertBothModes( assert, ['jquerymsg-bold-italics-msg'], mw.messages.get( 'jquerymsg-bold-italics-msg' ), 'Bold and italics nesting order preserved' ); + + mw.messages.set( 'jquerymsg-italics-bold-msg', 'It is <i><b>vital</b></i>' ); + assertBothModes( assert, ['jquerymsg-italics-bold-msg'], mw.messages.get( 'jquerymsg-italics-bold-msg' ), 'Italics and bold nesting order preserved' ); + + mw.messages.set( 'jquerymsg-italics-with-link', 'An <i>italicized [[link|wiki-link]]</i>' ); + + assert.htmlEqual( + formatParse( 'jquerymsg-italics-with-link' ), + 'An <i>italicized <a title="link" href="' + mw.html.escape( mw.util.getUrl( 'link' ) ) + '">wiki-link</i>', + 'Italics with link inside in parse mode' ); + assert.equal( - parser( 'gender-msg', 'Bob', user ), - 'Bob: blue', - 'Masculine from mw.user object' + formatText( 'jquerymsg-italics-with-link' ), + mw.messages.get( 'jquerymsg-italics-with-link' ), + 'Italics with link unchanged in text mode' ); - user.options.set( 'gender', 'unknown' ); - assert.equal( - parser( 'gender-msg', 'Foo', user ), - 'Foo: green', - 'Neutral from mw.user object' ); - assert.equal( - parser( 'gender-msg', 'Alice', 'female' ), - 'Alice: pink', - 'Feminine from string "female"' ); - assert.equal( - parser( 'gender-msg', 'User' ), - 'User: green', - 'Neutral when no parameter given' ); - assert.equal( - parser( 'gender-msg', 'User', 'unknown' ), - 'User: green', - 'Neutral from string "unknown"' + mw.messages.set( 'jquerymsg-italics-id-class', '<i id="foo" class="bar">Foo</i>' ); + assert.htmlEqual( + formatParse( 'jquerymsg-italics-id-class' ), + mw.messages.get( 'jquerymsg-italics-id-class' ), + 'ID and class are allowed' ); - mw.messages.set( 'gender-msg-one-form', '{{GENDER:$1|User}}: $2 {{PLURAL:$2|edit|edits}}' ); + mw.messages.set( 'jquerymsg-italics-onclick', '<i onclick="alert(\'foo\')">Foo</i>' ); + assert.htmlEqual( + formatParse( 'jquerymsg-italics-onclick' ), + '<i onclick="alert(\'foo\')">Foo</i>', + 'element with onclick is escaped because it is not allowed' + ); - assert.equal( - parser( 'gender-msg-one-form', 'male', 10 ), - 'User: 10 edits', - 'Gender neutral and plural form' + mw.messages.set( 'jquerymsg-script-msg', '<script >alert( "Who put this tag here?" );</script>' ); + assert.htmlEqual( + formatParse( 'jquerymsg-script-msg' ), + '<script >alert( "Who put this tag here?" );</script>', + 'Tag outside whitelist escaped in parse mode' ); + assert.equal( - parser( 'gender-msg-one-form', 'female', 1 ), - 'User: 1 edit', - 'Gender neutral and singular form' + formatText( 'jquerymsg-script-msg' ), + mw.messages.get( 'jquerymsg-script-msg' ), + 'Tag outside whitelist unchanged in text mode' ); - mw.messages.set( 'gender-msg-lowercase', '{{gender:$1|he|she}} is awesome' ); - assert.equal( - parser( 'gender-msg-lowercase', 'male' ), - 'he is awesome', - 'Gender masculine' + mw.messages.set( 'jquerymsg-script-link-msg', '<script>[[Foo|bar]]</script>' ); + assert.htmlEqual( + formatParse( 'jquerymsg-script-link-msg' ), + '<script><a title="Foo" href="' + mw.html.escape( mw.util.getUrl( 'Foo' ) ) + '">bar</a></script>', + 'Script tag text is escaped because that element is not allowed, but link inside is still HTML' + ); + + mw.messages.set( 'jquerymsg-mismatched-html', '<i class="important">test</b>' ); + assert.htmlEqual( + formatParse( 'jquerymsg-mismatched-html' ), + '<i class="important">test</b>', + 'Mismatched HTML start and end tag treated as text' ); + + // TODO (mattflaschen, 2013-03-18): It's not a security issue, but there's no real + // reason the htmlEmitter span needs to be here. It's an artifact of how emitting works. + mw.messages.set( 'jquerymsg-script-and-external-link', '<script>alert( "jquerymsg-script-and-external-link test" );</script> [http://example.com <i>Foo</i> bar]' ); + assert.htmlEqual( + formatParse( 'jquerymsg-script-and-external-link' ), + '<script>alert( "jquerymsg-script-and-external-link test" );</script> <a href="http://example.com"><span class="mediaWiki_htmlEmitter"><i>Foo</i> bar</span></a>', + 'HTML tags in external links not interfering with escaping of other tags' + ); + + mw.messages.set( 'jquerymsg-link-script', '[http://example.com <script>alert( "jquerymsg-link-script test" );</script>]' ); + assert.htmlEqual( + formatParse( 'jquerymsg-link-script' ), + '<a href="http://example.com"><span class="mediaWiki_htmlEmitter"><script>alert( "jquerymsg-link-script test" );</script></span></a>', + 'Non-whitelisted HTML tag in external link anchor treated as text' + ); + + // Intentionally not using htmlEqual for the quote tests + mw.messages.set( 'jquerymsg-double-quotes-preserved', '<i id="double">Double</i>' ); assert.equal( - parser( 'gender-msg-lowercase', 'female' ), - 'she is awesome', - 'Gender feminine' + formatParse( 'jquerymsg-double-quotes-preserved' ), + mw.messages.get( 'jquerymsg-double-quotes-preserved' ), + 'Attributes with double quotes are preserved as such' ); - mw.messages.set( 'gender-msg-wrong', '{{gender}} test' ); + mw.messages.set( 'jquerymsg-single-quotes-normalized-to-double', '<i id=\'single\'>Single</i>' ); assert.equal( - parser( 'gender-msg-wrong', 'female' ), - ' test', - 'Invalid syntax should result in {{gender}} simply being stripped away' + formatParse( 'jquerymsg-single-quotes-normalized-to-double' ), + '<i id="single">Single</i>', + 'Attributes with single quotes are normalized to double' ); -} ); + mw.messages.set( 'jquerymsg-escaped-double-quotes-attribute', '<i style="font-family:"Arial"">Styled</i>' ); + assert.htmlEqual( + formatParse( 'jquerymsg-escaped-double-quotes-attribute' ), + mw.messages.get( 'jquerymsg-escaped-double-quotes-attribute' ), + 'Escaped attributes are parsed correctly' + ); + + mw.messages.set( 'jquerymsg-escaped-single-quotes-attribute', '<i style=\'font-family:'Arial'\'>Styled</i>' ); + assert.htmlEqual( + formatParse( 'jquerymsg-escaped-single-quotes-attribute' ), + mw.messages.get( 'jquerymsg-escaped-single-quotes-attribute' ), + 'Escaped attributes are parsed correctly' + ); -QUnit.test( 'mw.jqueryMsg Grammar', 2, function ( assert ) { - var parser = mw.jqueryMsg.getMessageFunction(); - // Assume the grammar form grammar_case_foo is not valid in any language - mw.messages.set( 'grammar-msg', 'Przeszukaj {{GRAMMAR:grammar_case_foo|{{SITENAME}}}}' ); - assert.equal( parser( 'grammar-msg' ), 'Przeszukaj ' + mw.config.get( 'wgSiteName' ), 'Grammar Test with sitename' ); + mw.messages.set( 'jquerymsg-wikitext-contents-parsed', '<i>[http://example.com Example]</i>' ); + assert.htmlEqual( + formatParse( 'jquerymsg-wikitext-contents-parsed' ), + '<i><a href="http://example.com">Example</a></i>', + 'Contents of valid tag are treated as wikitext, so external link is parsed' + ); - mw.messages.set( 'grammar-msg-wrong-syntax', 'Przeszukaj {{GRAMMAR:grammar_case_xyz}}' ); - assert.equal( parser( 'grammar-msg-wrong-syntax' ), 'Przeszukaj ' , 'Grammar Test with wrong grammar template syntax' ); + mw.messages.set( 'jquerymsg-wikitext-contents-script', '<i><script>Script inside</script></i>' ); + assert.htmlEqual( + formatParse( 'jquerymsg-wikitext-contents-script' ), + '<i><span class="mediaWiki_htmlEmitter"><script>Script inside</script></span></i>', + 'Contents of valid tag are treated as wikitext, so invalid HTML element is treated as text' + ); + + mw.messages.set( 'jquerymsg-unclosed-tag', 'Foo<tag>bar' ); + assert.htmlEqual( + formatParse( 'jquerymsg-unclosed-tag' ), + 'Foo<tag>bar', + 'Nonsupported unclosed tags are escaped' + ); + + mw.messages.set( 'jquerymsg-self-closing-tag', 'Foo<tag/>bar' ); + assert.htmlEqual( + formatParse( 'jquerymsg-self-closing-tag' ), + 'Foo<tag/>bar', + 'Self-closing tags don\'t cause a parse error' + ); } ); + +}( mediaWiki, jQuery ) ); diff --git a/tests/qunit/suites/resources/mediawiki/mediawiki.jscompat.test.js b/tests/qunit/suites/resources/mediawiki/mediawiki.jscompat.test.js index 2baa4f37..3328ce3f 100644 --- a/tests/qunit/suites/resources/mediawiki/mediawiki.jscompat.test.js +++ b/tests/qunit/suites/resources/mediawiki/mediawiki.jscompat.test.js @@ -1,62 +1,70 @@ -/* Some misc JavaScript compatibility tests, just to make sure the environments we run in are consistent */ - -QUnit.module( 'mediawiki.jscompat', QUnit.newMwEnvironment() ); - -QUnit.test( 'Variable with Unicode letter in name', 3, function ( assert ) { - var orig = "some token"; - var ŝablono = orig; - - assert.deepEqual( ŝablono, orig, 'ŝablono' ); - assert.deepEqual( \u015dablono, orig, '\\u015dablono' ); - assert.deepEqual( \u015Dablono, orig, '\\u015Dablono' ); -}); - -/* -// Not that we need this. ;) -// This fails on IE 6-8 -// Works on IE 9, Firefox 6, Chrome 14 -QUnit.test( 'Keyword workaround: "if" as variable name using Unicode escapes', function ( assert ) { - var orig = "another token"; - \u0069\u0066 = orig; - assert.deepEqual( \u0069\u0066, orig, '\\u0069\\u0066' ); -}); -*/ - -/* -// Not that we need this. ;) -// This fails on IE 6-9 -// Works on Firefox 6, Chrome 14 -QUnit.test( 'Keyword workaround: "if" as member variable name using Unicode escapes', function ( assert ) { - var orig = "another token"; - var foo = {}; - foo.\u0069\u0066 = orig; - assert.deepEqual( foo.\u0069\u0066, orig, 'foo.\\u0069\\u0066' ); -}); -*/ - -QUnit.test( 'Stripping of single initial newline from textarea\'s literal contents (bug 12130)', function ( assert ) { - var maxn = 4; - QUnit.expect( maxn * 2 ); - - function repeat( str, n ) { - if ( n <= 0 ) { - return ''; - } else { - var out = new Array(n); - for ( var i = 0; i < n; i++ ) { - out[i] = str; +/** + * Some misc JavaScript compatibility tests, + * just to make sure the environments we run in are consistent. + */ +( function ( $ ) { + QUnit.module( 'mediawiki.jscompat', QUnit.newMwEnvironment() ); + + QUnit.test( 'Variable with Unicode letter in name', 3, function ( assert ) { + var orig, ŝablono; + + orig = 'some token'; + ŝablono = orig; + + assert.deepEqual( ŝablono, orig, 'ŝablono' ); + assert.deepEqual( \u015dablono, orig, '\\u015dablono' ); + assert.deepEqual( \u015Dablono, orig, '\\u015Dablono' ); + } ); + + /* + // Not that we need this. ;) + // This fails on IE 6-8 + // Works on IE 9, Firefox 6, Chrome 14 + QUnit.test( 'Keyword workaround: "if" as variable name using Unicode escapes', function ( assert ) { + var orig = "another token"; + \u0069\u0066 = orig; + assert.deepEqual( \u0069\u0066, orig, '\\u0069\\u0066' ); + }); + */ + + /* + // Not that we need this. ;) + // This fails on IE 6-9 + // Works on Firefox 6, Chrome 14 + QUnit.test( 'Keyword workaround: "if" as member variable name using Unicode escapes', function ( assert ) { + var orig = "another token"; + var foo = {}; + foo.\u0069\u0066 = orig; + assert.deepEqual( foo.\u0069\u0066, orig, 'foo.\\u0069\\u0066' ); + }); + */ + + QUnit.test( 'Stripping of single initial newline from textarea\'s literal contents (bug 12130)', function ( assert ) { + var maxn, n, + expected, $textarea; + + maxn = 4; + QUnit.expect( maxn * 2 ); + + function repeat( str, n ) { + var out; + if ( n <= 0 ) { + return ''; + } else { + out = []; + out.length = n + 1; + return out.join( str ); } - return out.join(''); } - } - for ( var n = 0; n < maxn; n++ ) { - var expected = repeat('\n', n) + 'some text'; + for ( n = 0; n < maxn; n++ ) { + expected = repeat( '\n', n ) + 'some text'; - var $textarea = $('<textarea>\n' + expected + '</textarea>'); - assert.equal( $textarea.val(), expected, 'Expecting ' + n + ' newlines (HTML contained ' + (n + 1) + ')' ); + $textarea = $( '<textarea>\n' + expected + '</textarea>' ); + assert.equal( $textarea.val(), expected, 'Expecting ' + n + ' newlines (HTML contained ' + (n + 1) + ')' ); - var $textarea2 = $('<textarea>').val(expected); - assert.equal( $textarea2.val(), expected, 'Expecting ' + n + ' newlines (from DOM set with ' + n + ')' ); - } -}); + $textarea = $( '<textarea>' ).val( expected ); + assert.equal( $textarea.val(), expected, 'Expecting ' + n + ' newlines (from DOM set with ' + n + ')' ); + } + } ); +}( jQuery ) ); diff --git a/tests/qunit/suites/resources/mediawiki/mediawiki.language.test.js b/tests/qunit/suites/resources/mediawiki/mediawiki.language.test.js index 3fa2b099..9ca434f1 100644 --- a/tests/qunit/suites/resources/mediawiki/mediawiki.language.test.js +++ b/tests/qunit/suites/resources/mediawiki/mediawiki.language.test.js @@ -1,394 +1,443 @@ -QUnit.module( 'mediawiki.language', QUnit.newMwEnvironment({ - setup: function () { - this.liveLangData = mw.language.data.values; - mw.language.data.values = $.extend( true, {}, this.liveLangData ); - }, - teardown: function () { - // Restore - mw.language.data.values = this.liveLangData; - } -}) ); +( function ( mw, $ ) { + 'use strict'; -QUnit.test( 'mw.language getData and setData', function ( assert ) { - QUnit.expect( 2 ); + QUnit.module( 'mediawiki.language', QUnit.newMwEnvironment( { + setup: function () { + this.liveLangData = mw.language.data.values; + mw.language.data.values = $.extend( true, {}, this.liveLangData ); + }, + teardown: function () { + mw.language.data.values = this.liveLangData; + } + } ) ); - mw.language.setData( 'en', 'testkey', 'testvalue' ); - assert.equal( mw.language.getData( 'en', 'testkey' ), 'testvalue', 'Getter setter test for mw.language' ); - assert.equal( mw.language.getData( 'en', 'invalidkey' ), undefined, 'Getter setter test for mw.language with invalid key' ); -} ); + QUnit.test( 'mw.language getData and setData', 2, function ( assert ) { + mw.language.setData( 'en', 'testkey', 'testvalue' ); + assert.equal( mw.language.getData( 'en', 'testkey' ), 'testvalue', 'Getter setter test for mw.language' ); + assert.equal( mw.language.getData( 'en', 'invalidkey' ), undefined, 'Getter setter test for mw.language with invalid key' ); + } ); -function grammarTest( langCode, test ) { - // The test works only if the content language is opt.language - // because it requires [lang].js to be loaded. - QUnit.test( 'Grammar test for lang=' + langCode, function ( assert ) { - QUnit.expect( test.length ); + function grammarTest( langCode, test ) { + // The test works only if the content language is opt.language + // because it requires [lang].js to be loaded. + QUnit.test( 'Grammar test for lang=' + langCode, function ( assert ) { + QUnit.expect( test.length ); - for ( var i = 0 ; i < test.length; i++ ) { - assert.equal( - mw.language.convertGrammar( test[i].word, test[i].grammarForm ), - test[i].expected, - test[i].description - ); - } - }); -} + for ( var i = 0; i < test.length; i++ ) { + assert.equal( + mw.language.convertGrammar( test[i].word, test[i].grammarForm ), + test[i].expected, + test[i].description + ); + } + } ); + } -var grammarTests = { - bs: [ - { - word: 'word', - grammarForm: 'instrumental', - expected: 's word', - description: 'Grammar test for instrumental case' - }, - { - word: 'word', - grammarForm: 'lokativ', - expected: 'o word', - description: 'Grammar test for lokativ case' - } - ], + // These tests run only for the current UI language. + var grammarTests = { + bs: [ + { + word: 'word', + grammarForm: 'instrumental', + expected: 's word', + description: 'Grammar test for instrumental case' + }, + { + word: 'word', + grammarForm: 'lokativ', + expected: 'o word', + description: 'Grammar test for lokativ case' + } + ], - he: [ - { - word: "ויקיפדיה", - grammarForm: 'prefixed', - expected: "וויקיפדיה", - description: 'Duplicate the "Waw" if prefixed' - }, - { - word: "וולפגנג", - grammarForm: 'prefixed', - expected: "וולפגנג", - description: 'Duplicate the "Waw" if prefixed, but not if it is already duplicated.' - }, - { - word: "הקובץ", - grammarForm: 'prefixed', - expected: "קובץ", - description: 'Remove the "He" if prefixed' - }, - { - word: 'Wikipedia', - grammarForm: 'תחילית', - expected: '־Wikipedia', - description: 'GAdd a hyphen (maqaf) before non-Hebrew letters' - }, - { - word: '1995', - grammarForm: 'תחילית', - expected: '־1995', - description: 'Add a hyphen (maqaf) before numbers' - } - ], + he: [ + { + word: 'ויקיפדיה', + grammarForm: 'prefixed', + expected: 'וויקיפדיה', + description: 'Duplicate the "Waw" if prefixed' + }, + { + word: 'וולפגנג', + grammarForm: 'prefixed', + expected: 'וולפגנג', + description: 'Duplicate the "Waw" if prefixed, but not if it is already duplicated.' + }, + { + word: 'הקובץ', + grammarForm: 'prefixed', + expected: 'קובץ', + description: 'Remove the "He" if prefixed' + }, + { + word: 'Wikipedia', + grammarForm: 'תחילית', + expected: '־Wikipedia', + description: 'GAdd a hyphen (maqaf) before non-Hebrew letters' + }, + { + word: '1995', + grammarForm: 'תחילית', + expected: '־1995', + description: 'Add a hyphen (maqaf) before numbers' + } + ], - hsb: [ - { - word: 'word', - grammarForm: 'instrumental', - expected: 'z word', - description: 'Grammar test for instrumental case' - }, - { - word: 'word', - grammarForm: 'lokatiw', - expected: 'wo word', - description: 'Grammar test for lokatiw case' - } - ], + hsb: [ + { + word: 'word', + grammarForm: 'instrumental', + expected: 'z word', + description: 'Grammar test for instrumental case' + }, + { + word: 'word', + grammarForm: 'lokatiw', + expected: 'wo word', + description: 'Grammar test for lokatiw case' + } + ], - dsb: [ - { - word: 'word', - grammarForm: 'instrumental', - expected: 'z word', - description: 'Grammar test for instrumental case' - }, - { - word: 'word', - grammarForm: 'lokatiw', - expected: 'wo word', - description: 'Grammar test for lokatiw case' - } - ], + dsb: [ + { + word: 'word', + grammarForm: 'instrumental', + expected: 'z word', + description: 'Grammar test for instrumental case' + }, + { + word: 'word', + grammarForm: 'lokatiw', + expected: 'wo word', + description: 'Grammar test for lokatiw case' + } + ], - hy: [ - { - word: 'Մաունա', - grammarForm: 'genitive', - expected: 'Մաունայի', - description: 'Grammar test for genitive case' - }, - { - word: 'հետո', - grammarForm: 'genitive', - expected: 'հետոյի', - description: 'Grammar test for genitive case' - }, - { - word: 'գիրք', - grammarForm: 'genitive', - expected: 'գրքի', - description: 'Grammar test for genitive case' - }, - { - word: 'ժամանակի', - grammarForm: 'genitive', - expected: 'ժամանակիի', - description: 'Grammar test for genitive case' - } - ], + hy: [ + { + word: 'Մաունա', + grammarForm: 'genitive', + expected: 'Մաունայի', + description: 'Grammar test for genitive case' + }, + { + word: 'հետո', + grammarForm: 'genitive', + expected: 'հետոյի', + description: 'Grammar test for genitive case' + }, + { + word: 'գիրք', + grammarForm: 'genitive', + expected: 'գրքի', + description: 'Grammar test for genitive case' + }, + { + word: 'ժամանակի', + grammarForm: 'genitive', + expected: 'ժամանակիի', + description: 'Grammar test for genitive case' + } + ], - fi: [ - { - word: 'talo', - grammarForm: 'genitive', - expected: 'talon', - description: 'Grammar test for genitive case' - }, - { - word: 'linux', - grammarForm: 'genitive', - expected: 'linuxin', - description: 'Grammar test for genitive case' - }, - { - word: 'talo', - grammarForm: 'elative', - expected: 'talosta', - description: 'Grammar test for elative case' - }, - { - word: 'pastöroitu', - grammarForm: 'partitive', - expected: 'pastöroitua', - description: 'Grammar test for partitive case' - }, - { - word: 'talo', - grammarForm: 'partitive', - expected: 'taloa', - description: 'Grammar test for partitive case' - }, - { - word: 'talo', - grammarForm: 'illative', - expected: 'taloon', - description: 'Grammar test for illative case' - }, - { - word: 'linux', - grammarForm: 'inessive', - expected: 'linuxissa', - description: 'Grammar test for inessive case' - } - ], + fi: [ + { + word: 'talo', + grammarForm: 'genitive', + expected: 'talon', + description: 'Grammar test for genitive case' + }, + { + word: 'linux', + grammarForm: 'genitive', + expected: 'linuxin', + description: 'Grammar test for genitive case' + }, + { + word: 'talo', + grammarForm: 'elative', + expected: 'talosta', + description: 'Grammar test for elative case' + }, + { + word: 'pastöroitu', + grammarForm: 'partitive', + expected: 'pastöroitua', + description: 'Grammar test for partitive case' + }, + { + word: 'talo', + grammarForm: 'partitive', + expected: 'taloa', + description: 'Grammar test for partitive case' + }, + { + word: 'talo', + grammarForm: 'illative', + expected: 'taloon', + description: 'Grammar test for illative case' + }, + { + word: 'linux', + grammarForm: 'inessive', + expected: 'linuxissa', + description: 'Grammar test for inessive case' + } + ], - ru: [ - { - word: 'тесть', - grammarForm: 'genitive', - expected: 'тестя', - description: 'Grammar test for genitive case' - }, - { - word: 'привилегия', - grammarForm: 'genitive', - expected: 'привилегии', - description: 'Grammar test for genitive case' - }, - { - word: 'установка', - grammarForm: 'genitive', - expected: 'установки', - description: 'Grammar test for genitive case' - }, - { - word: 'похоти', - grammarForm: 'genitive', - expected: 'похотей', - description: 'Grammar test for genitive case' - }, - { - word: 'доводы', - grammarForm: 'genitive', - expected: 'доводов', - description: 'Grammar test for genitive case' - }, - { - word: 'песчаник', - grammarForm: 'genitive', - expected: 'песчаника', - description: 'Grammar test for genitive case' - } - ], + ru: [ + { + word: 'тесть', + grammarForm: 'genitive', + expected: 'тестя', + description: 'Grammar test for genitive case, тесть -> тестя' + }, + { + word: 'привилегия', + grammarForm: 'genitive', + expected: 'привилегии', + description: 'Grammar test for genitive case, привилегия -> привилегии' + }, + { + word: 'установка', + grammarForm: 'genitive', + expected: 'установки', + description: 'Grammar test for genitive case, установка -> установки' + }, + { + word: 'похоти', + grammarForm: 'genitive', + expected: 'похотей', + description: 'Grammar test for genitive case, похоти -> похотей' + }, + { + word: 'доводы', + grammarForm: 'genitive', + expected: 'доводов', + description: 'Grammar test for genitive case, доводы -> доводов' + }, + { + word: 'песчаник', + grammarForm: 'genitive', + expected: 'песчаника', + description: 'Grammar test for genitive case, песчаник -> песчаника' + }, + { + word: 'данные', + grammarForm: 'genitive', + expected: 'данных', + description: 'Grammar test for genitive case, данные -> данных' + }, + { + word: 'тесть', + grammarForm: 'prepositional', + expected: 'тесте', + description: 'Grammar test for prepositional case, тесть -> тесте' + }, + { + word: 'привилегия', + grammarForm: 'prepositional', + expected: 'привилегии', + description: 'Grammar test for prepositional case, привилегия -> привилегии' + }, + { + word: 'установка', + grammarForm: 'prepositional', + expected: 'установке', + description: 'Grammar test for prepositional case, установка -> установке' + }, + { + word: 'похоти', + grammarForm: 'prepositional', + expected: 'похотях', + description: 'Grammar test for prepositional case, похоти -> похотях' + }, + { + word: 'доводы', + grammarForm: 'prepositional', + expected: 'доводах', + description: 'Grammar test for prepositional case, доводы -> доводах' + }, + { + word: 'песчаник', + grammarForm: 'prepositional', + expected: 'песчанике', + description: 'Grammar test for prepositional case, песчаник -> песчанике' + }, + { + word: 'данные', + grammarForm: 'prepositional', + expected: 'данных', + description: 'Grammar test for prepositional case, данные -> данных' + } + ], + hu: [ + { + word: 'Wikipédiá', + grammarForm: 'rol', + expected: 'Wikipédiáról', + description: 'Grammar test for rol case' + }, + { + word: 'Wikipédiá', + grammarForm: 'ba', + expected: 'Wikipédiába', + description: 'Grammar test for ba case' + }, + { + word: 'Wikipédiá', + grammarForm: 'k', + expected: 'Wikipédiák', + description: 'Grammar test for k case' + } + ], - hu: [ - { - word: 'Wikipédiá', - grammarForm: 'rol', - expected: 'Wikipédiáról', - description: 'Grammar test for rol case' - }, - { - word: 'Wikipédiá', - grammarForm: 'ba', - expected: 'Wikipédiába', - description: 'Grammar test for ba case' - }, - { - word: 'Wikipédiá', - grammarForm: 'k', - expected: 'Wikipédiák', - description: 'Grammar test for k case' - } - ], + ga: [ + { + word: 'an Domhnach', + grammarForm: 'ainmlae', + expected: 'Dé Domhnaigh', + description: 'Grammar test for ainmlae case' + }, + { + word: 'an Luan', + grammarForm: 'ainmlae', + expected: 'Dé Luain', + description: 'Grammar test for ainmlae case' + }, + { + word: 'an Satharn', + grammarForm: 'ainmlae', + expected: 'Dé Sathairn', + description: 'Grammar test for ainmlae case' + } + ], - ga: [ - { - word: 'an Domhnach', - grammarForm: 'ainmlae', - expected: 'Dé Domhnaigh', - description: 'Grammar test for ainmlae case' - }, - { - word: 'an Luan', - grammarForm: 'ainmlae', - expected: 'Dé Luain', - description: 'Grammar test for ainmlae case' - }, - { - word: 'an Satharn', - grammarForm: 'ainmlae', - expected: 'Dé Sathairn', - description: 'Grammar test for ainmlae case' - } - ], + uk: [ + { + word: 'тесть', + grammarForm: 'genitive', + expected: 'тестя', + description: 'Grammar test for genitive case' + }, + { + word: 'Вікіпедія', + grammarForm: 'genitive', + expected: 'Вікіпедії', + description: 'Grammar test for genitive case' + }, + { + word: 'установка', + grammarForm: 'genitive', + expected: 'установки', + description: 'Grammar test for genitive case' + }, + { + word: 'похоти', + grammarForm: 'genitive', + expected: 'похотей', + description: 'Grammar test for genitive case' + }, + { + word: 'доводы', + grammarForm: 'genitive', + expected: 'доводов', + description: 'Grammar test for genitive case' + }, + { + word: 'песчаник', + grammarForm: 'genitive', + expected: 'песчаника', + description: 'Grammar test for genitive case' + }, + { + word: 'Вікіпедія', + grammarForm: 'accusative', + expected: 'Вікіпедію', + description: 'Grammar test for accusative case' + } + ], - uk: [ - { - word: 'тесть', - grammarForm: 'genitive', - expected: 'тестя', - description: 'Grammar test for genitive case' - }, - { - word: 'Вікіпедія', - grammarForm: 'genitive', - expected: 'Вікіпедії', - description: 'Grammar test for genitive case' - }, - { - word: 'установка', - grammarForm: 'genitive', - expected: 'установки', - description: 'Grammar test for genitive case' - }, - { - word: 'похоти', - grammarForm: 'genitive', - expected: 'похотей', - description: 'Grammar test for genitive case' - }, - { - word: 'доводы', - grammarForm: 'genitive', - expected: 'доводов', - description: 'Grammar test for genitive case' - }, - { - word: 'песчаник', - grammarForm: 'genitive', - expected: 'песчаника', - description: 'Grammar test for genitive case' - }, - { - word: 'Вікіпедія', - grammarForm: 'accusative', - expected: 'Вікіпедію', - description: 'Grammar test for accusative case' - } - ], + sl: [ + { + word: 'word', + grammarForm: 'orodnik', + expected: 'z word', + description: 'Grammar test for orodnik case' + }, + { + word: 'word', + grammarForm: 'mestnik', + expected: 'o word', + description: 'Grammar test for mestnik case' + } + ], - sl: [ - { - word: 'word', - grammarForm: 'orodnik', - expected: 'z word', - description: 'Grammar test for orodnik case' - }, - { - word: 'word', - grammarForm: 'mestnik', - expected: 'o word', - description: 'Grammar test for mestnik case' - } - ], + os: [ + { + word: 'бæстæ', + grammarForm: 'genitive', + expected: 'бæсты', + description: 'Grammar test for genitive case' + }, + { + word: 'бæстæ', + grammarForm: 'allative', + expected: 'бæстæм', + description: 'Grammar test for allative case' + }, + { + word: 'Тигр', + grammarForm: 'dative', + expected: 'Тигрæн', + description: 'Grammar test for dative case' + }, + { + word: 'цъити', + grammarForm: 'dative', + expected: 'цъитийæн', + description: 'Grammar test for dative case' + }, + { + word: 'лæппу', + grammarForm: 'genitive', + expected: 'лæппуйы', + description: 'Grammar test for genitive case' + }, + { + word: '2011', + grammarForm: 'equative', + expected: '2011-ау', + description: 'Grammar test for equative case' + } + ], - os: [ - { - word: 'бæстæ', - grammarForm: 'genitive', - expected: 'бæсты', - description: 'Grammar test for genitive case' - }, - { - word: 'бæстæ', - grammarForm: 'allative', - expected: 'бæстæм', - description: 'Grammar test for allative case' - }, - { - word: 'Тигр', - grammarForm: 'dative', - expected: 'Тигрæн', - description: 'Grammar test for dative case' - }, - { - word: 'цъити', - grammarForm: 'dative', - expected: 'цъитийæн', - description: 'Grammar test for dative case' - }, - { - word: 'лæппу', - grammarForm: 'genitive', - expected: 'лæппуйы', - description: 'Grammar test for genitive case' - }, - { - word: '2011', - grammarForm: 'equative', - expected: '2011-ау', - description: 'Grammar test for equative case' - } - ], + la: [ + { + word: 'Translatio', + grammarForm: 'genitive', + expected: 'Translationis', + description: 'Grammar test for genitive case' + }, + { + word: 'Translatio', + grammarForm: 'accusative', + expected: 'Translationem', + description: 'Grammar test for accusative case' + }, + { + word: 'Translatio', + grammarForm: 'ablative', + expected: 'Translatione', + description: 'Grammar test for ablative case' + } + ] + }; - la: [ - { - word: 'Translatio', - grammarForm: 'genitive', - expected: 'Translationis', - description: 'Grammar test for genitive case' - }, - { - word: 'Translatio', - grammarForm: 'accusative', - expected: 'Translationem', - description: 'Grammar test for accusative case' - }, - { - word: 'Translatio', - grammarForm: 'ablative', - expected: 'Translatione', - description: 'Grammar test for ablative case' + $.each( grammarTests, function ( langCode, test ) { + if ( langCode === mw.config.get( 'wgUserLanguage' ) ) { + grammarTest( langCode, test ); } - ] -}; - -$.each( grammarTests, function ( langCode, test ) { - if ( langCode === mw.config.get( 'wgUserLanguage' ) ) { - grammarTest( langCode, test ); - } -}); + } ); +}( mediaWiki, jQuery ) ); diff --git a/tests/qunit/suites/resources/mediawiki/mediawiki.test.js b/tests/qunit/suites/resources/mediawiki/mediawiki.test.js index be59f1ad..bd4d1d21 100644 --- a/tests/qunit/suites/resources/mediawiki/mediawiki.test.js +++ b/tests/qunit/suites/resources/mediawiki/mediawiki.test.js @@ -1,649 +1,916 @@ -( function ( mw ) { - -QUnit.module( 'mediawiki', QUnit.newMwEnvironment() ); - -QUnit.test( 'Initial check', 8, function ( assert ) { - assert.ok( window.jQuery, 'jQuery defined' ); - assert.ok( window.$, '$j defined' ); - assert.ok( window.$j, '$j defined' ); - assert.strictEqual( window.$, window.jQuery, '$ alias to jQuery' ); - assert.strictEqual( window.$j, window.jQuery, '$j alias to jQuery' ); - - assert.ok( window.mediaWiki, 'mediaWiki defined' ); - assert.ok( window.mw, 'mw defined' ); - assert.strictEqual( window.mw, window.mediaWiki, 'mw alias to mediaWiki' ); -}); - -QUnit.test( 'mw.Map', 17, function ( assert ) { - var arry, conf, funky, globalConf, nummy, someValues; - - assert.ok( mw.Map, 'mw.Map defined' ); - - conf = new mw.Map(); - // Dummy variables - funky = function () {}; - arry = []; - nummy = 7; - - // Tests for input validation - assert.strictEqual( conf.get( 'inexistantKey' ), null, 'Map.get returns null if selection was a string and the key was not found' ); - assert.strictEqual( conf.set( 'myKey', 'myValue' ), true, 'Map.set returns boolean true if a value was set for a valid key string' ); - assert.strictEqual( conf.set( funky, 'Funky' ), false, 'Map.set returns boolean false if key was invalid (Function)' ); - assert.strictEqual( conf.set( arry, 'Arry' ), false, 'Map.set returns boolean false if key was invalid (Array)' ); - assert.strictEqual( conf.set( nummy, 'Nummy' ), false, 'Map.set returns boolean false if key was invalid (Number)' ); - assert.equal( conf.get( 'myKey' ), 'myValue', 'Map.get returns a single value value correctly' ); - assert.strictEqual( conf.get( nummy ), null, 'Map.get ruturns null if selection was invalid (Number)' ); - assert.strictEqual( conf.get( funky ), null, 'Map.get ruturns null if selection was invalid (Function)' ); - - // Multiple values at once - someValues = { - 'foo': 'bar', - 'lorem': 'ipsum', - 'MediaWiki': true - }; - assert.strictEqual( conf.set( someValues ), true, 'Map.set returns boolean true if multiple values were set by passing an object' ); - assert.deepEqual( conf.get( ['foo', 'lorem'] ), { - 'foo': 'bar', - 'lorem': 'ipsum' - }, 'Map.get returns multiple values correctly as an object' ); - - assert.deepEqual( conf.get( ['foo', 'notExist'] ), { - 'foo': 'bar', - 'notExist': null - }, 'Map.get return includes keys that were not found as null values' ); - - assert.strictEqual( conf.exists( 'foo' ), true, 'Map.exists returns boolean true if a key exists' ); - assert.strictEqual( conf.exists( 'notExist' ), false, 'Map.exists returns boolean false if a key does not exists' ); - - // Interacting with globals and accessing the values object - assert.strictEqual( conf.get(), conf.values, 'Map.get returns the entire values object by reference (if called without arguments)' ); - - conf.set( 'globalMapChecker', 'Hi' ); - - assert.ok( false === 'globalMapChecker' in window, 'new mw.Map did not store its values in the global window object by default' ); - - globalConf = new mw.Map( true ); - globalConf.set( 'anotherGlobalMapChecker', 'Hello' ); - - assert.ok( 'anotherGlobalMapChecker' in window, 'new mw.Map( true ) did store its values in the global window object' ); - - // Whitelist this global variable for QUnit's 'noglobal' mode - if ( QUnit.config.noglobals ) { - QUnit.config.pollution.push( 'anotherGlobalMapChecker' ); - } -}); - -QUnit.test( 'mw.config', 1, function ( assert ) { - assert.ok( mw.config instanceof mw.Map, 'mw.config instance of mw.Map' ); -}); - -QUnit.test( 'mw.message & mw.messages', 20, function ( assert ) { - var goodbye, hello, pluralMessage; - - assert.ok( mw.messages, 'messages defined' ); - assert.ok( mw.messages instanceof mw.Map, 'mw.messages instance of mw.Map' ); - assert.ok( mw.messages.set( 'hello', 'Hello <b>awesome</b> world' ), 'mw.messages.set: Register' ); - - hello = mw.message( 'hello' ); - - assert.equal( hello.format, 'plain', 'Message property "format" defaults to "plain"' ); - assert.strictEqual( hello.map, mw.messages, 'Message property "map" defaults to the global instance in mw.messages' ); - assert.equal( hello.key, 'hello', 'Message property "key" (currect key)' ); - assert.deepEqual( hello.parameters, [], 'Message property "parameters" defaults to an empty array' ); - - // Todo - assert.ok( hello.params, 'Message prototype "params"' ); - - hello.format = 'plain'; - assert.equal( hello.toString(), 'Hello <b>awesome</b> world', 'Message.toString returns the message as a string with the current "format"' ); - - assert.equal( hello.escaped(), 'Hello <b>awesome</b> world', 'Message.escaped returns the escaped message' ); - assert.equal( hello.format, 'escaped', 'Message.escaped correctly updated the "format" property' ); - - hello.parse(); - assert.equal( hello.format, 'parse', 'Message.parse correctly updated the "format" property' ); - - hello.plain(); - assert.equal( hello.format, 'plain', 'Message.plain correctly updated the "format" property' ); - - assert.strictEqual( hello.exists(), true, 'Message.exists returns true for existing messages' ); - - goodbye = mw.message( 'goodbye' ); - assert.strictEqual( goodbye.exists(), false, 'Message.exists returns false for nonexistent messages' ); - - assert.equal( goodbye.plain(), '<goodbye>', 'Message.toString returns plain <key> if format is "plain" and key does not exist' ); - // bug 30684 - assert.equal( goodbye.escaped(), '<goodbye>', 'Message.toString returns properly escaped <key> if format is "escaped" and key does not exist' ); - - assert.ok( mw.messages.set( 'pluraltestmsg', 'There {{PLURAL:$1|is|are}} $1 {{PLURAL:$1|result|results}}' ), 'mw.messages.set: Register' ); - pluralMessage = mw.message( 'pluraltestmsg' , 6 ); - assert.equal( pluralMessage.plain(), 'There are 6 results', 'plural get resolved when format is plain' ); - assert.equal( pluralMessage.parse(), 'There are 6 results', 'plural get resolved when format is parse' ); - -}); - -QUnit.test( 'mw.msg', 11, function ( assert ) { - assert.ok( mw.messages.set( 'hello', 'Hello <b>awesome</b> world' ), 'mw.messages.set: Register' ); - assert.equal( mw.msg( 'hello' ), 'Hello <b>awesome</b> world', 'Gets message with default options (existing message)' ); - assert.equal( mw.msg( 'goodbye' ), '<goodbye>', 'Gets message with default options (nonexistent message)' ); - - assert.ok( mw.messages.set( 'plural-item' , 'Found $1 {{PLURAL:$1|item|items}}' ) ); - assert.equal( mw.msg( 'plural-item', 5 ), 'Found 5 items', 'Apply plural for count 5' ); - assert.equal( mw.msg( 'plural-item', 0 ), 'Found 0 items', 'Apply plural for count 0' ); - assert.equal( mw.msg( 'plural-item', 1 ), 'Found 1 item', 'Apply plural for count 1' ); - - assert.ok( mw.messages.set('gender-plural-msg' , '{{GENDER:$1|he|she|they}} {{PLURAL:$2|is|are}} awesome' ) ); - assert.equal( mw.msg( 'gender-plural-msg', 'male', 1 ), 'he is awesome', 'Gender test for male, plural count 1' ); - assert.equal( mw.msg( 'gender-plural-msg', 'female', '1' ), 'she is awesome', 'Gender test for female, plural count 1' ); - assert.equal( mw.msg( 'gender-plural-msg', 'unknown', 10 ), 'they are awesome', 'Gender test for neutral, plural count 10' ); - -}); - -/** - * The sync style load test (for @import). This is, in a way, also an open bug for - * ResourceLoader ("execute js after styles are loaded"), but browsers don't offer a - * way to get a callback from when a stylesheet is loaded (that is, including any - * @import rules inside). To work around this, we'll have a little time loop to check - * if the styles apply. - * Note: This test originally used new Image() and onerror to get a callback - * when the url is loaded, but that is fragile since it doesn't monitor the - * same request as the css @import, and Safari 4 has issues with - * onerror/onload not being fired at all in weird cases like this. - */ -function assertStyleAsync( assert, $element, prop, val, fn ) { - var styleTestStart, - el = $element.get( 0 ), - styleTestTimeout = ( QUnit.config.testTimeout - 200 ) || 5000; - - function isCssImportApplied() { - // Trigger reflow, repaint, redraw, whatever (cross-browser) - var x = $element.css( 'height' ); - x = el.innerHTML; - el.className = el.className; - x = document.documentElement.clientHeight; - - return $element.css( prop ) === val; - } +( function ( mw, $ ) { + var specialCharactersPageName; + + // Since QUnitTestResources.php loads both mediawiki and mediawiki.jqueryMsg as + // dependencies, this only tests the monkey-patched behavior with the two of them combined. + + // See mediawiki.jqueryMsg.test.js for unit tests for jqueryMsg-specific functionality. + + QUnit.module( 'mediawiki', QUnit.newMwEnvironment( { + setup: function () { + // Messages used in multiple tests + mw.messages.set( { + 'other-message': 'Other Message', + 'mediawiki-test-pagetriage-del-talk-page-notify-summary': 'Notifying author of deletion nomination for [[$1]]', + 'gender-plural-msg': '{{GENDER:$1|he|she|they}} {{PLURAL:$2|is|are}} awesome', + 'grammar-msg': 'Przeszukaj {{GRAMMAR:grammar_case_foo|{{SITENAME}}}}', + 'formatnum-msg': '{{formatnum:$1}}', + 'int-msg': 'Some {{int:other-message}}', + 'mediawiki-test-version-entrypoints-index-php': '[https://www.mediawiki.org/wiki/Manual:index.php index.php]', + 'external-link-replace': 'Foo [$1 bar]' + } ); - function styleTestLoop() { - var styleTestSince = new Date().getTime() - styleTestStart; - // If it is passing or if we timed out, run the real test and stop the loop - if ( isCssImportApplied() || styleTestSince > styleTestTimeout ) { - assert.equal( $element.css( prop ), val, - 'style "' + prop + ': ' + val + '" from url is applied (after ' + styleTestSince + 'ms)' - ); + mw.config.set( { + wgArticlePath: '/wiki/$1', - if ( fn ) { - fn(); - } + // For formatnum tests + wgUserLanguage: 'en' + } ); - return; + specialCharactersPageName = '"Who" wants to be a millionaire & live on \'Exotic Island\'?'; } - // Otherwise, keep polling - setTimeout( styleTestLoop, 150 ); - } + } ) ); - // Start the loop - styleTestStart = new Date().getTime(); - styleTestLoop(); -} - -function urlStyleTest( selector, prop, val ) { - return QUnit.fixurl( - mw.config.get( 'wgScriptPath' ) + - '/tests/qunit/data/styleTest.css.php?' + - $.param( { - selector: selector, - prop: prop, - val: val - } ) - ); -} - -QUnit.asyncTest( 'mw.loader', 2, function ( assert ) { - var isAwesomeDone; - - mw.loader.testCallback = function () { - QUnit.start(); - assert.strictEqual( isAwesomeDone, undefined, 'Implementing module is.awesome: isAwesomeDone should still be undefined'); - isAwesomeDone = true; - }; - - mw.loader.implement( 'test.callback', [QUnit.fixurl( mw.config.get( 'wgScriptPath' ) + '/tests/qunit/data/callMwLoaderTestCallback.js' )], {}, {} ); - - mw.loader.using( 'test.callback', function () { - - // /sample/awesome.js declares the "mw.loader.testCallback" function - // which contains a call to start() and ok() - assert.strictEqual( isAwesomeDone, true, "test.callback module should've caused isAwesomeDone to be true" ); - delete mw.loader.testCallback; - - }, function () { - QUnit.start(); - assert.ok( false, 'Error callback fired while loader.using "test.callback" module' ); - }); -}); - -QUnit.test( 'mw.loader.implement( styles={ "css": [text, ..] } )', 2, function ( assert ) { - var $element = $( '<div class="mw-test-implement-a"></div>' ).appendTo( '#qunit-fixture' ); - - assert.notEqual( - $element.css( 'float' ), - 'right', - 'style is clear' - ); - - mw.loader.implement( - 'test.implement.a', - function () { - assert.equal( - $element.css( 'float' ), - 'right', - 'style is applied' - ); - }, - { - 'all': '.mw-test-implement-a { float: right; }' - }, - {} - ); - - mw.loader.load([ - 'test.implement.a' - ]); -} ); - -QUnit.asyncTest( 'mw.loader.implement( styles={ "url": { <media>: [url, ..] } } )', 7, function ( assert ) { - var $element1 = $( '<div class="mw-test-implement-b1"></div>' ).appendTo( '#qunit-fixture' ), - $element2 = $( '<div class="mw-test-implement-b2"></div>' ).appendTo( '#qunit-fixture' ), - $element3 = $( '<div class="mw-test-implement-b3"></div>' ).appendTo( '#qunit-fixture' ); - - assert.notEqual( - $element1.css( 'text-align' ), - 'center', - 'style is clear' - ); - assert.notEqual( - $element2.css( 'float' ), - 'left', - 'style is clear' - ); - assert.notEqual( - $element3.css( 'text-align' ), - 'right', - 'style is clear' - ); - - mw.loader.implement( - 'test.implement.b', - function () { - assertStyleAsync( assert, $element2, 'float', 'left', function () { - assert.notEqual( $element1.css( 'text-align' ), 'center', 'print style is not applied' ); + QUnit.test( 'Initial check', 8, function ( assert ) { + assert.ok( window.jQuery, 'jQuery defined' ); + assert.ok( window.$, '$j defined' ); + assert.ok( window.$j, '$j defined' ); + assert.strictEqual( window.$, window.jQuery, '$ alias to jQuery' ); + assert.strictEqual( window.$j, window.jQuery, '$j alias to jQuery' ); - QUnit.start(); - } ); - assertStyleAsync( assert, $element3, 'float', 'right', function () { - assert.notEqual( $element1.css( 'text-align' ), 'center', 'print style is not applied' ); + assert.ok( window.mediaWiki, 'mediaWiki defined' ); + assert.ok( window.mw, 'mw defined' ); + assert.strictEqual( window.mw, window.mediaWiki, 'mw alias to mediaWiki' ); + } ); - QUnit.start(); - } ); - }, - { - 'url': { - 'print': [urlStyleTest( '.mw-test-implement-b1', 'text-align', 'center' )], - 'screen': [ - // bug 40834: Make sure it actually works with more than 1 stylesheet reference - urlStyleTest( '.mw-test-implement-b2', 'float', 'left' ), - urlStyleTest( '.mw-test-implement-b3', 'float', 'right' ) - ] - } - }, - {} - ); - - mw.loader.load([ - 'test.implement.b' - ]); -} ); - -// Backwards compatibility -QUnit.test( 'mw.loader.implement( styles={ <media>: text } ) (back-compat)', 2, function ( assert ) { - var $element = $( '<div class="mw-test-implement-c"></div>' ).appendTo( '#qunit-fixture' ); - - assert.notEqual( - $element.css( 'float' ), - 'right', - 'style is clear' - ); - - mw.loader.implement( - 'test.implement.c', - function () { - assert.equal( - $element.css( 'float' ), - 'right', - 'style is applied' - ); - }, - { - 'all': '.mw-test-implement-c { float: right; }' - }, - {} - ); - - mw.loader.load([ - 'test.implement.c' - ]); -} ); - -// Backwards compatibility -QUnit.asyncTest( 'mw.loader.implement( styles={ <media>: [url, ..] } ) (back-compat)', 4, function ( assert ) { - var $element = $( '<div class="mw-test-implement-d"></div>' ).appendTo( '#qunit-fixture' ), - $element2 = $( '<div class="mw-test-implement-d2"></div>' ).appendTo( '#qunit-fixture' ); - - assert.notEqual( - $element.css( 'float' ), - 'right', - 'style is clear' - ); - assert.notEqual( - $element2.css( 'text-align' ), - 'center', - 'style is clear' - ); - - mw.loader.implement( - 'test.implement.d', - function () { - assertStyleAsync( assert, $element, 'float', 'right', function () { - - assert.notEqual( $element2.css( 'text-align' ), 'center', 'print style is not applied (bug 40500)' ); + QUnit.test( 'mw.Map', 27, function ( assert ) { + var arry, conf, funky, globalConf, nummy, someValues; - QUnit.start(); - } ); - }, - { - 'all': [urlStyleTest( '.mw-test-implement-d', 'float', 'right' )], - 'print': [urlStyleTest( '.mw-test-implement-d2', 'text-align', 'center' )] - }, - {} - ); - - mw.loader.load([ - 'test.implement.d' - ]); -} ); - -// @import (bug 31676) -QUnit.asyncTest( 'mw.loader.implement( styles has @import)', 5, function ( assert ) { - var isJsExecuted, $element; - - mw.loader.implement( - 'test.implement.import', - function () { - assert.strictEqual( isJsExecuted, undefined, 'javascript not executed multiple times' ); - isJsExecuted = true; - - assert.equal( mw.loader.getState( 'test.implement.import' ), 'ready', 'module state is "ready" while implement() is executing javascript' ); - - $element = $( '<div class="mw-test-implement-import">Foo bar</div>' ).appendTo( '#qunit-fixture' ); - - assert.equal( mw.msg( 'test-foobar' ), 'Hello Foobar, $1!', 'Messages are loaded before javascript execution' ); - - assertStyleAsync( assert, $element, 'float', 'right', function () { - assert.equal( $element.css( 'text-align' ),'center', - 'CSS styles after the @import rule are working' - ); + conf = new mw.Map(); + // Dummy variables + funky = function () {}; + arry = []; + nummy = 7; - QUnit.start(); - } ); - }, - { - 'css': [ - '@import url(\'' - + urlStyleTest( '.mw-test-implement-import', 'float', 'right' ) - + '\');\n' - + '.mw-test-implement-import { text-align: center; }' - ] - }, - { - 'test-foobar': 'Hello Foobar, $1!' - } - ); - - mw.loader.load( 'test.implement' ); - -}); - -QUnit.asyncTest( 'mw.loader.implement( only messages )' , 2, function ( assert ) { - assert.assertFalse( mw.messages.exists( 'bug_29107' ), 'Verify that the test message doesn\'t exist yet' ); - - mw.loader.implement( 'test.implement.msgs', [], {}, { 'bug_29107': 'loaded' } ); - mw.loader.using( 'test.implement.msgs', function() { - QUnit.start(); - assert.ok( mw.messages.exists( 'bug_29107' ), 'Bug 29107: messages-only module should implement ok' ); - }, function() { - QUnit.start(); - assert.ok( false, 'Error callback fired while implementing "test.implement.msgs" module' ); - }); -}); - -QUnit.test( 'mw.loader erroneous indirect dependency', 3, function ( assert ) { - mw.loader.register( [ - ['test.module1', '0'], - ['test.module2', '0', ['test.module1']], - ['test.module3', '0', ['test.module2']] - ] ); - mw.loader.implement( 'test.module1', function () { throw new Error( 'expected' ); }, {}, {} ); - assert.strictEqual( mw.loader.getState( 'test.module1' ), 'error', 'Expected "error" state for test.module1' ); - assert.strictEqual( mw.loader.getState( 'test.module2' ), 'error', 'Expected "error" state for test.module2' ); - assert.strictEqual( mw.loader.getState( 'test.module3' ), 'error', 'Expected "error" state for test.module3' ); -} ); - -QUnit.test( 'mw.loader out-of-order implementation', 9, function ( assert ) { - mw.loader.register( [ - ['test.module4', '0'], - ['test.module5', '0', ['test.module4']], - ['test.module6', '0', ['test.module5']] - ] ); - mw.loader.implement( 'test.module4', function () {}, {}, {} ); - assert.strictEqual( mw.loader.getState( 'test.module4' ), 'ready', 'Expected "ready" state for test.module4' ); - assert.strictEqual( mw.loader.getState( 'test.module5' ), 'registered', 'Expected "registered" state for test.module5' ); - assert.strictEqual( mw.loader.getState( 'test.module6' ), 'registered', 'Expected "registered" state for test.module6' ); - mw.loader.implement( 'test.module6', function () {}, {}, {} ); - assert.strictEqual( mw.loader.getState( 'test.module4' ), 'ready', 'Expected "ready" state for test.module4' ); - assert.strictEqual( mw.loader.getState( 'test.module5' ), 'registered', 'Expected "registered" state for test.module5' ); - assert.strictEqual( mw.loader.getState( 'test.module6' ), 'loaded', 'Expected "loaded" state for test.module6' ); - mw.loader.implement( 'test.module5', function() {}, {}, {} ); - assert.strictEqual( mw.loader.getState( 'test.module4' ), 'ready', 'Expected "ready" state for test.module4' ); - assert.strictEqual( mw.loader.getState( 'test.module5' ), 'ready', 'Expected "ready" state for test.module5' ); - assert.strictEqual( mw.loader.getState( 'test.module6' ), 'ready', 'Expected "ready" state for test.module6' ); -} ); - -QUnit.test( 'mw.loader missing dependency', 13, function ( assert ) { - mw.loader.register( [ - ['test.module7', '0'], - ['test.module8', '0', ['test.module7']], - ['test.module9', '0', ['test.module8']] - ] ); - mw.loader.implement( 'test.module8', function () {}, {}, {} ); - assert.strictEqual( mw.loader.getState( 'test.module7' ), 'registered', 'Expected "registered" state for test.module7' ); - assert.strictEqual( mw.loader.getState( 'test.module8' ), 'loaded', 'Expected "loaded" state for test.module8' ); - assert.strictEqual( mw.loader.getState( 'test.module9' ), 'registered', 'Expected "registered" state for test.module9' ); - mw.loader.state( 'test.module7', 'missing' ); - assert.strictEqual( mw.loader.getState( 'test.module7' ), 'missing', 'Expected "missing" state for test.module7' ); - assert.strictEqual( mw.loader.getState( 'test.module8' ), 'error', 'Expected "error" state for test.module8' ); - assert.strictEqual( mw.loader.getState( 'test.module9' ), 'error', 'Expected "error" state for test.module9' ); - mw.loader.implement( 'test.module9', function () {}, {}, {} ); - assert.strictEqual( mw.loader.getState( 'test.module7' ), 'missing', 'Expected "missing" state for test.module7' ); - assert.strictEqual( mw.loader.getState( 'test.module8' ), 'error', 'Expected "error" state for test.module8' ); - assert.strictEqual( mw.loader.getState( 'test.module9' ), 'error', 'Expected "error" state for test.module9' ); - mw.loader.using( - ['test.module7'], - function () { - assert.ok( false, "Success fired despite missing dependency" ); - assert.ok( true , "QUnit expected() count dummy" ); - }, - function ( e, dependencies ) { - assert.strictEqual( $.isArray( dependencies ), true, 'Expected array of dependencies' ); - assert.deepEqual( dependencies, ['test.module7'], 'Error callback called with module test.module7' ); - } - ); - mw.loader.using( - ['test.module9'], - function () { - assert.ok( false, "Success fired despite missing dependency" ); - assert.ok( true , "QUnit expected() count dummy" ); - }, - function ( e, dependencies ) { - assert.strictEqual( $.isArray( dependencies ), true, 'Expected array of dependencies' ); - dependencies.sort(); - assert.deepEqual( - dependencies, - ['test.module7', 'test.module8', 'test.module9'], - 'Error callback called with all three modules as dependencies' - ); + // Single get and set + + assert.strictEqual( conf.set( 'foo', 'Bar' ), true, 'Map.set returns boolean true if a value was set for a valid key string' ); + assert.equal( conf.get( 'foo' ), 'Bar', 'Map.get returns a single value value correctly' ); + + assert.strictEqual( conf.get( 'example' ), null, 'Map.get returns null if selection was a string and the key was not found' ); + assert.strictEqual( conf.get( 'example', arry ), arry, 'Map.get returns fallback by reference if the key was not found' ); + assert.strictEqual( conf.get( 'example', undefined ), undefined, 'Map.get supports `undefined` as fallback instead of `null`' ); + + assert.strictEqual( conf.get( 'constructor' ), null, 'Map.get does not look at Object.prototype of internal storage (constructor)' ); + assert.strictEqual( conf.get( 'hasOwnProperty' ), null, 'Map.get does not look at Object.prototype of internal storage (hasOwnProperty)' ); + + conf.set( 'hasOwnProperty', function () { return true; } ); + assert.strictEqual( conf.get( 'example', 'missing' ), 'missing', 'Map.get uses neutral hasOwnProperty method (positive)' ); + + conf.set( 'example', 'Foo' ); + conf.set( 'hasOwnProperty', function () { return false; } ); + assert.strictEqual( conf.get( 'example' ), 'Foo', 'Map.get uses neutral hasOwnProperty method (negative)' ); + + assert.strictEqual( conf.set( 'constructor', 42 ), true, 'Map.set for key "constructor"' ); + assert.strictEqual( conf.get( 'constructor' ), 42, 'Map.get for key "constructor"' ); + + assert.strictEqual( conf.set( 'ImUndefined', undefined ), true, 'Map.set allows setting value to `undefined`' ); + assert.equal( conf.get( 'ImUndefined', 'fallback' ), undefined , 'Map.get supports retreiving value of `undefined`' ); + + assert.strictEqual( conf.set( funky, 'Funky' ), false, 'Map.set returns boolean false if key was invalid (Function)' ); + assert.strictEqual( conf.set( arry, 'Arry' ), false, 'Map.set returns boolean false if key was invalid (Array)' ); + assert.strictEqual( conf.set( nummy, 'Nummy' ), false, 'Map.set returns boolean false if key was invalid (Number)' ); + + assert.strictEqual( conf.get( funky ), null, 'Map.get ruturns null if selection was invalid (Function)' ); + assert.strictEqual( conf.get( nummy ), null, 'Map.get ruturns null if selection was invalid (Number)' ); + + conf.set( String( nummy ), 'I used to be a number' ); + + assert.strictEqual( conf.exists( 'doesNotExist' ), false, 'Map.exists where property does not exist' ); + assert.strictEqual( conf.exists( 'ImUndefined' ), true, 'Map.exists where value is `undefined`' ); + assert.strictEqual( conf.exists( nummy ), false, 'Map.exists where key is invalid but looks like an existing key' ); + + // Multiple values at once + someValues = { + 'foo': 'bar', + 'lorem': 'ipsum', + 'MediaWiki': true + }; + assert.strictEqual( conf.set( someValues ), true, 'Map.set returns boolean true if multiple values were set by passing an object' ); + assert.deepEqual( conf.get( ['foo', 'lorem'] ), { + 'foo': 'bar', + 'lorem': 'ipsum' + }, 'Map.get returns multiple values correctly as an object' ); + + assert.deepEqual( conf.get( ['foo', 'notExist'] ), { + 'foo': 'bar', + 'notExist': null + }, 'Map.get return includes keys that were not found as null values' ); + + + // Interacting with globals and accessing the values object + assert.strictEqual( conf.get(), conf.values, 'Map.get returns the entire values object by reference (if called without arguments)' ); + + conf.set( 'globalMapChecker', 'Hi' ); + + assert.ok( 'globalMapChecker' in window === false, 'new mw.Map did not store its values in the global window object by default' ); + + globalConf = new mw.Map( true ); + globalConf.set( 'anotherGlobalMapChecker', 'Hello' ); + + assert.ok( 'anotherGlobalMapChecker' in window, 'new mw.Map( true ) did store its values in the global window object' ); + + // Whitelist this global variable for QUnit's 'noglobal' mode + if ( QUnit.config.noglobals ) { + QUnit.config.pollution.push( 'anotherGlobalMapChecker' ); } - ); -} ); - -QUnit.asyncTest( 'mw.loader dependency handling', 5, function ( assert ) { - mw.loader.addSource( - 'testloader', - { - loadScript: QUnit.fixurl( mw.config.get( 'wgScriptPath' ) + '/tests/qunit/data/load.mock.php' ) + } ); + + QUnit.test( 'mw.config', 1, function ( assert ) { + assert.ok( mw.config instanceof mw.Map, 'mw.config instance of mw.Map' ); + } ); + + QUnit.test( 'mw.message & mw.messages', 100, function ( assert ) { + var goodbye, hello; + + // Convenience method for asserting the same result for multiple formats + function assertMultipleFormats( messageArguments, formats, expectedResult, assertMessage ) { + var len = formats.length, format, i; + for ( i = 0; i < len; i++ ) { + format = formats[i]; + assert.equal( mw.message.apply( null, messageArguments )[format](), expectedResult, assertMessage + ' when format is ' + format ); + } } - ); - - mw.loader.register( [ - // [module, version, dependencies, group, source] - ['testMissing', '1', [], null, 'testloader'], - ['testUsesMissing', '1', ['testMissing'], null, 'testloader'], - ['testUsesNestedMissing', '1', ['testUsesMissing'], null, 'testloader'] - ] ); - - function verifyModuleStates() { - assert.equal( mw.loader.getState( 'testMissing' ), 'missing', 'Module not known to server must have state "missing"' ); - assert.equal( mw.loader.getState( 'testUsesMissing' ), 'error', 'Module with missing dependency must have state "error"' ); - assert.equal( mw.loader.getState( 'testUsesNestedMissing' ), 'error', 'Module with indirect missing dependency must have state "error"' ); - } - mw.loader.using( ['testUsesNestedMissing'], - function () { - assert.ok( false, 'Error handler should be invoked.' ); - assert.ok( true ); // Dummy to reach QUnit expect() + assert.ok( mw.messages, 'messages defined' ); + assert.ok( mw.messages instanceof mw.Map, 'mw.messages instance of mw.Map' ); + assert.ok( mw.messages.set( 'hello', 'Hello <b>awesome</b> world' ), 'mw.messages.set: Register' ); - verifyModuleStates(); + hello = mw.message( 'hello' ); - QUnit.start(); - }, - function ( e, badmodules ) { - assert.ok( true, 'Error handler should be invoked.' ); - // As soon as server spits out state('testMissing', 'missing'); - // it will bubble up and trigger the error callback. - // Therefor the badmodules array is not testUsesMissing or testUsesNestedMissing. - assert.deepEqual( badmodules, ['testMissing'], 'Bad modules as expected.' ); + // https://bugzilla.wikimedia.org/show_bug.cgi?id=44459 + assert.equal( hello.format, 'text', 'Message property "format" defaults to "text"' ); - verifyModuleStates(); + assert.strictEqual( hello.map, mw.messages, 'Message property "map" defaults to the global instance in mw.messages' ); + assert.equal( hello.key, 'hello', 'Message property "key" (currect key)' ); + assert.deepEqual( hello.parameters, [], 'Message property "parameters" defaults to an empty array' ); - QUnit.start(); + // Todo + assert.ok( hello.params, 'Message prototype "params"' ); + + hello.format = 'plain'; + assert.equal( hello.toString(), 'Hello <b>awesome</b> world', 'Message.toString returns the message as a string with the current "format"' ); + + assert.equal( hello.escaped(), 'Hello <b>awesome</b> world', 'Message.escaped returns the escaped message' ); + assert.equal( hello.format, 'escaped', 'Message.escaped correctly updated the "format" property' ); + + assert.ok( mw.messages.set( 'multiple-curly-brace', '"{{SITENAME}}" is the home of {{int:other-message}}' ), 'mw.messages.set: Register' ); + assertMultipleFormats( ['multiple-curly-brace'], ['text', 'parse'], '"' + mw.config.get( 'wgSiteName') + '" is the home of Other Message', 'Curly brace format works correctly' ); + assert.equal( mw.message( 'multiple-curly-brace' ).plain(), mw.messages.get( 'multiple-curly-brace' ), 'Plain format works correctly for curly brace message' ); + assert.equal( mw.message( 'multiple-curly-brace' ).escaped(), mw.html.escape( '"' + mw.config.get( 'wgSiteName') + '" is the home of Other Message' ), 'Escaped format works correctly for curly brace message' ); + + assert.ok( mw.messages.set( 'multiple-square-brackets-and-ampersand', 'Visit the [[Project:Community portal|community portal]] & [[Project:Help desk|help desk]]' ), 'mw.messages.set: Register' ); + assertMultipleFormats( ['multiple-square-brackets-and-ampersand'], ['plain', 'text'], mw.messages.get( 'multiple-square-brackets-and-ampersand' ), 'Square bracket message is not processed' ); + assert.equal( mw.message( 'multiple-square-brackets-and-ampersand' ).escaped(), 'Visit the [[Project:Community portal|community portal]] & [[Project:Help desk|help desk]]', 'Escaped format works correctly for square bracket message' ); + assert.htmlEqual( mw.message( 'multiple-square-brackets-and-ampersand' ).parse(), 'Visit the ' + + '<a title="Project:Community portal" href="/wiki/Project:Community_portal">community portal</a>' + + ' & <a title="Project:Help desk" href="/wiki/Project:Help_desk">help desk</a>', 'Internal links work with parse' ); + + assertMultipleFormats( ['mediawiki-test-version-entrypoints-index-php'], ['plain', 'text', 'escaped'], mw.messages.get( 'mediawiki-test-version-entrypoints-index-php' ), 'External link markup is unprocessed' ); + assert.htmlEqual( mw.message( 'mediawiki-test-version-entrypoints-index-php' ).parse(), '<a href="https://www.mediawiki.org/wiki/Manual:index.php">index.php</a>', 'External link works correctly in parse mode' ); + + assertMultipleFormats( ['external-link-replace', 'http://example.org/?x=y&z'], ['plain', 'text'] , 'Foo [http://example.org/?x=y&z bar]', 'Parameters are substituted but external link is not processed' ); + assert.equal( mw.message( 'external-link-replace', 'http://example.org/?x=y&z' ).escaped(), 'Foo [http://example.org/?x=y&z bar]', 'In escaped mode, parameters are substituted and ampersand is escaped, but external link is not processed' ); + assert.htmlEqual( mw.message( 'external-link-replace', 'http://example.org/?x=y&z' ).parse(), 'Foo <a href="http://example.org/?x=y&z">bar</a>', 'External link with replacement works in parse mode without double-escaping' ); + + hello.parse(); + assert.equal( hello.format, 'parse', 'Message.parse correctly updated the "format" property' ); + + hello.plain(); + assert.equal( hello.format, 'plain', 'Message.plain correctly updated the "format" property' ); + + hello.text(); + assert.equal( hello.format, 'text', 'Message.text correctly updated the "format" property' ); + + assert.strictEqual( hello.exists(), true, 'Message.exists returns true for existing messages' ); + + goodbye = mw.message( 'goodbye' ); + assert.strictEqual( goodbye.exists(), false, 'Message.exists returns false for nonexistent messages' ); + + assertMultipleFormats( ['goodbye'], ['plain', 'text'], '<goodbye>', 'Message.toString returns <key> if key does not exist' ); + // bug 30684 + assertMultipleFormats( ['goodbye'], ['parse', 'escaped'], '<goodbye>', 'Message.toString returns properly escaped <key> if key does not exist' ); + + assert.ok( mw.messages.set( 'plural-test-msg', 'There {{PLURAL:$1|is|are}} $1 {{PLURAL:$1|result|results}}' ), 'mw.messages.set: Register' ); + assertMultipleFormats( ['plural-test-msg', 6], ['text', 'parse', 'escaped'], 'There are 6 results', 'plural get resolved' ); + assert.equal( mw.message( 'plural-test-msg', 6 ).plain(), 'There {{PLURAL:6|is|are}} 6 {{PLURAL:6|result|results}}', 'Parameter is substituted but plural is not resolved in plain' ); + + assert.ok( mw.messages.set( 'plural-test-msg-explicit', 'There {{plural:$1|is one car|are $1 cars|0=are no cars|12=are a dozen cars}}' ), 'mw.messages.set: Register message with explicit plural forms' ); + assertMultipleFormats( ['plural-test-msg-explicit', 12], ['text', 'parse', 'escaped'], 'There are a dozen cars', 'explicit plural get resolved' ); + + assert.ok( mw.messages.set( 'plural-test-msg-explicit-beginning', 'Basket has {{plural:$1|0=no eggs|12=a dozen eggs|6=half a dozen eggs|one egg|$1 eggs}}' ), 'mw.messages.set: Register message with explicit plural forms' ); + assertMultipleFormats( ['plural-test-msg-explicit-beginning', 1], ['text', 'parse', 'escaped'], 'Basket has one egg', 'explicit plural given at beginning get resolved for singular' ); + assertMultipleFormats( ['plural-test-msg-explicit-beginning', 4], ['text', 'parse', 'escaped'], 'Basket has 4 eggs', 'explicit plural given at beginning get resolved for plural' ); + assertMultipleFormats( ['plural-test-msg-explicit-beginning', 6], ['text', 'parse', 'escaped'], 'Basket has half a dozen eggs', 'explicit plural given at beginning get resolved for 6' ); + assertMultipleFormats( ['plural-test-msg-explicit-beginning', 0], ['text', 'parse', 'escaped'], 'Basket has no eggs', 'explicit plural given at beginning get resolved for 0' ); + + + assertMultipleFormats( ['mediawiki-test-pagetriage-del-talk-page-notify-summary'], ['plain', 'text'], mw.messages.get( 'mediawiki-test-pagetriage-del-talk-page-notify-summary' ), 'Double square brackets with no parameters unchanged' ); + + assertMultipleFormats( ['mediawiki-test-pagetriage-del-talk-page-notify-summary', specialCharactersPageName], ['plain', 'text'], 'Notifying author of deletion nomination for [[' + specialCharactersPageName + ']]', 'Double square brackets with one parameter' ); + + assert.equal( mw.message( 'mediawiki-test-pagetriage-del-talk-page-notify-summary', specialCharactersPageName ).escaped(), 'Notifying author of deletion nomination for [[' + mw.html.escape( specialCharactersPageName ) + ']]', 'Double square brackets with one parameter, when escaped' ); + + + assert.ok( mw.messages.set( 'mediawiki-test-categorytree-collapse-bullet', '[<b>−</b>]' ), 'mw.messages.set: Register' ); + assert.equal( mw.message( 'mediawiki-test-categorytree-collapse-bullet' ).plain(), mw.messages.get( 'mediawiki-test-categorytree-collapse-bullet' ), 'Single square brackets unchanged in plain mode' ); + + assert.ok( mw.messages.set( 'mediawiki-test-wikieditor-toolbar-help-content-signature-result', '<a href=\'#\' title=\'{{#special:mypage}}\'>Username</a> (<a href=\'#\' title=\'{{#special:mytalk}}\'>talk</a>)' ), 'mw.messages.set: Register' ); + assert.equal( mw.message( 'mediawiki-test-wikieditor-toolbar-help-content-signature-result' ).plain(), mw.messages.get( 'mediawiki-test-wikieditor-toolbar-help-content-signature-result' ), 'HTML message with curly braces is not changed in plain mode' ); + + assertMultipleFormats( ['gender-plural-msg', 'male', 1], ['text', 'parse', 'escaped'], 'he is awesome', 'Gender and plural are resolved' ); + assert.equal( mw.message( 'gender-plural-msg', 'male', 1 ).plain(), '{{GENDER:male|he|she|they}} {{PLURAL:1|is|are}} awesome', 'Parameters are substituted, but gender and plural are not resolved in plain mode' ); + + assert.equal( mw.message( 'grammar-msg' ).plain(), mw.messages.get( 'grammar-msg' ), 'Grammar is not resolved in plain mode' ); + assertMultipleFormats( ['grammar-msg'], ['text', 'parse'], 'Przeszukaj ' + mw.config.get( 'wgSiteName' ), 'Grammar is resolved' ); + assert.equal( mw.message( 'grammar-msg' ).escaped(), 'Przeszukaj ' + mw.html.escape( mw.config.get( 'wgSiteName' ) ), 'Grammar is resolved in escaped mode' ); + + assertMultipleFormats( ['formatnum-msg', '987654321.654321'], ['text', 'parse', 'escaped'], '987,654,321.654', 'formatnum is resolved' ); + assert.equal( mw.message( 'formatnum-msg' ).plain(), mw.messages.get( 'formatnum-msg' ), 'formatnum is not resolved in plain mode' ); + + assertMultipleFormats( ['int-msg'], ['text', 'parse', 'escaped'], 'Some Other Message', 'int is resolved' ); + assert.equal( mw.message( 'int-msg' ).plain(), mw.messages.get( 'int-msg' ), 'int is not resolved in plain mode' ); + + assert.ok( mw.messages.set( 'mediawiki-italics-msg', '<i>Very</i> important' ), 'mw.messages.set: Register' ); + assertMultipleFormats( ['mediawiki-italics-msg'], ['plain', 'text', 'parse'], mw.messages.get( 'mediawiki-italics-msg' ), 'Simple italics unchanged' ); + assert.htmlEqual( + mw.message( 'mediawiki-italics-msg' ).escaped(), + '<i>Very</i> important', + 'Italics are escaped in escaped mode' + ); + + assert.ok( mw.messages.set( 'mediawiki-italics-with-link', 'An <i>italicized [[link|wiki-link]]</i>' ), 'mw.messages.set: Register' ); + assertMultipleFormats( ['mediawiki-italics-with-link'], ['plain', 'text'], mw.messages.get( 'mediawiki-italics-with-link' ), 'Italics with link unchanged' ); + assert.htmlEqual( + mw.message( 'mediawiki-italics-with-link' ).escaped(), + 'An <i>italicized [[link|wiki-link]]</i>', + 'Italics and link unchanged except for escaping in escaped mode' + ); + assert.htmlEqual( + mw.message( 'mediawiki-italics-with-link' ).parse(), + 'An <i>italicized <a title="link" href="' + mw.util.getUrl( 'link' ) + '">wiki-link</i>', + 'Italics with link inside in parse mode' + ); + + assert.ok( mw.messages.set( 'mediawiki-script-msg', '<script >alert( "Who put this script here?" );</script>' ), 'mw.messages.set: Register' ); + assertMultipleFormats( ['mediawiki-script-msg'], ['plain', 'text'], mw.messages.get( 'mediawiki-script-msg' ), 'Script unchanged' ); + assert.htmlEqual( + mw.message( 'mediawiki-script-msg' ).escaped(), + '<script >alert( "Who put this script here?" );</script>', + 'Script escaped when using escaped format' + ); + assert.htmlEqual( + mw.message( 'mediawiki-script-msg' ).parse(), + '<script >alert( "Who put this script here?" );</script>', + 'Script escaped when using parse format' + ); + + + } ); + + QUnit.test( 'mw.msg', 14, function ( assert ) { + assert.ok( mw.messages.set( 'hello', 'Hello <b>awesome</b> world' ), 'mw.messages.set: Register' ); + assert.equal( mw.msg( 'hello' ), 'Hello <b>awesome</b> world', 'Gets message with default options (existing message)' ); + assert.equal( mw.msg( 'goodbye' ), '<goodbye>', 'Gets message with default options (nonexistent message)' ); + + assert.ok( mw.messages.set( 'plural-item' , 'Found $1 {{PLURAL:$1|item|items}}' ), 'mw.messages.set: Register' ); + assert.equal( mw.msg( 'plural-item', 5 ), 'Found 5 items', 'Apply plural for count 5' ); + assert.equal( mw.msg( 'plural-item', 0 ), 'Found 0 items', 'Apply plural for count 0' ); + assert.equal( mw.msg( 'plural-item', 1 ), 'Found 1 item', 'Apply plural for count 1' ); + + assert.equal( mw.msg( 'mediawiki-test-pagetriage-del-talk-page-notify-summary', specialCharactersPageName ), 'Notifying author of deletion nomination for [[' + specialCharactersPageName + ']]', 'Double square brackets in mw.msg one parameter' ); + + assert.equal( mw.msg( 'gender-plural-msg', 'male', 1 ), 'he is awesome', 'Gender test for male, plural count 1' ); + assert.equal( mw.msg( 'gender-plural-msg', 'female', '1' ), 'she is awesome', 'Gender test for female, plural count 1' ); + assert.equal( mw.msg( 'gender-plural-msg', 'unknown', 10 ), 'they are awesome', 'Gender test for neutral, plural count 10' ); + + assert.equal( mw.msg( 'grammar-msg' ), 'Przeszukaj ' + mw.config.get( 'wgSiteName' ), 'Grammar is resolved' ); + + assert.equal( mw.msg( 'formatnum-msg', '987654321.654321' ), '987,654,321.654', 'formatnum is resolved' ); + + assert.equal( mw.msg( 'int-msg' ), 'Some Other Message', 'int is resolved' ); + } ); + + /** + * The sync style load test (for @import). This is, in a way, also an open bug for + * ResourceLoader ("execute js after styles are loaded"), but browsers don't offer a + * way to get a callback from when a stylesheet is loaded (that is, including any + * @import rules inside). To work around this, we'll have a little time loop to check + * if the styles apply. + * Note: This test originally used new Image() and onerror to get a callback + * when the url is loaded, but that is fragile since it doesn't monitor the + * same request as the css @import, and Safari 4 has issues with + * onerror/onload not being fired at all in weird cases like this. + */ + function assertStyleAsync( assert, $element, prop, val, fn ) { + var styleTestStart, + el = $element.get( 0 ), + styleTestTimeout = ( QUnit.config.testTimeout || 5000 ) - 200; + + function isCssImportApplied() { + // Trigger reflow, repaint, redraw, whatever (cross-browser) + var x = $element.css( 'height' ); + x = el.innerHTML; + el.className = el.className; + x = document.documentElement.clientHeight; + + return $element.css( prop ) === val; } - ); -} ); -QUnit.asyncTest( 'mw.loader( "//protocol-relative" ) (bug 30825)', 2, function ( assert ) { - // This bug was actually already fixed in 1.18 and later when discovered in 1.17. - // Test is for regressions! + function styleTestLoop() { + var styleTestSince = new Date().getTime() - styleTestStart; + // If it is passing or if we timed out, run the real test and stop the loop + if ( isCssImportApplied() || styleTestSince > styleTestTimeout ) { + assert.equal( $element.css( prop ), val, + 'style "' + prop + ': ' + val + '" from url is applied (after ' + styleTestSince + 'ms)' + ); - // Forge an URL to the test callback script - var target = QUnit.fixurl( - mw.config.get( 'wgServer' ) + mw.config.get( 'wgScriptPath' ) + '/tests/qunit/data/qunitOkCall.js' - ); + if ( fn ) { + fn(); + } - // Confirm that mw.loader.load() works with protocol-relative URLs - target = target.replace( /https?:/, '' ); + return; + } + // Otherwise, keep polling + setTimeout( styleTestLoop, 150 ); + } - assert.equal( target.substr( 0, 2 ), '//', - 'URL must be relative to test relative URLs!' - ); + // Start the loop + styleTestStart = new Date().getTime(); + styleTestLoop(); + } - // Async! - // The target calls QUnit.start - mw.loader.load( target ); -}); + function urlStyleTest( selector, prop, val ) { + return QUnit.fixurl( + mw.config.get( 'wgScriptPath' ) + + '/tests/qunit/data/styleTest.css.php?' + + $.param( { + selector: selector, + prop: prop, + val: val + } ) + ); + } -QUnit.test( 'mw.html', 13, function ( assert ) { - assert.throws( function () { - mw.html.escape(); - }, TypeError, 'html.escape throws a TypeError if argument given is not a string' ); + QUnit.asyncTest( 'mw.loader', 2, function ( assert ) { + var isAwesomeDone; - assert.equal( mw.html.escape( '<mw awesome="awesome" value=\'test\' />' ), - '<mw awesome="awesome" value='test' />', 'escape() escapes special characters to html entities' ); + mw.loader.testCallback = function () { + QUnit.start(); + assert.strictEqual( isAwesomeDone, undefined, 'Implementing module is.awesome: isAwesomeDone should still be undefined' ); + isAwesomeDone = true; + }; - assert.equal( mw.html.element(), - '<undefined/>', 'element() always returns a valid html string (even without arguments)' ); + mw.loader.implement( 'test.callback', [QUnit.fixurl( mw.config.get( 'wgScriptPath' ) + '/tests/qunit/data/callMwLoaderTestCallback.js' )], {}, {} ); - assert.equal( mw.html.element( 'div' ), '<div/>', 'element() Plain DIV (simple)' ); + mw.loader.using( 'test.callback', function () { - assert.equal( mw.html.element( 'div', {}, '' ), '<div></div>', 'element() Basic DIV (simple)' ); + // /sample/awesome.js declares the "mw.loader.testCallback" function + // which contains a call to start() and ok() + assert.strictEqual( isAwesomeDone, true, 'test.callback module should\'ve caused isAwesomeDone to be true' ); + delete mw.loader.testCallback; - assert.equal( - mw.html.element( - 'div', { - id: 'foobar' + }, function () { + QUnit.start(); + assert.ok( false, 'Error callback fired while loader.using "test.callback" module' ); + } ); + } ); + + QUnit.asyncTest( 'mw.loader.implement( styles={ "css": [text, ..] } )', 2, function ( assert ) { + var $element = $( '<div class="mw-test-implement-a"></div>' ).appendTo( '#qunit-fixture' ); + + assert.notEqual( + $element.css( 'float' ), + 'right', + 'style is clear' + ); + + mw.loader.implement( + 'test.implement.a', + function () { + assert.equal( + $element.css( 'float' ), + 'right', + 'style is applied' + ); + QUnit.start(); + }, + { + 'all': '.mw-test-implement-a { float: right; }' + }, + {} + ); + + mw.loader.load( [ + 'test.implement.a' + ] ); + } ); + + QUnit.asyncTest( 'mw.loader.implement( styles={ "url": { <media>: [url, ..] } } )', 7, function ( assert ) { + var $element1 = $( '<div class="mw-test-implement-b1"></div>' ).appendTo( '#qunit-fixture' ), + $element2 = $( '<div class="mw-test-implement-b2"></div>' ).appendTo( '#qunit-fixture' ), + $element3 = $( '<div class="mw-test-implement-b3"></div>' ).appendTo( '#qunit-fixture' ); + + assert.notEqual( + $element1.css( 'text-align' ), + 'center', + 'style is clear' + ); + assert.notEqual( + $element2.css( 'float' ), + 'left', + 'style is clear' + ); + assert.notEqual( + $element3.css( 'text-align' ), + 'right', + 'style is clear' + ); + + mw.loader.implement( + 'test.implement.b', + function () { + // Note: QUnit.start() must only be called when the entire test is + // complete. So, make sure that we don't start until *both* + // assertStyleAsync calls have completed. + var pending = 2; + assertStyleAsync( assert, $element2, 'float', 'left', function () { + assert.notEqual( $element1.css( 'text-align' ), 'center', 'print style is not applied' ); + + pending--; + if ( pending === 0 ) { + QUnit.start(); + } + } ); + assertStyleAsync( assert, $element3, 'float', 'right', function () { + assert.notEqual( $element1.css( 'text-align' ), 'center', 'print style is not applied' ); + + pending--; + if ( pending === 0 ) { + QUnit.start(); + } + } ); + }, + { + 'url': { + 'print': [urlStyleTest( '.mw-test-implement-b1', 'text-align', 'center' )], + 'screen': [ + // bug 40834: Make sure it actually works with more than 1 stylesheet reference + urlStyleTest( '.mw-test-implement-b2', 'float', 'left' ), + urlStyleTest( '.mw-test-implement-b3', 'float', 'right' ) + ] + } + }, + {} + ); + + mw.loader.load( [ + 'test.implement.b' + ] ); + } ); + + // Backwards compatibility + QUnit.asyncTest( 'mw.loader.implement( styles={ <media>: text } ) (back-compat)', 2, function ( assert ) { + var $element = $( '<div class="mw-test-implement-c"></div>' ).appendTo( '#qunit-fixture' ); + + assert.notEqual( + $element.css( 'float' ), + 'right', + 'style is clear' + ); + + mw.loader.implement( + 'test.implement.c', + function () { + assert.equal( + $element.css( 'float' ), + 'right', + 'style is applied' + ); + QUnit.start(); + }, + { + 'all': '.mw-test-implement-c { float: right; }' + }, + {} + ); + + mw.loader.load( [ + 'test.implement.c' + ] ); + } ); + + // Backwards compatibility + QUnit.asyncTest( 'mw.loader.implement( styles={ <media>: [url, ..] } ) (back-compat)', 4, function ( assert ) { + var $element = $( '<div class="mw-test-implement-d"></div>' ).appendTo( '#qunit-fixture' ), + $element2 = $( '<div class="mw-test-implement-d2"></div>' ).appendTo( '#qunit-fixture' ); + + assert.notEqual( + $element.css( 'float' ), + 'right', + 'style is clear' + ); + assert.notEqual( + $element2.css( 'text-align' ), + 'center', + 'style is clear' + ); + + mw.loader.implement( + 'test.implement.d', + function () { + assertStyleAsync( assert, $element, 'float', 'right', function () { + + assert.notEqual( $element2.css( 'text-align' ), 'center', 'print style is not applied (bug 40500)' ); + + QUnit.start(); + } ); + }, + { + 'all': [urlStyleTest( '.mw-test-implement-d', 'float', 'right' )], + 'print': [urlStyleTest( '.mw-test-implement-d2', 'text-align', 'center' )] + }, + {} + ); + + mw.loader.load( [ + 'test.implement.d' + ] ); + } ); + + // @import (bug 31676) + QUnit.asyncTest( 'mw.loader.implement( styles has @import)', 5, function ( assert ) { + var isJsExecuted, $element; + + mw.loader.implement( + 'test.implement.import', + function () { + assert.strictEqual( isJsExecuted, undefined, 'javascript not executed multiple times' ); + isJsExecuted = true; + + assert.equal( mw.loader.getState( 'test.implement.import' ), 'ready', 'module state is "ready" while implement() is executing javascript' ); + + $element = $( '<div class="mw-test-implement-import">Foo bar</div>' ).appendTo( '#qunit-fixture' ); + + assert.equal( mw.msg( 'test-foobar' ), 'Hello Foobar, $1!', 'Messages are loaded before javascript execution' ); + + assertStyleAsync( assert, $element, 'float', 'right', function () { + assert.equal( $element.css( 'text-align' ), 'center', + 'CSS styles after the @import rule are working' + ); + + QUnit.start(); + } ); + }, + { + 'css': [ + '@import url(\'' + + urlStyleTest( '.mw-test-implement-import', 'float', 'right' ) + + '\');\n' + + '.mw-test-implement-import { text-align: center; }' + ] + }, + { + 'test-foobar': 'Hello Foobar, $1!' } - ), - '<div id="foobar"/>', - 'html.element DIV (attribs)' ); + ); - assert.equal( mw.html.element( 'p', null, 12 ), '<p>12</p>', 'Numbers are valid content and should be casted to a string' ); + mw.loader.load( 'test.implement' ); - assert.equal( mw.html.element( 'p', { title: 12 }, '' ), '<p title="12"></p>', 'Numbers are valid attribute values' ); + } ); - // Example from https://www.mediawiki.org/wiki/ResourceLoader/Default_modules#mediaWiki.html - assert.equal( - mw.html.element( - 'div', - {}, - new mw.html.Raw( - mw.html.element( 'img', { src: '<' } ) - ) - ), - '<div><img src="<"/></div>', - 'Raw inclusion of another element' - ); - - assert.equal( - mw.html.element( - 'option', { - selected: true - }, 'Foo' - ), - '<option selected="selected">Foo</option>', - 'Attributes may have boolean values. True copies the attribute name to the value.' - ); - - assert.equal( - mw.html.element( - 'option', { - value: 'foo', - selected: false - }, 'Foo' - ), - '<option value="foo">Foo</option>', - 'Attributes may have boolean values. False keeps the attribute from output.' - ); - - assert.equal( mw.html.element( 'div', + QUnit.asyncTest( 'mw.loader.implement( only messages )', 2, function ( assert ) { + assert.assertFalse( mw.messages.exists( 'bug_29107' ), 'Verify that the test message doesn\'t exist yet' ); + + mw.loader.implement( 'test.implement.msgs', [], {}, { 'bug_29107': 'loaded' } ); + mw.loader.using( 'test.implement.msgs', function () { + QUnit.start(); + assert.ok( mw.messages.exists( 'bug_29107' ), 'Bug 29107: messages-only module should implement ok' ); + }, function () { + QUnit.start(); + assert.ok( false, 'Error callback fired while implementing "test.implement.msgs" module' ); + } ); + } ); + + QUnit.test( 'mw.loader erroneous indirect dependency', 3, function ( assert ) { + mw.loader.register( [ + ['test.module1', '0'], + ['test.module2', '0', ['test.module1']], + ['test.module3', '0', ['test.module2']] + ] ); + mw.loader.implement( 'test.module1', function () { + throw new Error( 'expected' ); + }, {}, {} ); + assert.strictEqual( mw.loader.getState( 'test.module1' ), 'error', 'Expected "error" state for test.module1' ); + assert.strictEqual( mw.loader.getState( 'test.module2' ), 'error', 'Expected "error" state for test.module2' ); + assert.strictEqual( mw.loader.getState( 'test.module3' ), 'error', 'Expected "error" state for test.module3' ); + } ); + + QUnit.test( 'mw.loader out-of-order implementation', 9, function ( assert ) { + mw.loader.register( [ + ['test.module4', '0'], + ['test.module5', '0', ['test.module4']], + ['test.module6', '0', ['test.module5']] + ] ); + mw.loader.implement( 'test.module4', function () { + }, {}, {} ); + assert.strictEqual( mw.loader.getState( 'test.module4' ), 'ready', 'Expected "ready" state for test.module4' ); + assert.strictEqual( mw.loader.getState( 'test.module5' ), 'registered', 'Expected "registered" state for test.module5' ); + assert.strictEqual( mw.loader.getState( 'test.module6' ), 'registered', 'Expected "registered" state for test.module6' ); + mw.loader.implement( 'test.module6', function () { + }, {}, {} ); + assert.strictEqual( mw.loader.getState( 'test.module4' ), 'ready', 'Expected "ready" state for test.module4' ); + assert.strictEqual( mw.loader.getState( 'test.module5' ), 'registered', 'Expected "registered" state for test.module5' ); + assert.strictEqual( mw.loader.getState( 'test.module6' ), 'loaded', 'Expected "loaded" state for test.module6' ); + mw.loader.implement( 'test.module5', function () { + }, {}, {} ); + assert.strictEqual( mw.loader.getState( 'test.module4' ), 'ready', 'Expected "ready" state for test.module4' ); + assert.strictEqual( mw.loader.getState( 'test.module5' ), 'ready', 'Expected "ready" state for test.module5' ); + assert.strictEqual( mw.loader.getState( 'test.module6' ), 'ready', 'Expected "ready" state for test.module6' ); + } ); + + QUnit.test( 'mw.loader missing dependency', 13, function ( assert ) { + mw.loader.register( [ + ['test.module7', '0'], + ['test.module8', '0', ['test.module7']], + ['test.module9', '0', ['test.module8']] + ] ); + mw.loader.implement( 'test.module8', function () { + }, {}, {} ); + assert.strictEqual( mw.loader.getState( 'test.module7' ), 'registered', 'Expected "registered" state for test.module7' ); + assert.strictEqual( mw.loader.getState( 'test.module8' ), 'loaded', 'Expected "loaded" state for test.module8' ); + assert.strictEqual( mw.loader.getState( 'test.module9' ), 'registered', 'Expected "registered" state for test.module9' ); + mw.loader.state( 'test.module7', 'missing' ); + assert.strictEqual( mw.loader.getState( 'test.module7' ), 'missing', 'Expected "missing" state for test.module7' ); + assert.strictEqual( mw.loader.getState( 'test.module8' ), 'error', 'Expected "error" state for test.module8' ); + assert.strictEqual( mw.loader.getState( 'test.module9' ), 'error', 'Expected "error" state for test.module9' ); + mw.loader.implement( 'test.module9', function () { + }, {}, {} ); + assert.strictEqual( mw.loader.getState( 'test.module7' ), 'missing', 'Expected "missing" state for test.module7' ); + assert.strictEqual( mw.loader.getState( 'test.module8' ), 'error', 'Expected "error" state for test.module8' ); + assert.strictEqual( mw.loader.getState( 'test.module9' ), 'error', 'Expected "error" state for test.module9' ); + mw.loader.using( + ['test.module7'], + function () { + assert.ok( false, 'Success fired despite missing dependency' ); + assert.ok( true, 'QUnit expected() count dummy' ); + }, + function ( e, dependencies ) { + assert.strictEqual( $.isArray( dependencies ), true, 'Expected array of dependencies' ); + assert.deepEqual( dependencies, ['test.module7'], 'Error callback called with module test.module7' ); + } + ); + mw.loader.using( + ['test.module9'], + function () { + assert.ok( false, 'Success fired despite missing dependency' ); + assert.ok( true, 'QUnit expected() count dummy' ); + }, + function ( e, dependencies ) { + assert.strictEqual( $.isArray( dependencies ), true, 'Expected array of dependencies' ); + dependencies.sort(); + assert.deepEqual( + dependencies, + ['test.module7', 'test.module8', 'test.module9'], + 'Error callback called with all three modules as dependencies' + ); + } + ); + } ); + + QUnit.asyncTest( 'mw.loader dependency handling', 5, function ( assert ) { + mw.loader.addSource( + 'testloader', + { + loadScript: QUnit.fixurl( mw.config.get( 'wgScriptPath' ) + '/tests/qunit/data/load.mock.php' ) + } + ); + + mw.loader.register( [ + // [module, version, dependencies, group, source] + ['testMissing', '1', [], null, 'testloader'], + ['testUsesMissing', '1', ['testMissing'], null, 'testloader'], + ['testUsesNestedMissing', '1', ['testUsesMissing'], null, 'testloader'] + ] ); + + function verifyModuleStates() { + assert.equal( mw.loader.getState( 'testMissing' ), 'missing', 'Module not known to server must have state "missing"' ); + assert.equal( mw.loader.getState( 'testUsesMissing' ), 'error', 'Module with missing dependency must have state "error"' ); + assert.equal( mw.loader.getState( 'testUsesNestedMissing' ), 'error', 'Module with indirect missing dependency must have state "error"' ); + } + + mw.loader.using( ['testUsesNestedMissing'], + function () { + assert.ok( false, 'Error handler should be invoked.' ); + assert.ok( true ); // Dummy to reach QUnit expect() + + verifyModuleStates(); + + QUnit.start(); + }, + function ( e, badmodules ) { + assert.ok( true, 'Error handler should be invoked.' ); + // As soon as server spits out state('testMissing', 'missing'); + // it will bubble up and trigger the error callback. + // Therefor the badmodules array is not testUsesMissing or testUsesNestedMissing. + assert.deepEqual( badmodules, ['testMissing'], 'Bad modules as expected.' ); + + verifyModuleStates(); + + QUnit.start(); + } + ); + } ); + + QUnit.asyncTest( 'mw.loader( "//protocol-relative" ) (bug 30825)', 2, function ( assert ) { + // This bug was actually already fixed in 1.18 and later when discovered in 1.17. + // Test is for regressions! + + // Forge an URL to the test callback script + var target = QUnit.fixurl( + mw.config.get( 'wgServer' ) + mw.config.get( 'wgScriptPath' ) + '/tests/qunit/data/qunitOkCall.js' + ); + + // Confirm that mw.loader.load() works with protocol-relative URLs + target = target.replace( /https?:/, '' ); + + assert.equal( target.substr( 0, 2 ), '//', + 'URL must be relative to test relative URLs!' + ); + + // Async! + // The target calls QUnit.start + mw.loader.load( target ); + } ); + + QUnit.test( 'mw.html', 13, function ( assert ) { + assert.throws( function () { + mw.html.escape(); + }, TypeError, 'html.escape throws a TypeError if argument given is not a string' ); + + assert.equal( mw.html.escape( '<mw awesome="awesome" value=\'test\' />' ), + '<mw awesome="awesome" value='test' />', 'escape() escapes special characters to html entities' ); + + assert.equal( mw.html.element(), + '<undefined/>', 'element() always returns a valid html string (even without arguments)' ); + + assert.equal( mw.html.element( 'div' ), '<div/>', 'element() Plain DIV (simple)' ); + + assert.equal( mw.html.element( 'div', {}, '' ), '<div></div>', 'element() Basic DIV (simple)' ); + + assert.equal( + mw.html.element( + 'div', { + id: 'foobar' + } + ), + '<div id="foobar"/>', + 'html.element DIV (attribs)' ); + + assert.equal( mw.html.element( 'p', null, 12 ), '<p>12</p>', 'Numbers are valid content and should be casted to a string' ); + + assert.equal( mw.html.element( 'p', { title: 12 }, '' ), '<p title="12"></p>', 'Numbers are valid attribute values' ); + + // Example from https://www.mediawiki.org/wiki/ResourceLoader/Default_modules#mediaWiki.html + assert.equal( + mw.html.element( + 'div', + {}, + new mw.html.Raw( + mw.html.element( 'img', { src: '<' } ) + ) + ), + '<div><img src="<"/></div>', + 'Raw inclusion of another element' + ); + + assert.equal( + mw.html.element( + 'option', { + selected: true + }, 'Foo' + ), + '<option selected="selected">Foo</option>', + 'Attributes may have boolean values. True copies the attribute name to the value.' + ); + + assert.equal( + mw.html.element( + 'option', { + value: 'foo', + selected: false + }, 'Foo' + ), + '<option value="foo">Foo</option>', + 'Attributes may have boolean values. False keeps the attribute from output.' + ); + + assert.equal( mw.html.element( 'div', null, 'a' ), - '<div>a</div>', - 'html.element DIV (content)' ); + '<div>a</div>', + 'html.element DIV (content)' ); - assert.equal( mw.html.element( 'a', + assert.equal( mw.html.element( 'a', { href: 'http://mediawiki.org/w/index.php?title=RL&action=history' }, 'a' ), - '<a href="http://mediawiki.org/w/index.php?title=RL&action=history">a</a>', - 'html.element DIV (attribs + content)' ); - -}); + '<a href="http://mediawiki.org/w/index.php?title=RL&action=history">a</a>', + 'html.element DIV (attribs + content)' ); + + } ); + + QUnit.test( 'mw.hook', 10, function ( assert ) { + var hook, add, fire, chars, callback; + + mw.hook( 'test.hook.unfired' ).add( function () { + assert.ok( false, 'Unfired hook' ); + } ); + + mw.hook( 'test.hook.basic' ).add( function () { + assert.ok( true, 'Basic callback' ); + } ); + mw.hook( 'test.hook.basic' ).fire(); + + mw.hook( 'test.hook.data' ).add( function ( data1, data2 ) { + assert.equal( data1, 'example', 'Fire with data (string param)' ); + assert.deepEqual( data2, ['two'], 'Fire with data (array param)' ); + } ); + mw.hook( 'test.hook.data' ).fire( 'example', ['two'] ); + + mw.hook( 'test.hook.chainable' ).add( function () { + assert.ok( true, 'Chainable' ); + } ).fire(); + + hook = mw.hook( 'test.hook.detach' ); + add = hook.add; + fire = hook.fire; + add( function ( x, y ) { + assert.deepEqual( [x, y], ['x', 'y'], 'Detached (contextless) with data' ); + } ); + fire( 'x', 'y' ); + + mw.hook( 'test.hook.fireBefore' ).fire().add( function () { + assert.ok( true, 'Invoke handler right away if it was fired before' ); + } ); + + mw.hook( 'test.hook.fireTwiceBefore' ).fire().fire().add( function () { + assert.ok( true, 'Invoke handler right away if it was fired before (only last one)' ); + } ); + + chars = []; + + mw.hook( 'test.hook.many' ) + .add( function ( chr ) { + chars.push( chr ); + } ) + .fire( 'x' ).fire( 'y' ).fire( 'z' ) + .add( function ( chr ) { + assert.equal( chr, 'z', 'Adding callback later invokes right away with last data' ); + } ); -}( mediaWiki ) ); + assert.deepEqual( chars, ['x', 'y', 'z'], 'Multiple callbacks with multiple fires' ); + + chars = []; + callback = function ( chr ) { + chars.push( chr ); + }; + + mw.hook( 'test.hook.variadic' ) + .add( + callback, + callback, + function ( chr ) { + chars.push( chr ); + }, + callback + ) + .fire( 'x' ) + .remove( + function () { + 'not-added'; + }, + callback + ) + .fire( 'y' ) + .remove( callback ) + .fire( 'z' ); + + assert.deepEqual( + chars, + ['x', 'x', 'x', 'x', 'y', 'z'], + '"add" and "remove" support variadic arguments. ' + + '"add" does not filter unique. ' + + '"remove" removes all equal by reference. ' + + '"remove" is silent if the function is not found' + ); + } ); + +}( mediaWiki, jQuery ) ); diff --git a/tests/qunit/suites/resources/mediawiki/mediawiki.user.test.js b/tests/qunit/suites/resources/mediawiki/mediawiki.user.test.js index 16c97dff..96be3d1f 100644 --- a/tests/qunit/suites/resources/mediawiki/mediawiki.user.test.js +++ b/tests/qunit/suites/resources/mediawiki/mediawiki.user.test.js @@ -1,56 +1,57 @@ -( function ( mw ) { - -QUnit.module( 'mediawiki.user', QUnit.newMwEnvironment() ); - -QUnit.test( 'options', 1, function ( assert ) { - assert.ok( mw.user.options instanceof mw.Map, 'options instance of mw.Map' ); -}); - -QUnit.test( 'user status', 9, function ( assert ) { - /** - * Tests can be run under three different conditions: - * 1) From tests/qunit/index.html, user will be anonymous. - * 2) Logged in on [[Special:JavaScriptTest/qunit]] - * 3) Anonymously at the same special page. - */ - - // Forge an anonymous user: - mw.config.set( 'wgUserName', null ); - - assert.strictEqual( mw.user.getName(), null, 'user.getName() returns null when anonymous' ); - assert.strictEqual( mw.user.name(), null, 'user.name() compatibility' ); - assert.assertTrue( mw.user.isAnon(), 'user.isAnon() returns true when anonymous' ); - assert.assertTrue( mw.user.anonymous(), 'user.anonymous() compatibility' ); - - // Not part of startUp module - mw.config.set( 'wgUserName', 'John' ); - - assert.equal( mw.user.getName(), 'John', 'user.getName() returns username when logged-in' ); - assert.equal( mw.user.name(), 'John', 'user.name() compatibility' ); - assert.assertFalse( mw.user.isAnon(), 'user.isAnon() returns false when logged-in' ); - assert.assertFalse( mw.user.anonymous(), 'user.anonymous() compatibility' ); - - assert.equal( mw.user.id(), 'John', 'user.id Returns username when logged-in' ); -}); - -QUnit.asyncTest( 'getGroups', 3, function ( assert ) { - mw.user.getGroups( function ( groups ) { - // First group should always be '*' - assert.equal( $.type( groups ), 'array', 'Callback gets an array' ); - assert.notStrictEqual( $.inArray( '*', groups ), -1, '"*"" is in the list' ); - // Sort needed because of different methods if creating the arrays, - // only the content matters. - assert.deepEqual( groups.sort(), mw.config.get( 'wgUserGroups' ).sort(), 'Array contains all groups, just like wgUserGroups' ); - QUnit.start(); - }); -}); - -QUnit.asyncTest( 'getRights', 1, function ( assert ) { - mw.user.getRights( function ( rights ) { - // First group should always be '*' - assert.equal( $.type( rights ), 'array', 'Callback gets an array' ); - QUnit.start(); - }); -}); - -}( mediaWiki ) ); +( function ( mw, $ ) { + QUnit.module( 'mediawiki.user', QUnit.newMwEnvironment() ); + + QUnit.test( 'options', 1, function ( assert ) { + assert.ok( mw.user.options instanceof mw.Map, 'options instance of mw.Map' ); + } ); + + QUnit.test( 'user status', 11, function ( assert ) { + /** + * Tests can be run under three different conditions: + * 1) From tests/qunit/index.html, user will be anonymous. + * 2) Logged in on [[Special:JavaScriptTest/qunit]] + * 3) Anonymously at the same special page. + */ + + // Forge an anonymous user: + mw.config.set( 'wgUserName', null ); + delete mw.config.values.wgUserId; + + assert.strictEqual( mw.user.getName(), null, 'user.getName() returns null when anonymous' ); + assert.strictEqual( mw.user.name(), null, 'user.name() compatibility' ); + assert.assertTrue( mw.user.isAnon(), 'user.isAnon() returns true when anonymous' ); + assert.assertTrue( mw.user.anonymous(), 'user.anonymous() compatibility' ); + assert.strictEqual( mw.user.getId(), 0, 'user.getId() returns 0 when anonymous' ); + + // Not part of startUp module + mw.config.set( 'wgUserName', 'John' ); + mw.config.set( 'wgUserId', 123 ); + + assert.equal( mw.user.getName(), 'John', 'user.getName() returns username when logged-in' ); + assert.equal( mw.user.name(), 'John', 'user.name() compatibility' ); + assert.assertFalse( mw.user.isAnon(), 'user.isAnon() returns false when logged-in' ); + assert.assertFalse( mw.user.anonymous(), 'user.anonymous() compatibility' ); + assert.strictEqual( mw.user.getId(), 123, 'user.getId() returns correct ID when logged-in' ); + + assert.equal( mw.user.id(), 'John', 'user.id Returns username when logged-in' ); + } ); + + QUnit.asyncTest( 'getGroups', 3, function ( assert ) { + mw.user.getGroups( function ( groups ) { + // First group should always be '*' + assert.equal( $.type( groups ), 'array', 'Callback gets an array' ); + assert.notStrictEqual( $.inArray( '*', groups ), -1, '"*"" is in the list' ); + // Sort needed because of different methods if creating the arrays, + // only the content matters. + assert.deepEqual( groups.sort(), mw.config.get( 'wgUserGroups' ).sort(), 'Array contains all groups, just like wgUserGroups' ); + QUnit.start(); + } ); + } ); + + QUnit.asyncTest( 'getRights', 1, function ( assert ) { + mw.user.getRights( function ( rights ) { + assert.equal( $.type( rights ), 'array', 'Callback gets an array' ); + QUnit.start(); + } ); + } ); +}( mediaWiki, jQuery ) ); diff --git a/tests/qunit/suites/resources/mediawiki/mediawiki.util.test.js b/tests/qunit/suites/resources/mediawiki/mediawiki.util.test.js index ababa8d9..9216f0af 100644 --- a/tests/qunit/suites/resources/mediawiki/mediawiki.util.test.js +++ b/tests/qunit/suites/resources/mediawiki/mediawiki.util.test.js @@ -1,288 +1,354 @@ -QUnit.module( 'mediawiki.util', QUnit.newMwEnvironment() ); - -QUnit.test( 'rawurlencode', 1, function ( assert ) { - assert.equal( mw.util.rawurlencode( 'Test:A & B/Here' ), 'Test%3AA%20%26%20B%2FHere' ); -}); - -QUnit.test( 'wikiUrlencode', 1, function ( assert ) { - assert.equal( mw.util.wikiUrlencode( 'Test:A & B/Here' ), 'Test:A_%26_B/Here' ); -}); - -QUnit.test( 'wikiGetlink', 3, function ( assert ) { - // Not part of startUp module - mw.config.set( 'wgArticlePath', '/wiki/$1' ); - mw.config.set( 'wgPageName', 'Foobar' ); - - var hrefA = mw.util.wikiGetlink( 'Sandbox' ); - assert.equal( hrefA, '/wiki/Sandbox', 'Simple title; Get link for "Sandbox"' ); - - var hrefB = mw.util.wikiGetlink( 'Foo:Sandbox ? 5+5=10 ! (test)/subpage' ); - assert.equal( hrefB, '/wiki/Foo:Sandbox_%3F_5%2B5%3D10_%21_%28test%29/subpage', - 'Advanced title; Get link for "Foo:Sandbox ? 5+5=10 ! (test)/subpage"' ); - - var hrefC = mw.util.wikiGetlink(); - assert.equal( hrefC, '/wiki/Foobar', 'Default title; Get link for current page ("Foobar")' ); -}); - -QUnit.test( 'wikiScript', 4, function ( assert ) { - mw.config.set({ - 'wgScript': '/w/i.php', // customized wgScript for bug 39103 - 'wgLoadScript': '/w/l.php', // customized wgLoadScript for bug 39103 - 'wgScriptPath': '/w', - 'wgScriptExtension': '.php' - }); - - assert.equal( mw.util.wikiScript(), mw.config.get( 'wgScript' ), 'wikiScript() returns wgScript' ); - assert.equal( mw.util.wikiScript( 'index' ), mw.config.get( 'wgScript' ), "wikiScript( 'index' ) returns wgScript" ); - assert.equal( mw.util.wikiScript( 'load' ), mw.config.get( 'wgLoadScript' ), "wikiScript( 'load' ) returns wgLoadScript" ); - assert.equal( mw.util.wikiScript( 'api' ), '/w/api.php', 'API path' ); -}); - -QUnit.test( 'addCSS', 3, function ( assert ) { - var $testEl = $( '<div>' ).attr( 'id', 'mw-addcsstest' ).appendTo( '#qunit-fixture' ); - - var style = mw.util.addCSS( '#mw-addcsstest { visibility: hidden; }' ); - assert.equal( typeof style, 'object', 'addCSS returned an object' ); - assert.strictEqual( style.disabled, false, 'property "disabled" is available and set to false' ); - - assert.equal( $testEl.css( 'visibility' ), 'hidden', 'Added style properties are in effect' ); - - // Clean up - $( style.ownerNode ).remove(); -}); - -QUnit.asyncTest( 'toggleToc', 4, function ( assert ) { - assert.strictEqual( mw.util.toggleToc(), null, 'Return null if there is no table of contents on the page.' ); - - var tocHtml = - '<table id="toc" class="toc"><tr><td>' + - '<div id="toctitle">' + +( function ( mw, $ ) { + QUnit.module( 'mediawiki.util', QUnit.newMwEnvironment( { + setup: function () { + this.taPrefix = mw.util.tooltipAccessKeyPrefix; + mw.util.tooltipAccessKeyPrefix = 'ctrl-alt-'; + }, + teardown: function () { + mw.util.tooltipAccessKeyPrefix = this.taPrefix; + } + } ) ); + + QUnit.test( 'rawurlencode', 1, function ( assert ) { + assert.equal( mw.util.rawurlencode( 'Test:A & B/Here' ), 'Test%3AA%20%26%20B%2FHere' ); + } ); + + QUnit.test( 'wikiUrlencode', 1, function ( assert ) { + assert.equal( mw.util.wikiUrlencode( 'Test:A & B/Here' ), 'Test:A_%26_B/Here' ); + } ); + + QUnit.test( 'getUrl', 4, function ( assert ) { + // Not part of startUp module + mw.config.set( 'wgArticlePath', '/wiki/$1' ); + mw.config.set( 'wgPageName', 'Foobar' ); + + var href = mw.util.getUrl( 'Sandbox' ); + assert.equal( href, '/wiki/Sandbox', 'Simple title; Get link for "Sandbox"' ); + + href = mw.util.getUrl( 'Foo:Sandbox ? 5+5=10 ! (test)/subpage' ); + assert.equal( href, '/wiki/Foo:Sandbox_%3F_5%2B5%3D10_%21_%28test%29/subpage', + 'Advanced title; Get link for "Foo:Sandbox ? 5+5=10 ! (test)/subpage"' ); + + href = mw.util.getUrl(); + assert.equal( href, '/wiki/Foobar', 'Default title; Get link for current page ("Foobar")' ); + + href = mw.util.getUrl( 'Sandbox', { action: 'edit' } ); + assert.equal( href, '/wiki/Sandbox?action=edit', + 'Simple title with query string; Get link for "Sandbox" with action=edit' ); + } ); + + QUnit.test( 'wikiScript', 4, function ( assert ) { + mw.config.set( { + 'wgScript': '/w/i.php', // customized wgScript for bug 39103 + 'wgLoadScript': '/w/l.php', // customized wgLoadScript for bug 39103 + 'wgScriptPath': '/w', + 'wgScriptExtension': '.php' + } ); + + assert.equal( mw.util.wikiScript(), mw.config.get( 'wgScript' ), + 'wikiScript() returns wgScript' + ); + assert.equal( mw.util.wikiScript( 'index' ), mw.config.get( 'wgScript' ), + 'wikiScript( index ) returns wgScript' + ); + assert.equal( mw.util.wikiScript( 'load' ), mw.config.get( 'wgLoadScript' ), + 'wikiScript( load ) returns wgLoadScript' + ); + assert.equal( mw.util.wikiScript( 'api' ), '/w/api.php', 'API path' ); + } ); + + QUnit.test( 'addCSS', 3, function ( assert ) { + var $el, style; + $el = $( '<div>' ).attr( 'id', 'mw-addcsstest' ).appendTo( '#qunit-fixture' ); + + style = mw.util.addCSS( '#mw-addcsstest { visibility: hidden; }' ); + assert.equal( typeof style, 'object', 'addCSS returned an object' ); + assert.strictEqual( style.disabled, false, 'property "disabled" is available and set to false' ); + + assert.equal( $el.css( 'visibility' ), 'hidden', 'Added style properties are in effect' ); + + // Clean up + $( style.ownerNode ).remove(); + } ); + + QUnit.asyncTest( 'toggleToc', 4, function ( assert ) { + var tocHtml, $toggleLink; + + function actionC() { + QUnit.start(); + } + + function actionB() { + assert.strictEqual( mw.util.toggleToc( $toggleLink, actionC ), true, 'Return boolean true if the TOC is now visible.' ); + } + + function actionA() { + assert.strictEqual( mw.util.toggleToc( $toggleLink, actionB ), false, 'Return boolean false if the TOC is now hidden.' ); + } + + assert.strictEqual( mw.util.toggleToc(), null, 'Return null if there is no table of contents on the page.' ); + + tocHtml = '<div id="toc" class="toc">' + + '<div id="toctitle">' + '<h2>Contents</h2>' + '<span class="toctoggle"> [<a href="#" class="internal" id="togglelink">Hide</a> ]</span>' + - '</div>' + - '<ul><li></li></ul>' + - '</td></tr></table>', - $toc = $(tocHtml).appendTo( '#qunit-fixture' ), + '</div>' + + '<ul><li></li></ul>' + + '</div>'; + $( tocHtml ).appendTo( '#qunit-fixture' ); $toggleLink = $( '#togglelink' ); - assert.strictEqual( $toggleLink.length, 1, 'Toggle link is appended to the page.' ); - - var actionC = function() { - QUnit.start(); - }; - var actionB = function() { - assert.strictEqual( mw.util.toggleToc( $toggleLink, actionC ), true, 'Return boolean true if the TOC is now visible.' ); - }; - var actionA = function() { - assert.strictEqual( mw.util.toggleToc( $toggleLink, actionB ), false, 'Return boolean false if the TOC is now hidden.' ); - }; - - actionA(); -}); - -QUnit.test( 'getParamValue', 5, function ( assert ) { - var url1 = 'http://example.org/?foo=wrong&foo=right#&foo=bad'; - - assert.equal( mw.util.getParamValue( 'foo', url1 ), 'right', 'Use latest one, ignore hash' ); - assert.strictEqual( mw.util.getParamValue( 'bar', url1 ), null, 'Return null when not found' ); - - var url2 = 'http://example.org/#&foo=bad'; - assert.strictEqual( mw.util.getParamValue( 'foo', url2 ), null, 'Ignore hash if param is not in querystring but in hash (bug 27427)' ); - - var url3 = 'example.org?' + $.param({ 'TEST': 'a b+c' }); - assert.strictEqual( mw.util.getParamValue( 'TEST', url3 ), 'a b+c', 'Bug 30441: getParamValue must understand "+" encoding of space' ); - - var url4 = 'example.org?' + $.param({ 'TEST': 'a b+c d' }); // check for sloppy code from r95332 :) - assert.strictEqual( mw.util.getParamValue( 'TEST', url4 ), 'a b+c d', 'Bug 30441: getParamValue must understand "+" encoding of space (multiple spaces)' ); -}); - -QUnit.test( 'tooltipAccessKey', 3, function ( assert ) { - assert.equal( typeof mw.util.tooltipAccessKeyPrefix, 'string', 'mw.util.tooltipAccessKeyPrefix must be a string' ); - assert.ok( mw.util.tooltipAccessKeyRegexp instanceof RegExp, 'mw.util.tooltipAccessKeyRegexp instance of RegExp' ); - assert.ok( mw.util.updateTooltipAccessKeys, 'mw.util.updateTooltipAccessKeys' ); -}); - -QUnit.test( '$content', 2, function ( assert ) { - assert.ok( mw.util.$content instanceof jQuery, 'mw.util.$content instance of jQuery' ); - assert.strictEqual( mw.util.$content.length, 1, 'mw.util.$content must have length of 1' ); -}); - - -/** - * Portlet names are prefixed with 'p-test' to avoid conflict with core - * when running the test suite under a wiki page. - * Previously, test elements where invisible to the selector since only - * one element can have a given id. - */ -QUnit.test( 'addPortletLink', 8, function ( assert ) { - var pTestTb, pCustom, vectorTabs, tbRL, cuQuux, $cuQuux, tbMW, $tbMW, tbRLDM, caFoo; - pTestTb = '\ - <div class="portlet" id="p-test-tb">\ - <h5>Toolbox</h5>\ - <ul class="body"></ul>\ - </div>'; - pCustom = '\ - <div class="portlet" id="p-test-custom">\ - <h5>Views</h5>\ - <ul class="body">\ - <li id="c-foo"><a href="#">Foo</a></li>\ - <li id="c-barmenu">\ - <ul>\ - <li id="c-bar-baz"><a href="#">Baz</a></a>\ - </ul>\ - </li>\ - </ul>\ - </div>'; - vectorTabs = '\ - <div id="p-test-views" class="vectorTabs">\ - <h5>Views</h5>\ - <ul></ul>\ - </div>'; - - $( '#qunit-fixture' ).append( pTestTb, pCustom, vectorTabs ); - - tbRL = mw.util.addPortletLink( 'p-test-tb', '//mediawiki.org/wiki/ResourceLoader', - 'ResourceLoader', 't-rl', 'More info about ResourceLoader on MediaWiki.org ', 'l' ); - - assert.ok( $.isDomElement( tbRL ), 'addPortletLink returns a valid DOM Element according to $.isDomElement' ); - - tbMW = mw.util.addPortletLink( 'p-test-tb', '//mediawiki.org/', - 'MediaWiki.org', 't-mworg', 'Go to MediaWiki.org ', 'm', tbRL ); - $tbMW = $( tbMW ); - - - assert.equal( $tbMW.attr( 'id' ), 't-mworg', 'Link has correct ID set' ); - assert.equal( $tbMW.closest( '.portlet' ).attr( 'id' ), 'p-test-tb', 'Link was inserted within correct portlet' ); - assert.equal( $tbMW.next().attr( 'id' ), 't-rl', 'Link is in the correct position (by passing nextnode)' ); - - cuQuux = mw.util.addPortletLink( 'p-test-custom', '#', 'Quux' ); - $cuQuux = $(cuQuux); - - assert.equal( - $( '#p-test-custom #c-barmenu ul li' ).length, - 1, - 'addPortletLink did not add the item to all <ul> elements in the portlet (bug 35082)' - ); - - tbRLDM = mw.util.addPortletLink( 'p-test-tb', '//mediawiki.org/wiki/RL/DM', - 'Default modules', 't-rldm', 'List of all default modules ', 'd', '#t-rl' ); - - assert.equal( $( tbRLDM ).next().attr( 'id' ), 't-rl', 'Link is in the correct position (by passing CSS selector)' ); - - caFoo = mw.util.addPortletLink( 'p-test-views', '#', 'Foo' ); - - assert.strictEqual( $tbMW.find( 'span').length, 0, 'No <span> element should be added for porlets without vectorTabs class.' ); - assert.strictEqual( $( caFoo ).find( 'span').length, 1, 'A <span> element should be added for porlets with vectorTabs class.' ); -}); - -QUnit.test( 'jsMessage', 1, function ( assert ) { - var a = mw.util.jsMessage( "MediaWiki is <b>Awesome</b>." ); - assert.ok( a, 'Basic checking of return value' ); - - // Clean up - $( '#mw-js-message' ).remove(); -}); - -QUnit.test( 'validateEmail', 6, function ( assert ) { - assert.strictEqual( mw.util.validateEmail( "" ), null, 'Should return null for empty string ' ); - assert.strictEqual( mw.util.validateEmail( "user@localhost" ), true, 'Return true for a valid e-mail address' ); - - // testEmailWithCommasAreInvalids - assert.strictEqual( mw.util.validateEmail( "user,foo@example.org" ), false, 'Emails with commas are invalid' ); - assert.strictEqual( mw.util.validateEmail( "userfoo@ex,ample.org" ), false, 'Emails with commas are invalid' ); - - // testEmailWithHyphens - assert.strictEqual( mw.util.validateEmail( "user-foo@example.org" ), true, 'Emails may contain a hyphen' ); - assert.strictEqual( mw.util.validateEmail( "userfoo@ex-ample.org" ), true, 'Emails may contain a hyphen' ); -}); - -QUnit.test( 'isIPv6Address', 40, function ( assert ) { - // Shortcuts - function assertFalseIPv6( addy, summary ) { - return assert.strictEqual( mw.util.isIPv6Address( addy ), false, summary ); - } - function assertTrueIPv6( addy, summary ) { - return assert.strictEqual( mw.util.isIPv6Address( addy ), true, summary ); - } - - // Based on IPTest.php > testisIPv6 - assertFalseIPv6( ':fc:100::', 'IPv6 starting with lone ":"' ); - assertFalseIPv6( 'fc:100:::', 'IPv6 ending with a ":::"' ); - assertFalseIPv6( 'fc:300', 'IPv6 with only 2 words' ); - assertFalseIPv6( 'fc:100:300', 'IPv6 with only 3 words' ); - - $.each( - ['fc:100::', - 'fc:100:a::', - 'fc:100:a:d::', - 'fc:100:a:d:1::', - 'fc:100:a:d:1:e::', - 'fc:100:a:d:1:e:ac::'], function ( i, addy ){ - assertTrueIPv6( addy, addy + ' is a valid IP' ); - }); - - assertFalseIPv6( 'fc:100:a:d:1:e:ac:0::', 'IPv6 with 8 words ending with "::"' ); - assertFalseIPv6( 'fc:100:a:d:1:e:ac:0:1::', 'IPv6 with 9 words ending with "::"' ); - - assertFalseIPv6( ':::' ); - assertFalseIPv6( '::0:', 'IPv6 ending in a lone ":"' ); - - assertTrueIPv6( '::', 'IPv6 zero address' ); - $.each( - ['::0', - '::fc', - '::fc:100', - '::fc:100:a', - '::fc:100:a:d', - '::fc:100:a:d:1', - '::fc:100:a:d:1:e', - '::fc:100:a:d:1:e:ac', - - 'fc:100:a:d:1:e:ac:0'], function ( i, addy ){ - assertTrueIPv6( addy, addy + ' is a valid IP' ); - }); - - assertFalseIPv6( '::fc:100:a:d:1:e:ac:0', 'IPv6 with "::" and 8 words' ); - assertFalseIPv6( '::fc:100:a:d:1:e:ac:0:1', 'IPv6 with 9 words' ); - - assertFalseIPv6( ':fc::100', 'IPv6 starting with lone ":"' ); - assertFalseIPv6( 'fc::100:', 'IPv6 ending with lone ":"' ); - assertFalseIPv6( 'fc:::100', 'IPv6 with ":::" in the middle' ); - - assertTrueIPv6( 'fc::100', 'IPv6 with "::" and 2 words' ); - assertTrueIPv6( 'fc::100:a', 'IPv6 with "::" and 3 words' ); - assertTrueIPv6( 'fc::100:a:d', 'IPv6 with "::" and 4 words' ); - assertTrueIPv6( 'fc::100:a:d:1', 'IPv6 with "::" and 5 words' ); - assertTrueIPv6( 'fc::100:a:d:1:e', 'IPv6 with "::" and 6 words' ); - assertTrueIPv6( 'fc::100:a:d:1:e:ac', 'IPv6 with "::" and 7 words' ); - assertTrueIPv6( '2001::df', 'IPv6 with "::" and 2 words' ); - assertTrueIPv6( '2001:5c0:1400:a::df', 'IPv6 with "::" and 5 words' ); - assertTrueIPv6( '2001:5c0:1400:a::df:2', 'IPv6 with "::" and 6 words' ); - - assertFalseIPv6( 'fc::100:a:d:1:e:ac:0', 'IPv6 with "::" and 8 words' ); - assertFalseIPv6( 'fc::100:a:d:1:e:ac:0:1', 'IPv6 with 9 words' ); -}); - -QUnit.test( 'isIPv4Address', 11, function ( assert ) { - // Shortcuts - function assertFalseIPv4( addy, summary ) { - assert.strictEqual( mw.util.isIPv4Address( addy ), false, summary ); - } - function assertTrueIPv4( addy, summary ) { - assert.strictEqual( mw.util.isIPv4Address( addy ), true, summary ); - } - - // Based on IPTest.php > testisIPv4 - assertFalseIPv4( false, 'Boolean false is not an IP' ); - assertFalseIPv4( true, 'Boolean true is not an IP' ); - assertFalseIPv4( '', 'Empty string is not an IP' ); - assertFalseIPv4( 'abc', '"abc" is not an IP' ); - assertFalseIPv4( ':', 'Colon is not an IP' ); - assertFalseIPv4( '124.24.52', 'IPv4 not enough quads' ); - assertFalseIPv4( '24.324.52.13', 'IPv4 out of range' ); - assertFalseIPv4( '.24.52.13', 'IPv4 starts with period' ); - - assertTrueIPv4( '124.24.52.13', '124.24.52.134 is a valid IP' ); - assertTrueIPv4( '1.24.52.13', '1.24.52.13 is a valid IP' ); - assertFalseIPv4( '74.24.52.13/20', 'IPv4 ranges are not recogzized as valid IPs' ); -}); + assert.strictEqual( $toggleLink.length, 1, 'Toggle link is appended to the page.' ); + + actionA(); + } ); + + QUnit.test( 'getParamValue', 5, function ( assert ) { + var url; + + url = 'http://example.org/?foo=wrong&foo=right#&foo=bad'; + assert.equal( mw.util.getParamValue( 'foo', url ), 'right', 'Use latest one, ignore hash' ); + assert.strictEqual( mw.util.getParamValue( 'bar', url ), null, 'Return null when not found' ); + + url = 'http://example.org/#&foo=bad'; + assert.strictEqual( mw.util.getParamValue( 'foo', url ), null, 'Ignore hash if param is not in querystring but in hash (bug 27427)' ); + + url = 'example.org?' + $.param( { 'TEST': 'a b+c' } ); + assert.strictEqual( mw.util.getParamValue( 'TEST', url ), 'a b+c', 'Bug 30441: getParamValue must understand "+" encoding of space' ); + + url = 'example.org?' + $.param( { 'TEST': 'a b+c d' } ); // check for sloppy code from r95332 :) + assert.strictEqual( mw.util.getParamValue( 'TEST', url ), 'a b+c d', 'Bug 30441: getParamValue must understand "+" encoding of space (multiple spaces)' ); + } ); + + QUnit.test( 'tooltipAccessKey', 4, function ( assert ) { + assert.equal( typeof mw.util.tooltipAccessKeyPrefix, 'string', 'tooltipAccessKeyPrefix must be a string' ); + assert.equal( $.type( mw.util.tooltipAccessKeyRegexp ), 'regexp', 'tooltipAccessKeyRegexp is a regexp' ); + assert.ok( mw.util.updateTooltipAccessKeys, 'updateTooltipAccessKeys is non-empty' ); + + 'Example [a]'.replace( mw.util.tooltipAccessKeyRegexp, function ( sub, m1, m2, m3, m4, m5, m6 ) { + assert.equal( m6, 'a', 'tooltipAccessKeyRegexp finds the accesskey hint' ); + } ); + } ); + + QUnit.test( '$content', 2, function ( assert ) { + assert.ok( mw.util.$content instanceof jQuery, 'mw.util.$content instance of jQuery' ); + assert.strictEqual( mw.util.$content.length, 1, 'mw.util.$content must have length of 1' ); + } ); + + /** + * Portlet names are prefixed with 'p-test' to avoid conflict with core + * when running the test suite under a wiki page. + * Previously, test elements where invisible to the selector since only + * one element can have a given id. + */ + QUnit.test( 'addPortletLink', 13, function ( assert ) { + var pTestTb, pCustom, vectorTabs, tbRL, cuQuux, $cuQuux, tbMW, $tbMW, tbRLDM, caFoo, + addedAfter, tbRLDMnonexistentid, tbRLDMemptyjquery; + + pTestTb = '\ + <div class="portlet" id="p-test-tb">\ + <h3>Toolbox</h3>\ + <ul class="body"></ul>\ + </div>'; + pCustom = '\ + <div class="portlet" id="p-test-custom">\ + <h3>Views</h3>\ + <ul class="body">\ + <li id="c-foo"><a href="#">Foo</a></li>\ + <li id="c-barmenu">\ + <ul>\ + <li id="c-bar-baz"><a href="#">Baz</a></a>\ + </ul>\ + </li>\ + </ul>\ + </div>'; + vectorTabs = '\ + <div id="p-test-views" class="vectorTabs">\ + <h3>Views</h3>\ + <ul></ul>\ + </div>'; + + $( '#qunit-fixture' ).append( pTestTb, pCustom, vectorTabs ); + + tbRL = mw.util.addPortletLink( 'p-test-tb', '//mediawiki.org/wiki/ResourceLoader', + 'ResourceLoader', 't-rl', 'More info about ResourceLoader on MediaWiki.org ', 'l' + ); + + assert.ok( $.isDomElement( tbRL ), 'addPortletLink returns a valid DOM Element according to $.isDomElement' ); + + tbMW = mw.util.addPortletLink( 'p-test-tb', '//mediawiki.org/', + 'MediaWiki.org', 't-mworg', 'Go to MediaWiki.org ', 'm', tbRL ); + $tbMW = $( tbMW ); + + assert.propEqual( + $tbMW.getAttrs(), + { + id: 't-mworg' + }, + 'Validate attributes of created element' + ); + + assert.propEqual( + $tbMW.find( 'a' ).getAttrs(), + { + href: '//mediawiki.org/', + title: 'Go to MediaWiki.org [ctrl-alt-m]', + accesskey: 'm' + }, + 'Validate attributes of anchor tag in created element' + ); + + assert.equal( $tbMW.closest( '.portlet' ).attr( 'id' ), 'p-test-tb', 'Link was inserted within correct portlet' ); + assert.strictEqual( $tbMW.next()[0], tbRL, 'Link is in the correct position (by passing nextnode)' ); + + cuQuux = mw.util.addPortletLink( 'p-test-custom', '#', 'Quux', null, 'Example [shift-x]', 'q' ); + $cuQuux = $( cuQuux ); + + assert.equal( $cuQuux.find( 'a' ).attr( 'title' ), 'Example [ctrl-alt-q]', 'Existing accesskey is stripped and updated' ); + + assert.equal( + $( '#p-test-custom #c-barmenu ul li' ).length, + 1, + 'addPortletLink did not add the item to all <ul> elements in the portlet (bug 35082)' + ); + + tbRLDM = mw.util.addPortletLink( 'p-test-tb', '//mediawiki.org/wiki/RL/DM', + 'Default modules', 't-rldm', 'List of all default modules ', 'd', '#t-rl' ); + + assert.equal( $( tbRLDM ).next().attr( 'id' ), 't-rl', 'Link is in the correct position (by passing CSS selector)' ); + + caFoo = mw.util.addPortletLink( 'p-test-views', '#', 'Foo' ); + + assert.strictEqual( $tbMW.find( 'span' ).length, 0, 'No <span> element should be added for porlets without vectorTabs class.' ); + assert.strictEqual( $( caFoo ).find( 'span' ).length, 1, 'A <span> element should be added for porlets with vectorTabs class.' ); + + addedAfter = mw.util.addPortletLink( 'p-test-tb', '#', 'After foo', 'post-foo', 'After foo', null, $( tbRL ) ); + assert.strictEqual( $( addedAfter ).next()[0], tbRL, 'Link is in the correct position (by passing a jQuery object as nextnode)' ); + + // test case - nonexistent id as next node + tbRLDMnonexistentid = mw.util.addPortletLink( 'p-test-tb', '//mediawiki.org/wiki/RL/DM', + 'Default modules', 't-rldm-nonexistent', 'List of all default modules ', 'd', '#t-rl-nonexistent' ); + + assert.equal( tbRLDMnonexistentid, $( '#p-test-tb li:last' )[0], 'Nonexistent id as nextnode adds the portlet at end' ); + + // test case - empty jquery object as next node + tbRLDMemptyjquery = mw.util.addPortletLink( 'p-test-tb', '//mediawiki.org/wiki/RL/DM', + 'Default modules', 't-rldm-empty-jquery', 'List of all default modules ', 'd', $( '#t-rl-nonexistent' ) ); + + assert.equal( tbRLDMemptyjquery, $( '#p-test-tb li:last' )[0], 'Empty jquery as nextnode adds the portlet at end' ); + } ); + + QUnit.test( 'jsMessage', 1, function ( assert ) { + var a = mw.util.jsMessage( 'MediaWiki is <b>Awesome</b>.' ); + assert.ok( a, 'Basic checking of return value' ); + + // Clean up + $( '#mw-js-message' ).remove(); + } ); + + QUnit.test( 'validateEmail', 6, function ( assert ) { + assert.strictEqual( mw.util.validateEmail( '' ), null, 'Should return null for empty string ' ); + assert.strictEqual( mw.util.validateEmail( 'user@localhost' ), true, 'Return true for a valid e-mail address' ); + + // testEmailWithCommasAreInvalids + assert.strictEqual( mw.util.validateEmail( 'user,foo@example.org' ), false, 'Emails with commas are invalid' ); + assert.strictEqual( mw.util.validateEmail( 'userfoo@ex,ample.org' ), false, 'Emails with commas are invalid' ); + + // testEmailWithHyphens + assert.strictEqual( mw.util.validateEmail( 'user-foo@example.org' ), true, 'Emails may contain a hyphen' ); + assert.strictEqual( mw.util.validateEmail( 'userfoo@ex-ample.org' ), true, 'Emails may contain a hyphen' ); + } ); + + QUnit.test( 'isIPv6Address', 40, function ( assert ) { + // Shortcuts + function assertFalseIPv6( addy, summary ) { + return assert.strictEqual( mw.util.isIPv6Address( addy ), false, summary ); + } + + function assertTrueIPv6( addy, summary ) { + return assert.strictEqual( mw.util.isIPv6Address( addy ), true, summary ); + } + + // Based on IPTest.php > testisIPv6 + assertFalseIPv6( ':fc:100::', 'IPv6 starting with lone ":"' ); + assertFalseIPv6( 'fc:100:::', 'IPv6 ending with a ":::"' ); + assertFalseIPv6( 'fc:300', 'IPv6 with only 2 words' ); + assertFalseIPv6( 'fc:100:300', 'IPv6 with only 3 words' ); + + $.each( + ['fc:100::', + 'fc:100:a::', + 'fc:100:a:d::', + 'fc:100:a:d:1::', + 'fc:100:a:d:1:e::', + 'fc:100:a:d:1:e:ac::'], function ( i, addy ) { + assertTrueIPv6( addy, addy + ' is a valid IP' ); + } ); + + assertFalseIPv6( 'fc:100:a:d:1:e:ac:0::', 'IPv6 with 8 words ending with "::"' ); + assertFalseIPv6( 'fc:100:a:d:1:e:ac:0:1::', 'IPv6 with 9 words ending with "::"' ); + + assertFalseIPv6( ':::' ); + assertFalseIPv6( '::0:', 'IPv6 ending in a lone ":"' ); + + assertTrueIPv6( '::', 'IPv6 zero address' ); + $.each( + ['::0', + '::fc', + '::fc:100', + '::fc:100:a', + '::fc:100:a:d', + '::fc:100:a:d:1', + '::fc:100:a:d:1:e', + '::fc:100:a:d:1:e:ac', + + 'fc:100:a:d:1:e:ac:0'], function ( i, addy ) { + assertTrueIPv6( addy, addy + ' is a valid IP' ); + } ); + + assertFalseIPv6( '::fc:100:a:d:1:e:ac:0', 'IPv6 with "::" and 8 words' ); + assertFalseIPv6( '::fc:100:a:d:1:e:ac:0:1', 'IPv6 with 9 words' ); + + assertFalseIPv6( ':fc::100', 'IPv6 starting with lone ":"' ); + assertFalseIPv6( 'fc::100:', 'IPv6 ending with lone ":"' ); + assertFalseIPv6( 'fc:::100', 'IPv6 with ":::" in the middle' ); + + assertTrueIPv6( 'fc::100', 'IPv6 with "::" and 2 words' ); + assertTrueIPv6( 'fc::100:a', 'IPv6 with "::" and 3 words' ); + assertTrueIPv6( 'fc::100:a:d', 'IPv6 with "::" and 4 words' ); + assertTrueIPv6( 'fc::100:a:d:1', 'IPv6 with "::" and 5 words' ); + assertTrueIPv6( 'fc::100:a:d:1:e', 'IPv6 with "::" and 6 words' ); + assertTrueIPv6( 'fc::100:a:d:1:e:ac', 'IPv6 with "::" and 7 words' ); + assertTrueIPv6( '2001::df', 'IPv6 with "::" and 2 words' ); + assertTrueIPv6( '2001:5c0:1400:a::df', 'IPv6 with "::" and 5 words' ); + assertTrueIPv6( '2001:5c0:1400:a::df:2', 'IPv6 with "::" and 6 words' ); + + assertFalseIPv6( 'fc::100:a:d:1:e:ac:0', 'IPv6 with "::" and 8 words' ); + assertFalseIPv6( 'fc::100:a:d:1:e:ac:0:1', 'IPv6 with 9 words' ); + } ); + + QUnit.test( 'isIPv4Address', 11, function ( assert ) { + // Shortcuts + function assertFalseIPv4( addy, summary ) { + assert.strictEqual( mw.util.isIPv4Address( addy ), false, summary ); + } + + function assertTrueIPv4( addy, summary ) { + assert.strictEqual( mw.util.isIPv4Address( addy ), true, summary ); + } + + // Based on IPTest.php > testisIPv4 + assertFalseIPv4( false, 'Boolean false is not an IP' ); + assertFalseIPv4( true, 'Boolean true is not an IP' ); + assertFalseIPv4( '', 'Empty string is not an IP' ); + assertFalseIPv4( 'abc', '"abc" is not an IP' ); + assertFalseIPv4( ':', 'Colon is not an IP' ); + assertFalseIPv4( '124.24.52', 'IPv4 not enough quads' ); + assertFalseIPv4( '24.324.52.13', 'IPv4 out of range' ); + assertFalseIPv4( '.24.52.13', 'IPv4 starts with period' ); + + assertTrueIPv4( '124.24.52.13', '124.24.52.134 is a valid IP' ); + assertTrueIPv4( '1.24.52.13', '1.24.52.13 is a valid IP' ); + assertFalseIPv4( '74.24.52.13/20', 'IPv4 ranges are not recogzized as valid IPs' ); + } ); +}( mediaWiki, jQuery ) ); diff --git a/tests/qunit/suites/resources/startup.test.js b/tests/qunit/suites/resources/startup.test.js new file mode 100644 index 00000000..76f32f7e --- /dev/null +++ b/tests/qunit/suites/resources/startup.test.js @@ -0,0 +1,129 @@ +/*global isCompatible: true */ +( function ( $ ) { + var testcases = { + // Supported: Compatible + gradeA: [ + // Chrome + 'Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_7; en-US) AppleWebKit/534.16 (KHTML, like Gecko) Chrome/10.0.648.205 Safari/534.16', + // Firefox 4+ + 'Mozilla/5.0 (Windows NT 6.1.1; rv:5.0) Gecko/20100101 Firefox/5.0', + 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.6; rv:9.0) Gecko/20100101 Firefox/9.0', + 'Mozilla/5.0 (Macintosh; I; Intel Mac OS X 11_7_9; de-LI; rv:1.9b4) Gecko/2012010317 Firefox/10.0a4', + 'Mozilla/5.0 (Windows NT 6.1; rv:12.0) Gecko/20120403211507 Firefox/12.0', + 'Mozilla/5.0 (Windows NT 6.2; Win64; x64; rv:16.0.1) Gecko/20121011 Firefox/16.0.1', + // Kindle Fire + 'Mozilla/5.0 (Linux; U; Android 2.3.4; en-us; Kindle Fire Build/GINGERBREAD) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Safari/533.1', + // Safari 5.0+ + 'Mozilla/5.0 (Macintosh; I; Intel Mac OS X 10_6_7; ru-ru) AppleWebKit/534.31+ (KHTML, like Gecko) Version/5.0.5 Safari/533.21.1', + // Opera 11+ + 'Opera/9.80 (Windows NT 6.1; U; ru) Presto/2.8.131 Version/11.10', + // Internet Explorer 6+ + 'Mozilla/5.0 (compatible; MSIE 6.0; Windows NT 5.1)', + 'Mozilla/5.0 (compatible; MSIE 7.0; Windows NT 6.0; en-US)', + 'Mozilla/5.0 (compatible; MSIE 8.0; Windows NT 5.2; Trident/4.0; Media Center PC 4.0; SLCC1; .NET CLR 3.0.04320)', + 'Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 7.1; Trident/5.0)', + 'Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; WOW64; Trident/6.0)', + // IE Mobile + 'Mozilla/5.0 (compatible; MSIE 9.0; Windows Phone OS 7.5; Trident/5.0; IEMobile/9.0; NOKIA; Lumia 800)', + // BlackBerry 6+ + 'Mozilla/5.0 (BlackBerry; U; BlackBerry 9300; en) AppleWebKit/534.8+ (KHTML, like Gecko) Version/6.0.0.570 Mobile Safari/534.8+', + 'Mozilla/5.0 (BlackBerry; U; BlackBerry 9900; en) AppleWebKit/534.11+ (KHTML, like Gecko) Version/7.1.0.346 Mobile Safari/534.11+', + 'Mozilla/5.0 (BB10; Touch) AppleWebKit/537.3+ (KHTML, like Gecko) Version/10.0.9.386 Mobile Safari/537.3+', + // Open WebOS 1.4+ (HP Veer 4G) + 'Mozilla/5.0 (webOS/2.1.2; U; en-US) AppleWebKit/532.2 (KHTML, like Gecko) Version/1.0 Safari/532.2 P160UNA/1.0', + // Firefox Mobile + 'Mozilla/5.0 (Mobile; rv:14.0) Gecko/14.0 Firefox/14.0', + // iOS + 'Mozilla/5.0 (ipod: U;CPU iPhone OS 2_2 like Mac OS X: es_es) AppleWebKit/525.18.1 (KHTML, like Gecko) Version/3.0 Mobile/3B48b Safari/419.3', + 'Mozilla/5.0 (iPhone; U; CPU like Mac OS X; en) AppleWebKit/420.1 (KHTML, like Gecko) Version/3.0 Mobile/3B48b Safari/419.3', + // Android + 'Mozilla/5.0 (Linux; U; Android 2.1; en-us; Nexus One Build/ERD62) AppleWebKit/530.17 (KHTML, like Gecko) Version/4.0 Mobile Safari/530.17' + ], + // Supported: Uncompatible, serve basic content + gradeB: [ + // Internet Explorer < 6 + 'Mozilla/2.0 (compatible; MSIE 3.03; Windows 3.1)', + 'Mozilla/4.0 (compatible; MSIE 4.01; Windows 95)', + 'Mozilla/4.0 (compatible; MSIE 5.0; Windows 98;)', + 'Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)', + // Firefox < 3.6 + 'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.0.2) Gecko/20060308 Firefox/1.5.0.2', + 'Mozilla/5.0 (X11; U; Linux i686; nl; rv:1.8.1.1) Gecko/20070311 Firefox/2.0.0.1', + // BlackBerry < 6 + 'BlackBerry9300/5.0.0.716 Profile/MIDP-2.1 Configuration/CLDC-1.1 VendorID/133', + 'BlackBerry7250/4.0.0 Profile/MIDP-2.0 Configuration/CLDC-1.1', + // Open WebOS < 1.5 (Palm Pre, Palm Pixi) + 'Mozilla/5.0 (webOS/1.0; U; en-US) AppleWebKit/525.27.1 (KHTML, like Gecko) Version/1.0 Safari/525.27.1 Pre/1.0', + 'Mozilla/5.0 (webOS/1.4.0; U; en-US) AppleWebKit/532.2 (KHTML, like Gecko) Version/1.0 Safari/532.2 Pixi/1.1 ', + // SymbianOS + 'NokiaN95_8GB-3;Mozilla/5.0 SymbianOS/9.2;U;Series60/3.1 NokiaN95_8GB-3/11.2.011 Profile/MIDP-2.0 Configuration/CLDC-1.1 AppleWebKit/413 (KHTML, like Gecko)', + 'Nokia7610/2.0 (5.0509.0) SymbianOS/7.0s Series60/2.1 Profile/MIDP-2.0 Configuration/CLDC-1.0 ', + 'Mozilla/5.0 (SymbianOS/9.1; U; [en]; SymbianOS/91 Series60/3.0) AppleWebKit/413 (KHTML, like Gecko) Safari/413', + 'Mozilla/5.0 (SymbianOS/9.3; Series60/3.2 NokiaE52-2/091.003; Profile/MIDP-2.1 Configuration/CLDC-1.1 ) AppleWebKit/533.4 (KHTML, like Gecko) NokiaBrowser/7.3.1.34 Mobile Safari/533.4', + // NetFront + 'Mozilla/4.0 (compatible; Linux 2.6.10) NetFront/3.3 Kindle/1.0 (screen 600x800)', + 'Mozilla/4.0 (compatible; Linux 2.6.22) NetFront/3.4 Kindle/2.0 (screen 824x1200; rotate)', + 'Mozilla/4.08 (Windows; Mobile Content Viewer/1.0) NetFront/3.2', + // Opera Mini + 'Opera/9.80 (J2ME/MIDP; Opera Mini/3.1.10423/22.387; U; en) Presto/2.5.25 Version/10.54', + 'Opera/9.50 (J2ME/MIDP; Opera Mini/4.0.10031/298; U; en)', + 'Opera/9.80 (J2ME/MIDP; Opera Mini/6.24093/26.1305; U; en) Presto/2.8.119 Version/10.54', + 'Opera/9.80 (Android; Opera Mini/7.29530/27.1407; U; en) Presto/2.8.119 Version/11.10', + // Ovi Browser + 'Mozilla/5.0 (Series40; NokiaX3-02/05.60; Profile/MIDP-2.1 Configuration/CLDC-1.1) Gecko/20100401 S40OviBrowser/3.2.0.0.6', + 'Mozilla/5.0 (Series40; Nokia305/05.92; Profile/MIDP-2.1 Configuration/CLDC-1.1) Gecko/20100401 S40OviBrowser/3.7.0.0.11' + ], + // No explicit support for or against these browsers, they're + // given a shot at Grade A at their own risk. + gradeX: [ + // Firefox 3.6 + 'Mozilla/5.0 (Windows; U; Windows NT 6.1; ru; rv:1.9.2.3) Gecko/20100401 Firefox/3.6.3', + // Gecko + 'Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.8.0.7) Gecko/20060928 (Debian|Debian-1.8.0.7-1) Epiphany/2.14', + 'Mozilla/5.0 (X11; U; Linux i686 (x86_64); en-US; rv:1.8.1.6) Gecko/20070817 IceWeasel/2.0.0.6-g2', + // KHTML + 'Mozilla/5.0 (compatible; Konqueror/4.3; Linux) KHTML/4.3.5 (like Gecko)', + // Text browsers + 'Links (2.1pre33; Darwin 8.11.0 Power Macintosh; x)', + 'Links (6.9; Unix 6.9-astral sparc; 80x25)', + 'Lynx/2.8.6rel.4 libwww-FM/2.14 SSL-MM/1.4.1 OpenSSL/0.9.8g', + 'w3m/0.5.1', + // Bots + 'Googlebot/2.1 (+http://www.google.com/bot.html)', + 'Mozilla/5.0 (compatible; googlebot/2.1; +http://www.google.com/bot.html)', + 'Mozilla/5.0 (compatible; YandexBot/3.0)', + // Scripts + 'curl/7.21.4 (universal-apple-darwin11.0) libcurl/7.21.4 OpenSSL/0.9.8r zlib/1.2.5', + 'Wget/1.9', + 'Wget/1.10.1 (Red Hat modified)', + // Unknown + 'I\'m an unknown browser', + // Empty + '' + ] + }; + + QUnit.module( 'startup', QUnit.newMwEnvironment() ); + + QUnit.test( 'isCompatible( Grade A )', testcases.gradeA.length, function ( assert ) { + $.each( testcases.gradeA, function ( i, ua ) { + assert.strictEqual( isCompatible( ua ), true, ua ); + } + ); + } ); + + QUnit.test( 'isCompatible( Grade B )', testcases.gradeB.length, function ( assert ) { + $.each( testcases.gradeB, function ( i, ua ) { + assert.strictEqual( isCompatible( ua ), false, ua ); + } + ); + } ); + + QUnit.test( 'isCompatible( Grade X )', testcases.gradeX.length, function ( assert ) { + $.each( testcases.gradeX, function ( i, ua ) { + assert.strictEqual( isCompatible( ua ), true, ua ); + } + ); + } ); + +}( jQuery ) ); diff --git a/tests/selenium/Selenium.php b/tests/selenium/Selenium.php deleted file mode 100644 index ecf7f9ec..00000000 --- a/tests/selenium/Selenium.php +++ /dev/null @@ -1,190 +0,0 @@ -<?php -/** - * Selenium connector - * This is implemented as a singleton. - */ - -require( 'Testing/Selenium.php' ); - -class Selenium { - protected static $_instance = null; - - public $isStarted = false; - public $tester; - - protected $port; - protected $host; - protected $browser; - protected $browsers; - protected $logger; - protected $user; - protected $pass; - protected $timeout = 30000; - protected $verbose; - protected $junitlogfile; //processed by phpUnderControl - protected $runagainstgrid = false; - - /** - * @todo this shouldn't have to be static - */ - static protected $url; - - /** - * Override parent - */ - public function __construct() { - /** - * @todo this is an ugly hack to make information available to - * other tests. It should be fixed. - */ - if ( null === self::$_instance ) { - self::$_instance = $this; - } else { - throw new MWException( "Already have one Selenium instance." ); - } - } - - public function start() { - $this->tester = new Testing_Selenium( $this->browser, self::$url, $this->host, - $this->port, $this->timeout ); - if ( method_exists( $this->tester, "setVerbose" ) ) $this->tester->setVerbose( $this->verbose ); - - $this->tester->start(); - $this->isStarted = true; - } - - public function stop() { - $this->tester->stop(); - $this->tester = null; - $this->isStarted = false; - } - - public function login() { - if ( strlen( $this->user ) == 0 ) { - return; - } - $this->open( self::$url . '/index.php?title=Special:Userlogin' ); - $this->type( 'wpName1', $this->user ); - $this->type( 'wpPassword1', $this->pass ); - $this->click( "//input[@id='wpLoginAttempt']" ); - $this->waitForPageToLoad( 10000 ); - - // after login we redirect to the main page. So check whether the "Prefernces" top menu item exists - $value = $this->isElementPresent( "//li[@id='pt-preferences']" ); - - if ( $value != true ) { - throw new Testing_Selenium_Exception( "Login Failed" ); - } - - } - - public static function getInstance() { - if ( null === self::$_instance ) { - throw new MWException( "No instance set yet" ); - } - - return self::$_instance; - } - - public function loadPage( $title, $action ) { - $this->open( self::$url . '/index.php?title=' . $title . '&action=' . $action ); - } - - public function setLogger( $logger ) { - $this->logger = $logger; - } - - public function getLogger( ) { - return $this->logger; - } - - public function log( $message ) { - $this->logger->write( $message ); - } - - public function setUrl( $url ) { - self::$url = $url; - } - - static public function getUrl() { - return self::$url; - } - - public function setPort( $port ) { - $this->port = $port; - } - - public function getPort() { - return $this->port; - } - - public function setUser( $user ) { - $this->user = $user; - } - - // Function to get username - public function getUser() { - return $this->user; - } - - - public function setPass( $pass ) { - $this->pass = $pass; - } - - //add function to get password - public function getPass( ) { - return $this->pass; - } - - - public function setHost( $host ) { - $this->host = $host; - } - - public function setVerbose( $verbose ) { - $this->verbose = $verbose; - } - - public function setAvailableBrowsers( $availableBrowsers ) { - $this->browsers = $availableBrowsers; - } - - public function setJUnitLogfile( $junitlogfile ) { - $this->junitlogfile = $junitlogfile; - } - - public function getJUnitLogfile( ) { - return $this->junitlogfile; - } - - public function setRunAgainstGrid( $runagainstgrid ) { - $this->runagainstgrid = $runagainstgrid; - } - - public function setBrowser( $b ) { - if ($this->runagainstgrid) { - $this->browser = $b; - return true; - } - if ( !isset( $this->browsers[$b] ) ) { - throw new MWException( "Invalid Browser: $b.\n" ); - } - - $this->browser = $this->browsers[$b]; - } - - public function getAvailableBrowsers() { - return $this->browsers; - } - - public function __call( $name, $args ) { - $t = call_user_func_array( array( $this->tester, $name ), $args ); - return $t; - } - - // Prevent external cloning - protected function __clone() { } - // Prevent external construction - // protected function __construct() {} -} diff --git a/tests/selenium/SeleniumConfig.php b/tests/selenium/SeleniumConfig.php deleted file mode 100644 index 04cf8d88..00000000 --- a/tests/selenium/SeleniumConfig.php +++ /dev/null @@ -1,79 +0,0 @@ -<?php -if ( !defined( 'SELENIUMTEST' ) ) { - die( 1 ); -} - -class SeleniumConfig { - - /** - * Retreives the Selenium configuration values from an ini file. - * See sample config file in selenium_settings.ini.sample - * - */ - - public static function getSeleniumSettings ( &$seleniumSettings, - &$seleniumBrowsers, - &$seleniumTestSuites, - $seleniumConfigFile = null ) { - if ( strlen( $seleniumConfigFile ) == 0 ) { - global $wgSeleniumConfigFile; - if ( isset( $wgSeleniumConfigFile ) ) $seleniumConfigFile = $wgSeleniumConfigFile ; - } - - if ( strlen( $seleniumConfigFile ) == 0 || !file_exists( $seleniumConfigFile ) ) { - throw new MWException( "Unable to read local Selenium Settings from " . $seleniumConfigFile . "\n" ); - } - - $configArray = parse_ini_file( $seleniumConfigFile, true ); - if ( $configArray === false ) { - throw new MWException( "Error parsing " . $seleniumConfigFile . "\n" ); - } - - if ( array_key_exists( 'SeleniumSettings', $configArray) ) { - wfSuppressWarnings(); - //we may need to change how this is set. But for now leave it in the ini file - $seleniumBrowsers = $configArray['SeleniumSettings']['browsers']; - - $seleniumSettings['host'] = $configArray['SeleniumSettings']['host']; - $seleniumSettings['port'] = $configArray['SeleniumSettings']['port']; - $seleniumSettings['wikiUrl'] = $configArray['SeleniumSettings']['wikiUrl']; - $seleniumSettings['username'] = $configArray['SeleniumSettings']['username']; - $seleniumSettings['userPassword'] = $configArray['SeleniumSettings']['userPassword']; - $seleniumSettings['testBrowser'] = $configArray['SeleniumSettings']['testBrowser']; - $seleniumSettings['startserver'] = $configArray['SeleniumSettings']['startserver']; - $seleniumSettings['stopserver'] = $configArray['SeleniumSettings']['stopserver']; - $seleniumSettings['seleniumserverexecpath'] = $configArray['SeleniumSettings']['seleniumserverexecpath']; - $seleniumSettings['jUnitLogFile'] = $configArray['SeleniumSettings']['jUnitLogFile']; - $seleniumSettings['runAgainstGrid'] = $configArray['SeleniumSettings']['runAgainstGrid']; - - wfRestoreWarnings(); - } - if ( array_key_exists( 'SeleniumTests', $configArray) ) { - wfSuppressWarnings(); - $seleniumTestSuites = $configArray['SeleniumTests']['testSuite']; - wfRestoreWarnings(); - } - return true; - } - - private static function parse_ini_line( $iniLine ) { - static $specialValues = array( 'false' => false, 'true' => true, 'null' => null ); - list( $key, $value ) = explode( '=', $iniLine, 2 ); - $key = trim( $key ); - $value = trim( $value ); - - if ( isset( $specialValues[$value] ) ) { - $value = $specialValues[$value]; - } else { - $value = trim( $value, '"' ); - } - - /* Support one-level arrays */ - if ( preg_match( '/^([A-Za-z]+)\[([A-Za-z]+)\]/', $key, $m ) ) { - $key = $m[1]; - $value = array( $m[2] => $value ); - } - - return array( $key => $value ); - } -} diff --git a/tests/selenium/SeleniumLoader.php b/tests/selenium/SeleniumLoader.php deleted file mode 100644 index 8d5e7713..00000000 --- a/tests/selenium/SeleniumLoader.php +++ /dev/null @@ -1,9 +0,0 @@ -<?php - -class SeleniumLoader { - static function load() { - require_once( 'Testing/Selenium.php' ); - require_once( 'PHPUnit/Framework.php' ); - require_once( 'PHPUnit/Extensions/SeleniumTestCase.php' ); - } -} diff --git a/tests/selenium/SeleniumServerManager.php b/tests/selenium/SeleniumServerManager.php deleted file mode 100644 index 9e2d4aa8..00000000 --- a/tests/selenium/SeleniumServerManager.php +++ /dev/null @@ -1,239 +0,0 @@ -<?php -/** - * Selenium server manager - * - * @file - * @ingroup Testing - * Copyright (C) 2010 Dan Nessett <dnessett@yahoo.com> - * http://citizendium.org/ - * - * 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., - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - * http://www.gnu.org/copyleft/gpl.html - * - * @addtogroup Testing - */ - -class SeleniumServerManager { - private $SeleniumStartServer = false; - private $OS = ''; - private $SeleniumServerPid = 'NaN'; - private $SeleniumServerPort = 4444; - private $SeleniumServerStartTimeout = 10; // 10 secs. - private $SeleniumServerExecPath; - - public function __construct( $startServer, - $serverPort, - $serverExecPath ) { - $this->OS = (string) PHP_OS; - if ( isset( $startServer ) ) - $this->SeleniumStartServer = $startServer; - if ( isset( $serverPort ) ) - $this->SeleniumServerPort = $serverPort; - if ( isset( $serverExecPath ) ) - $this->SeleniumServerExecPath = $serverExecPath; - return; - } - - // Getters for certain private attributes. No setters, since they - // should not change after the manager object is created. - - public function getSeleniumStartServer() { - return $this->SeleniumStartServer; - } - - public function getSeleniumServerPort() { - return $this->SeleniumServerPort; - } - - public function getSeleniumServerPid() { - return $this->SeleniumServerPid; - } - - // Changing value of SeleniumStartServer allows starting server after - // creation of the class instance. Only allow setting SeleniumStartServer - // to true, since after server is started, it is shut down by stop(). - - public function setSeleniumStartServer( $startServer ) { - if ( $startServer == true ) $this->SeleniumStartServer = true; - } - - // return values are: 1) started - server started, 2) failed - - // server not started, 3) running - instructed to start server, but - // server already running - - public function start() { - - if ( !$this->SeleniumStartServer ) return 'failed'; - - // commented out cases are untested - - switch ( $this->OS ) { - case "Linux": -# case' CYGWIN_NT-5.1': - case 'Darwin': -# case 'FreeBSD': -# case 'HP-UX': -# case 'IRIX64': -# case 'NetBSD': -# case 'OpenBSD': -# case 'SunOS': -# case 'Unix': - // *nix based OS - return $this->startServerOnUnix(); - break; - case "Windows": - case "WIN32": - case "WINNT": - // Windows - return $this->startServerOnWindows(); - break; - default: - // An untested OS - return 'failed'; - break; - } - } - - public function stop() { - - // commented out cases are untested - - switch ( $this->OS ) { - case "Linux": -# case' CYGWIN_NT-5.1': - case 'Darwin': -# case 'FreeBSD': -# case 'HP-UX': -# case 'IRIX64': -# case 'NetBSD': -# case 'OpenBSD': -# case 'SunOS': -# case 'Unix': - // *nix based OS - return $this->stopServerOnUnix(); - break; - case "Windows": - case "WIN32": - case "WINNT": - // Windows - return $this->stopServerOnWindows(); - break; - default: - // An untested OS - return 'failed'; - break; - } - } - - private function startServerOnUnix() { - - $output = array(); - $user = $_ENV['USER']; - // @todo FIXME: This should be a little more generalized :) - if (PHP_OS == 'Darwin') { - // Mac OS X's ps barfs on the 'w' param, but doesn't need it. - $ps = "ps -U %s"; - } else { - // Good on Linux - $ps = "ps -U %s w"; - } - $psCommand = sprintf($ps, escapeshellarg($user)); - exec($psCommand . " | grep -i selenium-server", $output); - - // Start server. If there is already a server running, - // return running. - - if ( isset( $this->SeleniumServerExecPath ) ) { - $found = 0; - foreach ( $output as $string ) { - $found += preg_match( - '~^(.*)java(.+)-jar(.+)selenium-server~', - $string ); - } - if ( $found == 0 ) { - - // Didn't find the selenium server. Start it up. - // First set up comamand line suffix. - // NB: $! is pid of last job run in background - // The echo guarentees it is put into $op when - // the exec command is run. - - $commandSuffix = ' > /dev/null 2>&1'. ' & echo $!'; - $portText = ' -port ' . $this->SeleniumServerPort; - $command = "java -jar " . - escapeshellarg($this->SeleniumServerExecPath) . - $portText . $commandSuffix; - exec($command ,$op); - $pid = (int)$op[0]; - if ( $pid != "" ) - $this->SeleniumServerPid = $pid; - else { - $this->SeleniumServerPid = 'NaN'; - // Server start failed. - return 'failed'; - } - // Wait for the server to startup and listen - // on its port. Note: this solution kinda - // stinks, since it uses a wait loop - dnessett - - wfSuppressWarnings(); - for ( $cnt = 1; - $cnt <= $this->SeleniumServerStartTimeout; - $cnt++ ) { - $fp = fsockopen ( 'localhost', - $this->SeleniumServerPort, - $errno, $errstr, 0 ); - if ( !$fp ) { - sleep( 1 ); - continue; - // Server start succeeded. - } else { - fclose ( $fp ); - return 'started'; - } - } - wfRestoreWarnings(); - echo ( "Starting Selenium server timed out.\n" ); - return 'failed'; - } - // server already running. - else return 'running'; - - } - // No Server execution path defined. - return 'failed'; - } - - private function startServerOnWindows() { - // Unimplemented. - return 'failed'; - } - - private function stopServerOnUnix() { - - if ( !empty( $this->SeleniumServerPid ) && - $this->SeleniumServerPid != 'NaN' ) { - exec( "kill -9 " . $this->SeleniumServerPid ); - return 'stopped'; - } - else return 'failed'; - } - - private function stopServerOnWindows() { - // Unimplemented. - return 'failed'; - - } -} diff --git a/tests/selenium/SeleniumTestCase.php b/tests/selenium/SeleniumTestCase.php deleted file mode 100644 index 7976c16a..00000000 --- a/tests/selenium/SeleniumTestCase.php +++ /dev/null @@ -1,127 +0,0 @@ -<?php -include("SeleniumTestConstants.php"); - -class SeleniumTestCase extends PHPUnit_Framework_TestCase { // PHPUnit_Extensions_SeleniumTestCase - protected $selenium; - - public function setUp() { - set_time_limit( 60 ); - $this->selenium = Selenium::getInstance(); - } - - public function tearDown() { - - } - - public function __call( $method, $args ) { - return call_user_func_array( array( $this->selenium, $method ), $args ); - } - - public function assertSeleniumAttributeEquals( $attribute, $value ) { - $attr = $this->getAttribute( $attribute ); - $this->assertEquals( $attr, $value ); - } - - public function assertSeleniumHTMLContains( $element, $text ) { - $innerHTML = $this->getText( $element ); - // or assertContains - $this->assertRegExp( "/$text/", $innerHTML ); - } - - - /** - * Create a test fixture page if one does not exist - * @param $pageName The fixture page name. If none is supplied, it uses SeleniumTestConstants::WIKI_INTERNAL_LINK - */ - function createTestPageIfMissing( $pageName = null ) { - if ( $pageName == null ) { - $pageName = SeleniumTestConstants::WIKI_INTERNAL_LINK; - } - $this->type( SeleniumTestConstants::INPUT_SEARCH_BOX, $pageName ); - $this->click( SeleniumTestConstants::BUTTON_SEARCH ); - $this->waitForPageToLoad( SeleniumTestConstants::WIKI_TEST_WAIT_TIME ); - $this->click( SeleniumTestConstants::LINK_START . $pageName ); - $this->waitForPageToLoad( SeleniumTestConstants::WIKI_TEST_WAIT_TIME ); - $location = $this->getLocation() . "\n"; - if ( strpos( $location, '&redlink=1') !== false ) { - $this->type( SeleniumTestConstants::TEXT_EDITOR, "Test fixture page. No real content here" ); - $this->click( SeleniumTestConstants::BUTTON_SAVE ); - $this->waitForPageToLoad( SeleniumTestConstants::WIKI_TEST_WAIT_TIME ); - $this->assertTrue( $this->isTextPresent( $pageName ), - $this->getText( SeleniumTestConstants::TEXT_PAGE_HEADING ) ); - } - } - - /** - * Create a test page using date as part of the name so that it is unique - * @param $pagePrefix The prefix to use for the page name. The current date will be appended to this to make it unique - * @param $watchThis Whether to add the page to my watchlist. Defaults to false. - */ - function createNewTestPage( $pagePrefix, $watchThis = false ) { - $pageName = $pagePrefix . date("Ymd-His"); - $this->type( SeleniumTestConstants::INPUT_SEARCH_BOX, $pageName ); - $this->click( SeleniumTestConstants::BUTTON_SEARCH ); - $this->waitForPageToLoad( SeleniumTestConstants::WIKI_TEST_WAIT_TIME ); - $this->click( SeleniumTestConstants::LINK_START . $pageName ); - $this->waitForPageToLoad( SeleniumTestConstants::WIKI_TEST_WAIT_TIME ); - $location = $this->getLocation() . "\n"; - $this->assertContains( '&redlink=1', $location ). - $this->type( SeleniumTestConstants::TEXT_EDITOR, "Test fixture page. No real content here" ); - if ( $watchThis ) { - $this->click( "wpWatchthis" ); - } - $this->click( SeleniumTestConstants::BUTTON_SAVE ); - $this->waitForPageToLoad( SeleniumTestConstants::WIKI_TEST_WAIT_TIME ); - $this->assertTrue( $this->isTextPresent( $pageName ), - $this->getText( SeleniumTestConstants::TEXT_PAGE_HEADING ) ); - return $pageName; - } - - public function getExistingPage(){ - $this->open( $this->getUrl() . - '/index.php?title=Main_Page&action=edit' ); - $this->type("searchInput", "new" ); - $this->click("searchGoButton"); - $this->waitForPageToLoad("30000"); - } - - public function getNewPage($pageName){ - - $this->open( $this->getUrl() . - '/index.php?title=Main_Page&action=edit' ); - $this->type("searchInput", $pageName ); - $this->click("searchGoButton"); - $this->waitForPageToLoad("30000"); - $this->click("link=".$pageName); - $this->waitForPageToLoad("600000"); - - - } - // Loading the mediawiki editor - public function loadWikiEditor(){ - $this->open( $this->getUrl() . - '/index.php?title=Main_Page&action=edit' ); - } - - // Clear the content of the mediawiki editor - public function clearWikiEditor(){ - $this->type("wpTextbox1", ""); - } - - // Click on the 'Show preview' button of the mediawiki editor - public function clickShowPreviewBtn(){ - $this->click("wpPreview"); - } - - // Click on the 'Save Page' button of the mediawiki editor - public function clickSavePageBtn(){ - $this->click("wpSave"); - } - - // Click on the 'Edit' link - public function clickEditLink(){ - $this->click("link=Edit"); - $this->waitForPageToLoad("30000"); - } - -} diff --git a/tests/selenium/SeleniumTestConsoleLogger.php b/tests/selenium/SeleniumTestConsoleLogger.php deleted file mode 100644 index b6f5496c..00000000 --- a/tests/selenium/SeleniumTestConsoleLogger.php +++ /dev/null @@ -1,25 +0,0 @@ -<?php - -class SeleniumTestConsoleLogger { - public function __construct() { - // Prepare testsuite for immediate output - @ini_set( 'zlib.output_compression', 0 ); - @ini_set( 'implicit_flush', 1 ); - for ( $i = 0; $i < ob_get_level(); $i++ ) { - ob_end_flush(); - } - ob_implicit_flush( 1 ); - } - - public function write( $message, $mode = false ) { - $out = ''; - // if ( $mode == SeleniumTestSuite::RESULT_OK ) $out .= '<font color="green">'; - $out .= htmlentities( $message ); - // if ( $mode == SeleniumTestSuite::RESULT_OK ) $out .= '</font>'; - if ( $mode != SeleniumTestSuite::CONTINUE_LINE ) { - $out .= "\n"; - } - - echo $out; - } -} diff --git a/tests/selenium/SeleniumTestConstants.php b/tests/selenium/SeleniumTestConstants.php deleted file mode 100644 index 1defb73c..00000000 --- a/tests/selenium/SeleniumTestConstants.php +++ /dev/null @@ -1,24 +0,0 @@ -<?php - -class SeleniumTestConstants { - const WIKI_TEST_WAIT_TIME = 3000; // Waiting time - - //commonly used links - const LINK_MAIN_PAGE = 'link=Main page'; - const LINK_RANDOM_PAGE = 'link=Random article'; - const TEXT_PAGE_HEADING = 'firstHeading'; - - const LINK_START = 'link='; - const TEXT_EDITOR = 'wpTextbox1'; - const LINK_PREVIEW = 'wpPreview'; - const LINK_EDIT = 'link=Edit'; - - const WIKI_SEARCH_PAGE = 'Hair (musical)'; // Page name to search - const WIKI_TEXT_SEARCH = 'TV'; // Text to search - const WIKI_INTERNAL_LINK = 'Wikieditor-Fixture-Page'; // Exisiting page name to add as an internal tag - - const INPUT_SEARCH_BOX = 'searchInput'; - const BUTTON_SEARCH = 'mw-searchButton'; - const BUTTON_SAVE = 'wpSave'; -} - diff --git a/tests/selenium/SeleniumTestHTMLLogger.php b/tests/selenium/SeleniumTestHTMLLogger.php deleted file mode 100644 index 21332cf0..00000000 --- a/tests/selenium/SeleniumTestHTMLLogger.php +++ /dev/null @@ -1,36 +0,0 @@ -<?php - -class SeleniumTestHTMLLogger { - public function setHeaders() { - global $wgOut; - $wgOut->addHeadItem( 'selenium', '<style type="text/css"> - .selenium pre { - overflow-x: auto; /* Use horizontal scroller if needed; for Firefox 2, not needed in Firefox 3 */ - white-space: pre-wrap; /* css-3 */ - white-space: -moz-pre-wrap !important; /* Mozilla, since 1999 */ - white-space: -pre-wrap; /* Opera 4-6 */ - white-space: -o-pre-wrap; /* Opera 7 */ - /* width: 99%; */ - word-wrap: break-word; /* Internet Explorer 5.5+ */ - } - .selenium-success { color: green } - </style>' ); - } - - public function write( $message, $mode = false ) { - global $wgOut; - $out = ''; - if ( $mode == SeleniumTestSuite::RESULT_OK ) { - $out .= '<span class="selenium-success">'; - } - $out .= htmlspecialchars( $message ); - if ( $mode == SeleniumTestSuite::RESULT_OK ) { - $out .= '</span>'; - } - if ( $mode != SeleniumTestSuite::CONTINUE_LINE ) { - $out .= '<br />'; - } - - $wgOut->addHTML( $out ); - } -} diff --git a/tests/selenium/SeleniumTestListener.php b/tests/selenium/SeleniumTestListener.php deleted file mode 100644 index 9436f672..00000000 --- a/tests/selenium/SeleniumTestListener.php +++ /dev/null @@ -1,68 +0,0 @@ -<?php - -class SeleniumTestListener implements PHPUnit_Framework_TestListener { - private $logger; - private $tests_ok = 0; - private $tests_failed = 0; - - public function __construct( $loggerInstance ) { - $this->logger = $loggerInstance; - } - - public function addError( PHPUnit_Framework_Test $test, Exception $e, $time ) { - $this->logger->write( 'Error: ' . $e->getMessage() ); - $this->tests_failed++; - } - - public function addFailure( PHPUnit_Framework_Test $test, PHPUnit_Framework_AssertionFailedError $e, $time ) - { - $this->logger->write( 'Failed: ' . $e->getMessage() ); - $this->tests_failed++; - } - - public function addIncompleteTest( PHPUnit_Framework_Test $test, Exception $e, $time ) - { - $this->logger->write( 'Incomplete.' ); - $this->tests_failed++; - } - - public function addSkippedTest( PHPUnit_Framework_Test $test, Exception $e, $time ) - { - $this->logger->write( 'Skipped.' ); - $this->tests_failed++; - } - - public function startTest( PHPUnit_Framework_Test $test ) { - $this->logger->write( - 'Testing ' . $test->getName() . ' ... ', - SeleniumTestSuite::CONTINUE_LINE - ); - } - - public function endTest( PHPUnit_Framework_Test $test, $time ) { - if ( !$test->hasFailed() ) { - $this->logger->write( 'OK', SeleniumTestSuite::RESULT_OK ); - $this->tests_ok++; - } - } - - public function startTestSuite( PHPUnit_Framework_TestSuite $suite ) { - $this->logger->write( 'Testsuite ' . $suite->getName() . ' started.' ); - $this->tests_ok = 0; - $this->tests_failed = 0; - } - - public function endTestSuite( PHPUnit_Framework_TestSuite $suite ) { - $this->logger->write('Testsuite ' . $suite->getName() . ' ended.' ); - if ( $this->tests_ok > 0 || $this->tests_failed > 0 ) { - $this->logger->write( ' OK: ' . $this->tests_ok . ' Failed: ' . $this->tests_failed ); - } - $this->tests_ok = 0; - $this->tests_failed = 0; - } - - public function statusMessage( $message ) { - $this->logger->write( $message ); - } -} - diff --git a/tests/selenium/SeleniumTestSuite.php b/tests/selenium/SeleniumTestSuite.php deleted file mode 100644 index 81a630f8..00000000 --- a/tests/selenium/SeleniumTestSuite.php +++ /dev/null @@ -1,57 +0,0 @@ -<?php - -abstract class SeleniumTestSuite extends PHPUnit_Framework_TestSuite { - private $selenium; - private $isSetUp = false; - private $loginBeforeTests = true; - private $triggerClientTestResources = true; - - // Do not add line break after test output - const CONTINUE_LINE = 1; - const RESULT_OK = 2; - const RESULT_ERROR = 3; - - public abstract function addTests(); - - public function setUp() { - // Hack because because PHPUnit version 3.0.6 which is on prototype does not - // run setUp as part of TestSuite::run - if ( $this->isSetUp ) { - return; - } - $this->isSetUp = true; - $this->selenium = Selenium::getInstance(); - $this->selenium->start(); - if ( $this->triggerClientTestResources ) { - $this->selenium->open( $this->selenium->getUrl() . '/index.php?setupTestSuite=' . $this->getName() ); - //wait a little longer for the db operation - $this->selenium->waitForPageToLoad( 6000 ); - } - if ( $this->loginBeforeTests ) { - $this->login(); - } - } - - public function tearDown() { - if ( $this->triggerClientTestResources ) { - $this->selenium->open( $this->selenium->getUrl() . '/index.php?clearTestSuite=' . $this->getName() ); - } - $this->selenium->stop(); - } - - public function login() { - $this->selenium->login(); - } - - public function loadPage( $title, $action ) { - $this->selenium->loadPage( $title, $action ); - } - - protected function setLoginBeforeTests( $loginBeforeTests = true ) { - $this->loginBeforeTests = $loginBeforeTests; - } - - protected function setTriggerClientTestResources( $triggerClientTestResources = true ) { - $this->triggerClientTestResources = $triggerClientTestResources; - } -} diff --git a/tests/selenium/data/SimpleSeleniumTestDB.sql b/tests/selenium/data/SimpleSeleniumTestDB.sql deleted file mode 100644 index d688c3b9..00000000 --- a/tests/selenium/data/SimpleSeleniumTestDB.sql +++ /dev/null @@ -1,1453 +0,0 @@ --- MySQL dump 10.13 Distrib 5.1.41, for debian-linux-gnu (x86_64) --- --- Host: localhost Database: test_wiki --- ------------------------------------------------------ --- Server version 5.1.41-3ubuntu12.7 - -/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; -/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; -/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; -/*!40101 SET NAMES utf8 */; -/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */; -/*!40103 SET TIME_ZONE='+00:00' */; -/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; -/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; -/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; -/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; - --- --- Table structure for table `mw_archive` --- - -DROP TABLE IF EXISTS `mw_archive`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; -CREATE TABLE `mw_archive` ( - `ar_namespace` int(11) NOT NULL DEFAULT '0', - `ar_title` varchar(255) CHARACTER SET latin1 COLLATE latin1_bin NOT NULL DEFAULT '', - `ar_text` mediumblob NOT NULL, - `ar_comment` tinyblob NOT NULL, - `ar_user` int(10) unsigned NOT NULL DEFAULT '0', - `ar_user_text` varchar(255) CHARACTER SET latin1 COLLATE latin1_bin NOT NULL, - `ar_timestamp` binary(14) NOT NULL DEFAULT '\0\0\0\0\0\0\0\0\0\0\0\0\0\0', - `ar_minor_edit` tinyint(4) NOT NULL DEFAULT '0', - `ar_flags` tinyblob NOT NULL, - `ar_rev_id` int(10) unsigned DEFAULT NULL, - `ar_text_id` int(10) unsigned DEFAULT NULL, - `ar_deleted` tinyint(3) unsigned NOT NULL DEFAULT '0', - `ar_len` int(10) unsigned DEFAULT NULL, - `ar_page_id` int(10) unsigned DEFAULT NULL, - `ar_parent_id` int(10) unsigned DEFAULT NULL, - KEY `name_title_timestamp` (`ar_namespace`,`ar_title`,`ar_timestamp`), - KEY `usertext_timestamp` (`ar_user_text`,`ar_timestamp`), - KEY `ar_revid` (`ar_rev_id`) -) ENGINE=InnoDB DEFAULT CHARSET=latin1; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Dumping data for table `mw_archive` --- - -LOCK TABLES `mw_archive` WRITE; -/*!40000 ALTER TABLE `mw_archive` DISABLE KEYS */; -/*!40000 ALTER TABLE `mw_archive` ENABLE KEYS */; -UNLOCK TABLES; - --- --- Table structure for table `mw_category` --- - -DROP TABLE IF EXISTS `mw_category`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; -CREATE TABLE `mw_category` ( - `cat_id` int(10) unsigned NOT NULL AUTO_INCREMENT, - `cat_title` varchar(255) CHARACTER SET latin1 COLLATE latin1_bin NOT NULL, - `cat_pages` int(11) NOT NULL DEFAULT '0', - `cat_subcats` int(11) NOT NULL DEFAULT '0', - `cat_files` int(11) NOT NULL DEFAULT '0', - `cat_hidden` tinyint(3) unsigned NOT NULL DEFAULT '0', - PRIMARY KEY (`cat_id`), - UNIQUE KEY `cat_title` (`cat_title`), - KEY `cat_pages` (`cat_pages`) -) ENGINE=InnoDB DEFAULT CHARSET=latin1; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Dumping data for table `mw_category` --- - -LOCK TABLES `mw_category` WRITE; -/*!40000 ALTER TABLE `mw_category` DISABLE KEYS */; -/*!40000 ALTER TABLE `mw_category` ENABLE KEYS */; -UNLOCK TABLES; - --- --- Table structure for table `mw_categorylinks` --- - -DROP TABLE IF EXISTS `mw_categorylinks`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; -CREATE TABLE `mw_categorylinks` ( - `cl_from` int(10) unsigned NOT NULL DEFAULT '0', - `cl_to` varchar(255) CHARACTER SET latin1 COLLATE latin1_bin NOT NULL DEFAULT '', - `cl_sortkey` varbinary(230) NOT NULL DEFAULT '', - `cl_sortkey_prefix` varchar(255) CHARACTER SET latin1 COLLATE latin1_bin NOT NULL DEFAULT '', - `cl_timestamp` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - `cl_collation` varbinary(32) NOT NULL DEFAULT '', - `cl_type` enum('page','subcat','file') NOT NULL DEFAULT 'page', - UNIQUE KEY `cl_from` (`cl_from`,`cl_to`), - KEY `cl_sortkey` (`cl_to`,`cl_type`,`cl_sortkey`,`cl_from`), - KEY `cl_timestamp` (`cl_to`,`cl_timestamp`), - KEY `cl_collation` (`cl_collation`) -) ENGINE=InnoDB DEFAULT CHARSET=latin1; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Dumping data for table `mw_categorylinks` --- - -LOCK TABLES `mw_categorylinks` WRITE; -/*!40000 ALTER TABLE `mw_categorylinks` DISABLE KEYS */; -/*!40000 ALTER TABLE `mw_categorylinks` ENABLE KEYS */; -UNLOCK TABLES; - --- --- Table structure for table `mw_change_tag` --- - -DROP TABLE IF EXISTS `mw_change_tag`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; -CREATE TABLE `mw_change_tag` ( - `ct_rc_id` int(11) DEFAULT NULL, - `ct_log_id` int(11) DEFAULT NULL, - `ct_rev_id` int(11) DEFAULT NULL, - `ct_tag` varchar(255) NOT NULL, - `ct_params` blob, - UNIQUE KEY `change_tag_rc_tag` (`ct_rc_id`,`ct_tag`), - UNIQUE KEY `change_tag_log_tag` (`ct_log_id`,`ct_tag`), - UNIQUE KEY `change_tag_rev_tag` (`ct_rev_id`,`ct_tag`), - KEY `change_tag_tag_id` (`ct_tag`,`ct_rc_id`,`ct_rev_id`,`ct_log_id`) -) ENGINE=InnoDB DEFAULT CHARSET=latin1; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Dumping data for table `mw_change_tag` --- - -LOCK TABLES `mw_change_tag` WRITE; -/*!40000 ALTER TABLE `mw_change_tag` DISABLE KEYS */; -/*!40000 ALTER TABLE `mw_change_tag` ENABLE KEYS */; -UNLOCK TABLES; - --- --- Table structure for table `mw_external_user` --- - -DROP TABLE IF EXISTS `mw_external_user`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; -CREATE TABLE `mw_external_user` ( - `eu_local_id` int(10) unsigned NOT NULL, - `eu_external_id` varchar(255) CHARACTER SET latin1 COLLATE latin1_bin NOT NULL, - PRIMARY KEY (`eu_local_id`), - UNIQUE KEY `eu_external_id` (`eu_external_id`) -) ENGINE=InnoDB DEFAULT CHARSET=latin1; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Dumping data for table `mw_external_user` --- - -LOCK TABLES `mw_external_user` WRITE; -/*!40000 ALTER TABLE `mw_external_user` DISABLE KEYS */; -/*!40000 ALTER TABLE `mw_external_user` ENABLE KEYS */; -UNLOCK TABLES; - --- --- Table structure for table `mw_externallinks` --- - -DROP TABLE IF EXISTS `mw_externallinks`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; -CREATE TABLE `mw_externallinks` ( - `el_from` int(10) unsigned NOT NULL DEFAULT '0', - `el_to` blob NOT NULL, - `el_index` blob NOT NULL, - KEY `el_from` (`el_from`,`el_to`(40)), - KEY `el_to` (`el_to`(60),`el_from`), - KEY `el_index` (`el_index`(60)) -) ENGINE=InnoDB DEFAULT CHARSET=latin1; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Dumping data for table `mw_externallinks` --- - -LOCK TABLES `mw_externallinks` WRITE; -/*!40000 ALTER TABLE `mw_externallinks` DISABLE KEYS */; -INSERT INTO `mw_externallinks` VALUES (1,'http://meta.wikimedia.org/wiki/Help:Contents','http://org.wikimedia.meta./wiki/Help:Contents'),(1,'http://www.mediawiki.org/wiki/Manual:Configuration_settings','http://org.mediawiki.www./wiki/Manual:Configuration_settings'),(1,'http://www.mediawiki.org/wiki/Manual:FAQ','http://org.mediawiki.www./wiki/Manual:FAQ'),(1,'https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce','https://org.wikimedia.lists./mailman/listinfo/mediawiki-announce'); -/*!40000 ALTER TABLE `mw_externallinks` ENABLE KEYS */; -UNLOCK TABLES; - --- --- Table structure for table `mw_filearchive` --- - -DROP TABLE IF EXISTS `mw_filearchive`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; -CREATE TABLE `mw_filearchive` ( - `fa_id` int(11) NOT NULL AUTO_INCREMENT, - `fa_name` varchar(255) CHARACTER SET latin1 COLLATE latin1_bin NOT NULL DEFAULT '', - `fa_archive_name` varchar(255) CHARACTER SET latin1 COLLATE latin1_bin DEFAULT '', - `fa_storage_group` varbinary(16) DEFAULT NULL, - `fa_storage_key` varbinary(64) DEFAULT '', - `fa_deleted_user` int(11) DEFAULT NULL, - `fa_deleted_timestamp` binary(14) DEFAULT '\0\0\0\0\0\0\0\0\0\0\0\0\0\0', - `fa_deleted_reason` text, - `fa_size` int(10) unsigned DEFAULT '0', - `fa_width` int(11) DEFAULT '0', - `fa_height` int(11) DEFAULT '0', - `fa_metadata` mediumblob, - `fa_bits` int(11) DEFAULT '0', - `fa_media_type` enum('UNKNOWN','BITMAP','DRAWING','AUDIO','VIDEO','MULTIMEDIA','OFFICE','TEXT','EXECUTABLE','ARCHIVE') DEFAULT NULL, - `fa_major_mime` enum('unknown','application','audio','image','text','video','message','model','multipart') DEFAULT 'unknown', - `fa_minor_mime` varbinary(100) DEFAULT 'unknown', - `fa_description` tinyblob, - `fa_user` int(10) unsigned DEFAULT '0', - `fa_user_text` varchar(255) CHARACTER SET latin1 COLLATE latin1_bin DEFAULT NULL, - `fa_timestamp` binary(14) DEFAULT '\0\0\0\0\0\0\0\0\0\0\0\0\0\0', - `fa_deleted` tinyint(3) unsigned NOT NULL DEFAULT '0', - PRIMARY KEY (`fa_id`), - KEY `fa_name` (`fa_name`,`fa_timestamp`), - KEY `fa_storage_group` (`fa_storage_group`,`fa_storage_key`), - KEY `fa_deleted_timestamp` (`fa_deleted_timestamp`), - KEY `fa_user_timestamp` (`fa_user_text`,`fa_timestamp`) -) ENGINE=InnoDB DEFAULT CHARSET=latin1; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Dumping data for table `mw_filearchive` --- - -LOCK TABLES `mw_filearchive` WRITE; -/*!40000 ALTER TABLE `mw_filearchive` DISABLE KEYS */; -/*!40000 ALTER TABLE `mw_filearchive` ENABLE KEYS */; -UNLOCK TABLES; - --- --- Table structure for table `mw_hitcounter` --- - -DROP TABLE IF EXISTS `mw_hitcounter`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; -CREATE TABLE `mw_hitcounter` ( - `hc_id` int(10) unsigned NOT NULL -) ENGINE=MEMORY DEFAULT CHARSET=latin1 MAX_ROWS=25000; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Dumping data for table `mw_hitcounter` --- - -LOCK TABLES `mw_hitcounter` WRITE; -/*!40000 ALTER TABLE `mw_hitcounter` DISABLE KEYS */; -/*!40000 ALTER TABLE `mw_hitcounter` ENABLE KEYS */; -UNLOCK TABLES; - --- --- Table structure for table `mw_image` --- - -DROP TABLE IF EXISTS `mw_image`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; -CREATE TABLE `mw_image` ( - `img_name` varchar(255) CHARACTER SET latin1 COLLATE latin1_bin NOT NULL DEFAULT '', - `img_size` int(10) unsigned NOT NULL DEFAULT '0', - `img_width` int(11) NOT NULL DEFAULT '0', - `img_height` int(11) NOT NULL DEFAULT '0', - `img_metadata` mediumblob NOT NULL, - `img_bits` int(11) NOT NULL DEFAULT '0', - `img_media_type` enum('UNKNOWN','BITMAP','DRAWING','AUDIO','VIDEO','MULTIMEDIA','OFFICE','TEXT','EXECUTABLE','ARCHIVE') DEFAULT NULL, - `img_major_mime` enum('unknown','application','audio','image','text','video','message','model','multipart') NOT NULL DEFAULT 'unknown', - `img_minor_mime` varbinary(100) NOT NULL DEFAULT 'unknown', - `img_description` tinyblob NOT NULL, - `img_user` int(10) unsigned NOT NULL DEFAULT '0', - `img_user_text` varchar(255) CHARACTER SET latin1 COLLATE latin1_bin NOT NULL, - `img_timestamp` varbinary(14) NOT NULL DEFAULT '', - `img_sha1` varbinary(32) NOT NULL DEFAULT '', - PRIMARY KEY (`img_name`), - KEY `img_usertext_timestamp` (`img_user_text`,`img_timestamp`), - KEY `img_size` (`img_size`), - KEY `img_timestamp` (`img_timestamp`), - KEY `img_sha1` (`img_sha1`) -) ENGINE=InnoDB DEFAULT CHARSET=latin1; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Dumping data for table `mw_image` --- - -LOCK TABLES `mw_image` WRITE; -/*!40000 ALTER TABLE `mw_image` DISABLE KEYS */; -/*!40000 ALTER TABLE `mw_image` ENABLE KEYS */; -UNLOCK TABLES; - --- --- Table structure for table `mw_imagelinks` --- - -DROP TABLE IF EXISTS `mw_imagelinks`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; -CREATE TABLE `mw_imagelinks` ( - `il_from` int(10) unsigned NOT NULL DEFAULT '0', - `il_to` varchar(255) CHARACTER SET latin1 COLLATE latin1_bin NOT NULL DEFAULT '', - UNIQUE KEY `il_from` (`il_from`,`il_to`), - UNIQUE KEY `il_to` (`il_to`,`il_from`) -) ENGINE=InnoDB DEFAULT CHARSET=latin1; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Dumping data for table `mw_imagelinks` --- - -LOCK TABLES `mw_imagelinks` WRITE; -/*!40000 ALTER TABLE `mw_imagelinks` DISABLE KEYS */; -/*!40000 ALTER TABLE `mw_imagelinks` ENABLE KEYS */; -UNLOCK TABLES; - --- --- Table structure for table `mw_interwiki` --- - -DROP TABLE IF EXISTS `mw_interwiki`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; -CREATE TABLE `mw_interwiki` ( - `iw_prefix` varchar(32) NOT NULL, - `iw_url` blob NOT NULL, - `iw_api` blob NOT NULL, - `iw_wikiid` varchar(64) NOT NULL, - `iw_local` tinyint(1) NOT NULL, - `iw_trans` tinyint(4) NOT NULL DEFAULT '0', - UNIQUE KEY `iw_prefix` (`iw_prefix`) -) ENGINE=InnoDB DEFAULT CHARSET=latin1; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Dumping data for table `mw_interwiki` --- - -LOCK TABLES `mw_interwiki` WRITE; -/*!40000 ALTER TABLE `mw_interwiki` DISABLE KEYS */; -INSERT INTO `mw_interwiki` VALUES ('acronym','http://www.acronymfinder.com/af-query.asp?String=exact&Acronym=$1','','',0,0),('advogato','http://www.advogato.org/$1','','',0,0),('annotationwiki','http://www.seedwiki.com/page.cfm?wikiid=368&doc=$1','','',0,0),('arxiv','http://www.arxiv.org/abs/$1','','',0,0),('c2find','http://c2.com/cgi/wiki?FindPage&value=$1','','',0,0),('cache','http://www.google.com/search?q=cache:$1','','',0,0),('commons','http://commons.wikimedia.org/wiki/$1','','',0,0),('corpknowpedia','http://corpknowpedia.org/wiki/index.php/$1','','',0,0),('dictionary','http://www.dict.org/bin/Dict?Database=*&Form=Dict1&Strategy=*&Query=$1','','',0,0),('disinfopedia','http://www.disinfopedia.org/wiki.phtml?title=$1','','',0,0),('docbook','http://wiki.docbook.org/topic/$1','','',0,0),('doi','http://dx.doi.org/$1','','',0,0),('drumcorpswiki','http://www.drumcorpswiki.com/index.php/$1','','',0,0),('dwjwiki','http://www.suberic.net/cgi-bin/dwj/wiki.cgi?$1','','',0,0),('elibre','http://enciclopedia.us.es/index.php/$1','','',0,0),('emacswiki','http://www.emacswiki.org/cgi-bin/wiki.pl?$1','','',0,0),('foldoc','http://foldoc.org/?$1','','',0,0),('foxwiki','http://fox.wikis.com/wc.dll?Wiki~$1','','',0,0),('freebsdman','http://www.FreeBSD.org/cgi/man.cgi?apropos=1&query=$1','','',0,0),('gej','http://www.esperanto.de/cgi-bin/aktivikio/wiki.pl?$1','','',0,0),('gentoo-wiki','http://gentoo-wiki.com/$1','','',0,0),('google','http://www.google.com/search?q=$1','','',0,0),('googlegroups','http://groups.google.com/groups?q=$1','','',0,0),('hammondwiki','http://www.dairiki.org/HammondWiki/$1','','',0,0),('hewikisource','http://he.wikisource.org/wiki/$1','','',1,0),('hrwiki','http://www.hrwiki.org/index.php/$1','','',0,0),('imdb','http://us.imdb.com/Title?$1','','',0,0),('jargonfile','http://sunir.org/apps/meta.pl?wiki=JargonFile&redirect=$1','','',0,0),('jspwiki','http://www.jspwiki.org/wiki/$1','','',0,0),('keiki','http://kei.ki/en/$1','','',0,0),('kmwiki','http://kmwiki.wikispaces.com/$1','','',0,0),('linuxwiki','http://linuxwiki.de/$1','','',0,0),('lojban','http://www.lojban.org/tiki/tiki-index.php?page=$1','','',0,0),('lqwiki','http://wiki.linuxquestions.org/wiki/$1','','',0,0),('lugkr','http://lug-kr.sourceforge.net/cgi-bin/lugwiki.pl?$1','','',0,0),('mathsongswiki','http://SeedWiki.com/page.cfm?wikiid=237&doc=$1','','',0,0),('meatball','http://www.usemod.com/cgi-bin/mb.pl?$1','','',0,0),('mediawikiwiki','http://www.mediawiki.org/wiki/$1','','',0,0),('mediazilla','https://bugzilla.wikimedia.org/$1','','',1,0),('memoryalpha','http://www.memory-alpha.org/en/index.php/$1','','',0,0),('metawiki','http://sunir.org/apps/meta.pl?$1','','',0,0),('metawikimedia','http://meta.wikimedia.org/wiki/$1','','',0,0),('moinmoin','http://purl.net/wiki/moin/$1','','',0,0),('mozillawiki','http://wiki.mozilla.org/index.php/$1','','',0,0),('mw','http://www.mediawiki.org/wiki/$1','','',0,0),('oeis','http://www.research.att.com/cgi-bin/access.cgi/as/njas/sequences/eisA.cgi?Anum=$1','','',0,0),('openfacts','http://openfacts.berlios.de/index.phtml?title=$1','','',0,0),('openwiki','http://openwiki.com/?$1','','',0,0),('pmeg','http://www.bertilow.com/pmeg/$1.php','','',0,0),('ppr','http://c2.com/cgi/wiki?$1','','',0,0),('pythoninfo','http://wiki.python.org/moin/$1','','',0,0),('rfc','http://www.rfc-editor.org/rfc/rfc$1.txt','','',0,0),('s23wiki','http://is-root.de/wiki/index.php/$1','','',0,0),('seattlewiki','http://seattle.wikia.com/wiki/$1','','',0,0),('seattlewireless','http://seattlewireless.net/?$1','','',0,0),('senseislibrary','http://senseis.xmp.net/?$1','','',0,0),('sourceforge','http://sourceforge.net/$1','','',0,0),('squeak','http://wiki.squeak.org/squeak/$1','','',0,0),('susning','http://www.susning.nu/$1','','',0,0),('svgwiki','http://wiki.svg.org/$1','','',0,0),('tavi','http://tavi.sourceforge.net/$1','','',0,0),('tejo','http://www.tejo.org/vikio/$1','','',0,0),('theopedia','http://www.theopedia.com/$1','','',0,0),('tmbw','http://www.tmbw.net/wiki/$1','','',0,0),('tmnet','http://www.technomanifestos.net/?$1','','',0,0),('tmwiki','http://www.EasyTopicMaps.com/?page=$1','','',0,0),('twiki','http://twiki.org/cgi-bin/view/$1','','',0,0),('uea','http://www.tejo.org/uea/$1','','',0,0),('unreal','http://wiki.beyondunreal.com/wiki/$1','','',0,0),('usemod','http://www.usemod.com/cgi-bin/wiki.pl?$1','','',0,0),('vinismo','http://vinismo.com/en/$1','','',0,0),('webseitzwiki','http://webseitz.fluxent.com/wiki/$1','','',0,0),('why','http://clublet.com/c/c/why?$1','','',0,0),('wiki','http://c2.com/cgi/wiki?$1','','',0,0),('wikia','http://www.wikia.com/wiki/$1','','',0,0),('wikibooks','http://en.wikibooks.org/wiki/$1','','',1,0),('wikicities','http://www.wikia.com/wiki/$1','','',0,0),('wikif1','http://www.wikif1.org/$1','','',0,0),('wikihow','http://www.wikihow.com/$1','','',0,0),('wikimedia','http://wikimediafoundation.org/wiki/$1','','',0,0),('wikinews','http://en.wikinews.org/wiki/$1','','',1,0),('wikinfo','http://www.wikinfo.org/index.php/$1','','',0,0),('wikipedia','http://en.wikipedia.org/wiki/$1','','',1,0),('wikiquote','http://en.wikiquote.org/wiki/$1','','',1,0),('wikisource','http://wikisource.org/wiki/$1','','',1,0),('wikispecies','http://species.wikimedia.org/wiki/$1','','',1,0),('wikitravel','http://wikitravel.org/en/$1','','',0,0),('wikiversity','http://en.wikiversity.org/wiki/$1','','',1,0),('wikt','http://en.wiktionary.org/wiki/$1','','',1,0),('wiktionary','http://en.wiktionary.org/wiki/$1','','',1,0),('wlug','http://www.wlug.org.nz/$1','','',0,0),('zwiki','http://zwiki.org/$1','','',0,0),('zzz wiki','http://wiki.zzz.ee/index.php/$1','','',0,0); -/*!40000 ALTER TABLE `mw_interwiki` ENABLE KEYS */; -UNLOCK TABLES; - --- --- Table structure for table `mw_ipblocks` --- - -DROP TABLE IF EXISTS `mw_ipblocks`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; -CREATE TABLE `mw_ipblocks` ( - `ipb_id` int(11) NOT NULL AUTO_INCREMENT, - `ipb_address` tinyblob NOT NULL, - `ipb_user` int(10) unsigned NOT NULL DEFAULT '0', - `ipb_by` int(10) unsigned NOT NULL DEFAULT '0', - `ipb_by_text` varchar(255) CHARACTER SET latin1 COLLATE latin1_bin NOT NULL DEFAULT '', - `ipb_reason` tinyblob NOT NULL, - `ipb_timestamp` binary(14) NOT NULL DEFAULT '\0\0\0\0\0\0\0\0\0\0\0\0\0\0', - `ipb_auto` tinyint(1) NOT NULL DEFAULT '0', - `ipb_anon_only` tinyint(1) NOT NULL DEFAULT '0', - `ipb_create_account` tinyint(1) NOT NULL DEFAULT '1', - `ipb_enable_autoblock` tinyint(1) NOT NULL DEFAULT '1', - `ipb_expiry` varbinary(14) NOT NULL DEFAULT '', - `ipb_range_start` tinyblob NOT NULL, - `ipb_range_end` tinyblob NOT NULL, - `ipb_deleted` tinyint(1) NOT NULL DEFAULT '0', - `ipb_block_email` tinyint(1) NOT NULL DEFAULT '0', - `ipb_allow_usertalk` tinyint(1) NOT NULL DEFAULT '0', - PRIMARY KEY (`ipb_id`), - UNIQUE KEY `ipb_address` (`ipb_address`(255),`ipb_user`,`ipb_auto`,`ipb_anon_only`), - KEY `ipb_user` (`ipb_user`), - KEY `ipb_range` (`ipb_range_start`(8),`ipb_range_end`(8)), - KEY `ipb_timestamp` (`ipb_timestamp`), - KEY `ipb_expiry` (`ipb_expiry`) -) ENGINE=InnoDB DEFAULT CHARSET=latin1; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Dumping data for table `mw_ipblocks` --- - -LOCK TABLES `mw_ipblocks` WRITE; -/*!40000 ALTER TABLE `mw_ipblocks` DISABLE KEYS */; -/*!40000 ALTER TABLE `mw_ipblocks` ENABLE KEYS */; -UNLOCK TABLES; - --- --- Table structure for table `mw_iwlinks` --- - -DROP TABLE IF EXISTS `mw_iwlinks`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; -CREATE TABLE `mw_iwlinks` ( - `iwl_from` int(10) unsigned NOT NULL DEFAULT '0', - `iwl_prefix` varbinary(20) NOT NULL DEFAULT '', - `iwl_title` varchar(255) CHARACTER SET latin1 COLLATE latin1_bin NOT NULL DEFAULT '', - UNIQUE KEY `iwl_from` (`iwl_from`,`iwl_prefix`,`iwl_title`), - UNIQUE KEY `iwl_prefix_title_from` (`iwl_prefix`,`iwl_title`,`iwl_from`) -) ENGINE=InnoDB DEFAULT CHARSET=latin1; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Dumping data for table `mw_iwlinks` --- - -LOCK TABLES `mw_iwlinks` WRITE; -/*!40000 ALTER TABLE `mw_iwlinks` DISABLE KEYS */; -/*!40000 ALTER TABLE `mw_iwlinks` ENABLE KEYS */; -UNLOCK TABLES; - --- --- Table structure for table `mw_job` --- - -DROP TABLE IF EXISTS `mw_job`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; -CREATE TABLE `mw_job` ( - `job_id` int(10) unsigned NOT NULL AUTO_INCREMENT, - `job_cmd` varbinary(60) NOT NULL DEFAULT '', - `job_namespace` int(11) NOT NULL, - `job_title` varchar(255) CHARACTER SET latin1 COLLATE latin1_bin NOT NULL, - `job_params` blob NOT NULL, - PRIMARY KEY (`job_id`), - KEY `job_cmd` (`job_cmd`,`job_namespace`,`job_title`,`job_params`(128)) -) ENGINE=InnoDB DEFAULT CHARSET=latin1; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Dumping data for table `mw_job` --- - -LOCK TABLES `mw_job` WRITE; -/*!40000 ALTER TABLE `mw_job` DISABLE KEYS */; -/*!40000 ALTER TABLE `mw_job` ENABLE KEYS */; -UNLOCK TABLES; - --- --- Table structure for table `mw_l10n_cache` --- - -DROP TABLE IF EXISTS `mw_l10n_cache`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; -CREATE TABLE `mw_l10n_cache` ( - `lc_lang` varbinary(32) NOT NULL, - `lc_key` varchar(255) NOT NULL, - `lc_value` mediumblob NOT NULL, - KEY `lc_lang_key` (`lc_lang`,`lc_key`) -) ENGINE=InnoDB DEFAULT CHARSET=latin1; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Dumping data for table `mw_l10n_cache` --- - -LOCK TABLES `mw_l10n_cache` WRITE; -/*!40000 ALTER TABLE `mw_l10n_cache` DISABLE KEYS */; -/*!40000 ALTER TABLE `mw_l10n_cache` ENABLE KEYS */; -UNLOCK TABLES; - --- --- Table structure for table `mw_langlinks` --- - -DROP TABLE IF EXISTS `mw_langlinks`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; -CREATE TABLE `mw_langlinks` ( - `ll_from` int(10) unsigned NOT NULL DEFAULT '0', - `ll_lang` varbinary(20) NOT NULL DEFAULT '', - `ll_title` varchar(255) CHARACTER SET latin1 COLLATE latin1_bin NOT NULL DEFAULT '', - UNIQUE KEY `ll_from` (`ll_from`,`ll_lang`), - KEY `ll_lang` (`ll_lang`,`ll_title`) -) ENGINE=InnoDB DEFAULT CHARSET=latin1; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Dumping data for table `mw_langlinks` --- - -LOCK TABLES `mw_langlinks` WRITE; -/*!40000 ALTER TABLE `mw_langlinks` DISABLE KEYS */; -/*!40000 ALTER TABLE `mw_langlinks` ENABLE KEYS */; -UNLOCK TABLES; - --- --- Table structure for table `mw_log_search` --- - -DROP TABLE IF EXISTS `mw_log_search`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; -CREATE TABLE `mw_log_search` ( - `ls_field` varbinary(32) NOT NULL, - `ls_value` varchar(255) NOT NULL, - `ls_log_id` int(10) unsigned NOT NULL DEFAULT '0', - UNIQUE KEY `ls_field_val` (`ls_field`,`ls_value`,`ls_log_id`), - KEY `ls_log_id` (`ls_log_id`) -) ENGINE=InnoDB DEFAULT CHARSET=latin1; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Dumping data for table `mw_log_search` --- - -LOCK TABLES `mw_log_search` WRITE; -/*!40000 ALTER TABLE `mw_log_search` DISABLE KEYS */; -/*!40000 ALTER TABLE `mw_log_search` ENABLE KEYS */; -UNLOCK TABLES; - --- --- Table structure for table `mw_logging` --- - -DROP TABLE IF EXISTS `mw_logging`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; -CREATE TABLE `mw_logging` ( - `log_id` int(10) unsigned NOT NULL AUTO_INCREMENT, - `log_type` varbinary(32) NOT NULL DEFAULT '', - `log_action` varbinary(32) NOT NULL DEFAULT '', - `log_timestamp` binary(14) NOT NULL DEFAULT '19700101000000', - `log_user` int(10) unsigned NOT NULL DEFAULT '0', - `log_user_text` varchar(255) CHARACTER SET latin1 COLLATE latin1_bin NOT NULL DEFAULT '', - `log_namespace` int(11) NOT NULL DEFAULT '0', - `log_title` varchar(255) CHARACTER SET latin1 COLLATE latin1_bin NOT NULL DEFAULT '', - `log_page` int(10) unsigned DEFAULT NULL, - `log_comment` varchar(255) NOT NULL DEFAULT '', - `log_params` blob NOT NULL, - `log_deleted` tinyint(3) unsigned NOT NULL DEFAULT '0', - PRIMARY KEY (`log_id`), - KEY `type_time` (`log_type`,`log_timestamp`), - KEY `user_time` (`log_user`,`log_timestamp`), - KEY `page_time` (`log_namespace`,`log_title`,`log_timestamp`), - KEY `times` (`log_timestamp`), - KEY `log_user_type_time` (`log_user`,`log_type`,`log_timestamp`), - KEY `log_page_id_time` (`log_page`,`log_timestamp`) -) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=latin1; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Dumping data for table `mw_logging` --- - -LOCK TABLES `mw_logging` WRITE; -/*!40000 ALTER TABLE `mw_logging` DISABLE KEYS */; -INSERT INTO `mw_logging` VALUES (1,'patrol','patrol','20110110173131',1,'WikiSysop',0,'TestResources',2,'','2\n0\n1',0); -/*!40000 ALTER TABLE `mw_logging` ENABLE KEYS */; -UNLOCK TABLES; - --- --- Table structure for table `mw_math` --- - -DROP TABLE IF EXISTS `mw_math`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; -CREATE TABLE `mw_math` ( - `math_inputhash` varbinary(16) NOT NULL, - `math_outputhash` varbinary(16) NOT NULL, - `math_html_conservativeness` tinyint(4) NOT NULL, - `math_html` text, - `math_mathml` text, - UNIQUE KEY `math_inputhash` (`math_inputhash`) -) ENGINE=InnoDB DEFAULT CHARSET=latin1; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Dumping data for table `mw_math` --- - -LOCK TABLES `mw_math` WRITE; -/*!40000 ALTER TABLE `mw_math` DISABLE KEYS */; -/*!40000 ALTER TABLE `mw_math` ENABLE KEYS */; -UNLOCK TABLES; - --- --- Table structure for table `mw_module_deps` --- - -DROP TABLE IF EXISTS `mw_module_deps`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; -CREATE TABLE `mw_module_deps` ( - `md_module` varbinary(255) NOT NULL, - `md_skin` varbinary(32) NOT NULL, - `md_deps` mediumblob NOT NULL, - UNIQUE KEY `md_module_skin` (`md_module`,`md_skin`) -) ENGINE=InnoDB DEFAULT CHARSET=latin1; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Dumping data for table `mw_module_deps` --- - -LOCK TABLES `mw_module_deps` WRITE; -/*!40000 ALTER TABLE `mw_module_deps` DISABLE KEYS */; -INSERT INTO `mw_module_deps` VALUES ('ext.vector.collapsibleNav','vector','[\"\\/home\\/pdhanda\\/deployment\\/extensions\\/Vector\\/modules\\/.\\/images\\/portal-break.png\",\"\\/home\\/pdhanda\\/deployment\\/extensions\\/Vector\\/modules\\/.\\/images\\/open.png\",\"\\/home\\/pdhanda\\/deployment\\/extensions\\/Vector\\/modules\\/.\\/images\\/closed-ltr.png\"]'),('jquery.wikiEditor','vector','[\"\\/home\\/pdhanda\\/deployment\\/extensions\\/WikiEditor\\/modules\\/.\\/images\\/toolbar\\/loading.gif\"]'),('jquery.wikiEditor.toolbar','vector','{\"0\":\"\\/home\\/pdhanda\\/deployment\\/extensions\\/WikiEditor\\/modules\\/.\\/images\\/toolbar\\/base.png\",\"1\":\"\\/home\\/pdhanda\\/deployment\\/extensions\\/WikiEditor\\/modules\\/.\\/images\\/toolbar\\/loading.gif\",\"2\":\"\\/home\\/pdhanda\\/deployment\\/extensions\\/WikiEditor\\/modules\\/.\\/images\\/toolbar\\/button-sprite.png\",\"3\":\"\\/home\\/pdhanda\\/deployment\\/extensions\\/WikiEditor\\/modules\\/.\\/images\\/toolbar\\/arrow-right.png\",\"4\":\"\\/home\\/pdhanda\\/deployment\\/extensions\\/WikiEditor\\/modules\\/.\\/images\\/toolbar\\/arrow-left.png\",\"5\":\"\\/home\\/pdhanda\\/deployment\\/extensions\\/WikiEditor\\/modules\\/.\\/images\\/toolbar\\/arrow-down.png\",\"7\":\"\\/home\\/pdhanda\\/deployment\\/extensions\\/WikiEditor\\/modules\\/.\\/images\\/toolbar\\/loading-small.gif\"}'),('mediawiki.legacy.shared','vector','[\"\\/home\\/pdhanda\\/deployment\\/skins\\/common\\/images\\/feed-icon.png\",\"\\/home\\/pdhanda\\/deployment\\/skins\\/common\\/images\\/remove.png\",\"\\/home\\/pdhanda\\/deployment\\/skins\\/common\\/images\\/add.png\",\"\\/home\\/pdhanda\\/deployment\\/skins\\/common\\/images\\/ajax-loader.gif\",\"\\/home\\/pdhanda\\/deployment\\/skins\\/common\\/images\\/spinner.gif\",\"\\/home\\/pdhanda\\/deployment\\/skins\\/common\\/images\\/help-question.gif\",\"\\/home\\/pdhanda\\/deployment\\/skins\\/common\\/images\\/help-question-hover.gif\"]'),('skins.vector','vector','{\"0\":\"\\/home\\/pdhanda\\/deployment\\/skins\\/vector\\/images\\/page-base.png\",\"1\":\"\\/home\\/pdhanda\\/deployment\\/skins\\/vector\\/images\\/border.png\",\"2\":\"\\/home\\/pdhanda\\/deployment\\/skins\\/vector\\/images\\/page-fade.png\",\"4\":\"\\/home\\/pdhanda\\/deployment\\/skins\\/vector\\/images\\/tab-break.png\",\"5\":\"\\/home\\/pdhanda\\/deployment\\/skins\\/vector\\/images\\/tab-normal-fade.png\",\"6\":\"\\/home\\/pdhanda\\/deployment\\/skins\\/vector\\/images\\/tab-current-fade.png\",\"8\":\"\\/home\\/pdhanda\\/deployment\\/skins\\/vector\\/images\\/arrow-down-icon.png\",\"11\":\"\\/home\\/pdhanda\\/deployment\\/skins\\/vector\\/images\\/search-fade.png\",\"12\":\"\\/home\\/pdhanda\\/deployment\\/skins\\/vector\\/images\\/portal-break.png\",\"14\":\"\\/home\\/pdhanda\\/deployment\\/skins\\/vector\\/images\\/preferences-break.png\",\"16\":\"\\/home\\/pdhanda\\/deployment\\/skins\\/vector\\/images\\/preferences-fade.png\",\"17\":\"\\/home\\/pdhanda\\/deployment\\/skins\\/vector\\/images\\/preferences-base.png\",\"18\":\"\\/home\\/pdhanda\\/deployment\\/skins\\/vector\\/images\\/bullet-icon.png\",\"19\":\"\\/home\\/pdhanda\\/deployment\\/skins\\/vector\\/images\\/external-link-ltr-icon.png\",\"20\":\"\\/home\\/pdhanda\\/deployment\\/skins\\/vector\\/images\\/lock-icon.png\",\"21\":\"\\/home\\/pdhanda\\/deployment\\/skins\\/vector\\/images\\/mail-icon.png\",\"22\":\"\\/home\\/pdhanda\\/deployment\\/skins\\/vector\\/images\\/news-icon.png\",\"23\":\"\\/home\\/pdhanda\\/deployment\\/skins\\/vector\\/images\\/file-icon.png\",\"24\":\"\\/home\\/pdhanda\\/deployment\\/skins\\/vector\\/images\\/talk-icon.png\",\"25\":\"\\/home\\/pdhanda\\/deployment\\/skins\\/vector\\/images\\/audio-icon.png\",\"26\":\"\\/home\\/pdhanda\\/deployment\\/skins\\/vector\\/images\\/video-icon.png\",\"27\":\"\\/home\\/pdhanda\\/deployment\\/skins\\/vector\\/images\\/document-icon.png\",\"28\":\"\\/home\\/pdhanda\\/deployment\\/skins\\/vector\\/images\\/user-icon.png\",\"29\":\"\\/home\\/pdhanda\\/deployment\\/skins\\/vector\\/images\\/watch-icons.png\",\"30\":\"\\/home\\/pdhanda\\/deployment\\/skins\\/vector\\/images\\/watch-icon-loading.gif\"}'); -/*!40000 ALTER TABLE `mw_module_deps` ENABLE KEYS */; -UNLOCK TABLES; - --- --- Table structure for table `mw_msg_resource` --- - -DROP TABLE IF EXISTS `mw_msg_resource`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; -CREATE TABLE `mw_msg_resource` ( - `mr_resource` varbinary(255) NOT NULL, - `mr_lang` varbinary(32) NOT NULL, - `mr_blob` mediumblob NOT NULL, - `mr_timestamp` binary(14) NOT NULL, - UNIQUE KEY `mr_resource_lang` (`mr_resource`,`mr_lang`) -) ENGINE=InnoDB DEFAULT CHARSET=latin1; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Dumping data for table `mw_msg_resource` --- - -LOCK TABLES `mw_msg_resource` WRITE; -/*!40000 ALTER TABLE `mw_msg_resource` DISABLE KEYS */; -INSERT INTO `mw_msg_resource` VALUES ('ext.vector.collapsibleNav','en','{\"vector-collapsiblenav-more\":\"More languages\"}','20110108005000'),('ext.vector.collapsibleTabs','en','{}','20110108005000'),('ext.vector.simpleSearch','en','{\"vector-simplesearch-search\":\"Search\",\"vector-simplesearch-containing\":\"containing...\"}','20110108005000'),('ext.wikiEditor','en','{}','20110110172914'),('ext.wikiEditor.toolbar','en','{\"wikieditor-toolbar-loading\":\"Loading...\",\"wikieditor-toolbar-tool-bold\":\"Bold\",\"wikieditor-toolbar-tool-bold-example\":\"Bold text\",\"wikieditor-toolbar-tool-italic\":\"Italic\",\"wikieditor-toolbar-tool-italic-example\":\"Italic text\",\"wikieditor-toolbar-tool-ilink\":\"Internal link\",\"wikieditor-toolbar-tool-ilink-example\":\"Link title\",\"wikieditor-toolbar-tool-xlink\":\"External link (remember http:\\/\\/ prefix)\",\"wikieditor-toolbar-tool-xlink-example\":\"http:\\/\\/www.example.com link title\",\"wikieditor-toolbar-tool-link\":\"Link\",\"wikieditor-toolbar-tool-link-title\":\"Insert link\",\"wikieditor-toolbar-tool-link-int\":\"To a wiki page\",\"wikieditor-toolbar-tool-link-int-target\":\"Target page or URL:\",\"wikieditor-toolbar-tool-link-int-target-tooltip\":\"Page title or URL\",\"wikieditor-toolbar-tool-link-int-text\":\"Text to display:\",\"wikieditor-toolbar-tool-link-int-text-tooltip\":\"Text to be displayed\",\"wikieditor-toolbar-tool-link-ext\":\"To an external web page\",\"wikieditor-toolbar-tool-link-ext-target\":\"Link URL:\",\"wikieditor-toolbar-tool-link-ext-text\":\"Link text:\",\"wikieditor-toolbar-tool-link-insert\":\"Insert link\",\"wikieditor-toolbar-tool-link-cancel\":\"Cancel\",\"wikieditor-toolbar-tool-link-int-target-status-exists\":\"Page exists\",\"wikieditor-toolbar-tool-link-int-target-status-notexists\":\"Page does not exist\",\"wikieditor-toolbar-tool-link-int-target-status-invalid\":\"Invalid title\",\"wikieditor-toolbar-tool-link-int-target-status-external\":\"External link\",\"wikieditor-toolbar-tool-link-int-target-status-loading\":\"Checking page existence...\",\"wikieditor-toolbar-tool-link-int-invalid\":\"The title you specified is invalid.\",\"wikieditor-toolbar-tool-link-lookslikeinternal\":\"The URL you specified looks like it was intended as a link to another wiki page.\\nDo you want to make it an internal link?\",\"wikieditor-toolbar-tool-link-lookslikeinternal-int\":\"Internal link\",\"wikieditor-toolbar-tool-link-lookslikeinternal-ext\":\"External link\",\"wikieditor-toolbar-tool-link-empty\":\"You did not enter anything to link to.\",\"wikieditor-toolbar-tool-file\":\"Embedded file\",\"wikieditor-toolbar-tool-file-pre\":\"$1{{ns:file}}:\",\"wikieditor-toolbar-tool-file-example\":\"Example.jpg\",\"wikieditor-toolbar-tool-reference\":\"Reference\",\"wikieditor-toolbar-tool-reference-title\":\"Insert reference\",\"wikieditor-toolbar-tool-reference-cancel\":\"Cancel\",\"wikieditor-toolbar-tool-reference-text\":\"Reference text\",\"wikieditor-toolbar-tool-reference-insert\":\"Insert\",\"wikieditor-toolbar-tool-reference-example\":\"Insert footnote text here\",\"wikieditor-toolbar-tool-signature\":\"Signature and timestamp\",\"wikieditor-toolbar-section-advanced\":\"Advanced\",\"wikieditor-toolbar-tool-heading\":\"Heading\",\"wikieditor-toolbar-tool-heading-1\":\"Level 1\",\"wikieditor-toolbar-tool-heading-2\":\"Level 2\",\"wikieditor-toolbar-tool-heading-3\":\"Level 3\",\"wikieditor-toolbar-tool-heading-4\":\"Level 4\",\"wikieditor-toolbar-tool-heading-5\":\"Level 5\",\"wikieditor-toolbar-tool-heading-example\":\"Heading text\",\"wikieditor-toolbar-group-format\":\"Format\",\"wikieditor-toolbar-tool-ulist\":\"Bulleted list\",\"wikieditor-toolbar-tool-ulist-example\":\"Bulleted list item\",\"wikieditor-toolbar-tool-olist\":\"Numbered list\",\"wikieditor-toolbar-tool-olist-example\":\"Numbered list item\",\"wikieditor-toolbar-tool-indent\":\"Indentation\",\"wikieditor-toolbar-tool-indent-example\":\"Indented line\",\"wikieditor-toolbar-tool-nowiki\":\"No wiki formatting\",\"wikieditor-toolbar-tool-nowiki-example\":\"Insert non-formatted text here\",\"wikieditor-toolbar-tool-redirect\":\"Redirect\",\"wikieditor-toolbar-tool-redirect-example\":\"Target page name\",\"wikieditor-toolbar-tool-big\":\"Big\",\"wikieditor-toolbar-tool-big-example\":\"Big text\",\"wikieditor-toolbar-tool-small\":\"Small\",\"wikieditor-toolbar-tool-small-example\":\"Small text\",\"wikieditor-toolbar-tool-superscript\":\"Superscript\",\"wikieditor-toolbar-tool-superscript-example\":\"Superscript text\",\"wikieditor-toolbar-tool-subscript\":\"Subscript\",\"wikieditor-toolbar-tool-subscript-example\":\"Subscript text\",\"wikieditor-toolbar-group-insert\":\"Insert\",\"wikieditor-toolbar-tool-gallery\":\"Picture gallery\",\"wikieditor-toolbar-tool-gallery-example\":\"{{ns:file}}:Example.jpg|Caption1\\n{{ns:file}}:Example.jpg|Caption2\",\"wikieditor-toolbar-tool-newline\":\"New line\",\"wikieditor-toolbar-tool-table\":\"Table\",\"wikieditor-toolbar-tool-table-example-old\":\"-\\n! header 1\\n! header 2\\n! header 3\\n|-\\n| row 1, cell 1\\n| row 1, cell 2\\n| row 1, cell 3\\n|-\\n| row 2, cell 1\\n| row 2, cell 2\\n| row 2, cell 3\",\"wikieditor-toolbar-tool-table-example-cell-text\":\"Cell text\",\"wikieditor-toolbar-tool-table-example\":\"Example\",\"wikieditor-toolbar-tool-table-example-header\":\"Header text\",\"wikieditor-toolbar-tool-table-title\":\"Insert table\",\"wikieditor-toolbar-tool-table-dimensions-rows\":\"Rows\",\"wikieditor-toolbar-tool-table-dimensions-columns\":\"Columns\",\"wikieditor-toolbar-tool-table-dimensions-header\":\"Add header row\",\"wikieditor-toolbar-tool-table-wikitable\":\"Style with borders\",\"wikieditor-toolbar-tool-table-sortable\":\"Make table sortable\",\"wikieditor-toolbar-tool-table-insert\":\"Insert\",\"wikieditor-toolbar-tool-table-cancel\":\"Cancel\",\"wikieditor-toolbar-tool-table-example-text\":\"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut nec purus diam. Sed aliquam imperdiet nunc quis lacinia. Donec rutrum consectetur placerat. Sed volutpat neque non purus faucibus id ultricies enim euismod.\",\"wikieditor-toolbar-tool-table-toomany\":\"Inserting a table with more than $1 cells is not possible with this dialog.\",\"wikieditor-toolbar-tool-table-invalidnumber\":\"You have not entered a valid number of rows or columns.\",\"wikieditor-toolbar-tool-table-zero\":\"You cannot insert a table with zero rows or columns.\",\"wikieditor-toolbar-tool-replace\":\"Search and replace\",\"wikieditor-toolbar-tool-replace-title\":\"Search and replace\",\"wikieditor-toolbar-tool-replace-search\":\"Search for:\",\"wikieditor-toolbar-tool-replace-replace\":\"Replace with:\",\"wikieditor-toolbar-tool-replace-case\":\"Match case\",\"wikieditor-toolbar-tool-replace-regex\":\"Treat search string as a regular expression\",\"wikieditor-toolbar-tool-replace-button-findnext\":\"Find next\",\"wikieditor-toolbar-tool-replace-button-replacenext\":\"Replace next\",\"wikieditor-toolbar-tool-replace-button-replaceall\":\"Replace all\",\"wikieditor-toolbar-tool-replace-close\":\"Close\",\"wikieditor-toolbar-tool-replace-nomatch\":\"Your search did not match anything.\",\"wikieditor-toolbar-tool-replace-success\":\"$1 replacement(s) made.\",\"wikieditor-toolbar-tool-replace-emptysearch\":\"You did not enter anything to search for.\",\"wikieditor-toolbar-tool-replace-invalidregex\":\"The regular expression you entered is invalid: $1\",\"wikieditor-toolbar-section-characters\":\"Special characters\",\"wikieditor-toolbar-characters-page-latin\":\"Latin\",\"wikieditor-toolbar-characters-page-latinextended\":\"Latin extended\",\"wikieditor-toolbar-characters-page-ipa\":\"IPA\",\"wikieditor-toolbar-characters-page-symbols\":\"Symbols\",\"wikieditor-toolbar-characters-page-greek\":\"Greek\",\"wikieditor-toolbar-characters-page-cyrillic\":\"Cyrillic\",\"wikieditor-toolbar-characters-page-arabic\":\"Arabic\",\"wikieditor-toolbar-characters-page-persian\":\"Persian\",\"wikieditor-toolbar-characters-page-hebrew\":\"Hebrew\",\"wikieditor-toolbar-characters-page-bangla\":\"Bangla\",\"wikieditor-toolbar-characters-page-telugu\":\"Telugu\",\"wikieditor-toolbar-characters-page-sinhala\":\"Sinhala\",\"wikieditor-toolbar-characters-page-gujarati\":\"Gujarati\",\"wikieditor-toolbar-characters-page-thai\":\"Thai\",\"wikieditor-toolbar-characters-page-lao\":\"Lao\",\"wikieditor-toolbar-characters-page-khmer\":\"Khmer\",\"wikieditor-toolbar-section-help\":\"Help\",\"wikieditor-toolbar-help-heading-description\":\"Description\",\"wikieditor-toolbar-help-heading-syntax\":\"What you type\",\"wikieditor-toolbar-help-heading-result\":\"What you get\",\"wikieditor-toolbar-help-page-format\":\"Formatting\",\"wikieditor-toolbar-help-page-link\":\"Links\",\"wikieditor-toolbar-help-page-heading\":\"Headings\",\"wikieditor-toolbar-help-page-list\":\"Lists\",\"wikieditor-toolbar-help-page-file\":\"Files\",\"wikieditor-toolbar-help-page-reference\":\"References\",\"wikieditor-toolbar-help-page-discussion\":\"Discussion\",\"wikieditor-toolbar-help-content-bold-description\":\"Bold\",\"wikieditor-toolbar-help-content-bold-syntax\":\"\'\'\'Bold text\'\'\'\",\"wikieditor-toolbar-help-content-bold-result\":\"<strong>Bold text<\\/strong>\",\"wikieditor-toolbar-help-content-italic-description\":\"Italic\",\"wikieditor-toolbar-help-content-italic-syntax\":\"\'\'Italic text\'\'\",\"wikieditor-toolbar-help-content-italic-result\":\"<em>Italic text<\\/em>\",\"wikieditor-toolbar-help-content-bolditalic-description\":\"Bold & italic\",\"wikieditor-toolbar-help-content-bolditalic-syntax\":\"\'\'\'\'\'Bold & italic text\'\'\'\'\'\",\"wikieditor-toolbar-help-content-bolditalic-result\":\"<strong><em>Bold & italic text<\\/em><\\/strong>\",\"wikieditor-toolbar-help-content-ilink-description\":\"Internal link\",\"wikieditor-toolbar-help-content-ilink-syntax\":\"[[Page title|Link label]]<br \\/>[[Page title]]\",\"wikieditor-toolbar-help-content-ilink-result\":\"<a href=\'#\'>Link label<\\/a><br \\/><a href=\'#\'>Page title<\\/a>\",\"wikieditor-toolbar-help-content-xlink-description\":\"External link\",\"wikieditor-toolbar-help-content-xlink-syntax\":\"[http:\\/\\/www.example.org Link label]<br \\/>[http:\\/\\/www.example.org]<br \\/>http:\\/\\/www.example.org\",\"wikieditor-toolbar-help-content-xlink-result\":\"<a href=\'#\' class=\'external\'>Link label<\\/a><br \\/><a href=\'#\' class=\'external autonumber\'>[1]<\\/a><br \\/><a href=\'#\' class=\'external\'>http:\\/\\/www.example.org<\\/a>\",\"wikieditor-toolbar-help-content-heading1-description\":\"<wikieditor-toolbar-help-content-heading1-description>\",\"wikieditor-toolbar-help-content-heading1-syntax\":\"<wikieditor-toolbar-help-content-heading1-syntax>\",\"wikieditor-toolbar-help-content-heading1-result\":\"<wikieditor-toolbar-help-content-heading1-result>\",\"wikieditor-toolbar-help-content-heading2-description\":\"2nd level heading\",\"wikieditor-toolbar-help-content-heading2-syntax\":\"== Heading text ==\",\"wikieditor-toolbar-help-content-heading2-result\":\"<h2>Heading text<\\/h2>\",\"wikieditor-toolbar-help-content-heading3-description\":\"3rd level heading\",\"wikieditor-toolbar-help-content-heading3-syntax\":\"=== Heading text ===\",\"wikieditor-toolbar-help-content-heading3-result\":\"<h3>Heading text<\\/h3>\",\"wikieditor-toolbar-help-content-heading4-description\":\"4th level heading\",\"wikieditor-toolbar-help-content-heading4-syntax\":\"==== Heading text ====\",\"wikieditor-toolbar-help-content-heading4-result\":\"<h4>Heading text<\\/h4>\",\"wikieditor-toolbar-help-content-heading5-description\":\"5th level heading\",\"wikieditor-toolbar-help-content-heading5-syntax\":\"===== Heading text =====\",\"wikieditor-toolbar-help-content-heading5-result\":\"<h5>Heading text<\\/h5>\",\"wikieditor-toolbar-help-content-ulist-description\":\"Bulleted list\",\"wikieditor-toolbar-help-content-ulist-syntax\":\"* List item<br \\/>* List item\",\"wikieditor-toolbar-help-content-ulist-result\":\"<ul><li>List item<\\/li><li>List item<\\/li><\\/ul>\",\"wikieditor-toolbar-help-content-olist-description\":\"Numbered list\",\"wikieditor-toolbar-help-content-olist-syntax\":\"# List item<br \\/># List item\",\"wikieditor-toolbar-help-content-olist-result\":\"<ol><li>List item<\\/li><li>List item<\\/li><\\/ol>\",\"wikieditor-toolbar-help-content-file-description\":\"Embedded file\",\"wikieditor-toolbar-help-content-file-syntax\":\"[[{{ns:file}}:Example.png|thumb|Caption text]]\",\"wikieditor-toolbar-help-content-file-result\":\"<div style=\'width:104px;\' class=\'thumbinner\'><a title=\'Caption text\' class=\'image\' href=\'#\'><img height=\'50\' width=\'100\' border=\'0\' class=\'thumbimage\' src=\'extensions\\/UsabilityInitiative\\/images\\/wikiEditor\\/toolbar\\/example-image.png\' alt=\'\'\\/><\\/a><div class=\'thumbcaption\'><div class=\'magnify\'><a title=\'Enlarge\' class=\'internal\' href=\'#\'><img height=\'11\' width=\'15\' alt=\'\' src=\'$1\\/common\\/images\\/magnify-clip.png\'\\/><\\/a><\\/div>Caption text<\\/div><\\/div>\",\"wikieditor-toolbar-help-content-reference-description\":\"Reference\",\"wikieditor-toolbar-help-content-reference-syntax\":\"Page text.<ref name=\\\"test\\\">[http:\\/\\/www.example.org Link text], additional text.<\\/ref>\",\"wikieditor-toolbar-help-content-reference-result\":\"Page text.<sup><a href=\'#\'>[1]<\\/a><\\/sup>\",\"wikieditor-toolbar-help-content-rereference-description\":\"Additional use of same reference\",\"wikieditor-toolbar-help-content-rereference-syntax\":\"<ref name=\\\"test\\\" \\/>\",\"wikieditor-toolbar-help-content-rereference-result\":\"Page text.<sup><a href=\'#\'>[1]<\\/a><\\/sup>\",\"wikieditor-toolbar-help-content-showreferences-description\":\"Display references\",\"wikieditor-toolbar-help-content-showreferences-syntax\":\"<references \\/>\",\"wikieditor-toolbar-help-content-showreferences-result\":\"<ol class=\'references\'><li id=\'cite_note-test-0\'><b><a title=\'\' href=\'#\'>^<\\/a><\\/b> <a rel=\'nofollow\' title=\'http:\\/\\/www.example.org\' class=\'external text\' href=\'#\'>Link text<\\/a>, additional text.<\\/li><\\/ol>\",\"wikieditor-toolbar-help-content-signaturetimestamp-description\":\"Signature with timestamp\",\"wikieditor-toolbar-help-content-signaturetimestamp-syntax\":\"~~~~\",\"wikieditor-toolbar-help-content-signaturetimestamp-result\":\"<a href=\'#\' title=\'{{#special:mypage}}\'>Username<\\/a> (<a href=\'#\' title=\'{{#special:mytalk}}\'>talk<\\/a>) 15:54, 10 June 2009 (UTC)\",\"wikieditor-toolbar-help-content-signature-description\":\"Signature\",\"wikieditor-toolbar-help-content-signature-syntax\":\"~~~\",\"wikieditor-toolbar-help-content-signature-result\":\"<a href=\'#\' title=\'{{#special:mypage}}\'>Username<\\/a> (<a href=\'#\' title=\'{{#special:mytalk}}\'>talk<\\/a>)\",\"wikieditor-toolbar-help-content-indent-description\":\"Indent\",\"wikieditor-toolbar-help-content-indent-syntax\":\"Normal text<br \\/>:Indented text<br \\/>::Indented text\",\"wikieditor-toolbar-help-content-indent-result\":\"Normal text<dl><dd>Indented text<dl><dd>Indented text<\\/dd><\\/dl><\\/dd><\\/dl>\"}','20110110172914'),('jquery.async','en','{}','20110110172915'),('jquery.autoEllipsis','en','{}','20110110172915'),('jquery.checkboxShiftClick','en','{}','20110110172915'),('jquery.client','en','{}','20110110172915'),('jquery.collapsibleTabs','en','{}','20110110172915'),('jquery.cookie','en','{}','20110110172915'),('jquery.delayedBind','en','{}','20110110172915'),('jquery.highlightText','en','{}','20110110172915'),('jquery.makeCollapsible','en','{\"collapsible-expand\":\"Expand\",\"collapsible-collapse\":\"Collapse\"}','20110110172915'),('jquery.placeholder','en','{}','20110110172915'),('jquery.suggestions','en','{}','20110110172915'),('jquery.tabIndex','en','{}','20110110172915'),('jquery.textSelection','en','{}','20110110172915'),('jquery.wikiEditor','en','{\"wikieditor-wikitext-tab\":\"Wikitext\",\"wikieditor-loading\":\"Loading\"}','20110110172914'),('jquery.wikiEditor.toolbar','en','{}','20110110172914'),('mediawiki.action.watch.ajax','en','{}','20110110172915'),('mediawiki.language','en','{}','20110110172915'),('mediawiki.legacy.ajax','en','{\"watch\":\"Watch\",\"unwatch\":\"Unwatch\",\"watching\":\"Watching...\",\"unwatching\":\"Unwatching...\",\"tooltip-ca-watch\":\"Add this page to your watchlist\",\"tooltip-ca-unwatch\":\"Remove this page from your watchlist\"}','20110110172915'),('mediawiki.legacy.edit','en','{}','20110110172915'),('mediawiki.legacy.wikibits','en','{\"showtoc\":\"show\",\"hidetoc\":\"hide\"}','20110110172915'),('mediawiki.util','en','{}','20110110172915'); -/*!40000 ALTER TABLE `mw_msg_resource` ENABLE KEYS */; -UNLOCK TABLES; - --- --- Table structure for table `mw_msg_resource_links` --- - -DROP TABLE IF EXISTS `mw_msg_resource_links`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; -CREATE TABLE `mw_msg_resource_links` ( - `mrl_resource` varbinary(255) NOT NULL, - `mrl_message` varbinary(255) NOT NULL, - UNIQUE KEY `mrl_message_resource` (`mrl_message`,`mrl_resource`) -) ENGINE=InnoDB DEFAULT CHARSET=latin1; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Dumping data for table `mw_msg_resource_links` --- - -LOCK TABLES `mw_msg_resource_links` WRITE; -/*!40000 ALTER TABLE `mw_msg_resource_links` DISABLE KEYS */; -INSERT INTO `mw_msg_resource_links` VALUES ('jquery.makeCollapsible','collapsible-collapse'),('jquery.makeCollapsible','collapsible-expand'),('mediawiki.legacy.wikibits','hidetoc'),('mediawiki.legacy.wikibits','showtoc'),('mediawiki.legacy.ajax','tooltip-ca-unwatch'),('mediawiki.legacy.ajax','tooltip-ca-watch'),('mediawiki.legacy.ajax','unwatch'),('mediawiki.legacy.ajax','unwatching'),('ext.vector.collapsibleNav','vector-collapsiblenav-more'),('ext.vector.simpleSearch','vector-simplesearch-containing'),('ext.vector.simpleSearch','vector-simplesearch-search'),('mediawiki.legacy.ajax','watch'),('mediawiki.legacy.ajax','watching'),('jquery.wikiEditor','wikieditor-loading'),('ext.wikiEditor.toolbar','wikieditor-toolbar-characters-page-arabic'),('ext.wikiEditor.toolbar','wikieditor-toolbar-characters-page-bangla'),('ext.wikiEditor.toolbar','wikieditor-toolbar-characters-page-cyrillic'),('ext.wikiEditor.toolbar','wikieditor-toolbar-characters-page-greek'),('ext.wikiEditor.toolbar','wikieditor-toolbar-characters-page-gujarati'),('ext.wikiEditor.toolbar','wikieditor-toolbar-characters-page-hebrew'),('ext.wikiEditor.toolbar','wikieditor-toolbar-characters-page-ipa'),('ext.wikiEditor.toolbar','wikieditor-toolbar-characters-page-khmer'),('ext.wikiEditor.toolbar','wikieditor-toolbar-characters-page-lao'),('ext.wikiEditor.toolbar','wikieditor-toolbar-characters-page-latin'),('ext.wikiEditor.toolbar','wikieditor-toolbar-characters-page-latinextended'),('ext.wikiEditor.toolbar','wikieditor-toolbar-characters-page-persian'),('ext.wikiEditor.toolbar','wikieditor-toolbar-characters-page-sinhala'),('ext.wikiEditor.toolbar','wikieditor-toolbar-characters-page-symbols'),('ext.wikiEditor.toolbar','wikieditor-toolbar-characters-page-telugu'),('ext.wikiEditor.toolbar','wikieditor-toolbar-characters-page-thai'),('ext.wikiEditor.toolbar','wikieditor-toolbar-group-format'),('ext.wikiEditor.toolbar','wikieditor-toolbar-group-insert'),('ext.wikiEditor.toolbar','wikieditor-toolbar-help-content-bold-description'),('ext.wikiEditor.toolbar','wikieditor-toolbar-help-content-bold-result'),('ext.wikiEditor.toolbar','wikieditor-toolbar-help-content-bold-syntax'),('ext.wikiEditor.toolbar','wikieditor-toolbar-help-content-bolditalic-description'),('ext.wikiEditor.toolbar','wikieditor-toolbar-help-content-bolditalic-result'),('ext.wikiEditor.toolbar','wikieditor-toolbar-help-content-bolditalic-syntax'),('ext.wikiEditor.toolbar','wikieditor-toolbar-help-content-file-description'),('ext.wikiEditor.toolbar','wikieditor-toolbar-help-content-file-result'),('ext.wikiEditor.toolbar','wikieditor-toolbar-help-content-file-syntax'),('ext.wikiEditor.toolbar','wikieditor-toolbar-help-content-heading1-description'),('ext.wikiEditor.toolbar','wikieditor-toolbar-help-content-heading1-result'),('ext.wikiEditor.toolbar','wikieditor-toolbar-help-content-heading1-syntax'),('ext.wikiEditor.toolbar','wikieditor-toolbar-help-content-heading2-description'),('ext.wikiEditor.toolbar','wikieditor-toolbar-help-content-heading2-result'),('ext.wikiEditor.toolbar','wikieditor-toolbar-help-content-heading2-syntax'),('ext.wikiEditor.toolbar','wikieditor-toolbar-help-content-heading3-description'),('ext.wikiEditor.toolbar','wikieditor-toolbar-help-content-heading3-result'),('ext.wikiEditor.toolbar','wikieditor-toolbar-help-content-heading3-syntax'),('ext.wikiEditor.toolbar','wikieditor-toolbar-help-content-heading4-description'),('ext.wikiEditor.toolbar','wikieditor-toolbar-help-content-heading4-result'),('ext.wikiEditor.toolbar','wikieditor-toolbar-help-content-heading4-syntax'),('ext.wikiEditor.toolbar','wikieditor-toolbar-help-content-heading5-description'),('ext.wikiEditor.toolbar','wikieditor-toolbar-help-content-heading5-result'),('ext.wikiEditor.toolbar','wikieditor-toolbar-help-content-heading5-syntax'),('ext.wikiEditor.toolbar','wikieditor-toolbar-help-content-ilink-description'),('ext.wikiEditor.toolbar','wikieditor-toolbar-help-content-ilink-result'),('ext.wikiEditor.toolbar','wikieditor-toolbar-help-content-ilink-syntax'),('ext.wikiEditor.toolbar','wikieditor-toolbar-help-content-indent-description'),('ext.wikiEditor.toolbar','wikieditor-toolbar-help-content-indent-result'),('ext.wikiEditor.toolbar','wikieditor-toolbar-help-content-indent-syntax'),('ext.wikiEditor.toolbar','wikieditor-toolbar-help-content-italic-description'),('ext.wikiEditor.toolbar','wikieditor-toolbar-help-content-italic-result'),('ext.wikiEditor.toolbar','wikieditor-toolbar-help-content-italic-syntax'),('ext.wikiEditor.toolbar','wikieditor-toolbar-help-content-olist-description'),('ext.wikiEditor.toolbar','wikieditor-toolbar-help-content-olist-result'),('ext.wikiEditor.toolbar','wikieditor-toolbar-help-content-olist-syntax'),('ext.wikiEditor.toolbar','wikieditor-toolbar-help-content-reference-description'),('ext.wikiEditor.toolbar','wikieditor-toolbar-help-content-reference-result'),('ext.wikiEditor.toolbar','wikieditor-toolbar-help-content-reference-syntax'),('ext.wikiEditor.toolbar','wikieditor-toolbar-help-content-rereference-description'),('ext.wikiEditor.toolbar','wikieditor-toolbar-help-content-rereference-result'),('ext.wikiEditor.toolbar','wikieditor-toolbar-help-content-rereference-syntax'),('ext.wikiEditor.toolbar','wikieditor-toolbar-help-content-showreferences-description'),('ext.wikiEditor.toolbar','wikieditor-toolbar-help-content-showreferences-result'),('ext.wikiEditor.toolbar','wikieditor-toolbar-help-content-showreferences-syntax'),('ext.wikiEditor.toolbar','wikieditor-toolbar-help-content-signature-description'),('ext.wikiEditor.toolbar','wikieditor-toolbar-help-content-signature-result'),('ext.wikiEditor.toolbar','wikieditor-toolbar-help-content-signature-syntax'),('ext.wikiEditor.toolbar','wikieditor-toolbar-help-content-signaturetimestamp-description'),('ext.wikiEditor.toolbar','wikieditor-toolbar-help-content-signaturetimestamp-result'),('ext.wikiEditor.toolbar','wikieditor-toolbar-help-content-signaturetimestamp-syntax'),('ext.wikiEditor.toolbar','wikieditor-toolbar-help-content-ulist-description'),('ext.wikiEditor.toolbar','wikieditor-toolbar-help-content-ulist-result'),('ext.wikiEditor.toolbar','wikieditor-toolbar-help-content-ulist-syntax'),('ext.wikiEditor.toolbar','wikieditor-toolbar-help-content-xlink-description'),('ext.wikiEditor.toolbar','wikieditor-toolbar-help-content-xlink-result'),('ext.wikiEditor.toolbar','wikieditor-toolbar-help-content-xlink-syntax'),('ext.wikiEditor.toolbar','wikieditor-toolbar-help-heading-description'),('ext.wikiEditor.toolbar','wikieditor-toolbar-help-heading-result'),('ext.wikiEditor.toolbar','wikieditor-toolbar-help-heading-syntax'),('ext.wikiEditor.toolbar','wikieditor-toolbar-help-page-discussion'),('ext.wikiEditor.toolbar','wikieditor-toolbar-help-page-file'),('ext.wikiEditor.toolbar','wikieditor-toolbar-help-page-format'),('ext.wikiEditor.toolbar','wikieditor-toolbar-help-page-heading'),('ext.wikiEditor.toolbar','wikieditor-toolbar-help-page-link'),('ext.wikiEditor.toolbar','wikieditor-toolbar-help-page-list'),('ext.wikiEditor.toolbar','wikieditor-toolbar-help-page-reference'),('ext.wikiEditor.toolbar','wikieditor-toolbar-loading'),('ext.wikiEditor.toolbar','wikieditor-toolbar-section-advanced'),('ext.wikiEditor.toolbar','wikieditor-toolbar-section-characters'),('ext.wikiEditor.toolbar','wikieditor-toolbar-section-help'),('ext.wikiEditor.toolbar','wikieditor-toolbar-tool-big'),('ext.wikiEditor.toolbar','wikieditor-toolbar-tool-big-example'),('ext.wikiEditor.toolbar','wikieditor-toolbar-tool-bold'),('ext.wikiEditor.toolbar','wikieditor-toolbar-tool-bold-example'),('ext.wikiEditor.toolbar','wikieditor-toolbar-tool-file'),('ext.wikiEditor.toolbar','wikieditor-toolbar-tool-file-example'),('ext.wikiEditor.toolbar','wikieditor-toolbar-tool-file-pre'),('ext.wikiEditor.toolbar','wikieditor-toolbar-tool-gallery'),('ext.wikiEditor.toolbar','wikieditor-toolbar-tool-gallery-example'),('ext.wikiEditor.toolbar','wikieditor-toolbar-tool-heading'),('ext.wikiEditor.toolbar','wikieditor-toolbar-tool-heading-1'),('ext.wikiEditor.toolbar','wikieditor-toolbar-tool-heading-2'),('ext.wikiEditor.toolbar','wikieditor-toolbar-tool-heading-3'),('ext.wikiEditor.toolbar','wikieditor-toolbar-tool-heading-4'),('ext.wikiEditor.toolbar','wikieditor-toolbar-tool-heading-5'),('ext.wikiEditor.toolbar','wikieditor-toolbar-tool-heading-example'),('ext.wikiEditor.toolbar','wikieditor-toolbar-tool-ilink'),('ext.wikiEditor.toolbar','wikieditor-toolbar-tool-ilink-example'),('ext.wikiEditor.toolbar','wikieditor-toolbar-tool-indent'),('ext.wikiEditor.toolbar','wikieditor-toolbar-tool-indent-example'),('ext.wikiEditor.toolbar','wikieditor-toolbar-tool-italic'),('ext.wikiEditor.toolbar','wikieditor-toolbar-tool-italic-example'),('ext.wikiEditor.toolbar','wikieditor-toolbar-tool-link'),('ext.wikiEditor.toolbar','wikieditor-toolbar-tool-link-cancel'),('ext.wikiEditor.toolbar','wikieditor-toolbar-tool-link-empty'),('ext.wikiEditor.toolbar','wikieditor-toolbar-tool-link-ext'),('ext.wikiEditor.toolbar','wikieditor-toolbar-tool-link-ext-target'),('ext.wikiEditor.toolbar','wikieditor-toolbar-tool-link-ext-text'),('ext.wikiEditor.toolbar','wikieditor-toolbar-tool-link-insert'),('ext.wikiEditor.toolbar','wikieditor-toolbar-tool-link-int'),('ext.wikiEditor.toolbar','wikieditor-toolbar-tool-link-int-invalid'),('ext.wikiEditor.toolbar','wikieditor-toolbar-tool-link-int-target'),('ext.wikiEditor.toolbar','wikieditor-toolbar-tool-link-int-target-status-exists'),('ext.wikiEditor.toolbar','wikieditor-toolbar-tool-link-int-target-status-external'),('ext.wikiEditor.toolbar','wikieditor-toolbar-tool-link-int-target-status-invalid'),('ext.wikiEditor.toolbar','wikieditor-toolbar-tool-link-int-target-status-loading'),('ext.wikiEditor.toolbar','wikieditor-toolbar-tool-link-int-target-status-notexists'),('ext.wikiEditor.toolbar','wikieditor-toolbar-tool-link-int-target-tooltip'),('ext.wikiEditor.toolbar','wikieditor-toolbar-tool-link-int-text'),('ext.wikiEditor.toolbar','wikieditor-toolbar-tool-link-int-text-tooltip'),('ext.wikiEditor.toolbar','wikieditor-toolbar-tool-link-lookslikeinternal'),('ext.wikiEditor.toolbar','wikieditor-toolbar-tool-link-lookslikeinternal-ext'),('ext.wikiEditor.toolbar','wikieditor-toolbar-tool-link-lookslikeinternal-int'),('ext.wikiEditor.toolbar','wikieditor-toolbar-tool-link-title'),('ext.wikiEditor.toolbar','wikieditor-toolbar-tool-newline'),('ext.wikiEditor.toolbar','wikieditor-toolbar-tool-nowiki'),('ext.wikiEditor.toolbar','wikieditor-toolbar-tool-nowiki-example'),('ext.wikiEditor.toolbar','wikieditor-toolbar-tool-olist'),('ext.wikiEditor.toolbar','wikieditor-toolbar-tool-olist-example'),('ext.wikiEditor.toolbar','wikieditor-toolbar-tool-redirect'),('ext.wikiEditor.toolbar','wikieditor-toolbar-tool-redirect-example'),('ext.wikiEditor.toolbar','wikieditor-toolbar-tool-reference'),('ext.wikiEditor.toolbar','wikieditor-toolbar-tool-reference-cancel'),('ext.wikiEditor.toolbar','wikieditor-toolbar-tool-reference-example'),('ext.wikiEditor.toolbar','wikieditor-toolbar-tool-reference-insert'),('ext.wikiEditor.toolbar','wikieditor-toolbar-tool-reference-text'),('ext.wikiEditor.toolbar','wikieditor-toolbar-tool-reference-title'),('ext.wikiEditor.toolbar','wikieditor-toolbar-tool-replace'),('ext.wikiEditor.toolbar','wikieditor-toolbar-tool-replace-button-findnext'),('ext.wikiEditor.toolbar','wikieditor-toolbar-tool-replace-button-replaceall'),('ext.wikiEditor.toolbar','wikieditor-toolbar-tool-replace-button-replacenext'),('ext.wikiEditor.toolbar','wikieditor-toolbar-tool-replace-case'),('ext.wikiEditor.toolbar','wikieditor-toolbar-tool-replace-close'),('ext.wikiEditor.toolbar','wikieditor-toolbar-tool-replace-emptysearch'),('ext.wikiEditor.toolbar','wikieditor-toolbar-tool-replace-invalidregex'),('ext.wikiEditor.toolbar','wikieditor-toolbar-tool-replace-nomatch'),('ext.wikiEditor.toolbar','wikieditor-toolbar-tool-replace-regex'),('ext.wikiEditor.toolbar','wikieditor-toolbar-tool-replace-replace'),('ext.wikiEditor.toolbar','wikieditor-toolbar-tool-replace-search'),('ext.wikiEditor.toolbar','wikieditor-toolbar-tool-replace-success'),('ext.wikiEditor.toolbar','wikieditor-toolbar-tool-replace-title'),('ext.wikiEditor.toolbar','wikieditor-toolbar-tool-signature'),('ext.wikiEditor.toolbar','wikieditor-toolbar-tool-small'),('ext.wikiEditor.toolbar','wikieditor-toolbar-tool-small-example'),('ext.wikiEditor.toolbar','wikieditor-toolbar-tool-subscript'),('ext.wikiEditor.toolbar','wikieditor-toolbar-tool-subscript-example'),('ext.wikiEditor.toolbar','wikieditor-toolbar-tool-superscript'),('ext.wikiEditor.toolbar','wikieditor-toolbar-tool-superscript-example'),('ext.wikiEditor.toolbar','wikieditor-toolbar-tool-table'),('ext.wikiEditor.toolbar','wikieditor-toolbar-tool-table-cancel'),('ext.wikiEditor.toolbar','wikieditor-toolbar-tool-table-dimensions-columns'),('ext.wikiEditor.toolbar','wikieditor-toolbar-tool-table-dimensions-header'),('ext.wikiEditor.toolbar','wikieditor-toolbar-tool-table-dimensions-rows'),('ext.wikiEditor.toolbar','wikieditor-toolbar-tool-table-example'),('ext.wikiEditor.toolbar','wikieditor-toolbar-tool-table-example-cell-text'),('ext.wikiEditor.toolbar','wikieditor-toolbar-tool-table-example-header'),('ext.wikiEditor.toolbar','wikieditor-toolbar-tool-table-example-old'),('ext.wikiEditor.toolbar','wikieditor-toolbar-tool-table-example-text'),('ext.wikiEditor.toolbar','wikieditor-toolbar-tool-table-insert'),('ext.wikiEditor.toolbar','wikieditor-toolbar-tool-table-invalidnumber'),('ext.wikiEditor.toolbar','wikieditor-toolbar-tool-table-sortable'),('ext.wikiEditor.toolbar','wikieditor-toolbar-tool-table-title'),('ext.wikiEditor.toolbar','wikieditor-toolbar-tool-table-toomany'),('ext.wikiEditor.toolbar','wikieditor-toolbar-tool-table-wikitable'),('ext.wikiEditor.toolbar','wikieditor-toolbar-tool-table-zero'),('ext.wikiEditor.toolbar','wikieditor-toolbar-tool-ulist'),('ext.wikiEditor.toolbar','wikieditor-toolbar-tool-ulist-example'),('ext.wikiEditor.toolbar','wikieditor-toolbar-tool-xlink'),('ext.wikiEditor.toolbar','wikieditor-toolbar-tool-xlink-example'),('jquery.wikiEditor','wikieditor-wikitext-tab'); -/*!40000 ALTER TABLE `mw_msg_resource_links` ENABLE KEYS */; -UNLOCK TABLES; - --- --- Table structure for table `mw_objectcache` --- - -DROP TABLE IF EXISTS `mw_objectcache`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; -CREATE TABLE `mw_objectcache` ( - `keyname` varbinary(255) NOT NULL DEFAULT '', - `value` mediumblob, - `exptime` datetime DEFAULT NULL, - PRIMARY KEY (`keyname`), - KEY `exptime` (`exptime`) -) ENGINE=InnoDB DEFAULT CHARSET=latin1; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Dumping data for table `mw_objectcache` --- - -LOCK TABLES `mw_objectcache` WRITE; -/*!40000 ALTER TABLE `mw_objectcache` DISABLE KEYS */; -/*!40000 ALTER TABLE `mw_objectcache` ENABLE KEYS */; -UNLOCK TABLES; - --- --- Table structure for table `mw_oldimage` --- - -DROP TABLE IF EXISTS `mw_oldimage`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; -CREATE TABLE `mw_oldimage` ( - `oi_name` varchar(255) CHARACTER SET latin1 COLLATE latin1_bin NOT NULL DEFAULT '', - `oi_archive_name` varchar(255) CHARACTER SET latin1 COLLATE latin1_bin NOT NULL DEFAULT '', - `oi_size` int(10) unsigned NOT NULL DEFAULT '0', - `oi_width` int(11) NOT NULL DEFAULT '0', - `oi_height` int(11) NOT NULL DEFAULT '0', - `oi_bits` int(11) NOT NULL DEFAULT '0', - `oi_description` tinyblob NOT NULL, - `oi_user` int(10) unsigned NOT NULL DEFAULT '0', - `oi_user_text` varchar(255) CHARACTER SET latin1 COLLATE latin1_bin NOT NULL, - `oi_timestamp` binary(14) NOT NULL DEFAULT '\0\0\0\0\0\0\0\0\0\0\0\0\0\0', - `oi_metadata` mediumblob NOT NULL, - `oi_media_type` enum('UNKNOWN','BITMAP','DRAWING','AUDIO','VIDEO','MULTIMEDIA','OFFICE','TEXT','EXECUTABLE','ARCHIVE') DEFAULT NULL, - `oi_major_mime` enum('unknown','application','audio','image','text','video','message','model','multipart') NOT NULL DEFAULT 'unknown', - `oi_minor_mime` varbinary(100) NOT NULL DEFAULT 'unknown', - `oi_deleted` tinyint(3) unsigned NOT NULL DEFAULT '0', - `oi_sha1` varbinary(32) NOT NULL DEFAULT '', - KEY `oi_usertext_timestamp` (`oi_user_text`,`oi_timestamp`), - KEY `oi_name_timestamp` (`oi_name`,`oi_timestamp`), - KEY `oi_name_archive_name` (`oi_name`,`oi_archive_name`(14)), - KEY `oi_sha1` (`oi_sha1`) -) ENGINE=InnoDB DEFAULT CHARSET=latin1; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Dumping data for table `mw_oldimage` --- - -LOCK TABLES `mw_oldimage` WRITE; -/*!40000 ALTER TABLE `mw_oldimage` DISABLE KEYS */; -/*!40000 ALTER TABLE `mw_oldimage` ENABLE KEYS */; -UNLOCK TABLES; - --- --- Table structure for table `mw_page` --- - -DROP TABLE IF EXISTS `mw_page`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; -CREATE TABLE `mw_page` ( - `page_id` int(10) unsigned NOT NULL AUTO_INCREMENT, - `page_namespace` int(11) NOT NULL, - `page_title` varchar(255) CHARACTER SET latin1 COLLATE latin1_bin NOT NULL, - `page_restrictions` tinyblob NOT NULL, - `page_counter` bigint(20) unsigned NOT NULL DEFAULT '0', - `page_is_redirect` tinyint(3) unsigned NOT NULL DEFAULT '0', - `page_is_new` tinyint(3) unsigned NOT NULL DEFAULT '0', - `page_random` double unsigned NOT NULL, - `page_touched` binary(14) NOT NULL DEFAULT '\0\0\0\0\0\0\0\0\0\0\0\0\0\0', - `page_latest` int(10) unsigned NOT NULL, - `page_len` int(10) unsigned NOT NULL, - PRIMARY KEY (`page_id`), - UNIQUE KEY `name_title` (`page_namespace`,`page_title`), - KEY `page_random` (`page_random`), - KEY `page_len` (`page_len`) -) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=latin1; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Dumping data for table `mw_page` --- - -LOCK TABLES `mw_page` WRITE; -/*!40000 ALTER TABLE `mw_page` DISABLE KEYS */; -INSERT INTO `mw_page` VALUES (1,0,'Main_Page','',3,0,1,0.045389076294,'20110107184113',1,438),(2,0,'TestResources','',0,0,1,0.227355086893,'20110110173217',2,57); -/*!40000 ALTER TABLE `mw_page` ENABLE KEYS */; -UNLOCK TABLES; - --- --- Table structure for table `mw_page_props` --- - -DROP TABLE IF EXISTS `mw_page_props`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; -CREATE TABLE `mw_page_props` ( - `pp_page` int(11) NOT NULL, - `pp_propname` varbinary(60) NOT NULL, - `pp_value` blob NOT NULL, - UNIQUE KEY `pp_page_propname` (`pp_page`,`pp_propname`) -) ENGINE=InnoDB DEFAULT CHARSET=latin1; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Dumping data for table `mw_page_props` --- - -LOCK TABLES `mw_page_props` WRITE; -/*!40000 ALTER TABLE `mw_page_props` DISABLE KEYS */; -/*!40000 ALTER TABLE `mw_page_props` ENABLE KEYS */; -UNLOCK TABLES; - --- --- Table structure for table `mw_page_restrictions` --- - -DROP TABLE IF EXISTS `mw_page_restrictions`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; -CREATE TABLE `mw_page_restrictions` ( - `pr_page` int(11) NOT NULL, - `pr_type` varbinary(60) NOT NULL, - `pr_level` varbinary(60) NOT NULL, - `pr_cascade` tinyint(4) NOT NULL, - `pr_user` int(11) DEFAULT NULL, - `pr_expiry` varbinary(14) DEFAULT NULL, - `pr_id` int(10) unsigned NOT NULL AUTO_INCREMENT, - PRIMARY KEY (`pr_id`), - UNIQUE KEY `pr_pagetype` (`pr_page`,`pr_type`), - KEY `pr_typelevel` (`pr_type`,`pr_level`), - KEY `pr_level` (`pr_level`), - KEY `pr_cascade` (`pr_cascade`) -) ENGINE=InnoDB DEFAULT CHARSET=latin1; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Dumping data for table `mw_page_restrictions` --- - -LOCK TABLES `mw_page_restrictions` WRITE; -/*!40000 ALTER TABLE `mw_page_restrictions` DISABLE KEYS */; -/*!40000 ALTER TABLE `mw_page_restrictions` ENABLE KEYS */; -UNLOCK TABLES; - --- --- Table structure for table `mw_pagelinks` --- - -DROP TABLE IF EXISTS `mw_pagelinks`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; -CREATE TABLE `mw_pagelinks` ( - `pl_from` int(10) unsigned NOT NULL DEFAULT '0', - `pl_namespace` int(11) NOT NULL DEFAULT '0', - `pl_title` varchar(255) CHARACTER SET latin1 COLLATE latin1_bin NOT NULL DEFAULT '', - UNIQUE KEY `pl_from` (`pl_from`,`pl_namespace`,`pl_title`), - UNIQUE KEY `pl_namespace` (`pl_namespace`,`pl_title`,`pl_from`) -) ENGINE=InnoDB DEFAULT CHARSET=latin1; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Dumping data for table `mw_pagelinks` --- - -LOCK TABLES `mw_pagelinks` WRITE; -/*!40000 ALTER TABLE `mw_pagelinks` DISABLE KEYS */; -/*!40000 ALTER TABLE `mw_pagelinks` ENABLE KEYS */; -UNLOCK TABLES; - --- --- Table structure for table `mw_protected_titles` --- - -DROP TABLE IF EXISTS `mw_protected_titles`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; -CREATE TABLE `mw_protected_titles` ( - `pt_namespace` int(11) NOT NULL, - `pt_title` varchar(255) CHARACTER SET latin1 COLLATE latin1_bin NOT NULL, - `pt_user` int(10) unsigned NOT NULL, - `pt_reason` tinyblob, - `pt_timestamp` binary(14) NOT NULL, - `pt_expiry` varbinary(14) NOT NULL DEFAULT '', - `pt_create_perm` varbinary(60) NOT NULL, - UNIQUE KEY `pt_namespace_title` (`pt_namespace`,`pt_title`), - KEY `pt_timestamp` (`pt_timestamp`) -) ENGINE=InnoDB DEFAULT CHARSET=latin1; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Dumping data for table `mw_protected_titles` --- - -LOCK TABLES `mw_protected_titles` WRITE; -/*!40000 ALTER TABLE `mw_protected_titles` DISABLE KEYS */; -/*!40000 ALTER TABLE `mw_protected_titles` ENABLE KEYS */; -UNLOCK TABLES; - --- --- Table structure for table `mw_querycache` --- - -DROP TABLE IF EXISTS `mw_querycache`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; -CREATE TABLE `mw_querycache` ( - `qc_type` varbinary(32) NOT NULL, - `qc_value` int(10) unsigned NOT NULL DEFAULT '0', - `qc_namespace` int(11) NOT NULL DEFAULT '0', - `qc_title` varchar(255) CHARACTER SET latin1 COLLATE latin1_bin NOT NULL DEFAULT '', - KEY `qc_type` (`qc_type`,`qc_value`) -) ENGINE=InnoDB DEFAULT CHARSET=latin1; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Dumping data for table `mw_querycache` --- - -LOCK TABLES `mw_querycache` WRITE; -/*!40000 ALTER TABLE `mw_querycache` DISABLE KEYS */; -/*!40000 ALTER TABLE `mw_querycache` ENABLE KEYS */; -UNLOCK TABLES; - --- --- Table structure for table `mw_querycache_info` --- - -DROP TABLE IF EXISTS `mw_querycache_info`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; -CREATE TABLE `mw_querycache_info` ( - `qci_type` varbinary(32) NOT NULL DEFAULT '', - `qci_timestamp` binary(14) NOT NULL DEFAULT '19700101000000', - UNIQUE KEY `qci_type` (`qci_type`) -) ENGINE=InnoDB DEFAULT CHARSET=latin1; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Dumping data for table `mw_querycache_info` --- - -LOCK TABLES `mw_querycache_info` WRITE; -/*!40000 ALTER TABLE `mw_querycache_info` DISABLE KEYS */; -/*!40000 ALTER TABLE `mw_querycache_info` ENABLE KEYS */; -UNLOCK TABLES; - --- --- Table structure for table `mw_querycachetwo` --- - -DROP TABLE IF EXISTS `mw_querycachetwo`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; -CREATE TABLE `mw_querycachetwo` ( - `qcc_type` varbinary(32) NOT NULL, - `qcc_value` int(10) unsigned NOT NULL DEFAULT '0', - `qcc_namespace` int(11) NOT NULL DEFAULT '0', - `qcc_title` varchar(255) CHARACTER SET latin1 COLLATE latin1_bin NOT NULL DEFAULT '', - `qcc_namespacetwo` int(11) NOT NULL DEFAULT '0', - `qcc_titletwo` varchar(255) CHARACTER SET latin1 COLLATE latin1_bin NOT NULL DEFAULT '', - KEY `qcc_type` (`qcc_type`,`qcc_value`), - KEY `qcc_title` (`qcc_type`,`qcc_namespace`,`qcc_title`), - KEY `qcc_titletwo` (`qcc_type`,`qcc_namespacetwo`,`qcc_titletwo`) -) ENGINE=InnoDB DEFAULT CHARSET=latin1; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Dumping data for table `mw_querycachetwo` --- - -LOCK TABLES `mw_querycachetwo` WRITE; -/*!40000 ALTER TABLE `mw_querycachetwo` DISABLE KEYS */; -/*!40000 ALTER TABLE `mw_querycachetwo` ENABLE KEYS */; -UNLOCK TABLES; - --- --- Table structure for table `mw_recentchanges` --- - -DROP TABLE IF EXISTS `mw_recentchanges`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; -CREATE TABLE `mw_recentchanges` ( - `rc_id` int(11) NOT NULL AUTO_INCREMENT, - `rc_timestamp` varbinary(14) NOT NULL DEFAULT '', - `rc_cur_time` varbinary(14) NOT NULL DEFAULT '', - `rc_user` int(10) unsigned NOT NULL DEFAULT '0', - `rc_user_text` varchar(255) CHARACTER SET latin1 COLLATE latin1_bin NOT NULL, - `rc_namespace` int(11) NOT NULL DEFAULT '0', - `rc_title` varchar(255) CHARACTER SET latin1 COLLATE latin1_bin NOT NULL DEFAULT '', - `rc_comment` varchar(255) CHARACTER SET latin1 COLLATE latin1_bin NOT NULL DEFAULT '', - `rc_minor` tinyint(3) unsigned NOT NULL DEFAULT '0', - `rc_bot` tinyint(3) unsigned NOT NULL DEFAULT '0', - `rc_new` tinyint(3) unsigned NOT NULL DEFAULT '0', - `rc_cur_id` int(10) unsigned NOT NULL DEFAULT '0', - `rc_this_oldid` int(10) unsigned NOT NULL DEFAULT '0', - `rc_last_oldid` int(10) unsigned NOT NULL DEFAULT '0', - `rc_type` tinyint(3) unsigned NOT NULL DEFAULT '0', - `rc_moved_to_ns` tinyint(3) unsigned NOT NULL DEFAULT '0', - `rc_moved_to_title` varchar(255) CHARACTER SET latin1 COLLATE latin1_bin NOT NULL DEFAULT '', - `rc_patrolled` tinyint(3) unsigned NOT NULL DEFAULT '0', - `rc_ip` varbinary(40) NOT NULL DEFAULT '', - `rc_old_len` int(11) DEFAULT NULL, - `rc_new_len` int(11) DEFAULT NULL, - `rc_deleted` tinyint(3) unsigned NOT NULL DEFAULT '0', - `rc_logid` int(10) unsigned NOT NULL DEFAULT '0', - `rc_log_type` varbinary(255) DEFAULT NULL, - `rc_log_action` varbinary(255) DEFAULT NULL, - `rc_params` blob, - PRIMARY KEY (`rc_id`), - KEY `rc_timestamp` (`rc_timestamp`), - KEY `rc_namespace_title` (`rc_namespace`,`rc_title`), - KEY `rc_cur_id` (`rc_cur_id`), - KEY `new_name_timestamp` (`rc_new`,`rc_namespace`,`rc_timestamp`), - KEY `rc_ip` (`rc_ip`), - KEY `rc_ns_usertext` (`rc_namespace`,`rc_user_text`), - KEY `rc_user_text` (`rc_user_text`,`rc_timestamp`) -) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=latin1; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Dumping data for table `mw_recentchanges` --- - -LOCK TABLES `mw_recentchanges` WRITE; -/*!40000 ALTER TABLE `mw_recentchanges` DISABLE KEYS */; -INSERT INTO `mw_recentchanges` VALUES (1,'20110107184113','20110107184113',0,'MediaWiki Default',0,'Main_Page','',0,0,1,1,1,0,1,0,'',0,'::1',0,438,0,0,NULL,'',''),(2,'20110110173131','20110110173131',1,'WikiSysop',0,'TestResources','Created page with \"Test the the SimpleSelenium database was loaded correctly\"',0,0,1,2,2,0,1,0,'',1,'::1',0,57,0,0,NULL,'',''); -/*!40000 ALTER TABLE `mw_recentchanges` ENABLE KEYS */; -UNLOCK TABLES; - --- --- Table structure for table `mw_redirect` --- - -DROP TABLE IF EXISTS `mw_redirect`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; -CREATE TABLE `mw_redirect` ( - `rd_from` int(10) unsigned NOT NULL DEFAULT '0', - `rd_namespace` int(11) NOT NULL DEFAULT '0', - `rd_title` varchar(255) CHARACTER SET latin1 COLLATE latin1_bin NOT NULL DEFAULT '', - `rd_interwiki` varchar(32) DEFAULT NULL, - `rd_fragment` varchar(255) CHARACTER SET latin1 COLLATE latin1_bin DEFAULT NULL, - PRIMARY KEY (`rd_from`), - KEY `rd_ns_title` (`rd_namespace`,`rd_title`,`rd_from`) -) ENGINE=InnoDB DEFAULT CHARSET=latin1; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Dumping data for table `mw_redirect` --- - -LOCK TABLES `mw_redirect` WRITE; -/*!40000 ALTER TABLE `mw_redirect` DISABLE KEYS */; -/*!40000 ALTER TABLE `mw_redirect` ENABLE KEYS */; -UNLOCK TABLES; - --- --- Table structure for table `mw_revision` --- - -DROP TABLE IF EXISTS `mw_revision`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; -CREATE TABLE `mw_revision` ( - `rev_id` int(10) unsigned NOT NULL AUTO_INCREMENT, - `rev_page` int(10) unsigned NOT NULL, - `rev_text_id` int(10) unsigned NOT NULL, - `rev_comment` tinyblob NOT NULL, - `rev_user` int(10) unsigned NOT NULL DEFAULT '0', - `rev_user_text` varchar(255) CHARACTER SET latin1 COLLATE latin1_bin NOT NULL DEFAULT '', - `rev_timestamp` binary(14) NOT NULL DEFAULT '\0\0\0\0\0\0\0\0\0\0\0\0\0\0', - `rev_minor_edit` tinyint(3) unsigned NOT NULL DEFAULT '0', - `rev_deleted` tinyint(3) unsigned NOT NULL DEFAULT '0', - `rev_len` int(10) unsigned DEFAULT NULL, - `rev_parent_id` int(10) unsigned DEFAULT NULL, - PRIMARY KEY (`rev_id`), - UNIQUE KEY `rev_page_id` (`rev_page`,`rev_id`), - KEY `rev_timestamp` (`rev_timestamp`), - KEY `page_timestamp` (`rev_page`,`rev_timestamp`), - KEY `user_timestamp` (`rev_user`,`rev_timestamp`), - KEY `usertext_timestamp` (`rev_user_text`,`rev_timestamp`) -) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=latin1 MAX_ROWS=10000000 AVG_ROW_LENGTH=1024; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Dumping data for table `mw_revision` --- - -LOCK TABLES `mw_revision` WRITE; -/*!40000 ALTER TABLE `mw_revision` DISABLE KEYS */; -INSERT INTO `mw_revision` VALUES (1,1,1,'',0,'MediaWiki Default','20110107184113',0,0,438,0),(2,2,2,'Created page with \"Test the the SimpleSelenium database was loaded correctly\"',1,'WikiSysop','20110110173131',0,0,57,0); -/*!40000 ALTER TABLE `mw_revision` ENABLE KEYS */; -UNLOCK TABLES; - --- --- Table structure for table `mw_searchindex` --- - -DROP TABLE IF EXISTS `mw_searchindex`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; -CREATE TABLE `mw_searchindex` ( - `si_page` int(10) unsigned NOT NULL, - `si_title` varchar(255) NOT NULL DEFAULT '', - `si_text` mediumtext NOT NULL, - UNIQUE KEY `si_page` (`si_page`), - FULLTEXT KEY `si_title` (`si_title`), - FULLTEXT KEY `si_text` (`si_text`) -) ENGINE=MyISAM DEFAULT CHARSET=latin1; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Dumping data for table `mw_searchindex` --- - -LOCK TABLES `mw_searchindex` WRITE; -/*!40000 ALTER TABLE `mw_searchindex` DISABLE KEYS */; -INSERT INTO `mw_searchindex` VALUES (1,'main page',' mediawiki hasu800 been successfully installed. consult theu800 user user\'su800 guide foru800 information onu800 using theu800 wiki software. getting started getting started getting started configuration settings list mediawiki faqu800 mediawiki release mailing list '),(2,'testresources',' test theu800 theu800 simpleselenium database wasu800 loaded correctly '); -/*!40000 ALTER TABLE `mw_searchindex` ENABLE KEYS */; -UNLOCK TABLES; - --- --- Table structure for table `mw_site_stats` --- - -DROP TABLE IF EXISTS `mw_site_stats`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; -CREATE TABLE `mw_site_stats` ( - `ss_row_id` int(10) unsigned NOT NULL, - `ss_total_views` bigint(20) unsigned DEFAULT '0', - `ss_total_edits` bigint(20) unsigned DEFAULT '0', - `ss_good_articles` bigint(20) unsigned DEFAULT '0', - `ss_total_pages` bigint(20) DEFAULT '-1', - `ss_users` bigint(20) DEFAULT '-1', - `ss_active_users` bigint(20) DEFAULT '-1', - `ss_admins` int(11) DEFAULT '-1', - `ss_images` int(11) DEFAULT '0', - UNIQUE KEY `ss_row_id` (`ss_row_id`) -) ENGINE=InnoDB DEFAULT CHARSET=latin1; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Dumping data for table `mw_site_stats` --- - -LOCK TABLES `mw_site_stats` WRITE; -/*!40000 ALTER TABLE `mw_site_stats` DISABLE KEYS */; -INSERT INTO `mw_site_stats` VALUES (1,3,2,1,2,1,-1,-1,0); -/*!40000 ALTER TABLE `mw_site_stats` ENABLE KEYS */; -UNLOCK TABLES; - --- --- Table structure for table `mw_tag_summary` --- - -DROP TABLE IF EXISTS `mw_tag_summary`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; -CREATE TABLE `mw_tag_summary` ( - `ts_rc_id` int(11) DEFAULT NULL, - `ts_log_id` int(11) DEFAULT NULL, - `ts_rev_id` int(11) DEFAULT NULL, - `ts_tags` blob NOT NULL, - UNIQUE KEY `tag_summary_rc_id` (`ts_rc_id`), - UNIQUE KEY `tag_summary_log_id` (`ts_log_id`), - UNIQUE KEY `tag_summary_rev_id` (`ts_rev_id`) -) ENGINE=InnoDB DEFAULT CHARSET=latin1; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Dumping data for table `mw_tag_summary` --- - -LOCK TABLES `mw_tag_summary` WRITE; -/*!40000 ALTER TABLE `mw_tag_summary` DISABLE KEYS */; -/*!40000 ALTER TABLE `mw_tag_summary` ENABLE KEYS */; -UNLOCK TABLES; - --- --- Table structure for table `mw_templatelinks` --- - -DROP TABLE IF EXISTS `mw_templatelinks`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; -CREATE TABLE `mw_templatelinks` ( - `tl_from` int(10) unsigned NOT NULL DEFAULT '0', - `tl_namespace` int(11) NOT NULL DEFAULT '0', - `tl_title` varchar(255) CHARACTER SET latin1 COLLATE latin1_bin NOT NULL DEFAULT '', - UNIQUE KEY `tl_from` (`tl_from`,`tl_namespace`,`tl_title`), - UNIQUE KEY `tl_namespace` (`tl_namespace`,`tl_title`,`tl_from`) -) ENGINE=InnoDB DEFAULT CHARSET=latin1; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Dumping data for table `mw_templatelinks` --- - -LOCK TABLES `mw_templatelinks` WRITE; -/*!40000 ALTER TABLE `mw_templatelinks` DISABLE KEYS */; -/*!40000 ALTER TABLE `mw_templatelinks` ENABLE KEYS */; -UNLOCK TABLES; - --- --- Table structure for table `mw_text` --- - -DROP TABLE IF EXISTS `mw_text`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; -CREATE TABLE `mw_text` ( - `old_id` int(10) unsigned NOT NULL AUTO_INCREMENT, - `old_text` mediumblob NOT NULL, - `old_flags` tinyblob NOT NULL, - PRIMARY KEY (`old_id`) -) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=latin1 MAX_ROWS=10000000 AVG_ROW_LENGTH=10240; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Dumping data for table `mw_text` --- - -LOCK TABLES `mw_text` WRITE; -/*!40000 ALTER TABLE `mw_text` DISABLE KEYS */; -INSERT INTO `mw_text` VALUES (1,'\'\'\'MediaWiki has been successfully installed.\'\'\'\n\nConsult the [http://meta.wikimedia.org/wiki/Help:Contents User\'s Guide] for information on using the wiki software.\n\n== Getting started ==\n* [http://www.mediawiki.org/wiki/Manual:Configuration_settings Configuration settings list]\n* [http://www.mediawiki.org/wiki/Manual:FAQ MediaWiki FAQ]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki release mailing list]','utf-8'),(2,'Test the the SimpleSelenium database was loaded correctly','utf-8'); -/*!40000 ALTER TABLE `mw_text` ENABLE KEYS */; -UNLOCK TABLES; - --- --- Table structure for table `mw_trackbacks` --- - -DROP TABLE IF EXISTS `mw_trackbacks`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; -CREATE TABLE `mw_trackbacks` ( - `tb_id` int(11) NOT NULL AUTO_INCREMENT, - `tb_page` int(11) DEFAULT NULL, - `tb_title` varchar(255) NOT NULL, - `tb_url` blob NOT NULL, - `tb_ex` text, - `tb_name` varchar(255) DEFAULT NULL, - PRIMARY KEY (`tb_id`), - KEY `tb_page` (`tb_page`) -) ENGINE=InnoDB DEFAULT CHARSET=latin1; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Dumping data for table `mw_trackbacks` --- - -LOCK TABLES `mw_trackbacks` WRITE; -/*!40000 ALTER TABLE `mw_trackbacks` DISABLE KEYS */; -/*!40000 ALTER TABLE `mw_trackbacks` ENABLE KEYS */; -UNLOCK TABLES; - --- --- Table structure for table `mw_transcache` --- - -DROP TABLE IF EXISTS `mw_transcache`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; -CREATE TABLE `mw_transcache` ( - `tc_url` varbinary(255) NOT NULL, - `tc_contents` text, - `tc_time` binary(14) DEFAULT NULL, - UNIQUE KEY `tc_url_idx` (`tc_url`) -) ENGINE=InnoDB DEFAULT CHARSET=latin1; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Dumping data for table `mw_transcache` --- - -LOCK TABLES `mw_transcache` WRITE; -/*!40000 ALTER TABLE `mw_transcache` DISABLE KEYS */; -/*!40000 ALTER TABLE `mw_transcache` ENABLE KEYS */; -UNLOCK TABLES; - --- --- Table structure for table `mw_updatelog` --- - -DROP TABLE IF EXISTS `mw_updatelog`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; -CREATE TABLE `mw_updatelog` ( - `ul_key` varchar(255) NOT NULL, - `ul_value` blob, - PRIMARY KEY (`ul_key`) -) ENGINE=InnoDB DEFAULT CHARSET=latin1; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Dumping data for table `mw_updatelog` --- - -LOCK TABLES `mw_updatelog` WRITE; -/*!40000 ALTER TABLE `mw_updatelog` DISABLE KEYS */; -INSERT INTO `mw_updatelog` VALUES ('cl_fields_update',NULL),('convert transcache field',NULL),('mime_minor_length',NULL),('populate category',NULL),('populate rev_len',NULL),('populate rev_parent_id',NULL),('updatelist-1.18alpha-1294425799','a:128:{i:0;a:4:{i:0;s:8:\"addField\";i:1;s:8:\"ipblocks\";i:2;s:6:\"ipb_id\";i:3;s:18:\"patch-ipblocks.sql\";}i:1;a:4:{i:0;s:8:\"addField\";i:1;s:8:\"ipblocks\";i:2;s:10:\"ipb_expiry\";i:3;s:20:\"patch-ipb_expiry.sql\";}i:2;a:1:{i:0;s:17:\"doInterwikiUpdate\";}i:3;a:1:{i:0;s:13:\"doIndexUpdate\";}i:4;a:3:{i:0;s:8:\"addTable\";i:1;s:10:\"hitcounter\";i:2;s:20:\"patch-hitcounter.sql\";}i:5;a:4:{i:0;s:8:\"addField\";i:1;s:13:\"recentchanges\";i:2;s:7:\"rc_type\";i:3;s:17:\"patch-rc_type.sql\";}i:6;a:4:{i:0;s:8:\"addField\";i:1;s:4:\"user\";i:2;s:14:\"user_real_name\";i:3;s:23:\"patch-user-realname.sql\";}i:7;a:3:{i:0;s:8:\"addTable\";i:1;s:10:\"querycache\";i:2;s:20:\"patch-querycache.sql\";}i:8;a:3:{i:0;s:8:\"addTable\";i:1;s:11:\"objectcache\";i:2;s:21:\"patch-objectcache.sql\";}i:9;a:3:{i:0;s:8:\"addTable\";i:1;s:13:\"categorylinks\";i:2;s:23:\"patch-categorylinks.sql\";}i:10;a:1:{i:0;s:16:\"doOldLinksUpdate\";}i:11;a:1:{i:0;s:22:\"doFixAncientImagelinks\";}i:12;a:4:{i:0;s:8:\"addField\";i:1;s:13:\"recentchanges\";i:2;s:5:\"rc_ip\";i:3;s:15:\"patch-rc_ip.sql\";}i:13;a:4:{i:0;s:8:\"addIndex\";i:1;s:5:\"image\";i:2;s:7:\"PRIMARY\";i:3;s:28:\"patch-image_name_primary.sql\";}i:14;a:4:{i:0;s:8:\"addField\";i:1;s:13:\"recentchanges\";i:2;s:5:\"rc_id\";i:3;s:15:\"patch-rc_id.sql\";}i:15;a:4:{i:0;s:8:\"addField\";i:1;s:13:\"recentchanges\";i:2;s:12:\"rc_patrolled\";i:3;s:19:\"patch-rc-patrol.sql\";}i:16;a:3:{i:0;s:8:\"addTable\";i:1;s:7:\"logging\";i:2;s:17:\"patch-logging.sql\";}i:17;a:4:{i:0;s:8:\"addField\";i:1;s:4:\"user\";i:2;s:10:\"user_token\";i:3;s:20:\"patch-user_token.sql\";}i:18;a:4:{i:0;s:8:\"addField\";i:1;s:9:\"watchlist\";i:2;s:24:\"wl_notificationtimestamp\";i:3;s:28:\"patch-email-notification.sql\";}i:19;a:1:{i:0;s:17:\"doWatchlistUpdate\";}i:20;a:4:{i:0;s:9:\"dropField\";i:1;s:4:\"user\";i:2;s:33:\"user_emailauthenticationtimestamp\";i:3;s:30:\"patch-email-authentication.sql\";}i:21;a:1:{i:0;s:21:\"doSchemaRestructuring\";}i:22;a:4:{i:0;s:8:\"addField\";i:1;s:7:\"logging\";i:2;s:10:\"log_params\";i:3;s:20:\"patch-log_params.sql\";}i:23;a:4:{i:0;s:8:\"checkBin\";i:1;s:7:\"logging\";i:2;s:9:\"log_title\";i:3;s:23:\"patch-logging-title.sql\";}i:24;a:4:{i:0;s:8:\"addField\";i:1;s:7:\"archive\";i:2;s:9:\"ar_rev_id\";i:3;s:24:\"patch-archive-rev_id.sql\";}i:25;a:4:{i:0;s:8:\"addField\";i:1;s:4:\"page\";i:2;s:8:\"page_len\";i:3;s:18:\"patch-page_len.sql\";}i:26;a:4:{i:0;s:9:\"dropField\";i:1;s:8:\"revision\";i:2;s:17:\"inverse_timestamp\";i:3;s:27:\"patch-inverse_timestamp.sql\";}i:27;a:4:{i:0;s:8:\"addField\";i:1;s:8:\"revision\";i:2;s:11:\"rev_text_id\";i:3;s:21:\"patch-rev_text_id.sql\";}i:28;a:4:{i:0;s:8:\"addField\";i:1;s:8:\"revision\";i:2;s:11:\"rev_deleted\";i:3;s:21:\"patch-rev_deleted.sql\";}i:29;a:4:{i:0;s:8:\"addField\";i:1;s:5:\"image\";i:2;s:9:\"img_width\";i:3;s:19:\"patch-img_width.sql\";}i:30;a:4:{i:0;s:8:\"addField\";i:1;s:5:\"image\";i:2;s:12:\"img_metadata\";i:3;s:22:\"patch-img_metadata.sql\";}i:31;a:4:{i:0;s:8:\"addField\";i:1;s:4:\"user\";i:2;s:16:\"user_email_token\";i:3;s:26:\"patch-user_email_token.sql\";}i:32;a:4:{i:0;s:8:\"addField\";i:1;s:7:\"archive\";i:2;s:10:\"ar_text_id\";i:3;s:25:\"patch-archive-text_id.sql\";}i:33;a:1:{i:0;s:15:\"doNamespaceSize\";}i:34;a:4:{i:0;s:8:\"addField\";i:1;s:5:\"image\";i:2;s:14:\"img_media_type\";i:3;s:24:\"patch-img_media_type.sql\";}i:35;a:1:{i:0;s:17:\"doPagelinksUpdate\";}i:36;a:4:{i:0;s:9:\"dropField\";i:1;s:5:\"image\";i:2;s:8:\"img_type\";i:3;s:23:\"patch-drop_img_type.sql\";}i:37;a:1:{i:0;s:18:\"doUserUniqueUpdate\";}i:38;a:1:{i:0;s:18:\"doUserGroupsUpdate\";}i:39;a:4:{i:0;s:8:\"addField\";i:1;s:10:\"site_stats\";i:2;s:14:\"ss_total_pages\";i:3;s:27:\"patch-ss_total_articles.sql\";}i:40;a:3:{i:0;s:8:\"addTable\";i:1;s:12:\"user_newtalk\";i:2;s:22:\"patch-usernewtalk2.sql\";}i:41;a:3:{i:0;s:8:\"addTable\";i:1;s:10:\"transcache\";i:2;s:20:\"patch-transcache.sql\";}i:42;a:4:{i:0;s:8:\"addField\";i:1;s:9:\"interwiki\";i:2;s:8:\"iw_trans\";i:3;s:25:\"patch-interwiki-trans.sql\";}i:43;a:3:{i:0;s:8:\"addTable\";i:1;s:10:\"trackbacks\";i:2;s:20:\"patch-trackbacks.sql\";}i:44;a:1:{i:0;s:15:\"doWatchlistNull\";}i:45;a:4:{i:0;s:8:\"addIndex\";i:1;s:7:\"logging\";i:2;s:5:\"times\";i:3;s:29:\"patch-logging-times-index.sql\";}i:46;a:4:{i:0;s:8:\"addField\";i:1;s:8:\"ipblocks\";i:2;s:15:\"ipb_range_start\";i:3;s:25:\"patch-ipb_range_start.sql\";}i:47;a:1:{i:0;s:18:\"doPageRandomUpdate\";}i:48;a:4:{i:0;s:8:\"addField\";i:1;s:4:\"user\";i:2;s:17:\"user_registration\";i:3;s:27:\"patch-user_registration.sql\";}i:49;a:1:{i:0;s:21:\"doTemplatelinksUpdate\";}i:50;a:3:{i:0;s:8:\"addTable\";i:1;s:13:\"externallinks\";i:2;s:23:\"patch-externallinks.sql\";}i:51;a:3:{i:0;s:8:\"addTable\";i:1;s:3:\"job\";i:2;s:13:\"patch-job.sql\";}i:52;a:4:{i:0;s:8:\"addField\";i:1;s:10:\"site_stats\";i:2;s:9:\"ss_images\";i:3;s:19:\"patch-ss_images.sql\";}i:53;a:3:{i:0;s:8:\"addTable\";i:1;s:9:\"langlinks\";i:2;s:19:\"patch-langlinks.sql\";}i:54;a:3:{i:0;s:8:\"addTable\";i:1;s:15:\"querycache_info\";i:2;s:24:\"patch-querycacheinfo.sql\";}i:55;a:3:{i:0;s:8:\"addTable\";i:1;s:11:\"filearchive\";i:2;s:21:\"patch-filearchive.sql\";}i:56;a:4:{i:0;s:8:\"addField\";i:1;s:8:\"ipblocks\";i:2;s:13:\"ipb_anon_only\";i:3;s:23:\"patch-ipb_anon_only.sql\";}i:57;a:4:{i:0;s:8:\"addIndex\";i:1;s:13:\"recentchanges\";i:2;s:14:\"rc_ns_usertext\";i:3;s:31:\"patch-recentchanges-utindex.sql\";}i:58;a:4:{i:0;s:8:\"addIndex\";i:1;s:13:\"recentchanges\";i:2;s:12:\"rc_user_text\";i:3;s:28:\"patch-rc_user_text-index.sql\";}i:59;a:4:{i:0;s:8:\"addField\";i:1;s:4:\"user\";i:2;s:17:\"user_newpass_time\";i:3;s:27:\"patch-user_newpass_time.sql\";}i:60;a:3:{i:0;s:8:\"addTable\";i:1;s:8:\"redirect\";i:2;s:18:\"patch-redirect.sql\";}i:61;a:3:{i:0;s:8:\"addTable\";i:1;s:13:\"querycachetwo\";i:2;s:23:\"patch-querycachetwo.sql\";}i:62;a:4:{i:0;s:8:\"addField\";i:1;s:8:\"ipblocks\";i:2;s:20:\"ipb_enable_autoblock\";i:3;s:32:\"patch-ipb_optional_autoblock.sql\";}i:63;a:1:{i:0;s:26:\"doBacklinkingIndicesUpdate\";}i:64;a:4:{i:0;s:8:\"addField\";i:1;s:13:\"recentchanges\";i:2;s:10:\"rc_old_len\";i:3;s:16:\"patch-rc_len.sql\";}i:65;a:4:{i:0;s:8:\"addField\";i:1;s:4:\"user\";i:2;s:14:\"user_editcount\";i:3;s:24:\"patch-user_editcount.sql\";}i:66;a:1:{i:0;s:20:\"doRestrictionsUpdate\";}i:67;a:4:{i:0;s:8:\"addField\";i:1;s:7:\"logging\";i:2;s:6:\"log_id\";i:3;s:16:\"patch-log_id.sql\";}i:68;a:4:{i:0;s:8:\"addField\";i:1;s:8:\"revision\";i:2;s:13:\"rev_parent_id\";i:3;s:23:\"patch-rev_parent_id.sql\";}i:69;a:4:{i:0;s:8:\"addField\";i:1;s:17:\"page_restrictions\";i:2;s:5:\"pr_id\";i:3;s:35:\"patch-page_restrictions_sortkey.sql\";}i:70;a:4:{i:0;s:8:\"addField\";i:1;s:8:\"revision\";i:2;s:7:\"rev_len\";i:3;s:17:\"patch-rev_len.sql\";}i:71;a:4:{i:0;s:8:\"addField\";i:1;s:13:\"recentchanges\";i:2;s:10:\"rc_deleted\";i:3;s:20:\"patch-rc_deleted.sql\";}i:72;a:4:{i:0;s:8:\"addField\";i:1;s:7:\"logging\";i:2;s:11:\"log_deleted\";i:3;s:21:\"patch-log_deleted.sql\";}i:73;a:4:{i:0;s:8:\"addField\";i:1;s:7:\"archive\";i:2;s:10:\"ar_deleted\";i:3;s:20:\"patch-ar_deleted.sql\";}i:74;a:4:{i:0;s:8:\"addField\";i:1;s:8:\"ipblocks\";i:2;s:11:\"ipb_deleted\";i:3;s:21:\"patch-ipb_deleted.sql\";}i:75;a:4:{i:0;s:8:\"addField\";i:1;s:11:\"filearchive\";i:2;s:10:\"fa_deleted\";i:3;s:20:\"patch-fa_deleted.sql\";}i:76;a:4:{i:0;s:8:\"addField\";i:1;s:7:\"archive\";i:2;s:6:\"ar_len\";i:3;s:16:\"patch-ar_len.sql\";}i:77;a:4:{i:0;s:8:\"addField\";i:1;s:8:\"ipblocks\";i:2;s:15:\"ipb_block_email\";i:3;s:22:\"patch-ipb_emailban.sql\";}i:78;a:1:{i:0;s:28:\"doCategorylinksIndicesUpdate\";}i:79;a:4:{i:0;s:8:\"addField\";i:1;s:8:\"oldimage\";i:2;s:11:\"oi_metadata\";i:3;s:21:\"patch-oi_metadata.sql\";}i:80;a:4:{i:0;s:8:\"addIndex\";i:1;s:7:\"archive\";i:2;s:18:\"usertext_timestamp\";i:3;s:28:\"patch-archive-user-index.sql\";}i:81;a:4:{i:0;s:8:\"addIndex\";i:1;s:5:\"image\";i:2;s:22:\"img_usertext_timestamp\";i:3;s:26:\"patch-image-user-index.sql\";}i:82;a:4:{i:0;s:8:\"addIndex\";i:1;s:8:\"oldimage\";i:2;s:21:\"oi_usertext_timestamp\";i:3;s:29:\"patch-oldimage-user-index.sql\";}i:83;a:4:{i:0;s:8:\"addField\";i:1;s:7:\"archive\";i:2;s:10:\"ar_page_id\";i:3;s:25:\"patch-archive-page_id.sql\";}i:84;a:4:{i:0;s:8:\"addField\";i:1;s:5:\"image\";i:2;s:8:\"img_sha1\";i:3;s:18:\"patch-img_sha1.sql\";}i:85;a:3:{i:0;s:8:\"addTable\";i:1;s:16:\"protected_titles\";i:2;s:26:\"patch-protected_titles.sql\";}i:86;a:4:{i:0;s:8:\"addField\";i:1;s:8:\"ipblocks\";i:2;s:11:\"ipb_by_text\";i:3;s:21:\"patch-ipb_by_text.sql\";}i:87;a:3:{i:0;s:8:\"addTable\";i:1;s:10:\"page_props\";i:2;s:20:\"patch-page_props.sql\";}i:88;a:3:{i:0;s:8:\"addTable\";i:1;s:9:\"updatelog\";i:2;s:19:\"patch-updatelog.sql\";}i:89;a:3:{i:0;s:8:\"addTable\";i:1;s:8:\"category\";i:2;s:18:\"patch-category.sql\";}i:90;a:1:{i:0;s:20:\"doCategoryPopulation\";}i:91;a:4:{i:0;s:8:\"addField\";i:1;s:7:\"archive\";i:2;s:12:\"ar_parent_id\";i:3;s:22:\"patch-ar_parent_id.sql\";}i:92;a:4:{i:0;s:8:\"addField\";i:1;s:12:\"user_newtalk\";i:2;s:19:\"user_last_timestamp\";i:3;s:29:\"patch-user_last_timestamp.sql\";}i:93;a:1:{i:0;s:18:\"doPopulateParentId\";}i:94;a:4:{i:0;s:8:\"checkBin\";i:1;s:16:\"protected_titles\";i:2;s:8:\"pt_title\";i:3;s:27:\"patch-pt_title-encoding.sql\";}i:95;a:1:{i:0;s:28:\"doMaybeProfilingMemoryUpdate\";}i:96;a:1:{i:0;s:26:\"doFilearchiveIndicesUpdate\";}i:97;a:4:{i:0;s:8:\"addField\";i:1;s:10:\"site_stats\";i:2;s:15:\"ss_active_users\";i:3;s:25:\"patch-ss_active_users.sql\";}i:98;a:1:{i:0;s:17:\"doActiveUsersInit\";}i:99;a:4:{i:0;s:8:\"addField\";i:1;s:8:\"ipblocks\";i:2;s:18:\"ipb_allow_usertalk\";i:3;s:28:\"patch-ipb_allow_usertalk.sql\";}i:100;a:1:{i:0;s:14:\"doUniquePlTlIl\";}i:101;a:3:{i:0;s:8:\"addTable\";i:1;s:10:\"change_tag\";i:2;s:20:\"patch-change_tag.sql\";}i:102;a:3:{i:0;s:8:\"addTable\";i:1;s:11:\"tag_summary\";i:2;s:20:\"patch-change_tag.sql\";}i:103;a:3:{i:0;s:8:\"addTable\";i:1;s:9:\"valid_tag\";i:2;s:20:\"patch-change_tag.sql\";}i:104;a:3:{i:0;s:8:\"addTable\";i:1;s:15:\"user_properties\";i:2;s:25:\"patch-user_properties.sql\";}i:105;a:3:{i:0;s:8:\"addTable\";i:1;s:10:\"log_search\";i:2;s:20:\"patch-log_search.sql\";}i:106;a:1:{i:0;s:21:\"doLogSearchPopulation\";}i:107;a:4:{i:0;s:8:\"addField\";i:1;s:7:\"logging\";i:2;s:13:\"log_user_text\";i:3;s:23:\"patch-log_user_text.sql\";}i:108;a:3:{i:0;s:8:\"addTable\";i:1;s:10:\"l10n_cache\";i:2;s:20:\"patch-l10n_cache.sql\";}i:109;a:3:{i:0;s:8:\"addTable\";i:1;s:13:\"external_user\";i:2;s:23:\"patch-external_user.sql\";}i:110;a:4:{i:0;s:8:\"addIndex\";i:1;s:10:\"log_search\";i:2;s:12:\"ls_field_val\";i:3;s:33:\"patch-log_search-rename-index.sql\";}i:111;a:4:{i:0;s:8:\"addIndex\";i:1;s:10:\"change_tag\";i:2;s:17:\"change_tag_rc_tag\";i:3;s:28:\"patch-change_tag-indexes.sql\";}i:112;a:4:{i:0;s:8:\"addField\";i:1;s:8:\"redirect\";i:2;s:12:\"rd_interwiki\";i:3;s:22:\"patch-rd_interwiki.sql\";}i:113;a:1:{i:0;s:23:\"doUpdateTranscacheField\";}i:114;a:1:{i:0;s:14:\"renameEuWikiId\";}i:115;a:1:{i:0;s:22:\"doUpdateMimeMinorField\";}i:116;a:1:{i:0;s:16:\"doPopulateRevLen\";}i:117;a:3:{i:0;s:8:\"addTable\";i:1;s:7:\"iwlinks\";i:2;s:17:\"patch-iwlinks.sql\";}i:118;a:4:{i:0;s:8:\"addIndex\";i:1;s:7:\"iwlinks\";i:2;s:21:\"iwl_prefix_title_from\";i:3;s:27:\"patch-rename-iwl_prefix.sql\";}i:119;a:4:{i:0;s:8:\"addField\";i:1;s:9:\"updatelog\";i:2;s:8:\"ul_value\";i:3;s:18:\"patch-ul_value.sql\";}i:120;a:4:{i:0;s:8:\"addField\";i:1;s:9:\"interwiki\";i:2;s:6:\"iw_api\";i:3;s:27:\"patch-iw_api_and_wikiid.sql\";}i:121;a:4:{i:0;s:9:\"dropIndex\";i:1;s:7:\"iwlinks\";i:2;s:10:\"iwl_prefix\";i:3;s:25:\"patch-kill-iwl_prefix.sql\";}i:122;a:4:{i:0;s:9:\"dropIndex\";i:1;s:7:\"iwlinks\";i:2;s:21:\"iwl_prefix_from_title\";i:3;s:22:\"patch-kill-iwl_pft.sql\";}i:123;a:4:{i:0;s:8:\"addField\";i:1;s:13:\"categorylinks\";i:2;s:12:\"cl_collation\";i:3;s:40:\"patch-categorylinks-better-collation.sql\";}i:124;a:1:{i:0;s:16:\"doClFieldsUpdate\";}i:125;a:1:{i:0;s:17:\"doCollationUpdate\";}i:126;a:3:{i:0;s:8:\"addTable\";i:1;s:12:\"msg_resource\";i:2;s:22:\"patch-msg_resource.sql\";}i:127;a:3:{i:0;s:8:\"addTable\";i:1;s:11:\"module_deps\";i:2;s:21:\"patch-module_deps.sql\";}}'); -/*!40000 ALTER TABLE `mw_updatelog` ENABLE KEYS */; -UNLOCK TABLES; - --- --- Table structure for table `mw_user` --- - -DROP TABLE IF EXISTS `mw_user`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; -CREATE TABLE `mw_user` ( - `user_id` int(10) unsigned NOT NULL AUTO_INCREMENT, - `user_name` varchar(255) CHARACTER SET latin1 COLLATE latin1_bin NOT NULL DEFAULT '', - `user_real_name` varchar(255) CHARACTER SET latin1 COLLATE latin1_bin NOT NULL DEFAULT '', - `user_password` tinyblob NOT NULL, - `user_newpassword` tinyblob NOT NULL, - `user_newpass_time` binary(14) DEFAULT NULL, - `user_email` tinytext NOT NULL, - `user_touched` binary(14) NOT NULL DEFAULT '\0\0\0\0\0\0\0\0\0\0\0\0\0\0', - `user_token` binary(32) NOT NULL DEFAULT '\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0', - `user_email_authenticated` binary(14) DEFAULT NULL, - `user_email_token` binary(32) DEFAULT NULL, - `user_email_token_expires` binary(14) DEFAULT NULL, - `user_registration` binary(14) DEFAULT NULL, - `user_editcount` int(11) DEFAULT NULL, - PRIMARY KEY (`user_id`), - UNIQUE KEY `user_name` (`user_name`), - KEY `user_email_token` (`user_email_token`) -) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=latin1; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Dumping data for table `mw_user` --- - -LOCK TABLES `mw_user` WRITE; -/*!40000 ALTER TABLE `mw_user` DISABLE KEYS */; -INSERT INTO `mw_user` VALUES (1,'WikiSysop','',':B:9c595470:df2c1237ae75896744457e7dfbeb7f90','',NULL,'','','20110110173136','5e3b582786fa8150118cfa78f18de0c5',NULL,'\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0',NULL,'20110107184113',1); -/*!40000 ALTER TABLE `mw_user` ENABLE KEYS */; -UNLOCK TABLES; - --- --- Table structure for table `mw_user_groups` --- - -DROP TABLE IF EXISTS `mw_user_groups`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; -CREATE TABLE `mw_user_groups` ( - `ug_user` int(10) unsigned NOT NULL DEFAULT '0', - `ug_group` varbinary(16) NOT NULL DEFAULT '', - UNIQUE KEY `ug_user_group` (`ug_user`,`ug_group`), - KEY `ug_group` (`ug_group`) -) ENGINE=InnoDB DEFAULT CHARSET=latin1; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Dumping data for table `mw_user_groups` --- - -LOCK TABLES `mw_user_groups` WRITE; -/*!40000 ALTER TABLE `mw_user_groups` DISABLE KEYS */; -INSERT INTO `mw_user_groups` VALUES (1,'bureaucrat'),(1,'sysop'); -/*!40000 ALTER TABLE `mw_user_groups` ENABLE KEYS */; -UNLOCK TABLES; - --- --- Table structure for table `mw_user_newtalk` --- - -DROP TABLE IF EXISTS `mw_user_newtalk`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; -CREATE TABLE `mw_user_newtalk` ( - `user_id` int(11) NOT NULL DEFAULT '0', - `user_ip` varbinary(40) NOT NULL DEFAULT '', - `user_last_timestamp` binary(14) NOT NULL DEFAULT '\0\0\0\0\0\0\0\0\0\0\0\0\0\0', - KEY `user_id` (`user_id`), - KEY `user_ip` (`user_ip`) -) ENGINE=InnoDB DEFAULT CHARSET=latin1; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Dumping data for table `mw_user_newtalk` --- - -LOCK TABLES `mw_user_newtalk` WRITE; -/*!40000 ALTER TABLE `mw_user_newtalk` DISABLE KEYS */; -/*!40000 ALTER TABLE `mw_user_newtalk` ENABLE KEYS */; -UNLOCK TABLES; - --- --- Table structure for table `mw_user_properties` --- - -DROP TABLE IF EXISTS `mw_user_properties`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; -CREATE TABLE `mw_user_properties` ( - `up_user` int(11) NOT NULL, - `up_property` varbinary(32) NOT NULL, - `up_value` blob, - UNIQUE KEY `user_properties_user_property` (`up_user`,`up_property`), - KEY `user_properties_property` (`up_property`) -) ENGINE=InnoDB DEFAULT CHARSET=latin1; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Dumping data for table `mw_user_properties` --- - -LOCK TABLES `mw_user_properties` WRITE; -/*!40000 ALTER TABLE `mw_user_properties` DISABLE KEYS */; -/*!40000 ALTER TABLE `mw_user_properties` ENABLE KEYS */; -UNLOCK TABLES; - --- --- Table structure for table `mw_valid_tag` --- - -DROP TABLE IF EXISTS `mw_valid_tag`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; -CREATE TABLE `mw_valid_tag` ( - `vt_tag` varchar(255) NOT NULL, - PRIMARY KEY (`vt_tag`) -) ENGINE=InnoDB DEFAULT CHARSET=latin1; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Dumping data for table `mw_valid_tag` --- - -LOCK TABLES `mw_valid_tag` WRITE; -/*!40000 ALTER TABLE `mw_valid_tag` DISABLE KEYS */; -/*!40000 ALTER TABLE `mw_valid_tag` ENABLE KEYS */; -UNLOCK TABLES; - --- --- Table structure for table `mw_watchlist` --- - -DROP TABLE IF EXISTS `mw_watchlist`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; -CREATE TABLE `mw_watchlist` ( - `wl_user` int(10) unsigned NOT NULL, - `wl_namespace` int(11) NOT NULL DEFAULT '0', - `wl_title` varchar(255) CHARACTER SET latin1 COLLATE latin1_bin NOT NULL DEFAULT '', - `wl_notificationtimestamp` varbinary(14) DEFAULT NULL, - UNIQUE KEY `wl_user` (`wl_user`,`wl_namespace`,`wl_title`), - KEY `namespace_title` (`wl_namespace`,`wl_title`) -) ENGINE=InnoDB DEFAULT CHARSET=latin1; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Dumping data for table `mw_watchlist` --- - -LOCK TABLES `mw_watchlist` WRITE; -/*!40000 ALTER TABLE `mw_watchlist` DISABLE KEYS */; -/*!40000 ALTER TABLE `mw_watchlist` ENABLE KEYS */; -UNLOCK TABLES; -/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */; - -/*!40101 SET SQL_MODE=@OLD_SQL_MODE */; -/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; -/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */; -/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; -/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; -/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; -/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; - --- Dump completed on 2011-01-10 9:34:34 diff --git a/tests/selenium/data/SimpleSeleniumTestImages.zip b/tests/selenium/data/SimpleSeleniumTestImages.zip Binary files differdeleted file mode 100644 index 0374a1fb..00000000 --- a/tests/selenium/data/SimpleSeleniumTestImages.zip +++ /dev/null diff --git a/tests/selenium/data/Wikipedia-logo-v2-de.png b/tests/selenium/data/Wikipedia-logo-v2-de.png Binary files differdeleted file mode 100644 index 70385243..00000000 --- a/tests/selenium/data/Wikipedia-logo-v2-de.png +++ /dev/null diff --git a/tests/selenium/data/mediawiki118_fresh_installation.sql b/tests/selenium/data/mediawiki118_fresh_installation.sql deleted file mode 100644 index 7beb9e6a..00000000 --- a/tests/selenium/data/mediawiki118_fresh_installation.sql +++ /dev/null @@ -1,1543 +0,0 @@ --- MySQL dump 10.13 Distrib 5.1.41, for Win32 (ia32) --- --- Host: localhost Database: test_wiki --- ------------------------------------------------------ --- Server version 5.1.41 - -/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; -/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; -/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; -/*!40101 SET NAMES utf8 */; -/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */; -/*!40103 SET TIME_ZONE='+00:00' */; -/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; -/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; -/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; -/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; - --- --- Table structure for table `mw_archive` --- - -DROP TABLE IF EXISTS `mw_archive`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; -CREATE TABLE `mw_archive` ( - `ar_namespace` int(11) NOT NULL DEFAULT '0', - `ar_title` varchar(255) CHARACTER SET latin1 COLLATE latin1_bin NOT NULL DEFAULT '', - `ar_text` mediumblob NOT NULL, - `ar_comment` tinyblob NOT NULL, - `ar_user` int(10) unsigned NOT NULL DEFAULT '0', - `ar_user_text` varchar(255) CHARACTER SET latin1 COLLATE latin1_bin NOT NULL, - `ar_timestamp` binary(14) NOT NULL DEFAULT '\0\0\0\0\0\0\0\0\0\0\0\0\0\0', - `ar_minor_edit` tinyint(4) NOT NULL DEFAULT '0', - `ar_flags` tinyblob NOT NULL, - `ar_rev_id` int(10) unsigned DEFAULT NULL, - `ar_text_id` int(10) unsigned DEFAULT NULL, - `ar_deleted` tinyint(3) unsigned NOT NULL DEFAULT '0', - `ar_len` int(10) unsigned DEFAULT NULL, - `ar_page_id` int(10) unsigned DEFAULT NULL, - `ar_parent_id` int(10) unsigned DEFAULT NULL, - KEY `name_title_timestamp` (`ar_namespace`,`ar_title`,`ar_timestamp`), - KEY `usertext_timestamp` (`ar_user_text`,`ar_timestamp`), - KEY `ar_page_revid` (`ar_namespace`,`ar_title`,`ar_rev_id`) -) ENGINE=InnoDB DEFAULT CHARSET=latin1; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Dumping data for table `mw_archive` --- - -LOCK TABLES `mw_archive` WRITE; -/*!40000 ALTER TABLE `mw_archive` DISABLE KEYS */; -/*!40000 ALTER TABLE `mw_archive` ENABLE KEYS */; -UNLOCK TABLES; - --- --- Table structure for table `mw_category` --- - -DROP TABLE IF EXISTS `mw_category`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; -CREATE TABLE `mw_category` ( - `cat_id` int(10) unsigned NOT NULL AUTO_INCREMENT, - `cat_title` varchar(255) CHARACTER SET latin1 COLLATE latin1_bin NOT NULL, - `cat_pages` int(11) NOT NULL DEFAULT '0', - `cat_subcats` int(11) NOT NULL DEFAULT '0', - `cat_files` int(11) NOT NULL DEFAULT '0', - `cat_hidden` tinyint(3) unsigned NOT NULL DEFAULT '0', - PRIMARY KEY (`cat_id`), - UNIQUE KEY `cat_title` (`cat_title`), - KEY `cat_pages` (`cat_pages`) -) ENGINE=InnoDB DEFAULT CHARSET=latin1; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Dumping data for table `mw_category` --- - -LOCK TABLES `mw_category` WRITE; -/*!40000 ALTER TABLE `mw_category` DISABLE KEYS */; -/*!40000 ALTER TABLE `mw_category` ENABLE KEYS */; -UNLOCK TABLES; - --- --- Table structure for table `mw_categorylinks` --- - -DROP TABLE IF EXISTS `mw_categorylinks`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; -CREATE TABLE `mw_categorylinks` ( - `cl_from` int(10) unsigned NOT NULL DEFAULT '0', - `cl_to` varchar(255) CHARACTER SET latin1 COLLATE latin1_bin NOT NULL DEFAULT '', - `cl_sortkey` varbinary(230) NOT NULL DEFAULT '', - `cl_sortkey_prefix` varchar(255) CHARACTER SET latin1 COLLATE latin1_bin NOT NULL DEFAULT '', - `cl_timestamp` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - `cl_collation` varbinary(32) NOT NULL DEFAULT '', - `cl_type` enum('page','subcat','file') NOT NULL DEFAULT 'page', - UNIQUE KEY `cl_from` (`cl_from`,`cl_to`), - KEY `cl_sortkey` (`cl_to`,`cl_type`,`cl_sortkey`,`cl_from`), - KEY `cl_timestamp` (`cl_to`,`cl_timestamp`), - KEY `cl_collation` (`cl_collation`) -) ENGINE=InnoDB DEFAULT CHARSET=latin1; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Dumping data for table `mw_categorylinks` --- - -LOCK TABLES `mw_categorylinks` WRITE; -/*!40000 ALTER TABLE `mw_categorylinks` DISABLE KEYS */; -/*!40000 ALTER TABLE `mw_categorylinks` ENABLE KEYS */; -UNLOCK TABLES; - --- --- Table structure for table `mw_change_tag` --- - -DROP TABLE IF EXISTS `mw_change_tag`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; -CREATE TABLE `mw_change_tag` ( - `ct_rc_id` int(11) DEFAULT NULL, - `ct_log_id` int(11) DEFAULT NULL, - `ct_rev_id` int(11) DEFAULT NULL, - `ct_tag` varchar(255) NOT NULL, - `ct_params` blob, - UNIQUE KEY `change_tag_rc_tag` (`ct_rc_id`,`ct_tag`), - UNIQUE KEY `change_tag_log_tag` (`ct_log_id`,`ct_tag`), - UNIQUE KEY `change_tag_rev_tag` (`ct_rev_id`,`ct_tag`), - KEY `change_tag_tag_id` (`ct_tag`,`ct_rc_id`,`ct_rev_id`,`ct_log_id`) -) ENGINE=InnoDB DEFAULT CHARSET=latin1; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Dumping data for table `mw_change_tag` --- - -LOCK TABLES `mw_change_tag` WRITE; -/*!40000 ALTER TABLE `mw_change_tag` DISABLE KEYS */; -/*!40000 ALTER TABLE `mw_change_tag` ENABLE KEYS */; -UNLOCK TABLES; - --- --- Table structure for table `mw_external_user` --- - -DROP TABLE IF EXISTS `mw_external_user`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; -CREATE TABLE `mw_external_user` ( - `eu_local_id` int(10) unsigned NOT NULL, - `eu_external_id` varchar(255) CHARACTER SET latin1 COLLATE latin1_bin NOT NULL, - PRIMARY KEY (`eu_local_id`), - UNIQUE KEY `eu_external_id` (`eu_external_id`) -) ENGINE=InnoDB DEFAULT CHARSET=latin1; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Dumping data for table `mw_external_user` --- - -LOCK TABLES `mw_external_user` WRITE; -/*!40000 ALTER TABLE `mw_external_user` DISABLE KEYS */; -/*!40000 ALTER TABLE `mw_external_user` ENABLE KEYS */; -UNLOCK TABLES; - --- --- Table structure for table `mw_externallinks` --- - -DROP TABLE IF EXISTS `mw_externallinks`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; -CREATE TABLE `mw_externallinks` ( - `el_from` int(10) unsigned NOT NULL DEFAULT '0', - `el_to` blob NOT NULL, - `el_index` blob NOT NULL, - KEY `el_from` (`el_from`,`el_to`(40)), - KEY `el_to` (`el_to`(60),`el_from`), - KEY `el_index` (`el_index`(60)) -) ENGINE=InnoDB DEFAULT CHARSET=latin1; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Dumping data for table `mw_externallinks` --- - -LOCK TABLES `mw_externallinks` WRITE; -/*!40000 ALTER TABLE `mw_externallinks` DISABLE KEYS */; -INSERT INTO `mw_externallinks` VALUES (1,'http://meta.wikimedia.org/wiki/Help:Contents','http://org.wikimedia.meta./wiki/Help:Contents'); -INSERT INTO `mw_externallinks` VALUES (1,'http://www.mediawiki.org/wiki/Manual:Configuration_settings','http://org.mediawiki.www./wiki/Manual:Configuration_settings'); -INSERT INTO `mw_externallinks` VALUES (1,'http://www.mediawiki.org/wiki/Manual:FAQ','http://org.mediawiki.www./wiki/Manual:FAQ'); -INSERT INTO `mw_externallinks` VALUES (1,'https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce','https://org.wikimedia.lists./mailman/listinfo/mediawiki-announce'); -/*!40000 ALTER TABLE `mw_externallinks` ENABLE KEYS */; -UNLOCK TABLES; - --- --- Table structure for table `mw_filearchive` --- - -DROP TABLE IF EXISTS `mw_filearchive`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; -CREATE TABLE `mw_filearchive` ( - `fa_id` int(11) NOT NULL AUTO_INCREMENT, - `fa_name` varchar(255) CHARACTER SET latin1 COLLATE latin1_bin NOT NULL DEFAULT '', - `fa_archive_name` varchar(255) CHARACTER SET latin1 COLLATE latin1_bin DEFAULT '', - `fa_storage_group` varbinary(16) DEFAULT NULL, - `fa_storage_key` varbinary(64) DEFAULT '', - `fa_deleted_user` int(11) DEFAULT NULL, - `fa_deleted_timestamp` binary(14) DEFAULT '\0\0\0\0\0\0\0\0\0\0\0\0\0\0', - `fa_deleted_reason` text, - `fa_size` int(10) unsigned DEFAULT '0', - `fa_width` int(11) DEFAULT '0', - `fa_height` int(11) DEFAULT '0', - `fa_metadata` mediumblob, - `fa_bits` int(11) DEFAULT '0', - `fa_media_type` enum('UNKNOWN','BITMAP','DRAWING','AUDIO','VIDEO','MULTIMEDIA','OFFICE','TEXT','EXECUTABLE','ARCHIVE') DEFAULT NULL, - `fa_major_mime` enum('unknown','application','audio','image','text','video','message','model','multipart') DEFAULT 'unknown', - `fa_minor_mime` varbinary(100) DEFAULT 'unknown', - `fa_description` tinyblob, - `fa_user` int(10) unsigned DEFAULT '0', - `fa_user_text` varchar(255) CHARACTER SET latin1 COLLATE latin1_bin DEFAULT NULL, - `fa_timestamp` binary(14) DEFAULT '\0\0\0\0\0\0\0\0\0\0\0\0\0\0', - `fa_deleted` tinyint(3) unsigned NOT NULL DEFAULT '0', - PRIMARY KEY (`fa_id`), - KEY `fa_name` (`fa_name`,`fa_timestamp`), - KEY `fa_storage_group` (`fa_storage_group`,`fa_storage_key`), - KEY `fa_deleted_timestamp` (`fa_deleted_timestamp`), - KEY `fa_user_timestamp` (`fa_user_text`,`fa_timestamp`) -) ENGINE=InnoDB DEFAULT CHARSET=latin1; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Dumping data for table `mw_filearchive` --- - -LOCK TABLES `mw_filearchive` WRITE; -/*!40000 ALTER TABLE `mw_filearchive` DISABLE KEYS */; -/*!40000 ALTER TABLE `mw_filearchive` ENABLE KEYS */; -UNLOCK TABLES; - --- --- Table structure for table `mw_hitcounter` --- - -DROP TABLE IF EXISTS `mw_hitcounter`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; -CREATE TABLE `mw_hitcounter` ( - `hc_id` int(10) unsigned NOT NULL -) ENGINE=MEMORY DEFAULT CHARSET=latin1 MAX_ROWS=25000; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Dumping data for table `mw_hitcounter` --- - -LOCK TABLES `mw_hitcounter` WRITE; -/*!40000 ALTER TABLE `mw_hitcounter` DISABLE KEYS */; -/*!40000 ALTER TABLE `mw_hitcounter` ENABLE KEYS */; -UNLOCK TABLES; - --- --- Table structure for table `mw_image` --- - -DROP TABLE IF EXISTS `mw_image`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; -CREATE TABLE `mw_image` ( - `img_name` varchar(255) CHARACTER SET latin1 COLLATE latin1_bin NOT NULL DEFAULT '', - `img_size` int(10) unsigned NOT NULL DEFAULT '0', - `img_width` int(11) NOT NULL DEFAULT '0', - `img_height` int(11) NOT NULL DEFAULT '0', - `img_metadata` mediumblob NOT NULL, - `img_bits` int(11) NOT NULL DEFAULT '0', - `img_media_type` enum('UNKNOWN','BITMAP','DRAWING','AUDIO','VIDEO','MULTIMEDIA','OFFICE','TEXT','EXECUTABLE','ARCHIVE') DEFAULT NULL, - `img_major_mime` enum('unknown','application','audio','image','text','video','message','model','multipart') NOT NULL DEFAULT 'unknown', - `img_minor_mime` varbinary(100) NOT NULL DEFAULT 'unknown', - `img_description` tinyblob NOT NULL, - `img_user` int(10) unsigned NOT NULL DEFAULT '0', - `img_user_text` varchar(255) CHARACTER SET latin1 COLLATE latin1_bin NOT NULL, - `img_timestamp` varbinary(14) NOT NULL DEFAULT '', - `img_sha1` varbinary(32) NOT NULL DEFAULT '', - PRIMARY KEY (`img_name`), - KEY `img_usertext_timestamp` (`img_user_text`,`img_timestamp`), - KEY `img_size` (`img_size`), - KEY `img_timestamp` (`img_timestamp`), - KEY `img_sha1` (`img_sha1`) -) ENGINE=InnoDB DEFAULT CHARSET=latin1; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Dumping data for table `mw_image` --- - -LOCK TABLES `mw_image` WRITE; -/*!40000 ALTER TABLE `mw_image` DISABLE KEYS */; -/*!40000 ALTER TABLE `mw_image` ENABLE KEYS */; -UNLOCK TABLES; - --- --- Table structure for table `mw_imagelinks` --- - -DROP TABLE IF EXISTS `mw_imagelinks`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; -CREATE TABLE `mw_imagelinks` ( - `il_from` int(10) unsigned NOT NULL DEFAULT '0', - `il_to` varchar(255) CHARACTER SET latin1 COLLATE latin1_bin NOT NULL DEFAULT '', - UNIQUE KEY `il_from` (`il_from`,`il_to`), - UNIQUE KEY `il_to` (`il_to`,`il_from`) -) ENGINE=InnoDB DEFAULT CHARSET=latin1; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Dumping data for table `mw_imagelinks` --- - -LOCK TABLES `mw_imagelinks` WRITE; -/*!40000 ALTER TABLE `mw_imagelinks` DISABLE KEYS */; -/*!40000 ALTER TABLE `mw_imagelinks` ENABLE KEYS */; -UNLOCK TABLES; - --- --- Table structure for table `mw_interwiki` --- - -DROP TABLE IF EXISTS `mw_interwiki`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; -CREATE TABLE `mw_interwiki` ( - `iw_prefix` varchar(32) NOT NULL, - `iw_url` blob NOT NULL, - `iw_api` blob NOT NULL, - `iw_wikiid` varchar(64) NOT NULL, - `iw_local` tinyint(1) NOT NULL, - `iw_trans` tinyint(4) NOT NULL DEFAULT '0', - UNIQUE KEY `iw_prefix` (`iw_prefix`) -) ENGINE=InnoDB DEFAULT CHARSET=latin1; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Dumping data for table `mw_interwiki` --- - -LOCK TABLES `mw_interwiki` WRITE; -/*!40000 ALTER TABLE `mw_interwiki` DISABLE KEYS */; -INSERT INTO `mw_interwiki` VALUES ('acronym','http://www.acronymfinder.com/af-query.asp?String=exact&Acronym=$1','','',0,0); -INSERT INTO `mw_interwiki` VALUES ('advogato','http://www.advogato.org/$1','','',0,0); -INSERT INTO `mw_interwiki` VALUES ('annotationwiki','http://www.seedwiki.com/page.cfm?wikiid=368&doc=$1','','',0,0); -INSERT INTO `mw_interwiki` VALUES ('arxiv','http://www.arxiv.org/abs/$1','','',0,0); -INSERT INTO `mw_interwiki` VALUES ('c2find','http://c2.com/cgi/wiki?FindPage&value=$1','','',0,0); -INSERT INTO `mw_interwiki` VALUES ('cache','http://www.google.com/search?q=cache:$1','','',0,0); -INSERT INTO `mw_interwiki` VALUES ('commons','http://commons.wikimedia.org/wiki/$1','','',0,0); -INSERT INTO `mw_interwiki` VALUES ('corpknowpedia','http://corpknowpedia.org/wiki/index.php/$1','','',0,0); -INSERT INTO `mw_interwiki` VALUES ('dictionary','http://www.dict.org/bin/Dict?Database=*&Form=Dict1&Strategy=*&Query=$1','','',0,0); -INSERT INTO `mw_interwiki` VALUES ('disinfopedia','http://www.disinfopedia.org/wiki.phtml?title=$1','','',0,0); -INSERT INTO `mw_interwiki` VALUES ('docbook','http://wiki.docbook.org/topic/$1','','',0,0); -INSERT INTO `mw_interwiki` VALUES ('doi','http://dx.doi.org/$1','','',0,0); -INSERT INTO `mw_interwiki` VALUES ('drumcorpswiki','http://www.drumcorpswiki.com/index.php/$1','','',0,0); -INSERT INTO `mw_interwiki` VALUES ('dwjwiki','http://www.suberic.net/cgi-bin/dwj/wiki.cgi?$1','','',0,0); -INSERT INTO `mw_interwiki` VALUES ('elibre','http://enciclopedia.us.es/index.php/$1','','',0,0); -INSERT INTO `mw_interwiki` VALUES ('emacswiki','http://www.emacswiki.org/cgi-bin/wiki.pl?$1','','',0,0); -INSERT INTO `mw_interwiki` VALUES ('foldoc','http://foldoc.org/?$1','','',0,0); -INSERT INTO `mw_interwiki` VALUES ('foxwiki','http://fox.wikis.com/wc.dll?Wiki~$1','','',0,0); -INSERT INTO `mw_interwiki` VALUES ('freebsdman','http://www.FreeBSD.org/cgi/man.cgi?apropos=1&query=$1','','',0,0); -INSERT INTO `mw_interwiki` VALUES ('gej','http://www.esperanto.de/cgi-bin/aktivikio/wiki.pl?$1','','',0,0); -INSERT INTO `mw_interwiki` VALUES ('gentoo-wiki','http://gentoo-wiki.com/$1','','',0,0); -INSERT INTO `mw_interwiki` VALUES ('google','http://www.google.com/search?q=$1','','',0,0); -INSERT INTO `mw_interwiki` VALUES ('googlegroups','http://groups.google.com/groups?q=$1','','',0,0); -INSERT INTO `mw_interwiki` VALUES ('hammondwiki','http://www.dairiki.org/HammondWiki/$1','','',0,0); -INSERT INTO `mw_interwiki` VALUES ('hewikisource','http://he.wikisource.org/wiki/$1','','',1,0); -INSERT INTO `mw_interwiki` VALUES ('hrwiki','http://www.hrwiki.org/index.php/$1','','',0,0); -INSERT INTO `mw_interwiki` VALUES ('imdb','http://us.imdb.com/Title?$1','','',0,0); -INSERT INTO `mw_interwiki` VALUES ('jargonfile','http://sunir.org/apps/meta.pl?wiki=JargonFile&redirect=$1','','',0,0); -INSERT INTO `mw_interwiki` VALUES ('jspwiki','http://www.jspwiki.org/wiki/$1','','',0,0); -INSERT INTO `mw_interwiki` VALUES ('keiki','http://kei.ki/en/$1','','',0,0); -INSERT INTO `mw_interwiki` VALUES ('kmwiki','http://kmwiki.wikispaces.com/$1','','',0,0); -INSERT INTO `mw_interwiki` VALUES ('linuxwiki','http://linuxwiki.de/$1','','',0,0); -INSERT INTO `mw_interwiki` VALUES ('lojban','http://www.lojban.org/tiki/tiki-index.php?page=$1','','',0,0); -INSERT INTO `mw_interwiki` VALUES ('lqwiki','http://wiki.linuxquestions.org/wiki/$1','','',0,0); -INSERT INTO `mw_interwiki` VALUES ('lugkr','http://lug-kr.sourceforge.net/cgi-bin/lugwiki.pl?$1','','',0,0); -INSERT INTO `mw_interwiki` VALUES ('mathsongswiki','http://SeedWiki.com/page.cfm?wikiid=237&doc=$1','','',0,0); -INSERT INTO `mw_interwiki` VALUES ('meatball','http://www.usemod.com/cgi-bin/mb.pl?$1','','',0,0); -INSERT INTO `mw_interwiki` VALUES ('mediawikiwiki','http://www.mediawiki.org/wiki/$1','','',0,0); -INSERT INTO `mw_interwiki` VALUES ('mediazilla','https://bugzilla.wikimedia.org/$1','','',1,0); -INSERT INTO `mw_interwiki` VALUES ('memoryalpha','http://www.memory-alpha.org/en/index.php/$1','','',0,0); -INSERT INTO `mw_interwiki` VALUES ('metawiki','http://sunir.org/apps/meta.pl?$1','','',0,0); -INSERT INTO `mw_interwiki` VALUES ('metawikimedia','http://meta.wikimedia.org/wiki/$1','','',0,0); -INSERT INTO `mw_interwiki` VALUES ('moinmoin','http://purl.net/wiki/moin/$1','','',0,0); -INSERT INTO `mw_interwiki` VALUES ('mozillawiki','http://wiki.mozilla.org/index.php/$1','','',0,0); -INSERT INTO `mw_interwiki` VALUES ('mw','http://www.mediawiki.org/wiki/$1','','',0,0); -INSERT INTO `mw_interwiki` VALUES ('oeis','http://www.research.att.com/cgi-bin/access.cgi/as/njas/sequences/eisA.cgi?Anum=$1','','',0,0); -INSERT INTO `mw_interwiki` VALUES ('openfacts','http://openfacts.berlios.de/index.phtml?title=$1','','',0,0); -INSERT INTO `mw_interwiki` VALUES ('openwiki','http://openwiki.com/?$1','','',0,0); -INSERT INTO `mw_interwiki` VALUES ('pmeg','http://www.bertilow.com/pmeg/$1.php','','',0,0); -INSERT INTO `mw_interwiki` VALUES ('ppr','http://c2.com/cgi/wiki?$1','','',0,0); -INSERT INTO `mw_interwiki` VALUES ('pythoninfo','http://wiki.python.org/moin/$1','','',0,0); -INSERT INTO `mw_interwiki` VALUES ('rfc','http://www.rfc-editor.org/rfc/rfc$1.txt','','',0,0); -INSERT INTO `mw_interwiki` VALUES ('s23wiki','http://is-root.de/wiki/index.php/$1','','',0,0); -INSERT INTO `mw_interwiki` VALUES ('seattlewiki','http://seattle.wikia.com/wiki/$1','','',0,0); -INSERT INTO `mw_interwiki` VALUES ('seattlewireless','http://seattlewireless.net/?$1','','',0,0); -INSERT INTO `mw_interwiki` VALUES ('senseislibrary','http://senseis.xmp.net/?$1','','',0,0); -INSERT INTO `mw_interwiki` VALUES ('sourceforge','http://sourceforge.net/$1','','',0,0); -INSERT INTO `mw_interwiki` VALUES ('squeak','http://wiki.squeak.org/squeak/$1','','',0,0); -INSERT INTO `mw_interwiki` VALUES ('susning','http://www.susning.nu/$1','','',0,0); -INSERT INTO `mw_interwiki` VALUES ('svgwiki','http://wiki.svg.org/$1','','',0,0); -INSERT INTO `mw_interwiki` VALUES ('tavi','http://tavi.sourceforge.net/$1','','',0,0); -INSERT INTO `mw_interwiki` VALUES ('tejo','http://www.tejo.org/vikio/$1','','',0,0); -INSERT INTO `mw_interwiki` VALUES ('theopedia','http://www.theopedia.com/$1','','',0,0); -INSERT INTO `mw_interwiki` VALUES ('tmbw','http://www.tmbw.net/wiki/$1','','',0,0); -INSERT INTO `mw_interwiki` VALUES ('tmnet','http://www.technomanifestos.net/?$1','','',0,0); -INSERT INTO `mw_interwiki` VALUES ('tmwiki','http://www.EasyTopicMaps.com/?page=$1','','',0,0); -INSERT INTO `mw_interwiki` VALUES ('twiki','http://twiki.org/cgi-bin/view/$1','','',0,0); -INSERT INTO `mw_interwiki` VALUES ('uea','http://www.tejo.org/uea/$1','','',0,0); -INSERT INTO `mw_interwiki` VALUES ('unreal','http://wiki.beyondunreal.com/wiki/$1','','',0,0); -INSERT INTO `mw_interwiki` VALUES ('usemod','http://www.usemod.com/cgi-bin/wiki.pl?$1','','',0,0); -INSERT INTO `mw_interwiki` VALUES ('vinismo','http://vinismo.com/en/$1','','',0,0); -INSERT INTO `mw_interwiki` VALUES ('webseitzwiki','http://webseitz.fluxent.com/wiki/$1','','',0,0); -INSERT INTO `mw_interwiki` VALUES ('why','http://clublet.com/c/c/why?$1','','',0,0); -INSERT INTO `mw_interwiki` VALUES ('wiki','http://c2.com/cgi/wiki?$1','','',0,0); -INSERT INTO `mw_interwiki` VALUES ('wikia','http://www.wikia.com/wiki/$1','','',0,0); -INSERT INTO `mw_interwiki` VALUES ('wikibooks','http://en.wikibooks.org/wiki/$1','','',1,0); -INSERT INTO `mw_interwiki` VALUES ('wikicities','http://www.wikia.com/wiki/$1','','',0,0); -INSERT INTO `mw_interwiki` VALUES ('wikif1','http://www.wikif1.org/$1','','',0,0); -INSERT INTO `mw_interwiki` VALUES ('wikihow','http://www.wikihow.com/$1','','',0,0); -INSERT INTO `mw_interwiki` VALUES ('wikimedia','http://wikimediafoundation.org/wiki/$1','','',0,0); -INSERT INTO `mw_interwiki` VALUES ('wikinews','http://en.wikinews.org/wiki/$1','','',1,0); -INSERT INTO `mw_interwiki` VALUES ('wikinfo','http://www.wikinfo.org/index.php/$1','','',0,0); -INSERT INTO `mw_interwiki` VALUES ('wikipedia','http://en.wikipedia.org/wiki/$1','','',1,0); -INSERT INTO `mw_interwiki` VALUES ('wikiquote','http://en.wikiquote.org/wiki/$1','','',1,0); -INSERT INTO `mw_interwiki` VALUES ('wikisource','http://wikisource.org/wiki/$1','','',1,0); -INSERT INTO `mw_interwiki` VALUES ('wikispecies','http://species.wikimedia.org/wiki/$1','','',1,0); -INSERT INTO `mw_interwiki` VALUES ('wikitravel','http://wikitravel.org/en/$1','','',0,0); -INSERT INTO `mw_interwiki` VALUES ('wikiversity','http://en.wikiversity.org/wiki/$1','','',1,0); -INSERT INTO `mw_interwiki` VALUES ('wikt','http://en.wiktionary.org/wiki/$1','','',1,0); -INSERT INTO `mw_interwiki` VALUES ('wiktionary','http://en.wiktionary.org/wiki/$1','','',1,0); -INSERT INTO `mw_interwiki` VALUES ('wlug','http://www.wlug.org.nz/$1','','',0,0); -INSERT INTO `mw_interwiki` VALUES ('zwiki','http://zwiki.org/$1','','',0,0); -INSERT INTO `mw_interwiki` VALUES ('zzz wiki','http://wiki.zzz.ee/index.php/$1','','',0,0); -/*!40000 ALTER TABLE `mw_interwiki` ENABLE KEYS */; -UNLOCK TABLES; - --- --- Table structure for table `mw_ipblocks` --- - -DROP TABLE IF EXISTS `mw_ipblocks`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; -CREATE TABLE `mw_ipblocks` ( - `ipb_id` int(11) NOT NULL AUTO_INCREMENT, - `ipb_address` tinyblob NOT NULL, - `ipb_user` int(10) unsigned NOT NULL DEFAULT '0', - `ipb_by` int(10) unsigned NOT NULL DEFAULT '0', - `ipb_by_text` varchar(255) CHARACTER SET latin1 COLLATE latin1_bin NOT NULL DEFAULT '', - `ipb_reason` tinyblob NOT NULL, - `ipb_timestamp` binary(14) NOT NULL DEFAULT '\0\0\0\0\0\0\0\0\0\0\0\0\0\0', - `ipb_auto` tinyint(1) NOT NULL DEFAULT '0', - `ipb_anon_only` tinyint(1) NOT NULL DEFAULT '0', - `ipb_create_account` tinyint(1) NOT NULL DEFAULT '1', - `ipb_enable_autoblock` tinyint(1) NOT NULL DEFAULT '1', - `ipb_expiry` varbinary(14) NOT NULL DEFAULT '', - `ipb_range_start` tinyblob NOT NULL, - `ipb_range_end` tinyblob NOT NULL, - `ipb_deleted` tinyint(1) NOT NULL DEFAULT '0', - `ipb_block_email` tinyint(1) NOT NULL DEFAULT '0', - `ipb_allow_usertalk` tinyint(1) NOT NULL DEFAULT '0', - PRIMARY KEY (`ipb_id`), - UNIQUE KEY `ipb_address` (`ipb_address`(255),`ipb_user`,`ipb_auto`,`ipb_anon_only`), - KEY `ipb_user` (`ipb_user`), - KEY `ipb_range` (`ipb_range_start`(8),`ipb_range_end`(8)), - KEY `ipb_timestamp` (`ipb_timestamp`), - KEY `ipb_expiry` (`ipb_expiry`) -) ENGINE=InnoDB DEFAULT CHARSET=latin1; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Dumping data for table `mw_ipblocks` --- - -LOCK TABLES `mw_ipblocks` WRITE; -/*!40000 ALTER TABLE `mw_ipblocks` DISABLE KEYS */; -/*!40000 ALTER TABLE `mw_ipblocks` ENABLE KEYS */; -UNLOCK TABLES; - --- --- Table structure for table `mw_iwlinks` --- - -DROP TABLE IF EXISTS `mw_iwlinks`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; -CREATE TABLE `mw_iwlinks` ( - `iwl_from` int(10) unsigned NOT NULL DEFAULT '0', - `iwl_prefix` varbinary(20) NOT NULL DEFAULT '', - `iwl_title` varchar(255) CHARACTER SET latin1 COLLATE latin1_bin NOT NULL DEFAULT '', - UNIQUE KEY `iwl_from` (`iwl_from`,`iwl_prefix`,`iwl_title`), - UNIQUE KEY `iwl_prefix_title_from` (`iwl_prefix`,`iwl_title`,`iwl_from`) -) ENGINE=InnoDB DEFAULT CHARSET=latin1; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Dumping data for table `mw_iwlinks` --- - -LOCK TABLES `mw_iwlinks` WRITE; -/*!40000 ALTER TABLE `mw_iwlinks` DISABLE KEYS */; -/*!40000 ALTER TABLE `mw_iwlinks` ENABLE KEYS */; -UNLOCK TABLES; - --- --- Table structure for table `mw_job` --- - -DROP TABLE IF EXISTS `mw_job`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; -CREATE TABLE `mw_job` ( - `job_id` int(10) unsigned NOT NULL AUTO_INCREMENT, - `job_cmd` varbinary(60) NOT NULL DEFAULT '', - `job_namespace` int(11) NOT NULL, - `job_title` varchar(255) CHARACTER SET latin1 COLLATE latin1_bin NOT NULL, - `job_params` blob NOT NULL, - PRIMARY KEY (`job_id`), - KEY `job_cmd` (`job_cmd`,`job_namespace`,`job_title`,`job_params`(128)) -) ENGINE=InnoDB DEFAULT CHARSET=latin1; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Dumping data for table `mw_job` --- - -LOCK TABLES `mw_job` WRITE; -/*!40000 ALTER TABLE `mw_job` DISABLE KEYS */; -/*!40000 ALTER TABLE `mw_job` ENABLE KEYS */; -UNLOCK TABLES; - --- --- Table structure for table `mw_l10n_cache` --- - -DROP TABLE IF EXISTS `mw_l10n_cache`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; -CREATE TABLE `mw_l10n_cache` ( - `lc_lang` varbinary(32) NOT NULL, - `lc_key` varchar(255) NOT NULL, - `lc_value` mediumblob NOT NULL, - KEY `lc_lang_key` (`lc_lang`,`lc_key`) -) ENGINE=InnoDB DEFAULT CHARSET=latin1; -/*!40101 SET character_set_client = @saved_cs_client */; - - - --- --- Table structure for table `mw_langlinks` --- - -DROP TABLE IF EXISTS `mw_langlinks`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; -CREATE TABLE `mw_langlinks` ( - `ll_from` int(10) unsigned NOT NULL DEFAULT '0', - `ll_lang` varbinary(20) NOT NULL DEFAULT '', - `ll_title` varchar(255) CHARACTER SET latin1 COLLATE latin1_bin NOT NULL DEFAULT '', - UNIQUE KEY `ll_from` (`ll_from`,`ll_lang`), - KEY `ll_lang` (`ll_lang`,`ll_title`) -) ENGINE=InnoDB DEFAULT CHARSET=latin1; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Dumping data for table `mw_langlinks` --- - -LOCK TABLES `mw_langlinks` WRITE; -/*!40000 ALTER TABLE `mw_langlinks` DISABLE KEYS */; -/*!40000 ALTER TABLE `mw_langlinks` ENABLE KEYS */; -UNLOCK TABLES; - --- --- Table structure for table `mw_log_search` --- - -DROP TABLE IF EXISTS `mw_log_search`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; -CREATE TABLE `mw_log_search` ( - `ls_field` varbinary(32) NOT NULL, - `ls_value` varchar(255) NOT NULL, - `ls_log_id` int(10) unsigned NOT NULL DEFAULT '0', - UNIQUE KEY `ls_field_val` (`ls_field`,`ls_value`,`ls_log_id`), - KEY `ls_log_id` (`ls_log_id`) -) ENGINE=InnoDB DEFAULT CHARSET=latin1; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Dumping data for table `mw_log_search` --- - -LOCK TABLES `mw_log_search` WRITE; -/*!40000 ALTER TABLE `mw_log_search` DISABLE KEYS */; -/*!40000 ALTER TABLE `mw_log_search` ENABLE KEYS */; -UNLOCK TABLES; - --- --- Table structure for table `mw_logging` --- - -DROP TABLE IF EXISTS `mw_logging`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; -CREATE TABLE `mw_logging` ( - `log_id` int(10) unsigned NOT NULL AUTO_INCREMENT, - `log_type` varbinary(32) NOT NULL DEFAULT '', - `log_action` varbinary(32) NOT NULL DEFAULT '', - `log_timestamp` binary(14) NOT NULL DEFAULT '19700101000000', - `log_user` int(10) unsigned NOT NULL DEFAULT '0', - `log_user_text` varchar(255) CHARACTER SET latin1 COLLATE latin1_bin NOT NULL DEFAULT '', - `log_namespace` int(11) NOT NULL DEFAULT '0', - `log_title` varchar(255) CHARACTER SET latin1 COLLATE latin1_bin NOT NULL DEFAULT '', - `log_page` int(10) unsigned DEFAULT NULL, - `log_comment` varchar(255) NOT NULL DEFAULT '', - `log_params` blob NOT NULL, - `log_deleted` tinyint(3) unsigned NOT NULL DEFAULT '0', - PRIMARY KEY (`log_id`), - KEY `type_time` (`log_type`,`log_timestamp`), - KEY `user_time` (`log_user`,`log_timestamp`), - KEY `page_time` (`log_namespace`,`log_title`,`log_timestamp`), - KEY `times` (`log_timestamp`), - KEY `log_user_type_time` (`log_user`,`log_type`,`log_timestamp`), - KEY `log_page_id_time` (`log_page`,`log_timestamp`) -) ENGINE=InnoDB DEFAULT CHARSET=latin1; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Dumping data for table `mw_logging` --- - -LOCK TABLES `mw_logging` WRITE; -/*!40000 ALTER TABLE `mw_logging` DISABLE KEYS */; -/*!40000 ALTER TABLE `mw_logging` ENABLE KEYS */; -UNLOCK TABLES; - --- --- Table structure for table `mw_math` --- - -DROP TABLE IF EXISTS `mw_math`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; -CREATE TABLE `mw_math` ( - `math_inputhash` varbinary(16) NOT NULL, - `math_outputhash` varbinary(16) NOT NULL, - `math_html_conservativeness` tinyint(4) NOT NULL, - `math_html` text, - `math_mathml` text, - UNIQUE KEY `math_inputhash` (`math_inputhash`) -) ENGINE=InnoDB DEFAULT CHARSET=latin1; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Dumping data for table `mw_math` --- - -LOCK TABLES `mw_math` WRITE; -/*!40000 ALTER TABLE `mw_math` DISABLE KEYS */; -/*!40000 ALTER TABLE `mw_math` ENABLE KEYS */; -UNLOCK TABLES; - --- --- Table structure for table `mw_module_deps` --- - -DROP TABLE IF EXISTS `mw_module_deps`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; -CREATE TABLE `mw_module_deps` ( - `md_module` varbinary(255) NOT NULL, - `md_skin` varbinary(32) NOT NULL, - `md_deps` mediumblob NOT NULL, - UNIQUE KEY `md_module_skin` (`md_module`,`md_skin`) -) ENGINE=InnoDB DEFAULT CHARSET=latin1; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Dumping data for table `mw_module_deps` --- - -LOCK TABLES `mw_module_deps` WRITE; -/*!40000 ALTER TABLE `mw_module_deps` DISABLE KEYS */; -/*!40000 ALTER TABLE `mw_module_deps` ENABLE KEYS */; -UNLOCK TABLES; - --- --- Table structure for table `mw_msg_resource` --- - -DROP TABLE IF EXISTS `mw_msg_resource`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; -CREATE TABLE `mw_msg_resource` ( - `mr_resource` varbinary(255) NOT NULL, - `mr_lang` varbinary(32) NOT NULL, - `mr_blob` mediumblob NOT NULL, - `mr_timestamp` binary(14) NOT NULL, - UNIQUE KEY `mr_resource_lang` (`mr_resource`,`mr_lang`) -) ENGINE=InnoDB DEFAULT CHARSET=latin1; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Dumping data for table `mw_msg_resource` --- - -LOCK TABLES `mw_msg_resource` WRITE; -/*!40000 ALTER TABLE `mw_msg_resource` DISABLE KEYS */; -/*!40000 ALTER TABLE `mw_msg_resource` ENABLE KEYS */; -UNLOCK TABLES; - --- --- Table structure for table `mw_msg_resource_links` --- - -DROP TABLE IF EXISTS `mw_msg_resource_links`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; -CREATE TABLE `mw_msg_resource_links` ( - `mrl_resource` varbinary(255) NOT NULL, - `mrl_message` varbinary(255) NOT NULL, - UNIQUE KEY `mrl_message_resource` (`mrl_message`,`mrl_resource`) -) ENGINE=InnoDB DEFAULT CHARSET=latin1; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Dumping data for table `mw_msg_resource_links` --- - -LOCK TABLES `mw_msg_resource_links` WRITE; -/*!40000 ALTER TABLE `mw_msg_resource_links` DISABLE KEYS */; -/*!40000 ALTER TABLE `mw_msg_resource_links` ENABLE KEYS */; -UNLOCK TABLES; - --- --- Table structure for table `mw_objectcache` --- - -DROP TABLE IF EXISTS `mw_objectcache`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; -CREATE TABLE `mw_objectcache` ( - `keyname` varbinary(255) NOT NULL DEFAULT '', - `value` mediumblob, - `exptime` datetime DEFAULT NULL, - PRIMARY KEY (`keyname`), - KEY `exptime` (`exptime`) -) ENGINE=InnoDB DEFAULT CHARSET=latin1; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Dumping data for table `mw_objectcache` --- - -LOCK TABLES `mw_objectcache` WRITE; -/*!40000 ALTER TABLE `mw_objectcache` DISABLE KEYS */; -INSERT INTO `mw_objectcache` VALUES ('test_wiki-mw_:messages:en','K�2��.�2�R\ns\r\n���S�δ2��\0','2010-12-31 13:16:31'); -INSERT INTO `mw_objectcache` VALUES ('test_wiki-mw_:pcache:idhash:1-0!*!*!!en!*','�V[o�F�g~�����B��P��m)$TK�U��\0�����8$��9�a\rI�l��X>��ܹ�q�Sa,��ʕ�x��?[~ʃ|�.Z��$�8Y�\'�I�Y�K��-\04�U����J�\'&�uB)�:I������E�m�sk`�`k�Q�v��a���\rZ��t�0����+P%GE�ـ�JX;\n-s�P�@�B�b���8~�첒$��ea�υ��f��+���0[,�F�x��d�\'�z�0��BJ���=���J��c�\\��:��&B��T��\'��C��Fdÿ׆Fq����Gd����%8G�0��A�I��; Ԙ`�7�5�LI\r��(���{�c�����g+��8Qr�&�ͦ��A)�V��ЕPT��\\UƧtn��Z�e�SfJZ(V�P�}����0��O�N �=j�\\H��y�\\�U[h]T:���bd��u��+�j%\'�6k��f:E�;�@Yך��4���Ȁ��q���Z�º6<b�3��TU(d��,\n���Y|�e�\'�5��T�fU�8}�\"��m���/���}Uk�9o��;����|*R?�n��� 3d��g1��y�\\f8gk�����w��=��:/Y7���ۋ�^<�Ō����v#���i����C�#��6\Z�.0�Ua$4�=\Z���;��4����Y=���5���:kpΐq��Ŧ4��X���C��qYߵ-��Lj�����D�f�����:����(3t��14C��J�#����������WXT���Δy:�^�6�v�7����I�U�Ee��(�p0��ga�6Mj��Sc�,ѫ@��ޅ+R����A��xХ\'6���utǷbۛ��`j�8ؚ�G�IC<KS��5�|�krJ\ry�\\b3xP�����ua�@����$SS�`��tQ.gwW��\r@\'���w�� ��xZ(�>5{����dw�>�=J)\r�6t ��X����M���B�\n�ŖT����b>�qg����7��z�n7��vwr�-%u�-Qi�iX1��Ne���A#�v��ӧ�3��?','2010-12-31 13:16:31'); -INSERT INTO `mw_objectcache` VALUES ('test_wiki-mw_:pcache:idoptions:1','E���@D��`�\"v�ƣ��Wh��,�b�!�⭙7�L+�|}�t��I�$�<���F\rpSl�4����OJN`\r\Z����ծ���)��PY��$�K����գ9�Vjp72��E���c�Wp�2��cVxu7�� ����p#�r=.���[>y)Zp��','2010-12-31 13:16:31'); -INSERT INTO `mw_objectcache` VALUES ('test_wiki-mw_:resourceloader:filter:minify-css:3832ee25d9c44988461f5f339b9b6a48','+�26�Rr�MM�LTH�ɩV\0�Z(��(3�(R�d\r\0','2038-01-19 03:14:07'); -INSERT INTO `mw_objectcache` VALUES ('test_wiki-mw_:resourceloader:filter:minify-css:aa0df16258ad99a1d249e796b5067ed9','+�2��Rr�MM�LTH�ɩN��K-�Q.,�L�NJ,R\0��s�\r���V�\Z\0','2038-01-19 03:14:07'); -INSERT INTO `mw_objectcache` VALUES ('test_wiki-mw_:resourceloader:filter:minify-js:22814eeadc9cf0a9ebcd844e14198e66','m��r�0��y����r�&Qޡמ!\n�qQ�Xq;}���$��ވ� �c!]]].o5S�\n�)Fq��L^��?�s�F�!�O�M\\�������\0���N��Ɂ���լ����:��-�j��F��{ۅ�G�\"i�� \Z�6�K����!��Y]=�F[�~竍���䶃����`��9N�Ǵ���@�K��|z�?1�A��@J#_ԁ�7\'�l�1)J�͵�).�3z�f�T�A���Hњ�[#)�BzRA�7��\"T�*~SW���/P���B�Ŏ;\Z�ay�6����+U��?.$�6��-uT�v@h��s�&�����Nإb�fJ�~�]6��p��/q)�>�E�1��͔A\ne�L�g\ZE�`cW�����`fJ�E�a��>��b\n�ӑd�.u�do��[�\nt��b�+���l\Z?X*��Y�(�օ;�L�Jqťɝ���d$�\"�WzG�-@b~+�#�kǞَ�Ƃ~������P)B ����q�Җ2���r�Rl����`z �4�����ÝX�m�;�X݁t;r.�sA��R��y)�kA�\nR�JT��J�U��*�W��_ߟ�4@�vt��f���>����x���','2038-01-19 03:14:07'); -INSERT INTO `mw_objectcache` VALUES ('test_wiki-mw_:resourceloader:filter:minify-js:dd9440c19c575629ac5ec90e489cf62e','+�21�R���Ԕ�����L���Ĕ�\"��ĒT�j��̒T%+���ĔJ�ZMk.%k\0','2038-01-19 03:14:07'); -/*!40000 ALTER TABLE `mw_objectcache` ENABLE KEYS */; -UNLOCK TABLES; - --- --- Table structure for table `mw_oldimage` --- - -DROP TABLE IF EXISTS `mw_oldimage`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; -CREATE TABLE `mw_oldimage` ( - `oi_name` varchar(255) CHARACTER SET latin1 COLLATE latin1_bin NOT NULL DEFAULT '', - `oi_archive_name` varchar(255) CHARACTER SET latin1 COLLATE latin1_bin NOT NULL DEFAULT '', - `oi_size` int(10) unsigned NOT NULL DEFAULT '0', - `oi_width` int(11) NOT NULL DEFAULT '0', - `oi_height` int(11) NOT NULL DEFAULT '0', - `oi_bits` int(11) NOT NULL DEFAULT '0', - `oi_description` tinyblob NOT NULL, - `oi_user` int(10) unsigned NOT NULL DEFAULT '0', - `oi_user_text` varchar(255) CHARACTER SET latin1 COLLATE latin1_bin NOT NULL, - `oi_timestamp` binary(14) NOT NULL DEFAULT '\0\0\0\0\0\0\0\0\0\0\0\0\0\0', - `oi_metadata` mediumblob NOT NULL, - `oi_media_type` enum('UNKNOWN','BITMAP','DRAWING','AUDIO','VIDEO','MULTIMEDIA','OFFICE','TEXT','EXECUTABLE','ARCHIVE') DEFAULT NULL, - `oi_major_mime` enum('unknown','application','audio','image','text','video','message','model','multipart') NOT NULL DEFAULT 'unknown', - `oi_minor_mime` varbinary(100) NOT NULL DEFAULT 'unknown', - `oi_deleted` tinyint(3) unsigned NOT NULL DEFAULT '0', - `oi_sha1` varbinary(32) NOT NULL DEFAULT '', - KEY `oi_usertext_timestamp` (`oi_user_text`,`oi_timestamp`), - KEY `oi_name_timestamp` (`oi_name`,`oi_timestamp`), - KEY `oi_name_archive_name` (`oi_name`,`oi_archive_name`(14)), - KEY `oi_sha1` (`oi_sha1`) -) ENGINE=InnoDB DEFAULT CHARSET=latin1; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Dumping data for table `mw_oldimage` --- - -LOCK TABLES `mw_oldimage` WRITE; -/*!40000 ALTER TABLE `mw_oldimage` DISABLE KEYS */; -/*!40000 ALTER TABLE `mw_oldimage` ENABLE KEYS */; -UNLOCK TABLES; - --- --- Table structure for table `mw_page` --- - -DROP TABLE IF EXISTS `mw_page`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; -CREATE TABLE `mw_page` ( - `page_id` int(10) unsigned NOT NULL AUTO_INCREMENT, - `page_namespace` int(11) NOT NULL, - `page_title` varchar(255) CHARACTER SET latin1 COLLATE latin1_bin NOT NULL, - `page_restrictions` tinyblob NOT NULL, - `page_counter` bigint(20) unsigned NOT NULL DEFAULT '0', - `page_is_redirect` tinyint(3) unsigned NOT NULL DEFAULT '0', - `page_is_new` tinyint(3) unsigned NOT NULL DEFAULT '0', - `page_random` double unsigned NOT NULL, - `page_touched` binary(14) NOT NULL DEFAULT '\0\0\0\0\0\0\0\0\0\0\0\0\0\0', - `page_latest` int(10) unsigned NOT NULL, - `page_len` int(10) unsigned NOT NULL, - PRIMARY KEY (`page_id`), - UNIQUE KEY `name_title` (`page_namespace`,`page_title`), - KEY `page_random` (`page_random`), - KEY `page_len` (`page_len`) -) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=latin1; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Dumping data for table `mw_page` --- - -LOCK TABLES `mw_page` WRITE; -/*!40000 ALTER TABLE `mw_page` DISABLE KEYS */; -INSERT INTO `mw_page` VALUES (1,0,'Main_Page','',1,0,1,0.334989576352,'20101230131547',1,438); -/*!40000 ALTER TABLE `mw_page` ENABLE KEYS */; -UNLOCK TABLES; - --- --- Table structure for table `mw_page_props` --- - -DROP TABLE IF EXISTS `mw_page_props`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; -CREATE TABLE `mw_page_props` ( - `pp_page` int(11) NOT NULL, - `pp_propname` varbinary(60) NOT NULL, - `pp_value` blob NOT NULL, - UNIQUE KEY `pp_page_propname` (`pp_page`,`pp_propname`) -) ENGINE=InnoDB DEFAULT CHARSET=latin1; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Dumping data for table `mw_page_props` --- - -LOCK TABLES `mw_page_props` WRITE; -/*!40000 ALTER TABLE `mw_page_props` DISABLE KEYS */; -/*!40000 ALTER TABLE `mw_page_props` ENABLE KEYS */; -UNLOCK TABLES; - --- --- Table structure for table `mw_page_restrictions` --- - -DROP TABLE IF EXISTS `mw_page_restrictions`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; -CREATE TABLE `mw_page_restrictions` ( - `pr_page` int(11) NOT NULL, - `pr_type` varbinary(60) NOT NULL, - `pr_level` varbinary(60) NOT NULL, - `pr_cascade` tinyint(4) NOT NULL, - `pr_user` int(11) DEFAULT NULL, - `pr_expiry` varbinary(14) DEFAULT NULL, - `pr_id` int(10) unsigned NOT NULL AUTO_INCREMENT, - PRIMARY KEY (`pr_id`), - UNIQUE KEY `pr_pagetype` (`pr_page`,`pr_type`), - KEY `pr_typelevel` (`pr_type`,`pr_level`), - KEY `pr_level` (`pr_level`), - KEY `pr_cascade` (`pr_cascade`) -) ENGINE=InnoDB DEFAULT CHARSET=latin1; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Dumping data for table `mw_page_restrictions` --- - -LOCK TABLES `mw_page_restrictions` WRITE; -/*!40000 ALTER TABLE `mw_page_restrictions` DISABLE KEYS */; -/*!40000 ALTER TABLE `mw_page_restrictions` ENABLE KEYS */; -UNLOCK TABLES; - --- --- Table structure for table `mw_pagelinks` --- - -DROP TABLE IF EXISTS `mw_pagelinks`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; -CREATE TABLE `mw_pagelinks` ( - `pl_from` int(10) unsigned NOT NULL DEFAULT '0', - `pl_namespace` int(11) NOT NULL DEFAULT '0', - `pl_title` varchar(255) CHARACTER SET latin1 COLLATE latin1_bin NOT NULL DEFAULT '', - UNIQUE KEY `pl_from` (`pl_from`,`pl_namespace`,`pl_title`), - UNIQUE KEY `pl_namespace` (`pl_namespace`,`pl_title`,`pl_from`) -) ENGINE=InnoDB DEFAULT CHARSET=latin1; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Dumping data for table `mw_pagelinks` --- - -LOCK TABLES `mw_pagelinks` WRITE; -/*!40000 ALTER TABLE `mw_pagelinks` DISABLE KEYS */; -/*!40000 ALTER TABLE `mw_pagelinks` ENABLE KEYS */; -UNLOCK TABLES; - --- --- Table structure for table `mw_protected_titles` --- - -DROP TABLE IF EXISTS `mw_protected_titles`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; -CREATE TABLE `mw_protected_titles` ( - `pt_namespace` int(11) NOT NULL, - `pt_title` varchar(255) CHARACTER SET latin1 COLLATE latin1_bin NOT NULL, - `pt_user` int(10) unsigned NOT NULL, - `pt_reason` tinyblob, - `pt_timestamp` binary(14) NOT NULL, - `pt_expiry` varbinary(14) NOT NULL DEFAULT '', - `pt_create_perm` varbinary(60) NOT NULL, - UNIQUE KEY `pt_namespace_title` (`pt_namespace`,`pt_title`), - KEY `pt_timestamp` (`pt_timestamp`) -) ENGINE=InnoDB DEFAULT CHARSET=latin1; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Dumping data for table `mw_protected_titles` --- - -LOCK TABLES `mw_protected_titles` WRITE; -/*!40000 ALTER TABLE `mw_protected_titles` DISABLE KEYS */; -/*!40000 ALTER TABLE `mw_protected_titles` ENABLE KEYS */; -UNLOCK TABLES; - --- --- Table structure for table `mw_querycache` --- - -DROP TABLE IF EXISTS `mw_querycache`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; -CREATE TABLE `mw_querycache` ( - `qc_type` varbinary(32) NOT NULL, - `qc_value` int(10) unsigned NOT NULL DEFAULT '0', - `qc_namespace` int(11) NOT NULL DEFAULT '0', - `qc_title` varchar(255) CHARACTER SET latin1 COLLATE latin1_bin NOT NULL DEFAULT '', - KEY `qc_type` (`qc_type`,`qc_value`) -) ENGINE=InnoDB DEFAULT CHARSET=latin1; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Dumping data for table `mw_querycache` --- - -LOCK TABLES `mw_querycache` WRITE; -/*!40000 ALTER TABLE `mw_querycache` DISABLE KEYS */; -/*!40000 ALTER TABLE `mw_querycache` ENABLE KEYS */; -UNLOCK TABLES; - --- --- Table structure for table `mw_querycache_info` --- - -DROP TABLE IF EXISTS `mw_querycache_info`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; -CREATE TABLE `mw_querycache_info` ( - `qci_type` varbinary(32) NOT NULL DEFAULT '', - `qci_timestamp` binary(14) NOT NULL DEFAULT '19700101000000', - UNIQUE KEY `qci_type` (`qci_type`) -) ENGINE=InnoDB DEFAULT CHARSET=latin1; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Dumping data for table `mw_querycache_info` --- - -LOCK TABLES `mw_querycache_info` WRITE; -/*!40000 ALTER TABLE `mw_querycache_info` DISABLE KEYS */; -/*!40000 ALTER TABLE `mw_querycache_info` ENABLE KEYS */; -UNLOCK TABLES; - --- --- Table structure for table `mw_querycachetwo` --- - -DROP TABLE IF EXISTS `mw_querycachetwo`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; -CREATE TABLE `mw_querycachetwo` ( - `qcc_type` varbinary(32) NOT NULL, - `qcc_value` int(10) unsigned NOT NULL DEFAULT '0', - `qcc_namespace` int(11) NOT NULL DEFAULT '0', - `qcc_title` varchar(255) CHARACTER SET latin1 COLLATE latin1_bin NOT NULL DEFAULT '', - `qcc_namespacetwo` int(11) NOT NULL DEFAULT '0', - `qcc_titletwo` varchar(255) CHARACTER SET latin1 COLLATE latin1_bin NOT NULL DEFAULT '', - KEY `qcc_type` (`qcc_type`,`qcc_value`), - KEY `qcc_title` (`qcc_type`,`qcc_namespace`,`qcc_title`), - KEY `qcc_titletwo` (`qcc_type`,`qcc_namespacetwo`,`qcc_titletwo`) -) ENGINE=InnoDB DEFAULT CHARSET=latin1; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Dumping data for table `mw_querycachetwo` --- - -LOCK TABLES `mw_querycachetwo` WRITE; -/*!40000 ALTER TABLE `mw_querycachetwo` DISABLE KEYS */; -/*!40000 ALTER TABLE `mw_querycachetwo` ENABLE KEYS */; -UNLOCK TABLES; - --- --- Table structure for table `mw_recentchanges` --- - -DROP TABLE IF EXISTS `mw_recentchanges`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; -CREATE TABLE `mw_recentchanges` ( - `rc_id` int(11) NOT NULL AUTO_INCREMENT, - `rc_timestamp` varbinary(14) NOT NULL DEFAULT '', - `rc_cur_time` varbinary(14) NOT NULL DEFAULT '', - `rc_user` int(10) unsigned NOT NULL DEFAULT '0', - `rc_user_text` varchar(255) CHARACTER SET latin1 COLLATE latin1_bin NOT NULL, - `rc_namespace` int(11) NOT NULL DEFAULT '0', - `rc_title` varchar(255) CHARACTER SET latin1 COLLATE latin1_bin NOT NULL DEFAULT '', - `rc_comment` varchar(255) CHARACTER SET latin1 COLLATE latin1_bin NOT NULL DEFAULT '', - `rc_minor` tinyint(3) unsigned NOT NULL DEFAULT '0', - `rc_bot` tinyint(3) unsigned NOT NULL DEFAULT '0', - `rc_new` tinyint(3) unsigned NOT NULL DEFAULT '0', - `rc_cur_id` int(10) unsigned NOT NULL DEFAULT '0', - `rc_this_oldid` int(10) unsigned NOT NULL DEFAULT '0', - `rc_last_oldid` int(10) unsigned NOT NULL DEFAULT '0', - `rc_type` tinyint(3) unsigned NOT NULL DEFAULT '0', - `rc_moved_to_ns` tinyint(3) unsigned NOT NULL DEFAULT '0', - `rc_moved_to_title` varchar(255) CHARACTER SET latin1 COLLATE latin1_bin NOT NULL DEFAULT '', - `rc_patrolled` tinyint(3) unsigned NOT NULL DEFAULT '0', - `rc_ip` varbinary(40) NOT NULL DEFAULT '', - `rc_old_len` int(11) DEFAULT NULL, - `rc_new_len` int(11) DEFAULT NULL, - `rc_deleted` tinyint(3) unsigned NOT NULL DEFAULT '0', - `rc_logid` int(10) unsigned NOT NULL DEFAULT '0', - `rc_log_type` varbinary(255) DEFAULT NULL, - `rc_log_action` varbinary(255) DEFAULT NULL, - `rc_params` blob, - PRIMARY KEY (`rc_id`), - KEY `rc_timestamp` (`rc_timestamp`), - KEY `rc_namespace_title` (`rc_namespace`,`rc_title`), - KEY `rc_cur_id` (`rc_cur_id`), - KEY `new_name_timestamp` (`rc_new`,`rc_namespace`,`rc_timestamp`), - KEY `rc_ip` (`rc_ip`), - KEY `rc_ns_usertext` (`rc_namespace`,`rc_user_text`), - KEY `rc_user_text` (`rc_user_text`,`rc_timestamp`) -) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=latin1; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Dumping data for table `mw_recentchanges` --- - -LOCK TABLES `mw_recentchanges` WRITE; -/*!40000 ALTER TABLE `mw_recentchanges` DISABLE KEYS */; -INSERT INTO `mw_recentchanges` VALUES (1,'20101230131547','20101230131547',0,'MediaWiki Default',0,'Main_Page','',0,0,1,1,1,0,1,0,'',0,'::1',0,438,0,0,NULL,'',''); -/*!40000 ALTER TABLE `mw_recentchanges` ENABLE KEYS */; -UNLOCK TABLES; - --- --- Table structure for table `mw_redirect` --- - -DROP TABLE IF EXISTS `mw_redirect`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; -CREATE TABLE `mw_redirect` ( - `rd_from` int(10) unsigned NOT NULL DEFAULT '0', - `rd_namespace` int(11) NOT NULL DEFAULT '0', - `rd_title` varchar(255) CHARACTER SET latin1 COLLATE latin1_bin NOT NULL DEFAULT '', - `rd_interwiki` varchar(32) DEFAULT NULL, - `rd_fragment` varchar(255) CHARACTER SET latin1 COLLATE latin1_bin DEFAULT NULL, - PRIMARY KEY (`rd_from`), - KEY `rd_ns_title` (`rd_namespace`,`rd_title`,`rd_from`) -) ENGINE=InnoDB DEFAULT CHARSET=latin1; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Dumping data for table `mw_redirect` --- - -LOCK TABLES `mw_redirect` WRITE; -/*!40000 ALTER TABLE `mw_redirect` DISABLE KEYS */; -/*!40000 ALTER TABLE `mw_redirect` ENABLE KEYS */; -UNLOCK TABLES; - --- --- Table structure for table `mw_revision` --- - -DROP TABLE IF EXISTS `mw_revision`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; -CREATE TABLE `mw_revision` ( - `rev_id` int(10) unsigned NOT NULL AUTO_INCREMENT, - `rev_page` int(10) unsigned NOT NULL, - `rev_text_id` int(10) unsigned NOT NULL, - `rev_comment` tinyblob NOT NULL, - `rev_user` int(10) unsigned NOT NULL DEFAULT '0', - `rev_user_text` varchar(255) CHARACTER SET latin1 COLLATE latin1_bin NOT NULL DEFAULT '', - `rev_timestamp` binary(14) NOT NULL DEFAULT '\0\0\0\0\0\0\0\0\0\0\0\0\0\0', - `rev_minor_edit` tinyint(3) unsigned NOT NULL DEFAULT '0', - `rev_deleted` tinyint(3) unsigned NOT NULL DEFAULT '0', - `rev_len` int(10) unsigned DEFAULT NULL, - `rev_parent_id` int(10) unsigned DEFAULT NULL, - PRIMARY KEY (`rev_id`), - UNIQUE KEY `rev_page_id` (`rev_page`,`rev_id`), - KEY `rev_timestamp` (`rev_timestamp`), - KEY `page_timestamp` (`rev_page`,`rev_timestamp`), - KEY `user_timestamp` (`rev_user`,`rev_timestamp`), - KEY `usertext_timestamp` (`rev_user_text`,`rev_timestamp`) -) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=latin1 MAX_ROWS=10000000 AVG_ROW_LENGTH=1024; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Dumping data for table `mw_revision` --- - -LOCK TABLES `mw_revision` WRITE; -/*!40000 ALTER TABLE `mw_revision` DISABLE KEYS */; -INSERT INTO `mw_revision` VALUES (1,1,1,'',0,'MediaWiki Default','20101230131547',0,0,438,0); -/*!40000 ALTER TABLE `mw_revision` ENABLE KEYS */; -UNLOCK TABLES; - --- --- Table structure for table `mw_searchindex` --- - -DROP TABLE IF EXISTS `mw_searchindex`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; -CREATE TABLE `mw_searchindex` ( - `si_page` int(10) unsigned NOT NULL, - `si_title` varchar(255) NOT NULL DEFAULT '', - `si_text` mediumtext NOT NULL, - UNIQUE KEY `si_page` (`si_page`), - FULLTEXT KEY `si_title` (`si_title`), - FULLTEXT KEY `si_text` (`si_text`) -) ENGINE=MyISAM DEFAULT CHARSET=latin1; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Dumping data for table `mw_searchindex` --- - -LOCK TABLES `mw_searchindex` WRITE; -/*!40000 ALTER TABLE `mw_searchindex` DISABLE KEYS */; -INSERT INTO `mw_searchindex` VALUES (1,'main page',' mediawiki hasu800 been successfully installed. consult theu800 user user\'su800 guide foru800 information onu800 using theu800 wiki software. getting started getting started getting started configuration settings list mediawiki faqu800 mediawiki release mailing list '); -/*!40000 ALTER TABLE `mw_searchindex` ENABLE KEYS */; -UNLOCK TABLES; - --- --- Table structure for table `mw_site_stats` --- - -DROP TABLE IF EXISTS `mw_site_stats`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; -CREATE TABLE `mw_site_stats` ( - `ss_row_id` int(10) unsigned NOT NULL, - `ss_total_views` bigint(20) unsigned DEFAULT '0', - `ss_total_edits` bigint(20) unsigned DEFAULT '0', - `ss_good_articles` bigint(20) unsigned DEFAULT '0', - `ss_total_pages` bigint(20) DEFAULT '-1', - `ss_users` bigint(20) DEFAULT '-1', - `ss_active_users` bigint(20) DEFAULT '-1', - `ss_admins` int(11) DEFAULT '-1', - `ss_images` int(11) DEFAULT '0', - UNIQUE KEY `ss_row_id` (`ss_row_id`) -) ENGINE=InnoDB DEFAULT CHARSET=latin1; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Dumping data for table `mw_site_stats` --- - -LOCK TABLES `mw_site_stats` WRITE; -/*!40000 ALTER TABLE `mw_site_stats` DISABLE KEYS */; -/*!40000 ALTER TABLE `mw_site_stats` ENABLE KEYS */; -UNLOCK TABLES; - --- --- Table structure for table `mw_tag_summary` --- - -DROP TABLE IF EXISTS `mw_tag_summary`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; -CREATE TABLE `mw_tag_summary` ( - `ts_rc_id` int(11) DEFAULT NULL, - `ts_log_id` int(11) DEFAULT NULL, - `ts_rev_id` int(11) DEFAULT NULL, - `ts_tags` blob NOT NULL, - UNIQUE KEY `tag_summary_rc_id` (`ts_rc_id`), - UNIQUE KEY `tag_summary_log_id` (`ts_log_id`), - UNIQUE KEY `tag_summary_rev_id` (`ts_rev_id`) -) ENGINE=InnoDB DEFAULT CHARSET=latin1; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Dumping data for table `mw_tag_summary` --- - -LOCK TABLES `mw_tag_summary` WRITE; -/*!40000 ALTER TABLE `mw_tag_summary` DISABLE KEYS */; -/*!40000 ALTER TABLE `mw_tag_summary` ENABLE KEYS */; -UNLOCK TABLES; - --- --- Table structure for table `mw_templatelinks` --- - -DROP TABLE IF EXISTS `mw_templatelinks`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; -CREATE TABLE `mw_templatelinks` ( - `tl_from` int(10) unsigned NOT NULL DEFAULT '0', - `tl_namespace` int(11) NOT NULL DEFAULT '0', - `tl_title` varchar(255) CHARACTER SET latin1 COLLATE latin1_bin NOT NULL DEFAULT '', - UNIQUE KEY `tl_from` (`tl_from`,`tl_namespace`,`tl_title`), - UNIQUE KEY `tl_namespace` (`tl_namespace`,`tl_title`,`tl_from`) -) ENGINE=InnoDB DEFAULT CHARSET=latin1; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Dumping data for table `mw_templatelinks` --- - -LOCK TABLES `mw_templatelinks` WRITE; -/*!40000 ALTER TABLE `mw_templatelinks` DISABLE KEYS */; -/*!40000 ALTER TABLE `mw_templatelinks` ENABLE KEYS */; -UNLOCK TABLES; - --- --- Table structure for table `mw_text` --- - -DROP TABLE IF EXISTS `mw_text`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; -CREATE TABLE `mw_text` ( - `old_id` int(10) unsigned NOT NULL AUTO_INCREMENT, - `old_text` mediumblob NOT NULL, - `old_flags` tinyblob NOT NULL, - PRIMARY KEY (`old_id`) -) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=latin1 MAX_ROWS=10000000 AVG_ROW_LENGTH=10240; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Dumping data for table `mw_text` --- - -LOCK TABLES `mw_text` WRITE; -/*!40000 ALTER TABLE `mw_text` DISABLE KEYS */; -INSERT INTO `mw_text` VALUES (1,'\'\'\'MediaWiki has been successfully installed.\'\'\'\n\nConsult the [http://meta.wikimedia.org/wiki/Help:Contents User\'s Guide] for information on using the wiki software.\n\n== Getting started ==\n* [http://www.mediawiki.org/wiki/Manual:Configuration_settings Configuration settings list]\n* [http://www.mediawiki.org/wiki/Manual:FAQ MediaWiki FAQ]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki release mailing list]','utf-8'); -/*!40000 ALTER TABLE `mw_text` ENABLE KEYS */; -UNLOCK TABLES; - --- --- Table structure for table `mw_trackbacks` --- - -DROP TABLE IF EXISTS `mw_trackbacks`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; -CREATE TABLE `mw_trackbacks` ( - `tb_id` int(11) NOT NULL AUTO_INCREMENT, - `tb_page` int(11) DEFAULT NULL, - `tb_title` varchar(255) NOT NULL, - `tb_url` blob NOT NULL, - `tb_ex` text, - `tb_name` varchar(255) DEFAULT NULL, - PRIMARY KEY (`tb_id`), - KEY `tb_page` (`tb_page`) -) ENGINE=InnoDB DEFAULT CHARSET=latin1; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Dumping data for table `mw_trackbacks` --- - -LOCK TABLES `mw_trackbacks` WRITE; -/*!40000 ALTER TABLE `mw_trackbacks` DISABLE KEYS */; -/*!40000 ALTER TABLE `mw_trackbacks` ENABLE KEYS */; -UNLOCK TABLES; - --- --- Table structure for table `mw_transcache` --- - -DROP TABLE IF EXISTS `mw_transcache`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; -CREATE TABLE `mw_transcache` ( - `tc_url` varbinary(255) NOT NULL, - `tc_contents` text, - `tc_time` binary(14) NOT NULL, - UNIQUE KEY `tc_url_idx` (`tc_url`) -) ENGINE=InnoDB DEFAULT CHARSET=latin1; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Dumping data for table `mw_transcache` --- - -LOCK TABLES `mw_transcache` WRITE; -/*!40000 ALTER TABLE `mw_transcache` DISABLE KEYS */; -/*!40000 ALTER TABLE `mw_transcache` ENABLE KEYS */; -UNLOCK TABLES; - --- --- Table structure for table `mw_updatelog` --- - -DROP TABLE IF EXISTS `mw_updatelog`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; -CREATE TABLE `mw_updatelog` ( - `ul_key` varchar(255) NOT NULL, - `ul_value` blob, - PRIMARY KEY (`ul_key`) -) ENGINE=InnoDB DEFAULT CHARSET=latin1; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Dumping data for table `mw_updatelog` --- - -LOCK TABLES `mw_updatelog` WRITE; -/*!40000 ALTER TABLE `mw_updatelog` DISABLE KEYS */; -/*!40000 ALTER TABLE `mw_updatelog` ENABLE KEYS */; -UNLOCK TABLES; - --- --- Table structure for table `mw_user` --- - -DROP TABLE IF EXISTS `mw_user`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; -CREATE TABLE `mw_user` ( - `user_id` int(10) unsigned NOT NULL AUTO_INCREMENT, - `user_name` varchar(255) CHARACTER SET latin1 COLLATE latin1_bin NOT NULL DEFAULT '', - `user_real_name` varchar(255) CHARACTER SET latin1 COLLATE latin1_bin NOT NULL DEFAULT '', - `user_password` tinyblob NOT NULL, - `user_newpassword` tinyblob NOT NULL, - `user_newpass_time` binary(14) DEFAULT NULL, - `user_email` tinytext NOT NULL, - `user_touched` binary(14) NOT NULL DEFAULT '\0\0\0\0\0\0\0\0\0\0\0\0\0\0', - `user_token` binary(32) NOT NULL DEFAULT '\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0', - `user_email_authenticated` binary(14) DEFAULT NULL, - `user_email_token` binary(32) DEFAULT NULL, - `user_email_token_expires` binary(14) DEFAULT NULL, - `user_registration` binary(14) DEFAULT NULL, - `user_editcount` int(11) DEFAULT NULL, - PRIMARY KEY (`user_id`), - UNIQUE KEY `user_name` (`user_name`), - KEY `user_email_token` (`user_email_token`) -) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=latin1; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Dumping data for table `mw_user` --- - -LOCK TABLES `mw_user` WRITE; -/*!40000 ALTER TABLE `mw_user` DISABLE KEYS */; -INSERT INTO `mw_user` VALUES (1,'WikiSysop','',':B:b1373470:f7e87db0c9596055f39a1225b0c31508','',NULL,'','','20101230131552','de4ddde7c4eef6e3609f4287324a0a18',NULL,'\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0',NULL,'20101230131547',0); -/*!40000 ALTER TABLE `mw_user` ENABLE KEYS */; -UNLOCK TABLES; - --- --- Table structure for table `mw_user_groups` --- - -DROP TABLE IF EXISTS `mw_user_groups`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; -CREATE TABLE `mw_user_groups` ( - `ug_user` int(10) unsigned NOT NULL DEFAULT '0', - `ug_group` varbinary(16) NOT NULL DEFAULT '', - UNIQUE KEY `ug_user_group` (`ug_user`,`ug_group`), - KEY `ug_group` (`ug_group`) -) ENGINE=InnoDB DEFAULT CHARSET=latin1; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Dumping data for table `mw_user_groups` --- - -LOCK TABLES `mw_user_groups` WRITE; -/*!40000 ALTER TABLE `mw_user_groups` DISABLE KEYS */; -INSERT INTO `mw_user_groups` VALUES (1,'bureaucrat'); -INSERT INTO `mw_user_groups` VALUES (1,'sysop'); -/*!40000 ALTER TABLE `mw_user_groups` ENABLE KEYS */; -UNLOCK TABLES; - --- --- Table structure for table `mw_user_newtalk` --- - -DROP TABLE IF EXISTS `mw_user_newtalk`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; -CREATE TABLE `mw_user_newtalk` ( - `user_id` int(11) NOT NULL DEFAULT '0', - `user_ip` varbinary(40) NOT NULL DEFAULT '', - `user_last_timestamp` binary(14) NOT NULL DEFAULT '\0\0\0\0\0\0\0\0\0\0\0\0\0\0', - KEY `user_id` (`user_id`), - KEY `user_ip` (`user_ip`) -) ENGINE=InnoDB DEFAULT CHARSET=latin1; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Dumping data for table `mw_user_newtalk` --- - -LOCK TABLES `mw_user_newtalk` WRITE; -/*!40000 ALTER TABLE `mw_user_newtalk` DISABLE KEYS */; -/*!40000 ALTER TABLE `mw_user_newtalk` ENABLE KEYS */; -UNLOCK TABLES; - --- --- Table structure for table `mw_user_properties` --- - -DROP TABLE IF EXISTS `mw_user_properties`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; -CREATE TABLE `mw_user_properties` ( - `up_user` int(11) NOT NULL, - `up_property` varbinary(32) NOT NULL, - `up_value` blob, - UNIQUE KEY `user_properties_user_property` (`up_user`,`up_property`), - KEY `user_properties_property` (`up_property`) -) ENGINE=InnoDB DEFAULT CHARSET=latin1; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Dumping data for table `mw_user_properties` --- - -LOCK TABLES `mw_user_properties` WRITE; -/*!40000 ALTER TABLE `mw_user_properties` DISABLE KEYS */; -/*!40000 ALTER TABLE `mw_user_properties` ENABLE KEYS */; -UNLOCK TABLES; - --- --- Table structure for table `mw_valid_tag` --- - -DROP TABLE IF EXISTS `mw_valid_tag`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; -CREATE TABLE `mw_valid_tag` ( - `vt_tag` varchar(255) NOT NULL, - PRIMARY KEY (`vt_tag`) -) ENGINE=InnoDB DEFAULT CHARSET=latin1; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Dumping data for table `mw_valid_tag` --- - -LOCK TABLES `mw_valid_tag` WRITE; -/*!40000 ALTER TABLE `mw_valid_tag` DISABLE KEYS */; -/*!40000 ALTER TABLE `mw_valid_tag` ENABLE KEYS */; -UNLOCK TABLES; - --- --- Table structure for table `mw_watchlist` --- - -DROP TABLE IF EXISTS `mw_watchlist`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; -CREATE TABLE `mw_watchlist` ( - `wl_user` int(10) unsigned NOT NULL, - `wl_namespace` int(11) NOT NULL DEFAULT '0', - `wl_title` varchar(255) CHARACTER SET latin1 COLLATE latin1_bin NOT NULL DEFAULT '', - `wl_notificationtimestamp` varbinary(14) DEFAULT NULL, - UNIQUE KEY `wl_user` (`wl_user`,`wl_namespace`,`wl_title`), - KEY `namespace_title` (`wl_namespace`,`wl_title`) -) ENGINE=InnoDB DEFAULT CHARSET=latin1; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Dumping data for table `mw_watchlist` --- - -LOCK TABLES `mw_watchlist` WRITE; -/*!40000 ALTER TABLE `mw_watchlist` DISABLE KEYS */; -/*!40000 ALTER TABLE `mw_watchlist` ENABLE KEYS */; -UNLOCK TABLES; -/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */; - -/*!40101 SET SQL_MODE=@OLD_SQL_MODE */; -/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; -/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */; -/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; -/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; -/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; -/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; - --- Dump completed on 2010-12-31 1:20:11 diff --git a/tests/selenium/installer/MediaWikiButtonsAvailabilityTestCase.php b/tests/selenium/installer/MediaWikiButtonsAvailabilityTestCase.php deleted file mode 100644 index 8bca4b0d..00000000 --- a/tests/selenium/installer/MediaWikiButtonsAvailabilityTestCase.php +++ /dev/null @@ -1,102 +0,0 @@ -<?php - -/** - * MediaWikiButtonsAvailabilityTestCase - * - * @file - * @ingroup Maintenance - * Copyright (C) 2010 Nadeesha Weerasinghe <nadeesha@calcey.com> - * http://www.calcey.com/ - * - * 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., - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - * http://www.gnu.org/copyleft/gpl.html - * - * @addtogroup Maintenance - * - */ - - -require_once (__DIR__.'/'.'MediaWikiInstallationCommonFunction.php'); - -/** - * Test Case ID : 30 (http://www.mediawiki.org/wiki/New_installer/Test_plan) - * Test Case Name :'Back' and 'Continue' button availability - * Version : MediaWiki 1.18alpha -*/ - - -class MediaWikiButtonsAvailabilityTestCase extends MediaWikiInstallationCommonFunction { - - function setUp() { - parent::setUp(); - } - - - // Verify only 'Continue' button available on 'Language' page - public function testOnlyContinueButtonAvailability() { - - parent::navigateLanguagePage(); - - // Verify only 'Continue' button avaialble - $this->assertTrue( $this->isElementPresent( "submit-continue" )); - - // 'Back' button is not avaialble - $this->assertElementNotPresent( "submit-back" ); - } - - - // Verify 'Continue' and 'Back' buttons availability - public function testBothButtonsAvailability() { - - // Verify buttons availability on 'Welcome to MediaWiki' page - parent::navigateWelcometoMediaWikiPage(); - $this->assertTrue( $this->isElementPresent( "submit-back" )); - $this->assertTrue( $this->isElementPresent( "submit-continue" )); - parent::restartInstallation(); - - // Verify buttons availability on 'Connect to Database' page - parent::navigateConnetToDatabasePage(); - $this->assertTrue( $this->isElementPresent( "submit-back" )); - $this->assertTrue( $this->isElementPresent( "submit-continue" )); - parent::restartInstallation(); - - // Verify buttons availability on 'Database settings' page - $databaseName = DB_NAME_PREFIX."_db_settings"; - parent::navigateDatabaseSettingsPage( $databaseName ); - $this->assertTrue( $this->isElementPresent( "submit-back" )); - $this->assertTrue( $this->isElementPresent( "submit-continue" )); - parent::restartInstallation(); - - // Verify buttons availability on 'Name' page - $databaseName = DB_NAME_PREFIX."_name"; - parent::navigateNamePage( $databaseName ); - $this->assertTrue( $this->isElementPresent( "submit-back" )); - $this->assertTrue( $this->isElementPresent( "submit-continue" )); - parent::restartInstallation(); - - // Verify buttons availability on 'Options' page - $databaseName = DB_NAME_PREFIX."_options"; - parent::navigateOptionsPage( $databaseName ); - $this->assertTrue( $this->isElementPresent( "submit-back" )); - $this->assertTrue( $this->isElementPresent( "submit-continue" )); - parent::restartInstallation(); - - // Verify buttons availability on 'Install' page - $databaseName = DB_NAME_PREFIX."_install"; - parent::navigateInstallPage($databaseName); - $this->assertTrue( $this->isElementPresent( "submit-back" )); - $this->assertTrue( $this->isElementPresent( "submit-continue" )); - } -} diff --git a/tests/selenium/installer/MediaWikiDifferentDatabaseAccountTestCase.php b/tests/selenium/installer/MediaWikiDifferentDatabaseAccountTestCase.php deleted file mode 100644 index 8e2afe73..00000000 --- a/tests/selenium/installer/MediaWikiDifferentDatabaseAccountTestCase.php +++ /dev/null @@ -1,82 +0,0 @@ -<?php - -/** - * MediaWikiDifferentDatabaseAccountTestCase - * - * @file - * @ingroup Maintenance - * Copyright (C) 2010 Nadeesha Weerasinghe <nadeesha@calcey.com> - * http://www.calcey.com/ - * - * 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., - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - * http://www.gnu.org/copyleft/gpl.html - * - * @addtogroup Maintenance - * - */ - - -require_once ( __DIR__ . '/MediaWikiInstallationCommonFunction.php' ); - -/** - * Test Case ID : 04 (http://www.mediawiki.org/wiki/New_installer/Test_plan) - * Test Case Name : Install MediaWiki with different Database accounts for web access. - * Version : MediaWiki 1.18alpha -*/ - -class MediaWikiDifferentDatabaseAccountTestCase extends MediaWikiInstallationCommonFunction { - - function setUp() { - parent::setUp(); - } - - - // Install Mediawiki using 'MySQL' database type. - public function testDifferentDatabaseAccount() { - - $databaseName = DB_NAME_PREFIX."_dif_accounts"; - - // Navigate to the 'Database settings' page - parent::navigateDatabaseSettingsPage( $databaseName ); - - // Click on the 'Use the same account as for installation' check box - $this->click( "mysql__SameAccount" ); - - // Change the 'Database username' - $this->type( "mysql_wgDBuser", DB_WEB_USER ); - - // Enter 'Database password:' - $this->type( "mysql_wgDBpassword", DB_WEB_USER_PASSWORD ); - - // Select 'Create the account if it does not already exist' check box - $this->click( "mysql__CreateDBAccount" ); - parent::clickContinueButton(); - - // 'Name' page - parent::completeNamePage(); - - // 'Options' page - parent::clickContinueButton(); - - // 'Install' page - $this->assertEquals("Creating database user... done", - $this->getText( LINK_FORM."ul/li[3]")); - parent::clickContinueButton(); - - // 'Complete' page - parent::completePageSuccessfull(); - $this->chooseCancelOnNextConfirmation(); - } -} diff --git a/tests/selenium/installer/MediaWikiDifferntDatabasePrefixTestCase.php b/tests/selenium/installer/MediaWikiDifferntDatabasePrefixTestCase.php deleted file mode 100644 index 55ad4612..00000000 --- a/tests/selenium/installer/MediaWikiDifferntDatabasePrefixTestCase.php +++ /dev/null @@ -1,95 +0,0 @@ -<?php - -/** - * MediaWikiDifferntDatabasePrefixTestCase - * - * @file - * @ingroup Maintenance - * Copyright (C) 2010 Nadeesha Weerasinghe <nadeesha@calcey.com> - * http://www.calcey.com/ - * - * 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., - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - * http://www.gnu.org/copyleft/gpl.html - * - * @addtogroup Maintenance - * - */ - -require_once ( __DIR__ . '/MediaWikiInstallationCommonFunction.php' ); - -/** - * Test Case ID : 02 (http://www.mediawiki.org/wiki/New_installer/Test_plan) - * Test Case Name : Install MediaWiki with the same database and the different - * database prefixes(Share one database between multiple wikis). - * Version : MediaWiki 1.18alpha -*/ - -class MediaWikiDifferntDatabasePrefixTestCase extends MediaWikiInstallationCommonFunction { - - function setUp() { - parent::setUp(); - } - - // Install Mediawiki using 'MySQL' database type. - public function testDifferentDatabasePrefix() { - - $databaseName = DB_NAME_PREFIX."_db_prefix"; - parent::navigateInstallPage( $databaseName ); - - // To 'Options' page - parent::clickBackButton(); - - // To 'Name' page - parent::clickBackButton(); - - // To 'Database settings' page - parent::clickBackButton(); - - // To 'Connect to database' page - parent::clickBackButton(); - - // From 'Connect to database' page without database prefix - parent::clickContinueButton(); - - // Verify upgrade existing message - $this->assertEquals( "Upgrade existing installation", - $this->getText( LINK_DIV."h2" )); - - // To 'Connect to database' page - parent::clickBackButton(); - - // Input the database prefix - $this->type( "mysql_wgDBprefix", DATABASE_PREFIX ); - - // From 'Connect to database' page with database prefix - parent::clickContinueButton(); - - // To 'Complete' page - parent::clickContinueButton(); - parent::completeNamePage(); - parent::clickContinueButton(); - - // Verify already installed warning message - $this->assertEquals( "Install", - $this->getText( LINK_DIV."h2" )); - $this->assertEquals( "Warning: You seem to have already installed MediaWiki and are trying to install it again. Please proceed to the next page.", - $this->getText( LINK_FORM."div[1]" )); - - parent::clickContinueButton(); - parent::completePageSuccessfull(); - $this->chooseCancelOnNextConfirmation(); - parent::restartInstallation(); - } -} diff --git a/tests/selenium/installer/MediaWikiErrorsConnectToDatabasePageTestCase.php b/tests/selenium/installer/MediaWikiErrorsConnectToDatabasePageTestCase.php deleted file mode 100644 index 825ca424..00000000 --- a/tests/selenium/installer/MediaWikiErrorsConnectToDatabasePageTestCase.php +++ /dev/null @@ -1,136 +0,0 @@ -<?php - -/** - * MediaWikiErrorsConnectToDatabasePageTestCase - * - * @file - * @ingroup Maintenance - * Copyright (C) 2010 Nadeesha Weerasinghe <nadeesha@calcey.com> - * http://www.calcey.com/ - * - * 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., - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - * http://www.gnu.org/copyleft/gpl.html - * - * @addtogroup Maintenance - * - */ - - -require_once ( __DIR__ . '/MediaWikiInstallationCommonFunction.php' ); - -/** - * Test Case ID : 09 (http://www.mediawiki.org/wiki/New_installer/Test_plan) - * Test Case Name : Invalid/ blank values for fields in 'Connect to database' page. - * Version : MediaWiki 1.18alpha -*/ - -class MediaWikiErrorsConnectToDatabasePageTestCase extends MediaWikiInstallationCommonFunction { - - function setUp() { - parent::setUp(); - } - - // Verify warning messages for the 'Connet to database' page - public function testErrorsConnectToDatabasePage() { - - parent::navigateConnetToDatabasePage(); - - // Verify warning mesage for invalid database host - $this->type( "mysql_wgDBserver", INVALID_DB_HOST ); - parent::clickContinueButton(); - $this->assertEquals( "DB connection error: php_network_getaddresses: getaddrinfo failed: No such host is known. (".INVALID_DB_HOST.").", - $this->getText( LINK_DIV."div[2]/div[2]/p[1]" )); - $this->assertEquals( "Check the host, username and password below and try again.", - $this->getText( LINK_DIV."div[2]/div[2]/p[2]" )); - // Verify warning message for the blank database host - $this->type( "mysql_wgDBserver", "" ); - parent::clickContinueButton(); - $this->assertEquals( "MySQL 4.0.14 or later is required, you have .", - $this->getText( LINK_DIV."div[2]/div[2]" )); - - // Valid Database Host - $this->type( "mysql_wgDBserver", VALID_DB_HOST ); - - // Verify warning message for the invalid database name - $this->type( "mysql_wgDBname", INVALID_DB_NAME ); - parent::clickContinueButton(); - $this->assertEquals( "Invalid database name \"".INVALID_DB_NAME."\". Use only ASCII letters (a-z, A-Z), numbers (0-9) and underscores (_).", - $this->getText( LINK_DIV."div[2]/div[2]/p" )); - - // Verify warning message for the blank database name - $this->type( "mysql_wgDBname", ""); - parent::clickContinueButton(); - $this->assertEquals( "You must enter a value for \"Database name\"", - $this->getText( LINK_DIV."div[2]/div[2]" )); - - // valid Database name - $this->type( "mysql_wgDBname", VALID_DB_NAME); - - // Verify warning message for the invalid databaase prefix - $this->type( "mysql_wgDBprefix", INVALID_DB_PREFIX ); - parent::clickContinueButton(); - $this->assertEquals( "Invalid database prefix \"".INVALID_DB_PREFIX."\". Use only ASCII letters (a-z, A-Z), numbers (0-9) and underscores (_).", - $this->getText( LINK_DIV."div[2]/div[2]" )); - - // Valid Database prefix - $this->type( "mysql_wgDBprefix", VALID_DB_PREFIX ); - - // Verify warning message for the invalid database user name - $this->type( "mysql__InstallUser", INVALID_DB_USER_NAME ); - parent::clickContinueButton(); - $this->assertEquals( "DB connection error: Access denied for user '".INVALID_DB_USER_NAME."'@'localhost' (using password: NO) (localhost).", - $this->getText( LINK_DIV."div[2]/div[2]/p[1]" )); - $this->assertEquals( "Check the host, username and password below and try again.", - $this->getText( LINK_DIV."div[2]/div[2]/p[2]")); - - // Verify warning message for the blank database user name - $this->type( "mysql__InstallUser", "" ); - parent::clickContinueButton(); - $this->assertEquals( "DB connection error: Access denied for user 'SYSTEM'@'localhost' (using password: NO) (localhost).", - $this->getText( LINK_DIV."div[2]/div[2]/p[1]" )); - $this->assertEquals( "Check the host, username and password below and try again.", - $this->getText( LINK_DIV."div[2]/div[2]/p[2]" )); - - // Valid Database username - $this->type( "mysql__InstallUser", VALID_DB_USER_NAME ); - - // Verify warning message for the invalid password - $this->type( "mysql__InstallPassword", INVALID_DB_PASSWORD ); - parent::clickContinueButton(); - - $this->assertEquals( "DB connection error: Access denied for user 'root'@'localhost' (using password: YES) (localhost).", - $this->getText( LINK_DIV."div[2]/div[2]/p[1]" )); - $this->assertEquals( "Check the host, username and password below and try again.", - $this->getText( LINK_DIV."div[2]/div[2]/p[2]" )); - - // Verify warning message for the invalid username and password - $this->type( "mysql__InstallUser", INVALID_DB_USER_NAME ); - $this->type( "mysql__InstallPassword", INVALID_DB_PASSWORD ); - parent::clickContinueButton(); - $this->assertEquals( "DB connection error: Access denied for user '".INVALID_DB_USER_NAME."'@'localhost' (using password: YES) (localhost).", - $this->getText( LINK_DIV."div[2]/div[2]/p[1]" )); - $this->assertEquals( "Check the host, username and password below and try again.", - $this->getText( LINK_DIV."div[2]/div[2]/p[2]" )); - - // Valid username and valid password - $this->type( "mysql__InstallUser", VALID_DB_USER_NAME ); - $this->type( "mysql__InstallPassword", "" ); - parent::clickContinueButton(); - - // successfully completes the 'Connect to database' page - $this->assertEquals( "Database settings", - $this->getText( LINK_DIV."h2" )); - } -} diff --git a/tests/selenium/installer/MediaWikiErrorsNamepageTestCase.php b/tests/selenium/installer/MediaWikiErrorsNamepageTestCase.php deleted file mode 100644 index c2b35054..00000000 --- a/tests/selenium/installer/MediaWikiErrorsNamepageTestCase.php +++ /dev/null @@ -1,132 +0,0 @@ -<?php - -/** - * MediaWikiErrorsNamepageTestCase - * - * @file - * @ingroup Maintenance - * Copyright (C) 2010 Nadeesha Weerasinghe <nadeesha@calcey.com> - * http://www.calcey.com/ - * - * 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., - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - * http://www.gnu.org/copyleft/gpl.html - * - * @addtogroup Maintenance - * - */ - -/** - * Test Case ID : 10 (http://www.mediawiki.org/wiki/New_installer/Test_plan) - * Test Case Name : Invalid/ blank values for fields in 'Name' page. - * Version : MediaWiki 1.18alpha -*/ - -require_once ( __DIR__ . '/MediaWikiInstallationCommonFunction.php' ); - -class MediaWikiErrorsNamepageTestCase extends MediaWikiInstallationCommonFunction { - - function setUp() { - parent::setUp(); - } - - // Verify warning message for the 'Name' page - public function testErrorsNamePage() { - - $databaseName = DB_NAME_PREFIX."_error_name"; - - parent::navigateNamePage( $databaseName ); - - // Verify warning message for all blank fields - parent::clickContinueButton(); - $this->assertEquals( "Enter a site name.", - $this->getText( LINK_DIV."div[2]/div[2]" )); - $this->assertEquals( "Enter an administrator username.", - $this->getText( LINK_DIV."div[3]/div[2]" )); - $this->assertEquals( "Enter a password for the administrator account.", - $this->getText( LINK_DIV."div[4]/div[2]" )); - - // Verify warning message for the blank 'Site name' - $this->type( "config__AdminName", VALID_YOUR_NAME ); - $this->type( "config__AdminPassword", VALID_PASSWORD ); - $this->type( "config__AdminPassword2", VALID_PASSWORD_AGAIN ); - parent::clickContinueButton(); - $this->assertEquals( "Enter a site name.", - $this->getText( LINK_DIV."div[2]/div[2]" )); - - // Input valid 'Site name' - $this->type( "config_wgSitename", VALID_WIKI_NAME ); - - - // Verify warning message for the invalid "Project namespace' - $this->click( "config__NamespaceType_other" ); - $this->type( "config_wgMetaNamespace", INVALID_NAMESPACE ); - parent::clickContinueButton(); - $this->assertEquals( "The specified namespace \"\" is invalid. Specify a different project namespace.", - $this->getText( LINK_DIV."div[2]/div[2]" )); - - - // Verify warning message for the blank 'Project namespace' - $this->type( "config_wgSitename", VALID_WIKI_NAME ); - $this->click( "config__NamespaceType_other" ); - $this->type( "config_wgMetaNamespace" , "" ); - parent::clickContinueButton(); - $this->assertEquals( "The specified namespace \"\" is invalid. Specify a different project namespace.", - $this->getText( LINK_DIV."div[2]/div[2]" )); - - - // Valid 'Project namespace' - $this->click( "config__NamespaceType_other" ); - $this->type( "config_wgMetaNamespace", VALID_NAMESPACE ); - parent::clickContinueButton(); - - - // Valid 'Site name' - $this->click( "config__NamespaceType_site-name" ); - $this->type( "config_wgSitename", VALID_WIKI_NAME ); - - - // Verify warning message for blank 'Your name' - $this->type( "config__AdminName", " " ); - parent::clickContinueButton(); - $this->assertEquals( "Enter an administrator username.", - $this->getText( LINK_DIV."div[2]/div[2]" )); - - $this->type( "config_wgSitename", VALID_WIKI_NAME ); - // Verify warning message for blank 'Password' - $this->type( "config__AdminName", VALID_YOUR_NAME ); - $this->type( "config__AdminPassword", " " ); - parent::clickContinueButton(); - $this->assertEquals( "Enter a password for the administrator account.", - $this->getText( LINK_DIV."div[2]/div[2]" )); - - - // Verify warning message for the blank 'Password again' - $this->type( "config_wgSitename", VALID_WIKI_NAME ); - $this->type( "config__AdminPassword", VALID_PASSWORD ); - $this->type( "config__AdminPassword2", " " ); - parent::clickContinueButton(); - $this->assertEquals( "The two passwords you entered do not match.", - $this->getText( LINK_DIV."div[2]/div[2]" )); - - - // Verify warning message for the different'Password' and 'Password again' - $this->type( "config_wgSitename", VALID_WIKI_NAME ); - $this->type( "config__AdminPassword", VALID_PASSWORD ); - $this->type( "config__AdminPassword2", INVALID_PASSWORD_AGAIN ); - parent::clickContinueButton(); - $this->assertEquals( "The two passwords you entered do not match.", - $this->getText( LINK_DIV."div[2]/div[2]" )); - } -} diff --git a/tests/selenium/installer/MediaWikiHelpFieldHintTestCase.php b/tests/selenium/installer/MediaWikiHelpFieldHintTestCase.php deleted file mode 100644 index 78205cf8..00000000 --- a/tests/selenium/installer/MediaWikiHelpFieldHintTestCase.php +++ /dev/null @@ -1,140 +0,0 @@ -<?php - -/** - * MediaWikiHelpFieldHintTestCase - * - * @file - * @ingroup Maintenance - * Copyright (C) 2010 Nadeesha Weerasinghe <nadeesha@calcey.com> - * http://www.calcey.com/ - * - * 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., - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - * http://www.gnu.org/copyleft/gpl.html - * - * @addtogroup Maintenance - * - */ - -/** - * Test Case ID : 29 (http://www.mediawiki.org/wiki/New_installer/Test_plan) - * Test Case Name : Help field hint availability for the fields. - * Version : MediaWiki 1.18alpha -*/ - -require_once ( __DIR__ . '/MediaWikiInstallationCommonFunction.php' ); - -class MediaWikiHelpFieldHintTestCase extends MediaWikiInstallationCommonFunction { - - function setUp() { - parent::setUp(); - } - - - // Verify help field availability for the fields - public function testMySQLConnectToDatabaseFieldHint() { - - parent::navigateConnetToDatabasePage(); - - // Verify help field for 'Database host' - $this->click( "//div[@id='DB_wrapper_mysql']/div/div[1]/div/span[1]" ); - $this->assertEquals( MYSQL_DATABASE_HOST_HELP, - $this->getText( "//div[@id='DB_wrapper_mysql']/div/div[1]/div/span[2]" ) ); - - // Verify help field for 'Database name' - $this->click( "//div[@id='DB_wrapper_mysql']/fieldset[1]/div[1]/div[1]/div/span[1]" ); - $this->assertEquals( MYSQL_DATABASE_NAME_HELP, - $this->getText( "//div[@id='DB_wrapper_mysql']/fieldset[1]/div[1]/div[1]/div/span[2]" )); - - - // Verify help field for 'Database table prefix' - $this->click("//div[@id='DB_wrapper_mysql']/fieldset[1]/div[2]/div[1]/div/span[1]" ); - $this->assertEquals(MYSQL_DATABASE_TABLE_PREFIX_HELP, - $this->getText("//div[@id='DB_wrapper_mysql']/fieldset[1]/div[1]/div[1]/div/span[2]/p[1]" )); - - // Verify help field for 'Database username' - $this->click( "//div[@id='DB_wrapper_mysql']/fieldset[2]/div[1]/div[1]/div/span[1]" ); - $this->assertEquals( MYSQL_DATBASE_USERNAME_HELP, - $this->getText( "//div[@id='DB_wrapper_mysql']/fieldset[2]/div[1]/div[1]/div/span[2]" )); - - // Verify help field for 'Database password' - $this->click( "//div[@id='DB_wrapper_mysql']/fieldset[2]/div[2]/div[1]/div/span[1]" ); - $this->assertEquals( MYSQL_DATABASE_PASSWORD_HELP, - $this->getText( "//div[@id='DB_wrapper_mysql']/fieldset[2]/div[2]/div[1]/div/span[2]/p" )); - } - - - public function testSQLiteConnectToDatabaseFieldHint() { - - parent::navigateConnetToDatabasePage(); - $this->click( "DBType_sqlite" ); - - // Verify help field for 'SQLite data directory' - $this->click( "//div[@id='DB_wrapper_sqlite']/div[1]/div[1]/div/span[1]" ); - $this->assertEquals( SQLITE_DATA_DIRECTORY_HELP, - $this->getText( "//div[@id='DB_wrapper_sqlite']/div[1]/div[1]/div/span[2]" )); - - // Verify help field for 'Database name' - $this->click( "//div[@id='DB_wrapper_sqlite']/div[2]/div[1]/div/span[1]" ); - $this->assertEquals( SQLITE_DATABASE_NAME_HELP , $this->getText( "//div[@id='DB_wrapper_sqlite']/div[2]/div[1]/div/span[2]/p" )); - } - - - public function testDatabaseSettingsFieldHint() { - - $databaseName = DB_NAME_PREFIX."_db_field"; - parent::navigateDatabaseSettingsPage($databaseName); - - // Verify help field for 'Search engine' - $this->click( LINK_FORM."div[2]/span[1]" ); - $this->assertEquals( SEARCH_ENGINE_HELP, - $this->getText( LINK_FORM."div[2]/span[2]" )); - - // Verify help field for 'Database character set' - $this->click( LINK_FORM."div[4]/span[1]" ); - $this->assertEquals( DATABASE_CHARACTER_SET_HELP, - $this->getText( LINK_FORM."div[4]/span[2]")); - parent::restartInstallation(); - } - - - public function testNameFieldHint() { - - $databaseName = DB_NAME_PREFIX."_name_field"; - parent::navigateNamePage( $databaseName ); - - // Verify help field for 'Name of Wiki' - $this->click( LINK_FORM."div[1]/div[1]/div/span[1]" ); - $this->assertEquals( NAME_OF_WIKI_HELP, - $this->getText( LINK_FORM."div[1]/div[1]/div/span[2]/p" )); - - // Verify help field for 'Project namespace' - $this->click( LINK_FORM."div[2]/div[1]/div/span[1]" ); - $this->assertEquals( PROJECT_NAMESPACE_HELP, - $this->getText( LINK_FORM."div[2]/div[1]/div/span[2]/p")); - - // Verify help field for 'Your Name' - $this->click( LINK_FORM."fieldset/div[1]/div[1]/div/span[1]" ); - $this->assertEquals( USER_NAME_HELP, - $this->getText( LINK_FORM."fieldset/div[1]/div[1]/div/span[2]/p" )); - - // Verify help field for 'E mail address' - $this->click( LINK_FORM."fieldset/div[4]/div[1]/div/span[1]" ); - $this->assertEquals( EMAIL_ADDRESS_HELP, - $this->getText( LINK_FORM."fieldset/div[4]/div[1]/div/span[2]/p" )); - - parent::restartInstallation(); - } -} - diff --git a/tests/selenium/installer/MediaWikiInstallationCommonFunction.php b/tests/selenium/installer/MediaWikiInstallationCommonFunction.php deleted file mode 100644 index 353fa2ee..00000000 --- a/tests/selenium/installer/MediaWikiInstallationCommonFunction.php +++ /dev/null @@ -1,283 +0,0 @@ -<?php -/** - * MediaWikiInstallationCommonFunction - * - * @file - * @ingroup Maintenance - * Copyright (C) 2010 Nadeesha Weerasinghe <nadeesha@calcey.com> - * http://www.calcey.com/ - * - * 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., - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - * http://www.gnu.org/copyleft/gpl.html - * - * @addtogroup Maintenance - * - */ - -require_once 'PHPUnit/Extensions/SeleniumTestCase.php'; -require_once ( __DIR__ . '/MediaWikiInstallationConfig.php' ); -require_once ( __DIR__ . '/MediaWikiInstallationMessage.php' ); -require_once ( __DIR__ . '/MediaWikiInstallationVariables.php'); - - -class MediaWikiInstallationCommonFunction extends PHPUnit_Extensions_SeleniumTestCase { - - function setUp() { - $this->setBrowser( TEST_BROWSER ); - $this->setBrowserUrl("http://".HOST_NAME.":".PORT."/".DIRECTORY_NAME."/"); - } - - - public function navigateInitialpage() { - $this->open( "http://".HOST_NAME.":".PORT."/".DIRECTORY_NAME."/" ); - } - - - // Navigate to the 'Language' page - public function navigateLanguagePage() { - $this->open( "http://".HOST_NAME.":".PORT."/".DIRECTORY_NAME."/config/index.php" ); - } - - - // Navigate to the 'Welcome to MediaWiki' page - public function navigateWelcometoMediaWikiPage() { - $this->open( "http://".HOST_NAME.":".PORT."/".DIRECTORY_NAME."/config/index.php" ); - $this->click( "submit-continue "); - $this->waitForPageToLoad( PAGE_LOAD_TIME ); - } - - - // Navigate yo 'Connect to Database' page - public function navigateConnetToDatabasePage() { - $this->open( "http://".HOST_NAME.":".PORT."/".DIRECTORY_NAME."/config/index.php" ); - - // 'Welcome to MediaWiki!' page - $this->click( "submit-continue" ); - $this->waitForPageToLoad( PAGE_LOAD_TIME ); - - // 'Connect to Database' page - $this->click("submit-continue"); - $this->waitForPageToLoad( PAGE_LOAD_TIME ); - } - - - // Navigate to the 'Database Settings' page - public function navigateDatabaseSettingsPage( $databaseName ) { - - $this->open( "http://".HOST_NAME.":".PORT."/".DIRECTORY_NAME."/config/index.php" ); - - // 'Welcome to MediaWiki!' page - $this->click("submit-continue"); - $this->waitForPageToLoad( PAGE_LOAD_TIME ); - - // 'Connect to Database' page - $this->click("submit-continue"); - $this->waitForPageToLoad( PAGE_LOAD_TIME ); - - $this->type("mysql_wgDBname", $databaseName ); - $this->click( "submit-continue" ); - $this->waitForPageToLoad( PAGE_LOAD_TIME ); - } - - - // Navigate to the 'Name' page - public function navigateNamePage( $databaseName ) { - $this->open( "http://".HOST_NAME.":".PORT."/".DIRECTORY_NAME."/config/index.php" ); - - // 'Welcome to MediaWiki!' page - $this->click( "submit-continue" ); - $this->waitForPageToLoad( PAGE_LOAD_TIME ); - - // 'Connect to Database' page - $this->click( "submit-continue" ); - $this->waitForPageToLoad( PAGE_LOAD_TIME ); - - $this->type( "mysql_wgDBname", $databaseName ); - $this->click( "submit-continue" ); - $this->waitForPageToLoad( PAGE_LOAD_TIME ); - - // Database settings - $this->click( "submit-continue" ); - $this->waitForPageToLoad( PAGE_LOAD_TIME ); - } - - - // Navigate 'Options' page - public function navigateOptionsPage( $databaseName ) { - - $this->open( "http://".HOST_NAME.":".PORT."/".DIRECTORY_NAME."/config/index.php" ); - - // 'Welcome to MediaWiki!' page - $this->click( "submit-continue" ); - $this->waitForPageToLoad( PAGE_LOAD_TIME ); - - // 'Connect to Database' page - $this->click( "submit-continue" ); - $this->waitForPageToLoad( PAGE_LOAD_TIME ); - - $this->type( "mysql_wgDBname", $databaseName ); - $this->click( "submit-continue" ); - $this->waitForPageToLoad( PAGE_LOAD_TIME ); - - // Database settings - $this->click( "submit-continue" ); - $this->waitForPageToLoad( PAGE_LOAD_TIME ); - - // Name - $this->type( "config_wgSitename", NAME_OF_WIKI ); - $this->type( "config__AdminName", ADMIN_USER_NAME); - $this->type( "config__AdminPassword", ADMIN_PASSWORD ); - $this->type( "config__AdminPassword2", ADMIN_RETYPE_PASSWORD ); - $this->type( "config__AdminEmail", ADMIN_EMAIL_ADDRESS ); - - $this->click( "submit-continue" ); - $this->waitForPageToLoad( PAGE_LOAD_TIME ); - } - - - // Navigate 'Install' page - public function navigateInstallPage( $databaseName ) { - - $this->open( "http://".HOST_NAME.":".PORT."/".DIRECTORY_NAME."/config/index.php" ); - - // 'Welcome to MediaWiki!' page - $this->click( "submit-continue" ); - $this->waitForPageToLoad( PAGE_LOAD_TIME ); - - // 'Connect to Database' page - $this->click( "submit-continue" ); - $this->waitForPageToLoad( PAGE_LOAD_TIME ); - - $this->type( "mysql_wgDBname", $databaseName ); - $this->click( "submit-continue" ); - $this->waitForPageToLoad( PAGE_LOAD_TIME ); - - // Database settings - $this->click( "submit-continue" ); - $this->waitForPageToLoad( PAGE_LOAD_TIME ); - - // Name - $this->type( "config_wgSitename", NAME_OF_WIKI ); - $this->type( "config__AdminName", ADMIN_USER_NAME); - $this->type( "config__AdminPassword", ADMIN_PASSWORD ); - $this->type( "config__AdminPassword2", ADMIN_RETYPE_PASSWORD ); - $this->type( "config__AdminEmail", ADMIN_EMAIL_ADDRESS ); - - $this->click( "submit-continue" ); - $this->waitForPageToLoad( PAGE_LOAD_TIME ); - - // Options page - $this->click( "submit-continue" ); - $this->waitForPageToLoad( PAGE_LOAD_TIME ); - } - - - // Navigate to 'Complete' page - public function navigateCompletePage( $databaseName ) { - $this->open( "http://".HOST_NAME.":".PORT."/".DIRECTORY_NAME."/config/index.php" ); - - // 'Welcome to MediaWiki!' page - $this->click( "submit-continue" ); - $this->waitForPageToLoad( PAGE_LOAD_TIME ); - - // 'Connect to Database' page - $this->click( "submit-continue" ); - $this->waitForPageToLoad( PAGE_LOAD_TIME ); - - $this->type( "mysql_wgDBname", $databaseName ); - $this->click( "submit-continue" ); - $this->waitForPageToLoad( PAGE_LOAD_TIME ); - - // Database settings - $this->click( "submit-continue" ); - $this->waitForPageToLoad( PAGE_LOAD_TIME ); - - // Name - $this->type( "config_wgSitename", NAME_OF_WIKI ); - $this->type( "config__AdminName", ADMIN_USER_NAME); - $this->type( "config__AdminPassword", ADMIN_PASSWORD ); - $this->type( "config__AdminPassword2", ADMIN_RETYPE_PASSWORD ); - $this->type( "config__AdminEmail", ADMIN_EMAIL_ADDRESS ); - - $this->click( "submit-continue" ); - $this->waitForPageToLoad( PAGE_LOAD_TIME ); - - // Options page - $this->click( "submit-continue" ); - $this->waitForPageToLoad( PAGE_LOAD_TIME ); - - // Install page - $this->click( "submit-continue" ); - $this->waitForPageToLoad( PAGE_LOAD_TIME ); - $this->chooseCancelOnNextConfirmation(); - } - - - // Complete the Name page fields - public function completeNamePage() { - $this->type( "config_wgSitename", NAME_OF_WIKI ); - $this->type( "config__AdminName", ADMIN_USER_NAME); - $this->type( "config__AdminPassword", ADMIN_PASSWORD ); - $this->type( "config__AdminPassword2", ADMIN_RETYPE_PASSWORD ); - $this->type( "config__AdminEmail", ADMIN_EMAIL_ADDRESS ); - $this->click( "submit-continue" ); - $this->waitForPageToLoad( PAGE_LOAD_TIME); - } - - - // Clicking on the 'Continue' button in any MediaWiki page - public function clickContinueButton() { - $this->click( "submit-continue" ); - $this->waitForPageToLoad( PAGE_LOAD_TIME ); - } - - - // Clicking on the 'Back' button in any MediaWiki page - public function clickBackButton() { - $this->click( "submit-back" ); - $this->waitForPageToLoad( PAGE_LOAD_TIME ); - } - - - // Restarting the installation - public function restartInstallation() { - $this->click( "link=Restart installation" ); - $this->waitForPageToLoad( PAGE_LOAD_TIME ); - $this->click( "submit-restart" ); - $this->waitForPageToLoad( PAGE_LOAD_TIME ); - } - - - // Verify 'MediaWiki' logo available in the initial screen - public function mediaWikiLogoPresentInitialScreen() { - $this->assertTrue( $this->isElementPresent( "//img[@alt='The MediaWiki logo']" )); - } - - - // Verify 'MediaWiki' logo available - public function mediaWikiLogoPresent() { - $this->assertTrue( $this->isElementPresent( "//div[@id='p-logo']/a" )); - } - - - public function completePageSuccessfull() { - $this->assertEquals( "Complete!", - $this->getText( "//div[@id='bodyContent']/div/div/h2" )); - - // 'Congratulations!' text should be available in the 'Complete!' page. - $this->assertEquals( "Congratulations!", - $this->getText( "//div[@id='bodyContent']/div/div/div[2]/form/div[1]/div[2]/p[1]/b" )); - } -} diff --git a/tests/selenium/installer/MediaWikiInstallationConfig.php b/tests/selenium/installer/MediaWikiInstallationConfig.php deleted file mode 100644 index d86bcb85..00000000 --- a/tests/selenium/installer/MediaWikiInstallationConfig.php +++ /dev/null @@ -1,49 +0,0 @@ -<?php - -/** - * MediaWikiInstallationConfig - * - * @file - * @ingroup Maintenance - * Copyright (C) 2010 Nadeesha Weerasinghe <nadeesha@calcey.com> - * http://www.calcey.com/ - * - * 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., - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - * http://www.gnu.org/copyleft/gpl.html - * - * @addtogroup Maintenance - * - */ - - -/** - * MediaWikiInstallerTestSuite.php can be run one time successfully - * with current value of the 'DB_NAME_PREFIX'. - * If you wish to run the suite more than one time, you need to change - * the value of the 'DB_NAME_PREFIX'. -*/ -define('DB_NAME_PREFIX', "database_name" ); -define('DIRECTORY_NAME', "mediawiki" ); -define( 'PORT', "8080" ); -define( 'HOST_NAME', "localhost" ); - -/** - * Use the followings to run the test suite in different browsers. - * Firefox : *firefox - * IE : *iexplore - * Google chrome : *googlechrome - * Opera : *opera -*/ -define ( 'TEST_BROWSER', "*firefox" ); diff --git a/tests/selenium/installer/MediaWikiInstallationMessage.php b/tests/selenium/installer/MediaWikiInstallationMessage.php deleted file mode 100644 index a348b542..00000000 --- a/tests/selenium/installer/MediaWikiInstallationMessage.php +++ /dev/null @@ -1,57 +0,0 @@ -<?php - -/** - * MediaWikiInstallationConfig - * - * @file - * @ingroup Maintenance - * Copyright (C) 2010 Nadeesha Weerasinghe <nadeesha@calcey.com> - * http://www.calcey.com/ - * - * 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., - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - * http://www.gnu.org/copyleft/gpl.html - * - * @addtogroup Maintenance - * - */ - - -// 'MySQL' database type help field hint -define( 'MYSQL_DATABASE_HOST_HELP', "If your database server is on different server, enter the host name or IP address here. \nIf you are using shared web hosting, your hosting provider should give you the correct host name in their documentation. \nIf you are installing on a Windows server and using MySQL, using \"localhost\" may not work for the server name. If it does not, try \"127.0.0.1\" for the local IP address." ); -define( 'MYSQL_DATABASE_NAME_HELP', "Choose a name that identifies your wiki. It should not contain spaces or hyphens. \nIf you are using shared web hosting, your hosting provider will either give you a specific database name to use or let you create databases via a control panel." ); -define( 'MYSQL_DATABASE_TABLE_PREFIX_HELP', "Choose a name that identifies your wiki. It should not contain spaces or hyphens."); -define( 'MYSQL_DATBASE_USERNAME_HELP', "Enter the username that will be used to connect to the database during the installation process. This is not the username of the MediaWiki account; this is the username for your database." ); -define( 'MYSQL_DATABASE_PASSWORD_HELP', "Enter the password that will be used to connect to the database during the installation process. This is not the password for the MediaWiki account; this is the password for your database." ); - - -// 'SQLite' database type help field hint -define( 'SQLITE_DATA_DIRECTORY_HELP', "SQLite stores all data in a single file. \nThe directory you provide must be writable by the webserver during installation. \nIt should not be accessible via the web, this is why we're not putting it where your PHP files are. \nThe installer will write a .htaccess file along with it, but if that fails someone can gain access to your raw database. That includes raw user data (e-mail addresses, hashed passwords) as well as deleted revisions and other restricted data on the wiki. \nConsider putting the database somewhere else altogether, for example in /var/lib/mediawiki/yourwiki." ); -define( 'SQLITE_DATABASE_NAME_HELP', "Choose a name that identifies your wiki. Do not use spaces or hyphens. This will be used for the SQLite data file name."); - - -// 'Database settings' page hel0p field hint -define( 'SEARCH_ENGINE_HELP', "InnoDB is almost always the best option, since it has good concurrency support. \nMyISAM may be faster in single-user or read-only installations. MyISAM databases tend to get corrupted more often than InnoDB databases." ); -define( 'DATABASE_CHARACTER_SET_HELP', "In binary mode, MediaWiki stores UTF-8 text to the database in binary fields. This is more efficient than MySQL's UTF-8 mode, and allows you to use the full range of Unicode characters. \nIn UTF-8 mode, MySQL will know what character set your data is in, and can present and convert it appropriately, but it will not let you store characters above the Basic Multilingual Plane." ); - - -// 'Name' page help field hint -define( 'NAME_OF_WIKI_HELP', "This will appear in the title bar of the browser and in various other places."); -define( 'PROJECT_NAMESPACE_HELP', "Following Wikipedia's example, many wikis keep their policy pages separate from their content pages, in a \"project namespace\". All page titles in this namespace start with a certain prefix, which you can specify here. Traditionally, this prefix is derived from the name of the wiki, but it cannot contain punctuation characters such as \"#\" or \":\"." ); -define( 'USER_NAME_HELP', "Enter your preferred username here, for example \"Joe Bloggs\". This is the name you will use to log in to the wiki." ); -define( 'EMAIL_ADDRESS_HELP', "Enter an e-mail address here to allow you to receive e-mail from other users on the wiki, reset your password, and be notified of changes to pages on your watchlist." ); -define( 'SUBSCRIBE_MAILING_LIST_HELP', "This is a low-volume mailing list used for release announcements, including important security announcements. You should subscribe to it and update your MediaWiki installation when new versions come out." ); - - - diff --git a/tests/selenium/installer/MediaWikiInstallationVariables.php b/tests/selenium/installer/MediaWikiInstallationVariables.php deleted file mode 100644 index bb11d022..00000000 --- a/tests/selenium/installer/MediaWikiInstallationVariables.php +++ /dev/null @@ -1,77 +0,0 @@ -<?php - -/** - * MediaWikiInstallationConfig - * - * @file - * @ingroup Maintenance - * Copyright (C) 2010 Nadeesha Weerasinghe <nadeesha@calcey.com> - * http://www.calcey.com/ - * - * 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., - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - * http://www.gnu.org/copyleft/gpl.html - * - * @addtogroup Maintenance - * - */ - - -// Common variables -define('PAGE_LOAD_TIME', "80000" ); - -// Common links -define( 'LINK_DIV', "//div[@id='bodyContent']/div/div/"); -define( 'LINK_FORM', "//div[@id='bodyContent']/div/div/div[2]/form/" ); -define( 'LINK_RIGHT_FRAMEWORK', "//div[@id='bodyContent']/div/div/div[1]/ul[1]/"); - -// 'Name' page input values -define( 'NAME_OF_WIKI', "Site Name" ); -define( 'ADMIN_USER_NAME', "My Name" ); -define( 'ADMIN_PASSWORD', "12345" ); -define ( 'ADMIN_RETYPE_PASSWORD', "12345" ); -define ( 'ADMIN_EMAIL_ADDRESS', "admin@example.com" ); - - -// 'Name' page input values for warning messages -define( 'VALID_WIKI_NAME', "MyWiki" ); -define( 'VALID_YOUR_NAME', "FirstName LastName" ); -define( 'VALID_PASSWORD', "12345" ); -define( 'VALID_PASSWORD_AGAIN', "12345" ); -define( 'INVALID_PASSWORD_AGAIN', "123" ); -define( 'VALID_NAMESPACE', "Mynamespace" ); -define( 'INVALID_NAMESPACE', "##..##" ); - - -// 'Database settings' page input values -define( 'DB_WEB_USER', "different" ); -define('DB_WEB_USER_PASSWORD', "12345" ); - - -// 'Connet to database' page input values -define( 'DATABASE_PREFIX',"databaseprefix" ); - - -// 'Connet to database' page input values for warning messages -define( 'VALID_DB_HOST', "localhost" ); -define( 'INVALID_DB_HOST', "local" ); -define( 'INVALID_DB_NAME', "my-wiki" ); -define( 'VALID_DB_NAME', "my_wiki1"); -define( 'INVALID_DB_PREFIX', "database prefix" ); -define( 'VALID_DB_PREFIX', "database_prefix"); -define( 'INVALID_DB_USER_NAME', "roots" ); -define( 'VALID_DB_USER_NAME', "root"); -define( 'INVALID_DB_PASSWORD', "12345" ); - - diff --git a/tests/selenium/installer/MediaWikiInstallerTestSuite.php b/tests/selenium/installer/MediaWikiInstallerTestSuite.php deleted file mode 100644 index 58ccc7cd..00000000 --- a/tests/selenium/installer/MediaWikiInstallerTestSuite.php +++ /dev/null @@ -1,53 +0,0 @@ -<?php - -/** - * MediaWikiInstallerTestSuite - * - * @file - * @ingroup Maintenance - * Copyright (C) 2010 Nadeesha Weerasinghe <nadeesha@calcey.com> - * http://www.calcey.com/ - * - * 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., - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - * http://www.gnu.org/copyleft/gpl.html - * - * @addtogroup Maintenance - * - */ - -require_once 'PHPUnit/Framework.php'; -require_once 'PHPUnit/Framework/TestSuite.php'; - -require_once ( __DIR__ . '/MediaWikiUserInterfaceTestCase.php' ); -require_once ( __DIR__ . '/MediaWikiButtonsAvailabilityTestCase.php' ); -require_once ( __DIR__ . '/MediaWikiHelpFieldHintTestCase.php' ); -require_once ( __DIR__ . '/MediaWikiRightFrameworkLinksTestCase.php' ); -require_once ( __DIR__ . '/MediaWikiRestartInstallationTestCase.php' ); -require_once ( __DIR__ . '/MediaWikiErrorsConnectToDatabasePageTestCase.php' ); -require_once ( __DIR__ . '/MediaWikiErrorsNamepageTestCase.php' ); -require_once ( __DIR__ . '/MediaWikiMySQLDataBaseTestCase.php' ); -require_once ( __DIR__ . '/MediaWikiMySQLiteDataBaseTestCase.php' ); -require_once ( __DIR__ . '/MediaWikiUpgradeExistingDatabaseTestCase.php' ); -require_once ( __DIR__ . '/MediaWikiDifferntDatabasePrefixTestCase.php' ); -require_once ( __DIR__ . '/MediaWikiDifferentDatabaseAccountTestCase.php' ); -require_once ( __DIR__ . '/MediaWikiOnAlreadyInstalledTestCase.php' ); - - - - -$suite = new PHPUnit_Framework_TestSuite('ArrayTest'); -$result = new PHPUnit_Framework_TestResult; - -$suite->run($result); diff --git a/tests/selenium/installer/MediaWikiMySQLDataBaseTestCase.php b/tests/selenium/installer/MediaWikiMySQLDataBaseTestCase.php deleted file mode 100644 index 16d065c7..00000000 --- a/tests/selenium/installer/MediaWikiMySQLDataBaseTestCase.php +++ /dev/null @@ -1,78 +0,0 @@ -<?php - -/** - * MediaWikiOnAlreadyInstalledTestCase - * - * @file - * @ingroup Maintenance - * Copyright (C) 2010 Nadeesha Weerasinghe <nadeesha@calcey.com> - * http://www.calcey.com/ - * - * 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., - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - * http://www.gnu.org/copyleft/gpl.html - * - * @addtogroup Maintenance - * - */ - - -require_once (__DIR__.'/'.'MediaWikiInstallationCommonFunction.php'); - -/** - * Test Case ID : 01 (http://www.mediawiki.org/wiki/New_installer/Test_plan) - * Test Case Name : Install Mediawiki using 'MySQL' database type successfully - * Version : MediaWiki 1.18alpha -*/ - -class MediaWikiMySQLDataBaseTestCase extends MediaWikiInstallationCommonFunction { - - function setUp() { - parent::setUp(); - } - - // Verify MediaWiki installation using 'MySQL' database type - public function testMySQLDatabaseSuccess() { - - $databaseName = DB_NAME_PREFIX."_sql_db"; - - parent::navigateConnetToDatabasePage(); - - // Verify 'MySQL" is selected as the default database type - $this->assertEquals( "MySQL settings", $this->getText( "//div[@id='DB_wrapper_mysql']/h3" )); - - // Change 'Database name' - $defaultDbName = $this->getText( "mysql_wgDBname" ); - $this->type( "mysql_wgDBname", " "); - $this->type( "mysql_wgDBname", $databaseName ); - $this->assertNotEquals( $defaultDbName, $databaseName ); - - // 'Database settings' page - parent::clickContinueButton(); - - // 'Name' page - parent::clickContinueButton(); - parent::completeNamePage(); - - // 'Options page - parent::clickContinueButton(); - - // 'Install' page - parent::clickContinueButton(); - - // 'Complete' page - parent::completePageSuccessfull(); - parent::restartInstallation(); - } -} diff --git a/tests/selenium/installer/MediaWikiMySQLiteDataBaseTestCase.php b/tests/selenium/installer/MediaWikiMySQLiteDataBaseTestCase.php deleted file mode 100644 index 4ca69162..00000000 --- a/tests/selenium/installer/MediaWikiMySQLiteDataBaseTestCase.php +++ /dev/null @@ -1,79 +0,0 @@ -<?php - -/** - * MediaWikiMySQLiteataBaseTestCase - * - * @file - * @ingroup Maintenance - * Copyright (C) 2010 Nadeesha Weerasinghe <nadeesha@calcey.com> - * http://www.calcey.com/ - * - * 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., - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - * http://www.gnu.org/copyleft/gpl.html - * - * @addtogroup Maintenance - * - */ - - -require_once (__DIR__.'/'.'MediaWikiInstallationCommonFunction.php'); - -/** - * Test Case ID : 06 (http://www.mediawiki.org/wiki/New_installer/Test_plan) - * Test Case Name : Install Mediawiki using 'MySQL' database type successfully - * Version : MediaWiki 1.18alpha -*/ - -class MediaWikiMySQLiteDataBaseTestCase extends MediaWikiInstallationCommonFunction { - - function setUp() { - parent::setUp(); - } - - // Verify MediaWiki installation using 'MySQL' database type - public function testMySQLDatabaseSuccess() { - - $databaseName = DB_NAME_PREFIX."_sqlite_db"; - - parent::navigateConnetToDatabasePage(); - $this->click( "DBType_sqlite" ); - - // Select 'SQLite' database type - $this->assertEquals( "SQLite settings", $this->getText( "//div[@id='DB_wrapper_sqlite']/h3" )); - - // Change database name - $defaultDbName = $this->getText( "sqlite_wgDBname" ); - $this->type( "sqlite_wgDBname", " "); - $this->type( "sqlite_wgDBname", $databaseName ); - $this->assertNotEquals( $defaultDbName, $databaseName ); - - // 'Database settings' page - parent::clickContinueButton(); - - // 'Name' page - parent::clickContinueButton(); - parent::completeNamePage(); - - // 'Options page - parent::clickContinueButton(); - - // 'Install' page - parent::clickContinueButton(); - - // 'Complete' page - parent::completePageSuccessfull(); - parent::restartInstallation(); - } -} diff --git a/tests/selenium/installer/MediaWikiOnAlreadyInstalledTestCase.php b/tests/selenium/installer/MediaWikiOnAlreadyInstalledTestCase.php deleted file mode 100644 index 7a1b615c..00000000 --- a/tests/selenium/installer/MediaWikiOnAlreadyInstalledTestCase.php +++ /dev/null @@ -1,71 +0,0 @@ -<?php - -/** - * Selenium server manager - * - * @file - * @ingroup Maintenance - * Copyright (C) 2010 Nadeesha Weerasinghe <nadeesha@calcey.com> - * http://www.calcey.com/ - * - * 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., - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - * http://www.gnu.org/copyleft/gpl.html - * - * @addtogroup Maintenance - * - */ - - -require_once (__DIR__.'/'.'MediaWikiInstallationCommonFunction.php'); - - -/** - * Test Case ID : 03 (http://www.mediawiki.org/wiki/New_installer/Test_plan) - * Test Case Name : Install mediawiki on a already installed Mediawiki.] - * Version : MediaWiki 1.18alpha -*/ - -class MediaWikiOnAlreadyInstalledTestCase extends MediaWikiInstallationCommonFunction { - - function setUp() { - parent::setUp(); - } - - // Install Mediawiki using 'MySQL' database type. - public function testInstallOnAlreadyInstalled() { - - $databaseName = DB_NAME_PREFIX."_already_installed"; - parent::navigateInstallPage( $databaseName ); - - // 'Options' page - parent::clickBackButton(); - - // Install page - parent::clickContinueButton(); - - // 'Install' page should display after the 'Option' page - $this->assertEquals( "Install", $this->getText( LINK_DIV."h2" )); - - // Verify warning text displayed - $this->assertEquals( "Warning: You seem to have already installed MediaWiki and are trying to install it again. Please proceed to the next page.", - $this->getText( LINK_FORM."div[1]/div[2]" )); - - // Complete page - parent::clickContinueButton(); - parent::completePageSuccessfull(); - $this->chooseCancelOnNextConfirmation(); - parent::restartInstallation(); - } -} diff --git a/tests/selenium/installer/MediaWikiRestartInstallationTestCase.php b/tests/selenium/installer/MediaWikiRestartInstallationTestCase.php deleted file mode 100644 index ea87de08..00000000 --- a/tests/selenium/installer/MediaWikiRestartInstallationTestCase.php +++ /dev/null @@ -1,115 +0,0 @@ -<?php - -/** - * MediaWikiRestartInstallationTestCase - * - * @file - * @ingroup Maintenance - * Copyright (C) 2010 Nadeesha Weerasinghe <nadeesha@calcey.com> - * http://www.calcey.com/ - * - * 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., - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - * http://www.gnu.org/copyleft/gpl.html - * - * @addtogroup Maintenance - * - */ - - - -require_once (__DIR__.'/'.'MediaWikiInstallationCommonFunction.php'); - -/** - * Test Case ID : 11, 12 (http://www.mediawiki.org/wiki/New_installer/Test_plan) - * Test Case Name : Install mediawiki on a already installed Mediawiki. - * Version : MediaWiki 1.18alpha -*/ - -class MediaWikiRestartInstallationTestCase extends MediaWikiInstallationCommonFunction { - - function setUp() { - parent::setUp(); - } - - // Verify restarting the installation - public function testSuccessRestartInstallation() { - - $dbNameBeforeRestart = DB_NAME_PREFIX."_db_before"; - parent::navigateDatabaseSettingsPage( $dbNameBeforeRestart ); - - // Verify 'Restart installation' link available - $this->assertTrue($this->isElementPresent( "link=Restart installation" )); - - // Click 'Restart installation' - $this->click( "link=Restart installation "); - $this->waitForPageToLoad( PAGE_LOAD_TIME ); - - // 'Restart Installation' page displayed - $this->assertEquals( "Restart installation", $this->getText( LINK_DIV."h2")); - - // Restart warning message displayed - $this->assertTrue($this->isTextPresent( "exact:Do you want to clear all saved data that you have entered and restart the installation process?" )); - - // Click on the 'Yes, restart' button - $this->click( "submit-restart" ); - $this->waitForPageToLoad( PAGE_LOAD_TIME ); - - // Navigate to the initial installation page(Language). - $this->assertEquals( "Language", $this->getText( LINK_DIV."h2" )); - - // 'Welcome to MediaWiki!' page - parent::clickContinueButton(); - - // 'Connect to database' page - parent::clickContinueButton(); - - // saved data should be deleted - $dbNameAfterRestart = $this->getValue("mysql_wgDBname"); - $this->assertNotEquals($dbNameBeforeRestart, $dbNameAfterRestart); - } - - - // Verify cancelling restart - public function testCancelRestartInstallation() { - - $dbNameBeforeRestart = DB_NAME_PREFIX."_cancel_restart"; - - parent::navigateDatabaseSettingsPage( $dbNameBeforeRestart); - // Verify 'Restart installation' link available - $this->assertTrue($this->isElementPresent( "link=Restart installation" )); - - $this->click( "link=Restart installation" ); - $this->waitForPageToLoad( PAGE_LOAD_TIME ); - - // 'Restart Installation' page displayed - $this->assertEquals( "Restart installation", $this->getText( LINK_DIV."h2" )); - - // Restart warning message displayed - $this->assertTrue( $this->isTextPresent( "Do you want to clear all saved data that you have entered and restart the installation process?")); - - // Click on the 'Back' button - parent::clickBackButton(); - - // Navigates to the previous page - $this->assertEquals( "Database settings", $this->getText( LINK_DIV."h2" )); - - // 'Connect to database' page - parent::clickBackButton(); - - // Saved data remain on the page. - $dbNameAfterRestart = $this->getValue( "mysql_wgDBname" ); - $this->assertEquals( $dbNameBeforeRestart, $dbNameAfterRestart ); - } -} diff --git a/tests/selenium/installer/MediaWikiRightFrameworkLinksTestCase.php b/tests/selenium/installer/MediaWikiRightFrameworkLinksTestCase.php deleted file mode 100644 index 7b0fcf36..00000000 --- a/tests/selenium/installer/MediaWikiRightFrameworkLinksTestCase.php +++ /dev/null @@ -1,93 +0,0 @@ -<?php - -/** - * MediaWikiRightFrameworkLinksTestCase - * - * @file - * @ingroup Maintenance - * Copyright (C) 2010 Nadeesha Weerasinghe <nadeesha@calcey.com> - * http://www.calcey.com/ - * - * 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., - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - * http://www.gnu.org/copyleft/gpl.html - * - * @addtogroup Maintenance - * - */ - - -require_once (__DIR__.'/'.'MediaWikiInstallationCommonFunction.php'); - -/** - * Test Case ID : 14, 15, 16, 17 (http://www.mediawiki.org/wiki/New_installer/Test_plan) - * Test Case Name : User selects 'Read me' link. - * User selects 'Release notes' link. - * User selects 'Copying' link. - * User selects 'Upgrading' link. - * Version : MediaWiki 1.18alpha -*/ - - -class MediaWikiRightFrameworkLinksTestCase extends MediaWikiInstallationCommonFunction { - - function setUp() { - parent::setUp(); - } - - public function testLinksAvailability() { - - $this->open( "http://".HOST_NAME.":".PORT."/".DIRECTORY_NAME."/config/index.php" ); - - // Verify 'Read me' link availability - $this->assertTrue($this->isElementPresent( "link=Read me" )); - - // Verify 'Release notes' link availability - $this->assertTrue($this->isElementPresent( "link=Release notes" )); - - // Verify 'Copying' link availability - $this->assertTrue($this->isElementPresent( "link=Copying" )); - } - - public function testPageNavigation() { - - $this->open( "http://".HOST_NAME.":".PORT."/".DIRECTORY_NAME."/config/index.php" ); - - // Navigate to the 'Read me' page - $this->click( "link=Read me" ); - $this->waitForPageToLoad( PAGE_LOAD_TIME ); - $this->assertEquals( "Read me", $this->getText( LINK_DIV."h2[1]" )); - $this->assertTrue($this->isElementPresent( "submit-back" )); - parent::clickBackButton(); - - // Navigate to the 'Release notes' page - $this->click( "link=Release notes" ); - $this->waitForPageToLoad( PAGE_LOAD_TIME); - $this->assertEquals( "Release notes", $this->getText( LINK_DIV."h2[1]" )); - $this->assertTrue( $this->isElementPresent( "submit-back" )); - parent::clickBackButton(); - - // Navigate to the 'Copying' page - $this->click( "link=Copying" ); - $this->waitForPageToLoad( PAGE_LOAD_TIME ); - $this->assertEquals( "Copying", $this->getText( LINK_DIV."h2[1]" )); - $this->assertTrue($this->isElementPresent( "submit-back" )); - parent::clickBackButton(); - - // Navigate to the 'Upgrading' page - $this->click( "link=Upgrading" ); - $this->waitForPageToLoad( PAGE_LOAD_TIME ); - $this->assertEquals( "Upgrading", $this->getText( LINK_DIV."h2[1]" )); - } -} diff --git a/tests/selenium/installer/MediaWikiUpgradeExistingDatabaseTestCase.php b/tests/selenium/installer/MediaWikiUpgradeExistingDatabaseTestCase.php deleted file mode 100644 index 5cdc8d42..00000000 --- a/tests/selenium/installer/MediaWikiUpgradeExistingDatabaseTestCase.php +++ /dev/null @@ -1,117 +0,0 @@ -<?php - -/** - * MediaWikiUpgradeExistingDatabaseTestCase - * - * @file - * @ingroup Maintenance - * Copyright (C) 2010 Nadeesha Weerasinghe <nadeesha@calcey.com> - * http://www.calcey.com/ - * - * 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., - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - * http://www.gnu.org/copyleft/gpl.html - * - * @addtogroup Maintenance - * - */ - - -require_once (__DIR__.'/'.'MediaWikiInstallationCommonFunction.php'); - -/** - * Test Case ID : 05 (http://www.mediawiki.org/wiki/New_installer/Test_plan) - * Test Case Name : Install Mediawiki by updating the existing database. - * Version : MediaWiki 1.18alpha -*/ - - -class MediaWikiUpgradeExistingDatabaseTestCase extends MediaWikiInstallationCommonFunction { - - function setUp() { - parent::setUp(); - } - - // Install Mediawiki using 'MySQL' database type. - public function testUpgradeExistingDatabase() { - - $databaseName = DB_NAME_PREFIX."_upgrade_existing"; - parent::navigateInstallPage( $databaseName ); - - $this->open( "http://localhost:".PORT."/".DIRECTORY_NAME."/config/index.php" ); - $this->assertEquals( "Install", $this->getText( LINK_DIV."h2" )); - $this->assertEquals( "Warning: You seem to have already installed MediaWiki and are trying to install it again. Please proceed to the next page.", - $this->getText( LINK_DIV."div[2]/form/div[1]/div[2]" )); - - // 'Optionis' page - parent::clickBackButton(); - - // 'Name' page - parent::clickBackButton(); - - // 'Database settings' page - parent::clickBackButton(); - - // 'Connect to database' page - parent::clickBackButton(); - $this->type( "mysql_wgDBname", $databaseName ); - parent::clickContinueButton(); - - // 'Upgrade existing installation' page displayed next to the 'Connect to database' page. - $this->assertEquals( "Upgrade existing installation", $this->getText( LINK_DIV."h2" )); - - // Warning message displayed. - $this->assertEquals( "There are MediaWiki tables in this database. To upgrade them to MediaWiki 1.18alpha, click Continue.", - $this->getText( LINK_DIV."div[2]/form/div[1]/div[2]" )); - - parent::clickContinueButton(); - $this->assertEquals( "Upgrade existing installation", - $this->getText( LINK_DIV."h2" )); - - // 'Upgrade complete.' text display - $this->assertEquals("Upgrade complete.", - $this->getText("//div[@id='bodyContent']/div/div[1]/div[4]/form/div[1]/div[2]/p[1]")); - - $this->assertEquals("You can now Folder/index.php start using your wiki.", - $this->getText("//div[@id='bodyContent']/div/div[1]/div[4]/form/div[1]/div[2]/p[2]" )); - - $this->assertEquals( "Folder/index.php start using your wiki", - $this->getText( "link=Folder/index.php start using your wiki" )); - - $this->assertTrue($this->isElementPresent( "submit-regenerate" )); - $this->click( "submit-regenerate" ); - $this->waitForPageToLoad( PAGE_LOAD_TIME ); - $this->assertEquals( "Database settings", - $this->getText( LINK_DIV."h2" )); - - // 'Database settings' page - parent::clickContinueButton(); - - // Name page - parent::completeNamePage(); - - // Options page - parent::clickContinueButton(); - - // Install page - $this->assertEquals( "Warning: You seem to have already installed MediaWiki and are trying to install it again. Please proceed to the next page.", - $this->getText( LINK_FORM."div[1]/div[2]" )); - parent::clickContinueButton(); - - // complete - parent::completePageSuccessfull(); - $this->chooseCancelOnNextConfirmation(); - parent::restartInstallation(); - } -} diff --git a/tests/selenium/installer/MediaWikiUserInterfaceTestCase.php b/tests/selenium/installer/MediaWikiUserInterfaceTestCase.php deleted file mode 100644 index 15fad95f..00000000 --- a/tests/selenium/installer/MediaWikiUserInterfaceTestCase.php +++ /dev/null @@ -1,531 +0,0 @@ -<?php - -/** - * MediaWikiUserInterfaceTestCase - * - * @file - * @ingroup Maintenance - * Copyright (C) 2010 Nadeesha Weerasinghe <nadeesha@calcey.com> - * http://www.calcey.com/ - * - * 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., - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - * http://www.gnu.org/copyleft/gpl.html - * - * @addtogroup Maintenance - * - */ - -require_once (__DIR__.'/'.'MediaWikiInstallationCommonFunction.php'); - -/** - * Test Case ID : 18 - 27 (http://www.mediawiki.org/wiki/New_installer/Test_plan) - * Test Case Name : UI of MediaWiki initial/ Language/ Welcome to MediaWiki!/ Connect to database/ - * Database settings/ Name/ Options/ Install/ Complete/ Restart Inslation pages - * Version : MediaWiki 1.18alpha -*/ - - -class MediaWikiUserInterfaceTestCase extends MediaWikiInstallationCommonFunction { - - function setUp() { - parent::setUp(); - } - - - public function testInitialPageUI() { - - parent::navigateInitialpage(); - - // MediaWiki logo available - $this->assertTrue( $this->isElementPresent( "//img[@alt='The MediaWiki logo']" )); - - // 'MediaWiki 1.18alpha' text available - $this->assertEquals( "MediaWiki 1.18alpha", $this->getText( "//h1" )); - - // 'LocalSettings.php not found.' text available - $this->assertEquals( "LocalSettings.php not found.", $this->getText( "//p[1]" )); - - // 'Please set up the wiki first' text available - $this->assertEquals( "Please set up the wiki first.", $this->getText( "//p[2]" )); - - // 'set up the wiki' link available - $this->assertTrue($this->isElementPresent( "link=set up the wiki" )); - } - - - public function testlanguagePageUI() { - - parent::navigateLanguagePage(); - - // Verify 'Language' heading - $this->assertEquals( "Language", $this->getText( LINK_DIV."h2" )); - - // 'Your language' label available - $this->assertEquals( "Your language:", - $this->getText( LINK_FORM."div[1]/div[1]/label" )); - - // 'Your language' dropdown available - $this->assertTrue( $this->isElementPresent( "UserLang" )); - - // 'Wiki language' label available - $this->assertEquals( "Wiki language:", - $this->getText( LINK_FORM."div[2]/div[1]/label" )); - - // 'Wiki language' dropdown available - $this->assertTrue($this->isElementPresent( "ContLang" )); - } - - - public function testWelcometoMediaWikiUI() { - - parent::navigateWelcometoMediaWikiPage(); - - // Verify 'Welcome to MediaWiki!' heading - $this->assertEquals( "Welcome to MediaWiki!", - $this->getText( LINK_DIV."h2" )); - - // Verify environment ok text displayed. - $this->assertEquals( "The environment has been checked.You can install MediaWiki.", - $this->getText( LINK_DIV."div[6]/span" )); - } - - - public function testConnectToDatabaseUI() { - - parent::navigateConnetToDatabasePage(); - - // 'MYSQL radio button available - $this->assertEquals( "MySQL", - $this->getText( LINK_FORM."div[2]/div[2]/ul/li[1]/label" )); - $this->assertTrue( $this->isElementPresent( LINK_FORM."div[2]/div[2]/ul/li[1]" )); - - // 'SQLite' radio button available - $this->assertTrue( $this->isElementPresent( LINK_FORM."div[2]/div[2]/ul/li[2]" )); - $this->assertEquals( "SQLite", $this->getText( LINK_FORM."div[2]/div[2]/ul/li[2]/label ")); - - // 'Database host' label available - $this->assertEquals( "Database host:", $this->getText( "//div[@id='DB_wrapper_mysql']/div/div[1]/label" )); - - // 'Database host' text box default to 'localhost' - $this->assertEquals( "localhost", $this->getValue( "mysql_wgDBserver" )); - - // 'Identify this wiki' section available - $this->assertTrue( $this->isElementPresent( "//div[@id='DB_wrapper_mysql']/fieldset[1]/legend" )); - - // 'Identify this wiki' label available - $this->assertEquals( "Identify this wiki", $this->getText( "//div[@id='DB_wrapper_mysql']/fieldset[1]/legend" )); - - // 'Database name' lable available - $this->assertEquals( "Database name:", - $this->getText( "//div[@id='DB_wrapper_mysql']/fieldset[1]/div[1]/div[1]/label" )); - - // Verify 'Database name:' text box is default to 'my_wiki' - $this->assertEquals( "my_wiki", $this->getValue( "mysql_wgDBname" )); - - // Verify 'Database table prefix:' label available - $this->assertEquals( "Database table prefix:", - $this->getText( "//div[@id='DB_wrapper_mysql']/fieldset[1]/div[2]/div[1]/label" )); - - // 'User account for installation' section available - $this->assertTrue( $this->isElementPresent( "//div[@id='DB_wrapper_mysql']/fieldset[2]/legend" )); - - // 'User account for installation' label available - $this->assertEquals( "User account for installation", $this->getText( "//div[@id='DB_wrapper_mysql']/fieldset[2]/legend" )); - - // 'Database username' label available - $this->assertEquals( "Database username:", - $this->getText( "//div[@id='DB_wrapper_mysql']/fieldset[2]/div[1]/div[1]/label" )); - - // 'Database username' text box defaults to 'root' - $this->assertEquals("root", $this->getValue( "mysql__InstallUser" )); - - // 'Database password' label available - $this->assertEquals( "Database password:", - $this->getText( "//div[@id='DB_wrapper_mysql']/fieldset[2]/div[2]/div[1]/label" )); - } - - - - public function testDatabaseSettingsUI() { - - $databaseName = DB_NAME_PREFIX."_db_settings_UI"; - parent::navigateDatabaseSettingsPage( $databaseName ); - - // 'Database settings' text available. - $this->assertEquals( "Database settings", $this->getText( LINK_DIV."h2" )); - - // 'Database account for web access' section available - $this->assertTrue( $this->isElementPresent( LINK_FORM."fieldset" )); - - // 'Database account for web access' label available - $this->assertEquals( "Database account for web access", $this->getText( LINK_FORM."fieldset/legend" )); - - // 'Use the same account as for installation' check box available - $this->assertEquals( "Use the same account as for installation", $this->getText( LINK_FORM."fieldset/div[1]/label" )); - - // 'Use the same account as for installation' check box is selected by default - $this->assertEquals( "on", $this->getValue( "mysql__SameAccount" )); - - // 'Use the same account as for installation' check box deselected - $this->click( "mysql__SameAccount" ); - - // verify 'Use the same account as for installation' check box is not selected - $this->assertEquals( "off", $this->getValue( "mysql__SameAccount" )); - - // 'Database username' label available - $this->assertEquals( "Database username:", $this->getText( "//div[@id='dbOtherAccount']/div[1]/div[1]/label" )); - - // 'Database username' text box is default to the 'wikiuser' - $this->assertEquals( "wikiuser", $this->getValue( "mysql_wgDBuser" )); - - // 'Database password' label available - $this->assertEquals( "Database password:", $this->getText( "//div[@id='dbOtherAccount']/div[2]/div[1]/label" )); - - // 'Create the account if it does not already exist' label available - $this->assertEquals( "Create the account if it does not already exist", $this->getText( "//div[@id='dbOtherAccount']/div[4]/label" )); - - // 'Create the account if it does not already exist' check box is not selected by default - $this->assertEquals( "off" , $this->getValue( "mysql__CreateDBAccount" )); - - // 'Create the account if it does not already exist' check box selected - $this->click( "mysql__CreateDBAccount" ); - - // Verify 'Create the account if it does not already exist' check box is selected - $this->assertEquals( "on" , $this->getValue( "mysql__CreateDBAccount" )); - $this->click( "mysql__SameAccount" ); - $this->assertEquals( "on", $this->getValue( "mysql__SameAccount" )); - - // 'Storage engine' label available - $this->assertEquals( "Storage engine:", - $this->getText( LINK_FORM."div[1]/div[1]/label")); - - // 'InnoDB' label available - $this->assertEquals( "InnoDB", - $this->getText( LINK_FORM."div[1]/div[2]/ul/li[1]/label" )); - - // 'InnoDB' radio button available - $this->assertTrue( $this->isElementPresent( "mysql__MysqlEngine_InnoDB" )); - - // 'MyISAM' label available - $this->assertEquals( "MyISAM", $this->getText( LINK_FORM."div[1]/div[2]/ul/li[2]/label" )); - - // 'MyISAM' radio button available - $this->assertTrue($this->isElementPresent( "mysql__MysqlEngine_MyISAM" )); - - // 'Database character set' label available - $this->assertEquals( "Database character set:", - $this->getText( LINK_FORM."div[3]/div[1]/label" )); - - // 'Binary' radio button available - $this->assertTrue( $this->isElementPresent( "mysql__MysqlCharset_binary" )); - - // 'Binary' radio button available - $this->assertEquals( "Binary", $this->getText( LINK_FORM."div[3]/div[2]/ul/li[1]/label" )); - - // 'UTF-8' radio button available - $this->assertTrue( $this->isElementPresent( "mysql__MysqlCharset_utf8" )); - - // 'UTF-8' label available - $this->assertEquals( "UTF-8", $this->getText( LINK_FORM."div[3]/div[2]/ul/li[2]/label" )); - - // 'Binary' radio button is selected - $this->assertEquals( "on", $this->getValue( "mysql__MysqlCharset_binary" )); - } - - - - public function testNamePageUI() { - - $databaseName = DB_NAME_PREFIX."_name_UI"; - parent::navigateNamePage($databaseName); - - // 'Name of wiki' text box available - $this->assertEquals( "Name of wiki:", - $this->getText( LINK_FORM."div[1]/div[1]/label" )); - - $this->assertTrue( $this->isElementPresent( "config_wgSitename" )); - - // 'Project namespace' label available - $this->assertEquals( "Project namespace:", - $this->getText( LINK_FORM."div[2]/div[1]/label" )); - - // 'Same as the wiki name' radio button available - $this->assertTrue( $this->isElementPresent( "config__NamespaceType_site-name" )); - - // 'Project' radio button available - $this->assertTrue( $this->isElementPresent( "config__NamespaceType_generic" )); - - // 'Project' radio button available - $this->assertTrue( $this->isElementPresent( "config__NamespaceType_other" )); - - // 'Same as the wiki name' label available - $this->assertEquals( "Same as the wiki name:", - $this->getText( LINK_FORM."div[2]/div[2]/ul/li[1]/label" )); - - // 'Project' label available - $this->assertEquals("Project", - $this->getText( LINK_FORM."div[2]/div[2]/ul/li[2]/label" )); - - // 'Project' label available - $this->assertEquals( "Other (specify)", - $this->getText( LINK_FORM."div[2]/div[2]/ul/li[3]/label" )); - - // 'Same as the wiki name' radio button selected by default - $this->assertEquals( "on", $this->getValue( "config__NamespaceType_site-name" )); - - // 'Administrator account' section available - $this->assertTrue( $this->isElementPresent( LINK_FORM."fieldset" )); - - // 'Administrator account' label available - $this->assertEquals( "Administrator account", - $this->getText( LINK_FORM."fieldset/legend" )); - - // 'Your Name' label available - $this->assertEquals( "Your name:", - $this->getText( LINK_FORM."fieldset/div[1]/div[1]/label" )); - - // 'Your Name' text box available - $this->assertTrue( $this->isElementPresent( "config__AdminName" )); - - // 'Password' label available - $this->assertEquals( "Password:", - $this->getText( LINK_FORM."fieldset/div[2]/div[1]/label" )); - - // 'Password' text box available - $this->assertTrue( $this->isElementPresent( "config__AdminPassword" )); - - // 'Password again' label available - $this->assertEquals( "Password again:", - $this->getText( LINK_FORM."fieldset/div[3]/div[1]/label" )); - - // 'Password again' text box available - $this->assertTrue( $this->isElementPresent( "config__AdminPassword2" )); - - // 'Email address' label avaialble - $this->assertEquals( "E-mail address:", - $this->getText( LINK_FORM."fieldset/div[4]/div[1]/label" )); - - // 'Email address' text box available - $this->assertTrue( $this->isElementPresent( "config__AdminEmail" )); - - // Message displayed - $this->assertEquals( "You are almost done! You can now skip the remaining configuration and install the wiki right now.", - $this->getText( LINK_FORM."/div[4]/div[2]/p" )); - - // 'Ask me more questions.' radio button available - $this->assertTrue( $this->isElementPresent( "config__SkipOptional_continue" )); - - // 'Ask me more questions.' label available - $this->assertEquals( "Ask me more questions.", - $this->getText( LINK_FORM."div[5]/div[2]/ul/li[1]/label" )); - - // 'I'm bored already, just install the wiki' radio button is avaiable - $this->assertTrue( $this->isElementPresent( "config__SkipOptional_skip" )); - - // 'I'm bored already, just install the wiki' label available - $this->assertEquals( "I'm bored already, just install the wiki.", - $this->getText( LINK_FORM."div[5]/div[2]/ul/li[2]/label" )); - - // 'Ask me more questions.' radio button is default selected - $this->assertEquals( "on", $this->getValue( "config__SkipOptional_continue" )); - } - - - - public function testOptionPageUI() { - - $databaseName = DB_NAME_PREFIX."_options_UI"; - parent::navigateOptionsPage($databaseName); - - // 'Options' label available - $this->assertEquals( "Options", $this->getText( LINK_DIV."h2")); - - // 'Return e-mail address' label available - $this->assertEquals( "Return e-mail address:", $this->getText( "//div[@id='emailwrapper']/div[1]/div[1]/label" )); - - // 'Return e-mail address' text box available - $this->assertTrue( $this->isElementPresent( "config_wgPasswordSender" )); - - // Text 'apache@localhost' is default value of the 'Return e-mail address' text box - $this->assertEquals( "apache@localhost", $this->getValue( "config_wgPasswordSender" )); - - // 'Logo URL' label available - $this->assertEquals( "Logo URL:", $this->getText( LINK_FORM."fieldset[2]/div[3]/div[1]/label" )); - - // 'Logo URL' text box available - $this->assertTrue( $this->isElementPresent( "config_wgLogo" )); - - // Correct path available in the 'Logo URL' text box - $this->assertEquals( "/wiki/skins/common/images/wiki.png", $this->getValue( "config_wgLogo" )); - - // 'Enable file uploads' radio button available - $this->assertTrue( $this->isElementPresent( "config_wgEnableUploads" )); - - // 'Enable file uploads' label available - $this->assertEquals( "Enable file uploads", - $this->getText( LINK_FORM."fieldset[2]/div[1]/label" )); - - // 'Enable file uploads' check box is not selected - $this->assertEquals( "off", $this->getValue( "config_wgEnableUploads" )); - - $this->click( "config_wgEnableUploads" ); - - // 'Directory for deleted files' label available - $this->assertEquals( "Directory for deleted files:", - $this->getText( "//div[@id='uploadwrapper']/div/div[1]/label" )); - - // 'Directory for deleted files' text box available - $this->assertTrue( $this->isElementPresent( "config_wgDeletedDirectory" )); - - // Correct path available in the 'Directory for deleted files' text box - $this->assertEquals( "C:\\wamp\\www\\".DIRECTORY_NAME."/images/deleted", - $this->getValue( "config_wgDeletedDirectory" )); - } - - - - public function testInstallPageUI() { - - $databaseName = DB_NAME_PREFIX."_install_UI"; - parent::navigateInstallPage( $databaseName ); - - // Verify installation done messages display - $this->assertEquals( "Setting up database... done", - $this->getText( LINK_FORM."ul/li[1]" )); - $this->assertEquals( "Creating tables... done", - $this->getText( LINK_FORM."ul/li[2]" )); - $this->assertEquals( "Creating database user... done", - $this->getText( LINK_FORM."ul/li[3]" )); - $this->assertEquals( "Populating default interwiki table... done", - $this->getText( LINK_FORM."ul/li[4]" )); - $this->assertEquals( "Generating secret key... done", - $this->getText( LINK_FORM."ul/li[5]" )); - $this->assertEquals( "Generating default upgrade key... done", - $this->getText( LINK_FORM."ul/li[6]" )); - $this->assertEquals( "Creating administrator user account... done", - $this->getText( LINK_FORM."ul/li[7]" )); - $this->assertEquals( "Creating main page with default content... done", - $this->getText( LINK_FORM."ul/li[8]" )); - } - - - - public function testCompletePageUI() { - - $databaseName = DB_NAME_PREFIX."_complete_UI"; - parent::navigateCompletePage( $databaseName ); - - // 'Congratulations!' text display - $this->assertEquals("Congratulations!", - $this->getText( LINK_FORM."div[1]/div[2]/p[1]/b")); - // 'LocalSettings.php' generated message display - $this->assertEquals( "The installer has generated a LocalSettings.php file. It contains all your configuration.", - $this->getText( LINK_FORM."div[1]/div[2]/p[2]" )); - - // 'Download LocalSettings.php'' link available - $this->assertTrue( $this->isElementPresent( "link=Download LocalSettings.php" )); - - // 'enter your wiki' link available - $this->assertTrue($this->isElementPresent("link=Folder/index.php enter your wiki")); - } - - - - public function testRestartInstallation() { - - parent::navigateConnetToDatabasePage(); - $this->click( "link=Restart installation" ); - $this->waitForPageToLoad( PAGE_LOAD_TIME ); - - // Restart installation' label should be available. - $this->assertEquals( "Restart installation", $this->getText( LINK_DIV."h2" )); - - //'Do you want to clear all saved data that you have entered and restart the installation process?' label available - $this->assertEquals( "Do you want to clear all saved data that you have entered and restart the installation process?", - $this->getText( "//*[@id='bodyContent']/div/div/div[2]/form/div[1]/div[2]" )); - // 'Back' button available - $this->assertTrue($this->isElementPresent( "submit-back" )); - - // 'Restart' button available - $this->assertTrue($this->isElementPresent( "submit-restart" )); - } - - - - public function testMediaWikiLogoAvailability() { - - $databaseName = DB_NAME_PREFIX."_mediawiki_logo"; - parent::navigateInitialpage(); - parent::mediaWikiLogoPresentInitialScreen(); - $this->click( "link=set up the wiki" ); - $this->waitForPageToLoad( PAGE_LOAD_TIME ); - - // 'Language' page - parent::mediaWikiLogoPresent(); - parent::clickContinueButton(); - - // 'Welcome to MediaWiki' page - parent::mediaWikiLogoPresent(); - parent::clickContinueButton(); - - // 'Connet to database' page - parent::mediaWikiLogoPresent(); - $this->type("mysql_wgDBname", $databaseName ); - parent::clickContinueButton(); - - // 'Database setting' page - parent::mediaWikiLogoPresent(); - parent::clickContinueButton(); - - // 'Name' page - parent::mediaWikiLogoPresent(); - parent::completeNamePage(); - parent::clickContinueButton(); - - // 'Options' page - parent::mediaWikiLogoPresent(); - parent::clickContinueButton(); - - // 'Install' page - parent::mediaWikiLogoPresent(); - } - - - public function testRightFramework() { - - parent::navigateLanguagePage(); - // Verfy right framework texts display - $this->assertEquals( "Language", - $this->getText( LINK_RIGHT_FRAMEWORK."li[1]" )); - $this->assertEquals( "Existing wiki", - $this->getText( LINK_RIGHT_FRAMEWORK."li[2]" )); - $this->assertEquals( "Welcome to MediaWiki!", - $this->getText( LINK_RIGHT_FRAMEWORK."li[3]" )); - $this->assertEquals( "Connect to database", - $this->getText( LINK_RIGHT_FRAMEWORK."li[4]" )); - $this->assertEquals( "Upgrade existing installation", - $this->getText( LINK_RIGHT_FRAMEWORK."li[5]" )); - $this->assertEquals( "Database settings", - $this->getText( LINK_RIGHT_FRAMEWORK."li[6]" )); - $this->assertEquals( "Name", - $this->getText( LINK_RIGHT_FRAMEWORK."li[7]" )); - $this->assertEquals( "Options", - $this->getText( LINK_RIGHT_FRAMEWORK."li[8]" )); - $this->assertEquals( "Install", - $this->getText( LINK_RIGHT_FRAMEWORK."li[9]" )); - $this->assertEquals( "Complete!", - $this->getText( LINK_RIGHT_FRAMEWORK."li[10]/span" )); - } -} diff --git a/tests/selenium/installer/README.txt b/tests/selenium/installer/README.txt deleted file mode 100644 index bc880a8b..00000000 --- a/tests/selenium/installer/README.txt +++ /dev/null @@ -1,32 +0,0 @@ -== Details== - -Automated Selenium test scripts written for MediaWiki Installer is available at https://gerrit.wikimedia.org/r/gitweb?p=mediawiki/core.git;a=tree;f=tests/selenium/installer;hb=HEAD. -Detailed test cases available at http://www.mediawiki.org/wiki/New_installer/Test_plan. - -Version : MediaWiki 1.18alpha -Date : 27/12/2010 - -== Running tests == - -Test cases can be run independently or can run all the test cases using MediaWikiInstallerTestSuite.php within PHPUnit/Selenium. - - -== Dependencies == - -MediaWikiInstallationConfig.php - -Value of the 'DB_NAME_PREFIX' should be replace with the database name prefix. Several DB instances will get created to cover different installation scenarios starting with the above prefix. -You need to change the value of the 'DB_NAME_PREFIX' in MediaWikiInstallationConfig everytime you planned to -run the tests. -'DIRECTORY_NAME', 'PORT' and the 'HOST_NAME' should be replaced with your local values. -You may specify the test browser you wish to run the test using 'TEST_BROWSER'. Default browser is Firefox. - -Note : MediaWikiInstallerTestSuite.php has no dependency on 'Selenium' test framework. - - -== Known problems == - -If you run the MediaWikiInstallerTestSuite.php twice without changing the name of the database, the second run should be falied. -(Please read the more information on how to change the database name which is avaialable under 'Dependencies' section) - - diff --git a/tests/selenium/selenium_settings.ini.sample b/tests/selenium/selenium_settings.ini.sample deleted file mode 100644 index b1d88193..00000000 --- a/tests/selenium/selenium_settings.ini.sample +++ /dev/null @@ -1,32 +0,0 @@ -[SeleniumSettings] - -; Set up the available browsers that Selenium can control. -browsers[firefox] = "*firefox" -browsers[iexplorer] = "*iexploreproxy" -browsers[chrome] = "*chrome" - -; The simple configurations above usually work on Linux, but Windows and -; Mac OS X hosts may need to specify a full path: -;browsers[firefox] = "*firefox /Applications/Firefox.app/Contents/MacOS/firefox-bin" -;browsers[firefox] = "*firefox C:\Program Files\Mozilla Firefox\firefox.exe" - -host = "localhost" -port = "4444" -wikiUrl = "http://localhost/deployment" -username = "wikiuser" -userPassword = "wikipass" -testBrowser = "firefox" -startserver = -stopserver = -jUnitLogFile = -runAgainstGrid = false - -; To let the test runner start and stop the selenium server, it needs the full -; path to selenium-server.jar from the selenium-remote-control package. -seleniumserverexecpath = "/opt/local/selenium-remote-control-1.0.3/selenium-server-1.0.3/selenium-server.jar" - -[SeleniumTests] - -testSuite[SimpleSeleniumTestSuite] = "tests/selenium/suites/SimpleSeleniumTestSuite.php" -testSuite[WikiEditorTestSuite] = "extensions/WikiEditor/selenium/WikiEditorTestSuite.php" - diff --git a/tests/selenium/selenium_settings_grid.ini.sample b/tests/selenium/selenium_settings_grid.ini.sample deleted file mode 100644 index 3bbd534a..00000000 --- a/tests/selenium/selenium_settings_grid.ini.sample +++ /dev/null @@ -1,16 +0,0 @@ -[SeleniumSettings] - -host = "grid.tesla.usability.wikimedia.org" -port = "4444" -wikiUrl = "http://208.80.152.253:5001" -username = "wikiuser" -userPassword = "wikipass" -testBrowser = "Safari on OS X Snow Leopard" -jUnitLogFile = -runAgainstGrid = true -startserver = false -stopserver = false - -[SeleniumTests] - -testSuite[SimpleSeleniumTestSuite] = "tests/selenium/suites/SimpleSeleniumTestSuite.php" diff --git a/tests/selenium/suites/AddContentToNewPageTestCase.php b/tests/selenium/suites/AddContentToNewPageTestCase.php deleted file mode 100644 index 72e75e11..00000000 --- a/tests/selenium/suites/AddContentToNewPageTestCase.php +++ /dev/null @@ -1,182 +0,0 @@ -<?php - -/** - * Selenium server manager - * - * @file - * @ingroup Testing - * Copyright (C) 2010 Nadeesha Weerasinghe <nadeesha@calcey.com> - * http://www.calcey.com/ - * - * 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., - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - * http://www.gnu.org/copyleft/gpl.html - * - * @addtogroup Testing - * - */ - - -class AddContentToNewPageTestCase extends SeleniumTestCase { - - - // Add bold text and verify output - public function testAddBoldText() { - - $this->getExistingPage(); - $this->clickEditLink(); - $this->loadWikiEditor(); - $this->clearWikiEditor(); - $this->click( "//*[@id='mw-editbutton-bold']" ); - $this->clickShowPreviewBtn(); - $this->waitForPageToLoad( SeleniumTestConstants::WIKI_TEST_WAIT_TIME ); - - // Verify bold text displayed on mediawiki preview - $this->assertTrue($this->isElementPresent( "//div[@id='wikiPreview']/p/b" )); - $this->assertTrue($this->isTextPresent( "Bold text" )); - } - - // Add italic text and verify output - public function testAddItalicText() { - - $this->getExistingPage(); - $this->clickEditLink(); - $this->loadWikiEditor(); - $this->clearWikiEditor(); - $this->click( "//*[@id='mw-editbutton-italic']" ); - $this->clickShowPreviewBtn(); - $this->waitForPageToLoad( SeleniumTestConstants::WIKI_TEST_WAIT_TIME ); - - // Verify italic text displayed on mediawiki preview - $this->assertTrue($this->isElementPresent("//div[@id='wikiPreview']/p/i")); - $this->assertTrue($this->isTextPresent( "Italic text" )); - } - - // Add internal link for a new page and verify output in the preview - public function testAddInternalLinkNewPage() { - $this->getExistingPage(); - $this->clickEditLink(); - $this->loadWikiEditor(); - $this->clearWikiEditor(); - $this->click( "//*[@id='mw-editbutton-link']" ); - $this->clickShowPreviewBtn(); - $this->waitForPageToLoad( SeleniumTestConstants::WIKI_TEST_WAIT_TIME ); - - // Verify internal link displayed on mediawiki preview - $source = $this->getText( "//*[@id='wikiPreview']/p/a" ); - $correct = strstr( $source, "Link title" ); - $this->assertEquals( $correct, true ); - - $this->click( SeleniumTestConstants::LINK_START."Link title" ); - $this->waitForPageToLoad( SeleniumTestConstants::WIKI_TEST_WAIT_TIME ); - - // Verify internal link open as a new page - editing mode - $source = $this->getText( "firstHeading" ); - $correct = strstr( $source, "Editing Link title" ); - $this->assertEquals( $correct, true ); - } - - // Add external link and verify output in the preview - public function testAddExternalLink() { - $this->getExistingPage(); - $this->clickEditLink(); - $this->loadWikiEditor(); - $this->clearWikiEditor(); - $this->click( "//*[@id='mw-editbutton-extlink']" ); - $this->type( SeleniumTestConstants::TEXT_EDITOR, "[http://www.google.com Google]" ); - $this->clickShowPreviewBtn(); - $this->waitForPageToLoad( SeleniumTestConstants::WIKI_TEST_WAIT_TIME ); - - // Verify external links displayed on mediawiki preview - $source = $this->getText( "//*[@id='wikiPreview']/p/a" ); - $correct = strstr( $source, "Google" ); - $this->assertEquals( $correct, true ); - - $this->click( SeleniumTestConstants::LINK_START."Google" ); - $this->waitForPageToLoad( SeleniumTestConstants::WIKI_TEST_WAIT_TIME ); - - // Verify external link opens - $source = $this->getTitle(); - $correct = strstr( $source, "Google" ); - $this->assertEquals( $correct, true); - } - - // Add level 2 headline and verify output in the preview - public function testAddLevel2HeadLine() { - $blnElementPresent = false; - $blnTextPresent = false; - $this->getExistingPage(); - $this->clickEditLink(); - $this->loadWikiEditor(); - $this->clearWikiEditor(); - $this->click( "mw-editbutton-headline" ); - $this->clickShowPreviewBtn(); - $this->waitForPageToLoad( SeleniumTestConstants::WIKI_TEST_WAIT_TIME ); - $this->assertTrue($this->isElementPresent( "//div[@id='wikiPreview']/h2" )); - - // Verify level 2 headline displayed on mediawiki preview - $source = $this->getText( "//*[@id='Headline_text']" ); - $correct = strstr( $source, "Headline text" ); - $this->assertEquals( $correct, true ); - } - - // Add text with ignore wiki format and verify output the preview - public function testAddNoWikiFormat() { - $this->getExistingPage(); - $this->clickEditLink(); - $this->loadWikiEditor(); - $this->clearWikiEditor(); - $this->click( "//*[@id='mw-editbutton-nowiki']" ); - $this->clickShowPreviewBtn(); - $this->waitForPageToLoad( SeleniumTestConstants::WIKI_TEST_WAIT_TIME ); - - // Verify ignore wiki format text displayed on mediawiki preview - $source = $this->getText( "//div[@id='wikiPreview']/p" ); - $correct = strstr( $source, "Insert non-formatted text here" ); - $this->assertEquals( $correct, true ); - } - - // Add signature and verify output in the preview - public function testAddUserSignature() { - $this->getExistingPage(); - $this->clickEditLink(); - $this->loadWikiEditor(); - $this->clearWikiEditor(); - $this->click( "mw-editbutton-signature" ); - $this->clickShowPreviewBtn(); - $this->waitForPageToLoad( SeleniumTestConstants::WIKI_TEST_WAIT_TIME ); - - // Verify signature displayed on mediawiki preview - $source = $this->getText( "//*[@id='wikiPreview']/p/a" ); - $username = $this->getText( "//*[@id='pt-userpage']/a" ); - $correct = strstr( $source, $username ); - $this->assertEquals( $correct, true ); - } - - // Add horizontal line and verify output in the preview - public function testHorizontalLine() { - $this->getExistingPage(); - $this->clickEditLink(); - $this->loadWikiEditor(); - $this->clearWikiEditor(); - $this->click( "mw-editbutton-hr" ); - - $this->clickShowPreviewBtn(); - $this->waitForPageToLoad( SeleniumTestConstants::WIKI_TEST_WAIT_TIME ); - - // Verify horizontal line displayed on mediawiki preview - $this->assertTrue( $this->isElementPresent( "//div[@id='wikiPreview']/hr" )); - $this->deletePage( "new" ); - } -} diff --git a/tests/selenium/suites/AddNewPageTestCase.php b/tests/selenium/suites/AddNewPageTestCase.php deleted file mode 100644 index f3302e5e..00000000 --- a/tests/selenium/suites/AddNewPageTestCase.php +++ /dev/null @@ -1,65 +0,0 @@ -<?php - -/** - * Selenium server manager - * - * @file - * @ingroup Testing - * Copyright (C) 2010 Nadeesha Weerasinghe <nadeesha@calcey.com> - * http://www.calcey.com/ - * - * 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., - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - * http://www.gnu.org/copyleft/gpl.html - * - * @addtogroup Testing - * - */ - - -class AddNewPageTestCase extends SeleniumTestCase { - - // Verify adding a new page - public function testAddNewPage() { - $newPage = "new"; - $displayName = "New"; - $this->open( $this->getUrl() . - '/index.php?title=Main_Page&action=edit' ); - $this->type( "searchInput", $newPage ); - $this->click( "searchGoButton" ); - $this->waitForPageToLoad( SeleniumTestConstants::WIKI_TEST_WAIT_TIME ); - - // Verify 'Search results' text available - $source = $this->gettext( "firstHeading" ); - $correct = strstr( $source, "Search results" ); - $this->assertEquals( $correct, true); - - // Verify 'Create the page "<page name>" on this wiki' text available - $source = $this->gettext( "//div[@id='bodyContent']/div[4]/p/b" ); - $correct = strstr ( $source, "Create the page \"New\" on this wiki!" ); - $this->assertEquals( $correct, true ); - - $this->click( SeleniumTestConstants::LINK_START.$displayName ); - $this->waitForPageToLoad( SeleniumTestConstants::WIKI_TEST_WAIT_TIME ); - - $this->assertTrue($this->isElementPresent( SeleniumTestConstants::LINK_START."Create" )); - $this->type( "wpTextbox1", "add new test page" ); - $this->click( SeleniumTestConstants::BUTTON_SAVE ); - - // Verify new page added - $source = $this->gettext( "firstHeading" ); - $correct = strstr ( $source, $displayName ); - $this->assertEquals( $correct, true ); - } -} diff --git a/tests/selenium/suites/CreateAccountTestCase.php b/tests/selenium/suites/CreateAccountTestCase.php deleted file mode 100644 index 5708bcff..00000000 --- a/tests/selenium/suites/CreateAccountTestCase.php +++ /dev/null @@ -1,114 +0,0 @@ -<?php - -/** - * Selenium server manager - * - * @file - * @ingroup Testing - * Copyright (C) 2010 Nadeesha Weerasinghe <nadeesha@calcey.com> - * http://www.calcey.com/ - * - * 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., - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - * http://www.gnu.org/copyleft/gpl.html - * - * @addtogroup Testing - * - */ - -Class CreateAccountTestCase extends SeleniumTestCase { - - // Change these values before run the test - private $userName = "yourname4000"; - private $password = "yourpass4000"; - - // Verify 'Log in/create account' link existance in Main page. - public function testMainPageLink() { - - $this->click( "link=Log out" ); - $this->waitForPageToLoad( SeleniumTestConstants::WIKI_TEST_WAIT_TIME ); - - $this->open( $this->getUrl().'/index.php?title=Main_Page' ); - $this->assertTrue($this->isElementPresent( "link=Log in / create account" )); - } - - // Verify 'Create an account' link existance in 'Log in / create account' Page. - public function testCreateAccountPageLink() { - - $this->click( "link=Log out" ); - $this->waitForPageToLoad( SeleniumTestConstants::WIKI_TEST_WAIT_TIME ); - - $this->open( $this->getUrl().'/index.php?title=Main_Page' ); - - // click Log in / create account link to open Log in / create account' page - $this->click( "link=Log in / create account" ); - $this->waitForPageToLoad( SeleniumTestConstants::WIKI_TEST_WAIT_TIME ); - $this->assertTrue($this->isElementPresent( "link=Create an account" )); - } - - // Verify Create account - public function testCreateAccount() { - - $this->click( "link=Log out" ); - $this->waitForPageToLoad( SeleniumTestConstants::WIKI_TEST_WAIT_TIME ); - - $this->open( $this->getUrl().'/index.php?title=Main_Page' ); - - $this->click( "link=Log in / create account" ); - $this->waitForPageToLoad( SeleniumTestConstants::WIKI_TEST_WAIT_TIME ); - - $this->click( "link=Create an account" ); - $this->waitForPageToLoad( SeleniumTestConstants::WIKI_TEST_WAIT_TIME ); - - // Verify for blank user name - $this->type( "wpName2", "" ); - $this->click( "wpCreateaccount" ); - $this->waitForPageToLoad( SeleniumTestConstants::WIKI_TEST_WAIT_TIME ); - $this->assertEquals( "Login error\n You have not specified a valid user name.", - $this->getText( "//div[@id='bodyContent']/div[4]" )); - - // Verify for invalid user name - $this->type( "wpName2", "@" ); - $this->click("wpCreateaccount" ); - $this->waitForPageToLoad( SeleniumTestConstants::WIKI_TEST_WAIT_TIME ); - $this->assertEquals( "Login error\n You have not specified a valid user name.", - $this->getText( "//div[@id='bodyContent']/div[4]" )); - - // start of test for blank password - $this->type( "wpName2", $this->userName); - $this->type( "wpPassword2", "" ); - $this->click( "wpCreateaccount" ); - $this->waitForPageToLoad( SeleniumTestConstants::WIKI_TEST_WAIT_TIME ); - $this->assertEquals( "Login error\n Passwords must be at least 1 character.", - $this->getText("//div[@id='bodyContent']/div[4]" )); - - $this->type( "wpName2", $this->userName ); - $this->type( "wpPassword2", $this->password ); - $this->click( "wpCreateaccount" ); - $this->waitForPageToLoad( SeleniumTestConstants::WIKI_TEST_WAIT_TIME ); - $this->assertEquals( "Login error\n The passwords you entered do not match.", - $this->getText( "//div[@id='bodyContent']/div[4]" )); - - $this->type( "wpName2", $this->userName ); - $this->type( "wpPassword2", $this->password ); - $this->type( "wpRetype", $this->password ); - $this->click( "wpCreateaccount" ); - $this->waitForPageToLoad( SeleniumTestConstants::WIKI_TEST_WAIT_TIME ); - - // Verify successful account creation for valid combination of 'Username', 'Password', 'Retype password' - $this->assertEquals( "Welcome, ".ucfirst( $this->userName )."!", - $this->getText( "Welcome,_".ucfirst( $this->userName )."!" )); - } -} - diff --git a/tests/selenium/suites/DeletePageAdminTestCase.php b/tests/selenium/suites/DeletePageAdminTestCase.php deleted file mode 100644 index 9898188f..00000000 --- a/tests/selenium/suites/DeletePageAdminTestCase.php +++ /dev/null @@ -1,89 +0,0 @@ -<?php - -/** - * Selenium server manager - * - * @file - * @ingroup Testing - * Copyright (C) 2010 Nadeesha Weerasinghe <nadeesha@calcey.com> - * http://www.calcey.com/ - * - * 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., - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - * http://www.gnu.org/copyleft/gpl.html - * - * @addtogroup Testing - * - */ - - -class DeletePageAdminTestCase extends SeleniumTestCase { - - // Verify adding a new page - public function testDeletePage() { - - - $newPage = "new"; - $displayName = "New"; - - $this->open( $this->getUrl().'/index.php?title=Main_Page' ); - - $this->type( "searchInput", $newPage ); - $this->click( "searchGoButton" ); - $this->waitForPageToLoad( SeleniumTestConstants::WIKI_TEST_WAIT_TIME ); - $this->click( SeleniumTestConstants::LINK_START.$displayName ); - $this->waitForPageToLoad( SeleniumTestConstants::WIKI_TEST_WAIT_TIME ); - $this->type( SeleniumTestConstants::TEXT_EDITOR, $newPage." text" ); - $this->click( SeleniumTestConstants::BUTTON_SAVE ); - - $this->open( $this->getUrl() . - '/index.php?title=Main_Page&action=edit' ); - $this->click( SeleniumTestConstants::LINK_START."Log out" ); - $this->waitForPageToLoad( SeleniumTestConstants::WIKI_TEST_WAIT_TIME ); - $this->click( SeleniumTestConstants::LINK_START."Log in / create account" ); - $this->waitForPageToLoad( SeleniumTestConstants::WIKI_TEST_WAIT_TIME ); - - $this->type( "wpName1", $this->selenium->getUser() ); - $this->type( "wpPassword1", $this->selenium->getPass() ); - $this->click( "wpLoginAttempt" ); - $this->waitForPageToLoad( SeleniumTestConstants::WIKI_TEST_WAIT_TIME ); - $this->type( "searchInput", "new" ); - $this->click( "searchGoButton"); - $this->waitForPageToLoad( SeleniumTestConstants::WIKI_TEST_WAIT_TIME ); - - // Verify 'Delete' link displayed - $source = $this->gettext( SeleniumTestConstants::LINK_START."Delete" ); - $correct = strstr ( $source, "Delete" ); - $this->assertEquals($correct, true ); - - $this->click( SeleniumTestConstants::LINK_START."Delete" ); - $this->waitForPageToLoad( SeleniumTestConstants::WIKI_TEST_WAIT_TIME ); - - // Verify 'Delete' button available - $this->assertTrue($this->isElementPresent( "wpConfirmB" )); - - $this->click( "wpConfirmB" ); - $this->waitForPageToLoad( SeleniumTestConstants::WIKI_TEST_WAIT_TIME ); - - // Verify 'Action complete' text displayed - $source = $this->gettext( "firstHeading" ); - $correct = strstr ( $source, "Action complete" ); - $this->assertEquals( $correct, true ); - - // Verify '<Page Name> has been deleted. See deletion log for a record of recent deletions.' text displayed - $source = $this->gettext( "//div[@id='bodyContent']/p[1]" ); - $correct = strstr ( $source, "\"New\" has been deleted. See deletion log for a record of recent deletions." ); - $this->assertEquals( $correct, true ); - } -} diff --git a/tests/selenium/suites/EmailPasswordTestCase.php b/tests/selenium/suites/EmailPasswordTestCase.php deleted file mode 100644 index 88d9cf97..00000000 --- a/tests/selenium/suites/EmailPasswordTestCase.php +++ /dev/null @@ -1,81 +0,0 @@ -<?php - -/** - * Selenium server manager - * - * @file - * @ingroup Testing - * Copyright (C) 2010 Nadeesha Weerasinghe <nadeesha@calcey.com> - * http://www.calcey.com/ - * - * 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., - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - * http://www.gnu.org/copyleft/gpl.html - * - * @addtogroup Testing - * - */ - -class EmailPasswordTestCase extends SeleniumTestCase { - - // change user name for each and every test (with in 24 hours) - private $userName = "test1"; - - public function testEmailPasswordButton() { - - $this->click( SeleniumTestConstants::LINK_START."Log out" ); - $this->waitForPageToLoad( SeleniumTestConstants::WIKI_TEST_WAIT_TIME ); - - $this->open( $this->getUrl().'/index.php?title=Main_Page' ); - - // click Log in / create account link to open Log in / create account' page - $this->click( SeleniumTestConstants::LINK_START."Log in / create account" ); - $this->waitForPageToLoad( SeleniumTestConstants::WIKI_TEST_WAIT_TIME ); - $this->assertTrue($this->isElementPresent( "wpMailmypassword" )); - } - - // Verify Email password functionality - public function testEmailPasswordMessages() { - - $this->click( SeleniumTestConstants::LINK_START."Log out" ); - $this->waitForPageToLoad( SeleniumTestConstants::WIKI_TEST_WAIT_TIME ); - - $this->open( $this->getUrl().'/index.php?title=Main_Page' ); - - // click Log in / create account link to open Log in / create account' page - $this->click( SeleniumTestConstants::LINK_START."Log in / create account" ); - $this->waitForPageToLoad( SeleniumTestConstants::WIKI_TEST_WAIT_TIME ); - - $this->type( "wpName1", "" ); - $this->click( "wpMailmypassword" ); - $this->waitForPageToLoad( SeleniumTestConstants::WIKI_TEST_WAIT_TIME ); - $this->assertEquals( "Login error\n You have not specified a valid user name.", - $this->getText("//div[@id='bodyContent']/div[4]")); - - $this->type( "wpName1", $this->userName ); - $this->click( "wpMailmypassword" ); - $this->waitForPageToLoad( SeleniumTestConstants::WIKI_TEST_WAIT_TIME ); - - // Can not run on localhost - $this->assertEquals( "A new password has been sent to the e-mail address registered for ".ucfirst($this->userName).". Please log in again after you receive it.", - $this->getText("//div[@id='bodyContent']/div[4]" )); - - $this->type( "wpName1", $this->userName ); - $this->click( "wpMailmypassword" ); - $this->waitForPageToLoad( SeleniumTestConstants::WIKI_TEST_WAIT_TIME ); - $this->assertEquals( "Login error\n A password reminder has already been sent, within the last 24 hours. To prevent abuse, only one password reminder will be sent per 24 hours.", - $this->getText( "//div[@id='bodyContent']/div[4]" )); - } -} - diff --git a/tests/selenium/suites/MediaWikiEditorConfig.php b/tests/selenium/suites/MediaWikiEditorConfig.php deleted file mode 100644 index 072c3cb2..00000000 --- a/tests/selenium/suites/MediaWikiEditorConfig.php +++ /dev/null @@ -1,48 +0,0 @@ -<?php - -/** - * Selenium server manager - * - * @file - * @ingroup Testing - * Copyright (C) 2010 Nadeesha Weerasinghe <nadeesha@calcey.com> - * http://www.calcey.com/ - * - * 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., - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - * http://www.gnu.org/copyleft/gpl.html - * - * @addtogroup Testing - * - */ - -class MediaWikiEditorConfig { - - public static function getSettings(&$includeFiles, &$globalConfigs) { - $includes = array( - //files that needed to be included would go here - //commenting out because this does not exist - //'tests/selenium/suites/MediaWikiCommonFunction.php' - ); - $configs = array( - 'wgPageLoadTime' => "600000" - ); - $includeFiles = array_merge( $includeFiles, $includes ); - $globalConfigs = array_merge( $globalConfigs, $configs); - return true; - } -} - - - diff --git a/tests/selenium/suites/MediaWikiEditorTestSuite.php b/tests/selenium/suites/MediaWikiEditorTestSuite.php deleted file mode 100644 index c0aee9f5..00000000 --- a/tests/selenium/suites/MediaWikiEditorTestSuite.php +++ /dev/null @@ -1,18 +0,0 @@ -<?php - -class MediaWikiEditorTestSuite extends SeleniumTestSuite { - public function setUp() { - $this->setLoginBeforeTests( true ); - parent::setUp(); - } - public function addTests() { - $testFiles = array( - 'tests/selenium/suites/AddNewPageTestCase.php', - 'tests/selenium/suites/AddContentToNewPageTestCase.php', - 'tests/selenium/suites/PreviewPageTestCase.php', - 'tests/selenium/suites/SavePageTestCase.php', - ); - parent::addTestFiles( $testFiles ); - } -} - diff --git a/tests/selenium/suites/MediaWikiExtraTestSuite.php b/tests/selenium/suites/MediaWikiExtraTestSuite.php deleted file mode 100644 index 5cd0a349..00000000 --- a/tests/selenium/suites/MediaWikiExtraTestSuite.php +++ /dev/null @@ -1,20 +0,0 @@ -<?php - -class MediaWikiExtraTestSuite extends SeleniumTestSuite { - public function setUp() { - $this->setLoginBeforeTests( true ); - parent::setUp(); - } - public function addTests() { - $testFiles = array( - 'tests/selenium/suites/MyContributionsTestCase.php', - 'tests/selenium/suites/MyWatchListTestCase.php', - 'tests/selenium/suites/UserPreferencesTestCase.php', - 'tests/selenium/suites/MovePageTestCase.php', - 'tests/selenium/suites/PageSearchTestCase.php', - 'tests/selenium/suites/EmailPasswordTestCase.php', - 'tests/selenium/suites/CreateAccountTestCase.php' - ); - parent::addTestFiles( $testFiles ); - } -} diff --git a/tests/selenium/suites/MediawikiCoreSmokeTestCase.php b/tests/selenium/suites/MediawikiCoreSmokeTestCase.php deleted file mode 100644 index 6b8fc974..00000000 --- a/tests/selenium/suites/MediawikiCoreSmokeTestCase.php +++ /dev/null @@ -1,69 +0,0 @@ -<?php -/* - * Stub of tests be need as part of the hack-a-ton - */ -class MediawikiCoreSmokeTestCase extends SeleniumTestCase { - public function testUserLogin() { - - } - - public function testChangeUserPreference() { - - } - - /** - * TODO: generalize this test to be reusable for different skins - */ - public function testCreateNewPageVector() { - - } - - /** - * TODO: generalize this test to be reusable for different skins - */ - public function testEditExistingPageVector() { - - } - - /** - * TODO: generalize this test to be reusable for different skins - */ - public function testCreateNewPageMonobook() { - - } - - /** - * TODO: generalize this test to be reusable for different skins - */ - public function testEditExistingPageMonobook() { - - } - - public function testImageUpload() { - $this->login(); - $this->open( $this->getUrl() . - '/index.php?title=Special:Upload' ); - $this->type( 'wpUploadFile', __DIR__ . - "\\..\\data\\Wikipedia-logo-v2-de.png" ); - $this->check( 'wpIgnoreWarning' ); - $this->click( 'wpUpload' ); - $this->waitForPageToLoad( 30000 ); - - $this->assertSeleniumHTMLContains( - '//h1[@class="firstHeading"]', "Wikipedia-logo-v2-de.png" ); - - /* - $this->open( $this->getUrl() . '/index.php?title=Image:' - . ucfirst( $this->filename ) . '&action=delete' ); - $this->type( 'wpReason', 'Remove test file' ); - $this->click( 'mw-filedelete-submit' ); - $this->waitForPageToLoad( 10000 ); - - // Todo: This message is localized - $this->assertSeleniumHTMLContains( '//div[@id="bodyContent"]/p', - ucfirst( $this->filename ) . '.*has been deleted.' ); - */ - } - - -} diff --git a/tests/selenium/suites/MediawikiCoreSmokeTestSuite.php b/tests/selenium/suites/MediawikiCoreSmokeTestSuite.php deleted file mode 100644 index a9a9b4d6..00000000 --- a/tests/selenium/suites/MediawikiCoreSmokeTestSuite.php +++ /dev/null @@ -1,19 +0,0 @@ -<?php -/** - * Stubs for now. We're going to start populating this test. - */ -class MediawikiCoreSmokeTestSuite extends SeleniumTestSuite -{ - public function setUp() { - $this->setLoginBeforeTests( false ); - parent::setUp(); - } - public function addTests() { - $testFiles = array( - 'tests/selenium/suites/MediawikiCoreSmokeTestCase.php' - ); - parent::addTestFiles( $testFiles ); - } - - -} diff --git a/tests/selenium/suites/MovePageTestCase.php b/tests/selenium/suites/MovePageTestCase.php deleted file mode 100644 index 5263e7bb..00000000 --- a/tests/selenium/suites/MovePageTestCase.php +++ /dev/null @@ -1,117 +0,0 @@ -<?php - -/** - * Selenium server manager - * - * @file - * @ingroup Testing - * Copyright (C) 2010 Nadeesha Weerasinghe <nadeesha@calcey.com> - * http://www.calcey.com/ - * - * 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., - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - * http://www.gnu.org/copyleft/gpl.html - * - * @addtogroup Testing - * - */ - -class MovePageTestCase extends SeleniumTestCase { - - // Verify move(rename) wiki page - public function testMovePage() { - - $newPage = "mypage99"; - $displayName = "Mypage99"; - - $this->open( $this->getUrl() . - '/index.php?title=Main_Page&action=edit' ); - $this->type( "searchInput", $newPage ); - $this->click( "searchGoButton" ); - $this->waitForPageToLoad( SeleniumTestConstants::WIKI_TEST_WAIT_TIME ); - $this->click( "link=".$displayName ); - $this->waitForPageToLoad( SeleniumTestConstants::WIKI_TEST_WAIT_TIME ); - $this->type( SeleniumTestConstants::TEXT_EDITOR, $newPage." text" ); - $this->click( SeleniumTestConstants::BUTTON_SAVE ); - $this->waitForPageToLoad( SeleniumTestConstants::WIKI_TEST_WAIT_TIME ); - - // Verify link 'Move' available - $this->assertTrue($this->isElementPresent( "link=Move" )); - - $this->click( "link=Move" ); - $this->waitForPageToLoad( SeleniumTestConstants::WIKI_TEST_WAIT_TIME ); - - // Verify correct page name displayed under 'Move Page' field - $this->assertEquals($displayName, - $this->getText("//table[@id='mw-movepage-table']/tbody/tr[1]/td[2]/strong/a")); - $movePageName = $this->getText( "//table[@id='mw-movepage-table']/tbody/tr[1]/td[2]/strong/a" ); - - // Verify 'To new title' field has current page name as the default name - $newTitle = $this->getValue( "wpNewTitle" ); - $correct = strstr( $movePageName , $newTitle ); - $this->assertEquals( $correct, true ); - - $this->type( "wpNewTitle", $displayName ); - $this->click( "wpMove" ); - $this->waitForPageToLoad( SeleniumTestConstants::WIKI_TEST_WAIT_TIME ); - - // Verify warning message for the same source and destination titles - $this->assertEquals( "Source and destination titles are the same; cannot move a page over itself.", - $this->getText("//div[@id='bodyContent']/p[4]/strong" )); - - // Verify warning message for the blank title - $this->type( "wpNewTitle", "" ); - $this->click( "wpMove" ); - $this->waitForPageToLoad( SeleniumTestConstants::WIKI_TEST_WAIT_TIME ); - - // Verify warning message for the blank title - $this->assertEquals( "The requested page title was invalid, empty, or an incorrectly linked inter-language or inter-wiki title. It may contain one or more characters which cannot be used in titles.", - $this->getText( "//div[@id='bodyContent']/p[4]/strong" )); - - // Verify warning messages for the invalid titles - $this->type( "wpNewTitle", "# < > [ ] | { }" ); - $this->click( "wpMove" ); - $this->waitForPageToLoad( SeleniumTestConstants::WIKI_TEST_WAIT_TIME ); - $this->assertEquals( "The requested page title was invalid, empty, or an incorrectly linked inter-language or inter-wiki title. It may contain one or more characters which cannot be used in titles.", - $this->getText( "//div[@id='bodyContent']/p[4]/strong" )); - - $this->type( "wpNewTitle", $displayName."move" ); - $this->click( "wpMove" ); - $this->waitForPageToLoad( SeleniumTestConstants::WIKI_TEST_WAIT_TIME ); - - // Verify move success message displayed correctly - $this->assertEquals( "\"".$displayName."\" has been moved to \"".$displayName."move"."\"", - $this->getText( "//div[@id='bodyContent']/p[1]/b" )); - - $this->type( "searchInput", $newPage."move" ); - $this->click( "searchGoButton" ); - $this->waitForPageToLoad( SeleniumTestConstants::WIKI_TEST_WAIT_TIME ); - - // Verify search using new page name - $this->assertEquals( $displayName."move", $this->getText( "firstHeading" )); - - $this->type( "searchInput", $newPage ); - $this->click( "searchGoButton" ); - $this->waitForPageToLoad( SeleniumTestConstants::WIKI_TEST_WAIT_TIME ); - - // Verify search using old page name - $redirectPageName = $this->getText( "//*[@id='contentSub']" ); - $this->assertEquals( "(Redirected from ".$displayName.")" , $redirectPageName ); - - // newpage delete - $this->deletePage( $newPage."move" ); - $this->deletePage( $newPage ); - } -} - diff --git a/tests/selenium/suites/MyContributionsTestCase.php b/tests/selenium/suites/MyContributionsTestCase.php deleted file mode 100644 index b8d2d48d..00000000 --- a/tests/selenium/suites/MyContributionsTestCase.php +++ /dev/null @@ -1,65 +0,0 @@ -<?php - -/** - * Selenium server manager - * - * @file - * @ingroup Testing - * Copyright (C) 2010 Nadeesha Weerasinghe <nadeesha@calcey.com> - * http://www.calcey.com/ - * - * 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., - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - * http://www.gnu.org/copyleft/gpl.html - * - * @addtogroup Testing - * - */ - -require_once dirname( __DIR__ ) . '/SeleniumTestConstants.php'; - -class MyContributionsTestCase extends SeleniumTestCase { - - // Verify user contributions - public function testRecentChangesAvailability() { - - $newPage = $this->createNewTestPage( "MyContributionsTest" ); - - // Verify My contributions Link available - $this->assertTrue($this->isElementPresent( "link=Contributions" )); - - - $this->click( "link=Contributions" ); - $this->waitForPageToLoad( SeleniumTestConstants::WIKI_TEST_WAIT_TIME ); - - // Verify recent page adding available on My Contributions list - $this->assertEquals( $newPage, $this->getText( "link=".$newPage )); - - $this->type( SeleniumTestConstants::INPUT_SEARCH_BOX, $newPage ); - $this->click( SeleniumTestConstants::BUTTON_SEARCH ); - $this->waitForPageToLoad( SeleniumTestConstants::WIKI_TEST_WAIT_TIME ); - - $this->click( SeleniumTestConstants::LINK_EDIT ); - $this->waitForPageToLoad( SeleniumTestConstants::WIKI_TEST_WAIT_TIME ); - $this->type( SeleniumTestConstants::TEXT_EDITOR, $newPage . " text changed" ); - $this->click( SeleniumTestConstants::BUTTON_SAVE ); - $this->waitForPageToLoad( SeleniumTestConstants::WIKI_TEST_WAIT_TIME ); - $this->click( "link=Contributions" ); - $this->waitForPageToLoad( SeleniumTestConstants::WIKI_TEST_WAIT_TIME ); - - // Verify recent page changes available on My Contributions - $this->assertTrue( $this->isTextPresent( $newPage ) ); - } -} - diff --git a/tests/selenium/suites/MyWatchListTestCase.php b/tests/selenium/suites/MyWatchListTestCase.php deleted file mode 100644 index 998fab9d..00000000 --- a/tests/selenium/suites/MyWatchListTestCase.php +++ /dev/null @@ -1,57 +0,0 @@ -<?php - -/** - * Selenium server manager - * - * @file - * @ingroup Testing - * Copyright (C) 2010 Nadeesha Weerasinghe <nadeesha@calcey.com> - * http://www.calcey.com/ - * - * 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., - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - * http://www.gnu.org/copyleft/gpl.html - * - * @addtogroup Testing - * - */ - -require_once dirname( __DIR__ ) . '/SeleniumTestConstants.php'; - -class MyWatchListTestCase extends SeleniumTestCase { - - // Verify user watchlist - public function testMyWatchlist() { - - $pageName = $this->createNewTestPage( "MyWatchListTest", true ); - // Verify link 'My Watchlist' available - $this->assertTrue( $this->isElementPresent( SeleniumTestConstants::LINK_START."Watchlist" ) ); - - $this->click( SeleniumTestConstants::LINK_START."Watchlist" ); - $this->waitForPageToLoad( SeleniumTestConstants::WIKI_TEST_WAIT_TIME ); - - // Verify newly added page to the watchlist is available - $this->assertEquals( $pageName, $this->getText( SeleniumTestConstants::LINK_START.$pageName )); - - $this->click( SeleniumTestConstants::LINK_START.$pageName ); - $this->waitForPageToLoad( SeleniumTestConstants::WIKI_TEST_WAIT_TIME ); - $this->click( SeleniumTestConstants::LINK_EDIT ); - $this->waitForPageToLoad( SeleniumTestConstants::WIKI_TEST_WAIT_TIME ); - $this->click( "wpWatchthis" ); - $this->click( SeleniumTestConstants::BUTTON_SAVE ); - $this->assertFalse( $this->isElementPresent( SeleniumTestConstants::LINK_START.$pageName ) ); - //todo watch using the dropdown menu - } -} - diff --git a/tests/selenium/suites/PageDeleteTestSuite.php b/tests/selenium/suites/PageDeleteTestSuite.php deleted file mode 100644 index 256e3542..00000000 --- a/tests/selenium/suites/PageDeleteTestSuite.php +++ /dev/null @@ -1,16 +0,0 @@ -<?php - -class PageDeleteTestSuite extends SeleniumTestSuite { - public function setUp() { - $this->setLoginBeforeTests( true ); - parent::setUp(); - } - public function addTests() { - $testFiles = array( - 'tests/selenium/suites/DeletePageAdminTestCase.php' - ); - parent::addTestFiles( $testFiles ); - } - - -} diff --git a/tests/selenium/suites/PageSearchTestCase.php b/tests/selenium/suites/PageSearchTestCase.php deleted file mode 100644 index fe71eada..00000000 --- a/tests/selenium/suites/PageSearchTestCase.php +++ /dev/null @@ -1,105 +0,0 @@ -<?php - -/** - * Selenium server manager - * - * @file - * @ingroup Testing - * Copyright (C) 2010 Nadeesha Weerasinghe <nadeesha@calcey.com> - * http://www.calcey.com/ - * - * 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., - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - * http://www.gnu.org/copyleft/gpl.html - * - * @addtogroup Testing - * - */ - -class PageSearchTestCase extends SeleniumTestCase { - - // Verify the functionality of the 'Go' button - public function testPageSearchBtnGo() { - - $this->open( $this->getUrl() . - '/index.php?title=Main_Page&action=edit' ); - $this->type( SeleniumTestConstants::INPUT_SEARCH_BOX, "calcey qa" ); - $this->click( "searchGoButton" ); - $this->waitForPageToLoad( SeleniumTestConstants::WIKI_TEST_WAIT_TIME ); - - // Verify no page matched with the entered search text - $source = $this->gettext( "//div[@id='bodyContent']/div[4]/p/b" ); - $correct = strstr ( $source, "Create the page \"Calcey qa\" on this wiki!" ); - $this->assertEquals( $correct, true ); - - $this->click( "link=Calcey qa" ); - $this->waitForPageToLoad( SeleniumTestConstants::WIKI_TEST_WAIT_TIME ); - - $this->type( SeleniumTestConstants::TEXT_EDITOR , "Calcey QA team" ); - $this->click( "wpSave" ); - $this->waitForPageToLoad( SeleniumTestConstants::WIKI_TEST_WAIT_TIME ); - - } - - // Verify the functionality of the 'Search' button - public function testPageSearchBtnSearch() { - - $this->open( $this->getUrl() . - '/index.php?title=Main_Page&action=edit' ); - $this->type( SeleniumTestConstants::INPUT_SEARCH_BOX, "Calcey web" ); - $this->click( SeleniumTestConstants::BUTTON_SEARCH ); - $this->waitForPageToLoad( SeleniumTestConstants::WIKI_TEST_WAIT_TIME ); - - // Verify no page is available as the search text - $source = $this->gettext( "//div[@id='bodyContent']/div[4]/p[2]/b" ); - $correct = strstr ( $source, "Create the page \"Calcey web\" on this wiki!" ); - $this->assertEquals( $correct, true ); - - $this->click( "link=Calcey web" ); - $this->waitForPageToLoad( SeleniumTestConstants::WIKI_TEST_WAIT_TIME ); - - $this->type( SeleniumTestConstants::TEXT_EDITOR, "Calcey web team" ); - $this->click( SeleniumTestConstants::BUTTON_SAVE ); - $this->waitForPageToLoad( SeleniumTestConstants::WIKI_TEST_WAIT_TIME ); - - // Verify saved page is opened when the exact page name is given - $this->type( SeleniumTestConstants::INPUT_SEARCH_BOX, "Calcey web" ); - $this->click( SeleniumTestConstants::BUTTON_SEARCH ); - $this->waitForPageToLoad( SeleniumTestConstants::WIKI_TEST_WAIT_TIME ); - - // Verify exact page matched with the entered search text using 'Search' button - $source = $this->getText( "//*[@id='bodyContent']/div[4]/p/b" ); - $correct = strstr( $source, "There is a page named \"Calcey web\" on this wiki." ); - $this->assertEquals( $correct, true ); - - // Verify resutls available when partial page name is entered as the search text - $this->type( SeleniumTestConstants::INPUT_SEARCH_BOX, "Calcey" ); - $this->click( SeleniumTestConstants::BUTTON_SEARCH ); - $this->waitForPageToLoad( SeleniumTestConstants::WIKI_TEST_WAIT_TIME ); - - // Verify text avaialble in the search result under the page titles - if($this->isElementPresent( "Page_title_matches" )) { - $textPageTitle = $this->getText( "//*[@id='bodyContent']/div[4]/ul[1]/li[1]/div[1]/a" ); - $this->assertContains( 'Calcey', $textPageTitle ); - } - - // Verify text avaialble in the search result under the page text - if($this->isElementPresent( "Page_text_matches" )) { - $textPageText = $this->getText( "//*[@id='bodyContent']/div[4]/ul[2]/li[2]/div[2]/span" ); - $this->assertContains( 'Calcey', $textPageText ); - } - $this->deletePage("Calcey QA"); - $this->deletePage("Calcey web"); - } -} diff --git a/tests/selenium/suites/PreviewPageTestCase.php b/tests/selenium/suites/PreviewPageTestCase.php deleted file mode 100644 index 32206b98..00000000 --- a/tests/selenium/suites/PreviewPageTestCase.php +++ /dev/null @@ -1,53 +0,0 @@ -<?php - -/** - * Selenium server manager - * - * @file - * @ingroup Testing - * Copyright (C) 2010 Nadeesha Weerasinghe <nadeesha@calcey.com> - * http://www.calcey.com/ - * - * 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., - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - * http://www.gnu.org/copyleft/gpl.html - * - * @addtogroup Testing - * - */ - -class PreviewPageTestCase extends SeleniumTestCase { - - // Verify adding a new page - public function testPreviewPage() { - $wikiText = "Adding this page to test the \n Preview button functionality"; - $newPage = "Test Preview Page"; - $this->open( $this->getUrl() . - '/index.php?title=Main_Page&action=edit' ); - $this->getNewPage( $newPage ); - $this->type( SeleniumTestConstants::TEXT_EDITOR, $wikiText."" ); - $this->assertTrue($this->isElementPresent( "//*[@id='wpPreview']" )); - - $this->click( "wpPreview" ); - - // Verify saved page available - $source = $this->gettext( "firstHeading" ); - $correct = strstr( $source, "Test Preview Page" ); - $this->assertEquals( $correct, true); - - // Verify page content previewed succesfully - $contentOfPreviewPage = $this->getText( "//*[@id='content']" ); - $this->assertContains( $wikiText, $contentOfPreviewPage ); - } -} diff --git a/tests/selenium/suites/SavePageTestCase.php b/tests/selenium/suites/SavePageTestCase.php deleted file mode 100644 index 310ff20a..00000000 --- a/tests/selenium/suites/SavePageTestCase.php +++ /dev/null @@ -1,58 +0,0 @@ -<?php - -/** - * Selenium server manager - * - * @file - * @ingroup Testing - * Copyright (C) 2010 Nadeesha Weerasinghe <nadeesha@calcey.com> - * http://www.calcey.com/ - * - * 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., - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - * http://www.gnu.org/copyleft/gpl.html - * - * @addtogroup Testing - * - */ - -class SavePageTestCase extends SeleniumTestCase { - - // Verify adding a new page - public function testSavePage() { - $wikiText = "Adding this page to test the Save button functionality"; - $newPage = "Test Save Page"; - - $this->open( $this->getUrl() . - '/index.php?title=Main_Page&action=edit' ); - $this->getNewPage($newPage); - $this->type( SeleniumTestConstants::TEXT_EDITOR, $wikiText ); - - // verify 'Save' button available - $this->assertTrue($this->isElementPresent( SeleniumTestConstants::BUTTON_SAVE )); - $this->click( SeleniumTestConstants::BUTTON_SAVE ); - - // Verify saved page available - $source = $this->gettext( "firstHeading" ); - $correct = strstr( $source, "Test Save Page" ); - - // Verify Saved page name displayed correctly - $this->assertEquals( $correct, true ); - - // Verify page content saved succesfully - $contentOfSavedPage = $this->getText( "//*[@id='content']" ); - $this->assertContains( $wikiText, $contentOfSavedPage ); - $this->deletePage( $newPage ); - } -} diff --git a/tests/selenium/suites/SimpleSeleniumConfig.php b/tests/selenium/suites/SimpleSeleniumConfig.php deleted file mode 100644 index 54def35a..00000000 --- a/tests/selenium/suites/SimpleSeleniumConfig.php +++ /dev/null @@ -1,30 +0,0 @@ -<?php -class SimpleSeleniumConfig { - - public static function getSettings(&$includeFiles, &$globalConfigs, &$resourceFiles) { - global $IP; - $includes = array( - //files that needed to be included would go here - ); - $configs = array( - 'wgDBprefix' => 'mw_', - 'wgDBTableOptions' => 'ENGINE=InnoDB, DEFAULT CHARSET=binary', - 'wgDBmysql5' => 'false', - 'wgMainCacheType' => 'CACHE_NONE', - 'wgParserCacheType' => 'CACHE_NONE', - 'wgMemCachedServers'=> array(), - 'wgLanguageCode' => 'en', - 'wgSitename' => 'test_wiki', - 'wgDefaultSkin' => 'chick' - ); - $resources = array( - 'db' => "$IP/tests/selenium/data/SimpleSeleniumTestDB.sql", - 'images' => "$IP/tests/selenium/data/SimpleSeleniumTestImages.zip" - ); - - $includeFiles = array_merge( $includeFiles, $includes ); - $globalConfigs = array_merge( $globalConfigs, $configs); - $resourceFiles = array_merge( $resourceFiles, $resources ); - return true; - } -}
\ No newline at end of file diff --git a/tests/selenium/suites/SimpleSeleniumTestCase.php b/tests/selenium/suites/SimpleSeleniumTestCase.php deleted file mode 100644 index b87172e6..00000000 --- a/tests/selenium/suites/SimpleSeleniumTestCase.php +++ /dev/null @@ -1,39 +0,0 @@ -<?php -/* - * This test case is part of the SimpleSeleniumTestSuite. - * Configuration for these tests are documented as part of SimpleSeleniumTestSuite.php - */ -class SimpleSeleniumTestCase extends SeleniumTestCase { - public function testBasic() { - $this->open( $this->getUrl() . - '/index.php?title=Selenium&action=edit' ); - $this->type( "wpTextbox1", "This is a basic test" ); - $this->click( "wpPreview" ); - $this->waitForPageToLoad( SeleniumTestConstants::WIKI_TEST_WAIT_TIME ); - - // check result - $source = $this->getText( "//div[@id='wikiPreview']/p" ); - $correct = strstr( $source, "This is a basic test" ); - $this->assertEquals( $correct, true ); - } - - /** - * All this test really does is verify that a global var was set. - * It depends on $wgDefaultSkin = 'chick'; being set - */ - public function testGlobalVariableForDefaultSkin() { - $this->open( $this->getUrl() . '/index.php' ); - $bodyClass = $this->getAttribute( "//body/@class" ); - $this-> assertContains('skin-chick', $bodyClass, 'Chick skin not set'); - } - - /** - * Just verify that the test db was loaded correctly - */ - public function testDatabaseResourceLoadedCorrectly() { - $this->open( $this->getUrl() . '/index.php/TestResources?action=purge' ); - $testString = $this->gettext( "//body//*[@id='firstHeading']" ); - $this-> assertEquals('TestResources', $testString, 'Article that should be present in the test db was not found.'); - } - -} diff --git a/tests/selenium/suites/SimpleSeleniumTestSuite.php b/tests/selenium/suites/SimpleSeleniumTestSuite.php deleted file mode 100644 index 2e0c4ee2..00000000 --- a/tests/selenium/suites/SimpleSeleniumTestSuite.php +++ /dev/null @@ -1,26 +0,0 @@ -<?php -/** - * Sample test suite. - * Two ways to configure MW for these tests - * 1) If you are running multiple test suites, add the following in LocalSettings.php - * require_once("tests/selenium/SimpleSeleniumConfig.php"); - * $wgSeleniumTestConfigs['SimpleSeleniumTestSuite'] = 'SimpleSeleniumConfig::getSettings'; - * OR - * 2) Add the following to your Localsettings.php - * $wgDefaultSkin = 'chick'; - */ -class SimpleSeleniumTestSuite extends SeleniumTestSuite -{ - public function setUp() { - $this->setLoginBeforeTests( false ); - parent::setUp(); - } - public function addTests() { - $testFiles = array( - 'selenium/suites/SimpleSeleniumTestCase.php' - ); - parent::addTestFiles( $testFiles ); - } - - -} diff --git a/tests/selenium/suites/UserPreferencesTestCase.php b/tests/selenium/suites/UserPreferencesTestCase.php deleted file mode 100644 index 3ceb3a99..00000000 --- a/tests/selenium/suites/UserPreferencesTestCase.php +++ /dev/null @@ -1,179 +0,0 @@ -<?php - -/** - * Selenium server manager - * - * @file - * @ingroup Testing - * Copyright (C) 2010 Nadeesha Weerasinghe <nadeesha@calcey.com> - * http://www.calcey.com/ - * - * 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., - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - * http://www.gnu.org/copyleft/gpl.html - * - * @addtogroup Testing - * - */ - -class UserPreferencesTestCase extends SeleniumTestCase { - - // Verify user information - public function testUserInfoDisplay() { - - $this->open( $this->getUrl() . - '/index.php?title=Main_Page&action=edit' ); - $this->click( SeleniumTestConstants::LINK_START."My preferences" ); - $this->waitForPageToLoad( SeleniumTestConstants::WIKI_TEST_WAIT_TIME ); - - // Verify correct username displayed in User Preferences - $this->assertEquals( $this->getText( "//li[@id='pt-userpage']/a" ), - $this->getText( "//table[@id='mw-htmlform-info']/tbody/tr[1]/td[2]" )); - - // Verify existing Signature Displayed correctly - $this->assertEquals( $this->selenium->getUser(), - $this->getTable( "mw-htmlform-signature.0.1" ) ); - } - - // Verify change password - public function testChangePassword() { - - $this->open( $this->getUrl() . - '/index.php?title=Main_Page&action=edit' ); - $this->click( SeleniumTestConstants::LINK_START."My preferences" ); - $this->waitForPageToLoad( SeleniumTestConstants::WIKI_TEST_WAIT_TIME ); - - $this->click( SeleniumTestConstants::LINK_START."Change password" ); - $this->waitForPageToLoad( SeleniumTestConstants::WIKI_TEST_WAIT_TIME ); - - $this->type( "wpPassword", "12345" ); - $this->type( "wpNewPassword", "54321" ); - $this->type( "wpRetype", "54321" ); - $this->click( "//input[@value='Change password']" ); - $this->waitForPageToLoad( SeleniumTestConstants::WIKI_TEST_WAIT_TIME ); - - $this->assertEquals( "Preferences", $this->getText( "firstHeading" )); - - $this->click( SeleniumTestConstants::LINK_START."Change password" ); - $this->waitForPageToLoad( SeleniumTestConstants::WIKI_TEST_WAIT_TIME ); - - $this->type( "wpPassword", "54321" ); - $this->type( "wpNewPassword", "12345" ); - $this->type( "wpRetype", "12345" ); - $this->click( "//input[@value='Change password']" ); - $this->waitForPageToLoad( SeleniumTestConstants::WIKI_TEST_WAIT_TIME ); - $this->assertEquals( "Preferences", $this->getText( "firstHeading" )); - - $this->click( SeleniumTestConstants::LINK_START."Change password" ); - $this->waitForPageToLoad( SeleniumTestConstants::WIKI_TEST_WAIT_TIME ); - - $this->type( "wpPassword", "54321" ); - $this->type( "wpNewPassword", "12345" ); - $this->type( "wpRetype", "12345" ); - $this->click( "//input[@value='Change password']" ); - $this->waitForPageToLoad( SeleniumTestConstants::WIKI_TEST_WAIT_TIME ); - } - - // Verify successful preferences save - public function testSuccessfullSave() { - - $this->open( $this->getUrl() . - '/index.php?title=Main_Page&action=edit' ); - $this->click( SeleniumTestConstants::LINK_START."My preferences" ); - $this->waitForPageToLoad( SeleniumTestConstants::WIKI_TEST_WAIT_TIME ); - - $this->type( "mw-input-realname", "Test User" ); - $this->click( "prefcontrol" ); - $this->waitForPageToLoad( SeleniumTestConstants::WIKI_TEST_WAIT_TIME ); - - // Verify "Your preferences have been saved." message - $this->assertEquals( "Your preferences have been saved.", - $this->getText( "//div[@id='bodyContent']/div[4]/strong/p" )); - $this->type( "mw-input-realname", "" ); - $this->click( "prefcontrol" ); - $this->waitForPageToLoad( SeleniumTestConstants::WIKI_TEST_WAIT_TIME ); - - } - - // Verify change signature - public function testChangeSignature() { - - $this->open( $this->getUrl() . - '/index.php?title=Main_Page&action=edit' ); - $this->click( SeleniumTestConstants::LINK_START."My preferences" ); - $this->waitForPageToLoad( SeleniumTestConstants::WIKI_TEST_WAIT_TIME ); - - $this->type( "mw-input-nickname", "TestSignature" ); - $this->click( "prefcontrol" ); - $this->waitForPageToLoad( SeleniumTestConstants::WIKI_TEST_WAIT_TIME ); - - // Verify change user signature - $this->assertEquals( "TestSignature", $this->getText( SeleniumTestConstants::LINK_START."TestSignature" )); - $this->type( "mw-input-nickname", "Test" ); - $this->click( "prefcontrol" ); - $this->waitForPageToLoad( SeleniumTestConstants::WIKI_TEST_WAIT_TIME ); - } - - // Verify change date format - public function testChangeDateFormatTimeZone() { - - $this->open( $this->getUrl() . - '/index.php?title=Main_Page&action=edit' ); - - $this->click( SeleniumTestConstants::LINK_START."My preferences" ); - $this->waitForPageToLoad( SeleniumTestConstants::WIKI_TEST_WAIT_TIME ); - $this->click( SeleniumTestConstants::LINK_START."Date and time" ); - $this->waitForPageToLoad( SeleniumTestConstants::WIKI_TEST_WAIT_TIME ); - - $this->click( "mw-input-date-dmy" ); - $this->select( "mw-input-timecorrection", "label=Asia/Colombo" ); - $this->click( "prefcontrol" ); - $this->waitForPageToLoad( SeleniumTestConstants::WIKI_TEST_WAIT_TIME ); - - // Verify Date format and time zome saved - $this->assertEquals( "Your preferences have been saved.", - $this->getText( "//div[@id='bodyContent']/div[4]/strong/p" )); - } - - // Verify restoring all default settings - public function testSetAllDefault() { - - $this->open( $this->getUrl() . - '/index.php?title=Main_Page&action=edit' ); - $this->click( SeleniumTestConstants::LINK_START."My preferences" ); - $this->waitForPageToLoad( SeleniumTestConstants::WIKI_TEST_WAIT_TIME ); - - // Verify restoring all default settings - $this->assertEquals( "Restore all default settings", - $this->getText( SeleniumTestConstants::LINK_START."Restore all default settings" )); - - $this->click("//*[@id='preferences']/div/a"); - $this->waitForPageToLoad( SeleniumTestConstants::WIKI_TEST_WAIT_TIME ); - - // Verify 'This can not be undone' warning message displayed - $this->assertTrue($this->isElementPresent("//input[@value='Restore all default settings']")); - - // Verify 'Restore all default settings' button available - $this->assertEquals("You can use this page to reset your preferences to the site defaults. This cannot be undone.", - $this->getText("//div[@id='bodyContent']/p")); - - $this->click("//input[@value='Restore all default settings']"); - $this->waitForPageToLoad( SeleniumTestConstants::WIKI_TEST_WAIT_TIME ); - - // Verify preferences saved successfully - $this->assertEquals("Your preferences have been saved.", - $this->getText("//div[@id='bodyContent']/div[4]/strong/p")); - } -} - diff --git a/tests/testHelpers.inc b/tests/testHelpers.inc index 39e18c92..88e5885b 100644 --- a/tests/testHelpers.inc +++ b/tests/testHelpers.inc @@ -1,6 +1,56 @@ <?php +/** + * Recording for passing/failing tests. + * + * 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 + * + * @file + * @ingroup Testing + */ + +/** + * Interface to record parser test results. + * + * The ITestRecorder is a very simple interface to record the result of + * MediaWiki parser tests. One should call start() before running the + * full parser tests and end() once all the tests have been finished. + * After each test, you should use record() to keep track of your tests + * results. Finally, report() is used to generate a summary of your + * test run, one could dump it to the console for human consumption or + * register the result in a database for tracking purposes. + * + * @since 1.22 + */ +interface ITestRecorder { -class TestRecorder { + /** Called at beginning of the parser test run */ + public function start(); + + /** Called after each test */ + public function record( $test, $result ); + + /** Called before finishing the test run */ + public function report(); + + /** Called at the end of the parser test run */ + public function end(); + +} + +class TestRecorder implements ITestRecorder { var $parent; var $term; @@ -38,7 +88,7 @@ class TestRecorder { if ( $success == $total ) { print $this->term->color( 32 ) . "ALL TESTS PASSED!"; } else { - $failed = $total - $success ; + $failed = $total - $success; print $this->term->color( 31 ) . "$failed tests failed!"; } @@ -48,10 +98,10 @@ class TestRecorder { } } -class DbTestPreviewer extends TestRecorder { - protected $lb; // /< Database load balancer - protected $db; // /< Database connection to the main DB - protected $curRun; // /< run ID number for the current run +class DbTestPreviewer extends TestRecorder { + protected $lb; // /< Database load balancer + protected $db; // /< Database connection to the main DB + protected $curRun; // /< run ID number for the current run protected $prevRun; // /< run ID number for the previous run, if any protected $results; // /< Result array @@ -73,9 +123,9 @@ class DbTestPreviewer extends TestRecorder { function start() { parent::start(); - if ( ! $this->db->tableExists( 'testrun', __METHOD__ ) - || ! $this->db->tableExists( 'testitem', __METHOD__ ) ) - { + if ( !$this->db->tableExists( 'testrun', __METHOD__ ) + || !$this->db->tableExists( 'testitem', __METHOD__ ) + ) { print "WARNING> `testrun` table not found in database.\n"; $this->prevRun = false; } else { @@ -113,8 +163,8 @@ class DbTestPreviewer extends TestRecorder { foreach ( $res as $row ) { if ( !$this->parent->regex - || preg_match( "/{$this->parent->regex}/i", $row->ti_name ) ) - { + || preg_match( "/{$this->parent->regex}/i", $row->ti_name ) + ) { $prevResults[$row->ti_name] = $row->ti_success; } } @@ -174,34 +224,34 @@ class DbTestPreviewer extends TestRecorder { private function getTestStatusInfo( $testname, $after ) { // If we're looking at a test that has just been removed, then say when it first appeared. if ( $after == 'n' ) { - $changedRun = $this->db->selectField ( 'testitem', + $changedRun = $this->db->selectField( 'testitem', 'MIN(ti_run)', array( 'ti_name' => $testname ), __METHOD__ ); - $appear = $this->db->selectRow ( 'testrun', + $appear = $this->db->selectRow( 'testrun', array( 'tr_date', 'tr_mw_version' ), array( 'tr_id' => $changedRun ), __METHOD__ ); return "First recorded appearance: " - . date( "d-M-Y H:i:s", strtotime ( $appear->tr_date ) ) - . ", " . $appear->tr_mw_version; + . date( "d-M-Y H:i:s", strtotime( $appear->tr_date ) ) + . ", " . $appear->tr_mw_version; } // Otherwise, this test has previous recorded results. // See when this test last had a different result to what we're seeing now. $conds = array( - 'ti_name' => $testname, + 'ti_name' => $testname, 'ti_success' => ( $after == 'f' ? "1" : "0" ) ); if ( $this->curRun ) { - $conds[] = "ti_run != " . $this->db->addQuotes ( $this->curRun ); + $conds[] = "ti_run != " . $this->db->addQuotes( $this->curRun ); } - $changedRun = $this->db->selectField ( 'testitem', 'MAX(ti_run)', $conds, __METHOD__ ); + $changedRun = $this->db->selectField( 'testitem', 'MAX(ti_run)', $conds, __METHOD__ ); // If no record of ever having had a different result. - if ( is_null ( $changedRun ) ) { + if ( is_null( $changedRun ) ) { if ( $after == "f" ) { return "Has never passed"; } else { @@ -212,27 +262,26 @@ class DbTestPreviewer extends TestRecorder { // Otherwise, we're looking at a test whose status has changed. // (i.e. it used to work, but now doesn't; or used to fail, but is now fixed.) // In this situation, give as much info as we can as to when it changed status. - $pre = $this->db->selectRow ( 'testrun', + $pre = $this->db->selectRow( 'testrun', array( 'tr_date', 'tr_mw_version' ), array( 'tr_id' => $changedRun ), __METHOD__ ); - $post = $this->db->selectRow ( 'testrun', + $post = $this->db->selectRow( 'testrun', array( 'tr_date', 'tr_mw_version' ), - array( "tr_id > " . $this->db->addQuotes ( $changedRun ) ), + array( "tr_id > " . $this->db->addQuotes( $changedRun ) ), __METHOD__, array( "LIMIT" => 1, "ORDER BY" => 'tr_id' ) ); if ( $post ) { - $postDate = date( "d-M-Y H:i:s", strtotime ( $post->tr_date ) ) . ", {$post->tr_mw_version}"; + $postDate = date( "d-M-Y H:i:s", strtotime( $post->tr_date ) ) . ", {$post->tr_mw_version}"; } else { $postDate = 'now'; } return ( $after == "f" ? "Introduced" : "Fixed" ) . " between " - . date( "d-M-Y H:i:s", strtotime ( $pre->tr_date ) ) . ", " . $pre->tr_mw_version - . " and $postDate"; - + . date( "d-M-Y H:i:s", strtotime( $pre->tr_date ) ) . ", " . $pre->tr_mw_version + . " and $postDate"; } /** @@ -243,10 +292,9 @@ class DbTestPreviewer extends TestRecorder { $this->lb->closeAll(); parent::end(); } - } -class DbTestRecorder extends DbTestPreviewer { +class DbTestRecorder extends DbTestPreviewer { var $version; /** @@ -254,11 +302,11 @@ class DbTestRecorder extends DbTestPreviewer { * and all that fun stuff */ function start() { - $this->db->begin(); + $this->db->begin( __METHOD__ ); - if ( ! $this->db->tableExists( 'testrun' ) - || ! $this->db->tableExists( 'testitem' ) ) - { + if ( !$this->db->tableExists( 'testrun' ) + || !$this->db->tableExists( 'testitem' ) + ) { print "WARNING> `testrun` table not found in database. Trying to create table.\n"; $this->db->sourceFile( $this->db->patchPath( 'patch-testrun.sql' ) ); echo "OK, resuming.\n"; @@ -268,18 +316,18 @@ class DbTestRecorder extends DbTestPreviewer { $this->db->insert( 'testrun', array( - 'tr_date' => $this->db->timestamp(), - 'tr_mw_version' => $this->version, + 'tr_date' => $this->db->timestamp(), + 'tr_mw_version' => $this->version, 'tr_php_version' => phpversion(), - 'tr_db_version' => $this->db->getServerVersion(), - 'tr_uname' => php_uname() + 'tr_db_version' => $this->db->getServerVersion(), + 'tr_uname' => php_uname() ), __METHOD__ ); - if ( $this->db->getType() === 'postgres' ) { - $this->curRun = $this->db->currentSequenceValue( 'testrun_id_seq' ); - } else { - $this->curRun = $this->db->insertId(); - } + if ( $this->db->getType() === 'postgres' ) { + $this->curRun = $this->db->currentSequenceValue( 'testrun_id_seq' ); + } else { + $this->curRun = $this->db->insertId(); + } } /** @@ -293,8 +341,8 @@ class DbTestRecorder extends DbTestPreviewer { $this->db->insert( 'testitem', array( - 'ti_run' => $this->curRun, - 'ti_name' => $test, + 'ti_run' => $this->curRun, + 'ti_name' => $test, 'ti_success' => $result ? 1 : 0, ), __METHOD__ ); @@ -307,7 +355,8 @@ class TestFileIterator implements Iterator { private $parserTest; /* An instance of ParserTest (parserTests.php) or MediaWikiParserTest (phpunit) */ private $index = 0; private $test; - private $section = null; /** String|null: current test section being analyzed */ + private $section = null; + /** String|null: current test section being analyzed */ private $sectionData = array(); private $lineNum; private $eof; @@ -374,7 +423,7 @@ class TestFileIterator implements Iterator { $this->section = strtolower( $matches[1] ); if ( $this->section == 'endarticle' ) { - $this->checkSection( 'text' ); + $this->checkSection( 'text' ); $this->checkSection( 'article' ); $this->parserTest->addArticle( ParserTest::chomp( $this->sectionData['article'] ), $this->sectionData['text'], $this->lineNum ); @@ -417,8 +466,8 @@ class TestFileIterator implements Iterator { } if ( $this->section == 'end' ) { - $this->checkSection( 'test' ); - $this->checkSection( 'input' ); + $this->checkSection( 'test' ); + $this->checkSection( 'input' ); $this->checkSection( 'result' ); if ( !isset( $this->sectionData['options'] ) ) { @@ -430,7 +479,9 @@ class TestFileIterator implements Iterator { } if ( ( ( preg_match( '/\\bdisabled\\b/i', $this->sectionData['options'] ) && !$this->parserTest->runDisabled ) - || !preg_match( "/" . $this->parserTest->regex . "/i", $this->sectionData['test'] ) ) ) { + || ( preg_match( '/\\bparsoid\\b/i', $this->sectionData['options'] ) && !$this->parserTest->runParsoid ) + || !preg_match( "/" . $this->parserTest->regex . "/i", $this->sectionData['test'] ) ) + ) { # disabled test $this->clearSection(); @@ -443,23 +494,23 @@ class TestFileIterator implements Iterator { # We are really going to run the test, run pending hooks and hooks function wfDebug( __METHOD__ . " unleashing delayed test for: {$this->sectionData['test']}" ); $hooksResult = $delayedParserTest->unleash( $this->parserTest ); - if( !$hooksResult ) { + if ( !$hooksResult ) { # Some hook reported an issue. Abort. return false; } $this->test = array( - 'test' => ParserTest::chomp( $this->sectionData['test'] ), - 'input' => ParserTest::chomp( $this->sectionData['input'] ), - 'result' => ParserTest::chomp( $this->sectionData['result'] ), + 'test' => ParserTest::chomp( $this->sectionData['test'] ), + 'input' => ParserTest::chomp( $this->sectionData['input'] ), + 'result' => ParserTest::chomp( $this->sectionData['result'] ), 'options' => ParserTest::chomp( $this->sectionData['options'] ), - 'config' => ParserTest::chomp( $this->sectionData['config'] ), + 'config' => ParserTest::chomp( $this->sectionData['config'] ), ); return true; } - if ( isset ( $this->sectionData[$this->section] ) ) { + if ( isset( $this->sectionData[$this->section] ) ) { throw new MWException( "duplicate section '$this->section' at line {$this->lineNum} of $this->file\n" ); } @@ -483,7 +534,7 @@ class TestFileIterator implements Iterator { private function clearSection() { $this->sectionData = array(); $this->section = null; - + } /** @@ -492,21 +543,21 @@ class TestFileIterator implements Iterator { * Throw an exception if it is not set, referencing current section * and adding the current file name and line number * - * @param $token String: expected token that should have been mentionned before closing this section + * @param $token String: expected token that should have been mentionned before closing this section */ private function checkSection( $token ) { - if( is_null( $this->section ) ) { + if ( is_null( $this->section ) ) { throw new MWException( __METHOD__ . " can not verify a null section!\n" ); } - if( !isset($this->sectionData[$token]) ) { + if ( !isset( $this->sectionData[$token] ) ) { throw new MWException( sprintf( "'%s' without '%s' at line %s of %s\n", $this->section, $token, $this->lineNum, $this->file - )); + ) ); } return true; } @@ -530,32 +581,31 @@ class DelayedParserTest { * Call to this will erase any hooks function that were pending. */ public function reset() { - $this->hooks = array(); + $this->hooks = array(); $this->fnHooks = array(); } /** * Called whenever we actually want to run the hook. - * Should be the case if we found the parserTest is not disabled + * Should be the case if we found the parserTest is not disabled */ public function unleash( &$parserTest ) { - if( !($parserTest instanceof ParserTest || $parserTest instanceof NewParserTest - ) ) { + if ( !( $parserTest instanceof ParserTest || $parserTest instanceof NewParserTest ) ) { throw new MWException( __METHOD__ . " must be passed an instance of ParserTest or NewParserTest classes\n" ); } # Trigger delayed hooks. Any failure will make us abort - foreach( $this->hooks as $hook ) { + foreach ( $this->hooks as $hook ) { $ret = $parserTest->requireHook( $hook ); - if( !$ret ) { + if ( !$ret ) { return false; } } # Trigger delayed function hooks. Any failure will make us abort - foreach( $this->fnHooks as $fnHook ) { + foreach ( $this->fnHooks as $fnHook ) { $ret = $parserTest->requireFunctionHook( $fnHook ); - if( !$ret ) { + if ( !$ret ) { return false; } } @@ -571,6 +621,7 @@ class DelayedParserTest { public function requireHook( $hook ) { $this->hooks[] = $hook; } + /** * Similar to ParserTest object but does not run anything * Use unleash() to really execute the hook function |