summaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
authorPierre Schmitz <pierre@archlinux.de>2012-05-03 13:01:35 +0200
committerPierre Schmitz <pierre@archlinux.de>2012-05-03 13:01:35 +0200
commitd9022f63880ce039446fba8364f68e656b7bf4cb (patch)
tree16b40fbf17bf7c9ee6f4ead25b16dd192378050a /tests
parent27cf83d177256813e2e802241085fce5dd0f3fb9 (diff)
Update to MediaWiki 1.19.0
Diffstat (limited to 'tests')
-rw-r--r--tests/TestsAutoLoader.php3
-rw-r--r--tests/jasmine/.htaccess1
-rw-r--r--tests/jasmine/SpecRunner.html42
-rw-r--r--tests/jasmine/lib/jasmine-1.0.1/MIT.LICENSE20
-rw-r--r--tests/jasmine/lib/jasmine-1.0.1/jasmine-html.js188
-rw-r--r--tests/jasmine/lib/jasmine-1.0.1/jasmine.css166
-rw-r--r--tests/jasmine/lib/jasmine-1.0.1/jasmine.js2421
-rw-r--r--tests/jasmine/spec/mediawiki.Uri.spec.js307
-rw-r--r--tests/jasmine/spec/mediawiki.jqueryMsg.spec.data.js488
-rw-r--r--tests/jasmine/spec/mediawiki.jqueryMsg.spec.js389
-rw-r--r--tests/jasmine/spec_makers/makeJqueryMsgSpec.php114
-rw-r--r--tests/parser/README8
-rw-r--r--tests/parser/parserTest.inc124
-rw-r--r--tests/parser/parserTests.txt382
-rw-r--r--tests/parser/parserTestsParserHook.php26
-rw-r--r--tests/parser/parserTestsStaticParserHook.php58
-rw-r--r--tests/parser/preprocess/All_system_messages.expected2
-rw-r--r--tests/parser/preprocess/Factorial.expected2
-rw-r--r--tests/parserTests.php9
-rw-r--r--tests/phpunit/Makefile17
-rw-r--r--tests/phpunit/MediaWikiLangTestCase.php6
-rw-r--r--tests/phpunit/MediaWikiPHPUnitCommand.php30
-rw-r--r--tests/phpunit/MediaWikiTestCase.php113
-rw-r--r--tests/phpunit/StructureTest.php56
-rw-r--r--tests/phpunit/data/db/mysql/functions.sql12
-rw-r--r--tests/phpunit/data/db/postgres/functions.sql12
-rw-r--r--tests/phpunit/data/db/sqlite/tables-1.13.sql (renamed from tests/phpunit/includes/db/sqlite/tables-1.13.sql)2
-rw-r--r--tests/phpunit/data/db/sqlite/tables-1.15.sql (renamed from tests/phpunit/includes/db/sqlite/tables-1.15.sql)4
-rw-r--r--tests/phpunit/data/db/sqlite/tables-1.16.sql (renamed from tests/phpunit/includes/db/sqlite/tables-1.16.sql)0
-rw-r--r--tests/phpunit/data/db/sqlite/tables-1.17.sql (renamed from tests/phpunit/includes/db/sqlite/tables-1.17.sql)0
-rw-r--r--tests/phpunit/data/db/sqlite/tables-1.18.sql535
-rw-r--r--tests/phpunit/data/media/80x60-2layers.xcfbin0 -> 1162 bytes
-rw-r--r--tests/phpunit/data/media/80x60-Greyscale.xcfbin0 -> 667 bytes
-rw-r--r--tests/phpunit/data/media/80x60-RGB.xcfbin0 -> 677 bytes
-rw-r--r--tests/phpunit/data/media/Toll_Texas_1.svg150
-rw-r--r--tests/phpunit/data/media/iptc-invalid-psir.jpgbin0 -> 9574 bytes
-rw-r--r--tests/phpunit/includes/ArticleTablesTest.php31
-rw-r--r--tests/phpunit/includes/ArticleTest.php14
-rw-r--r--tests/phpunit/includes/BlockTest.php17
-rw-r--r--tests/phpunit/includes/EditPageTest.php33
-rw-r--r--tests/phpunit/includes/ExtraParserTest.php64
-rw-r--r--tests/phpunit/includes/FormOptionsInitializationTest.php4
-rw-r--r--tests/phpunit/includes/FormOptionsTest.php4
-rw-r--r--tests/phpunit/includes/GlobalFunctions/GlobalTest.php524
-rw-r--r--tests/phpunit/includes/GlobalFunctions/GlobalWithDBTest.php29
-rw-r--r--tests/phpunit/includes/GlobalFunctions/wfAssembleUrlTest.php111
-rw-r--r--tests/phpunit/includes/GlobalFunctions/wfBCP47Test.php133
-rw-r--r--tests/phpunit/includes/GlobalFunctions/wfBaseNameTest.php36
-rw-r--r--tests/phpunit/includes/GlobalFunctions/wfExpandUrlTest.php (renamed from tests/phpunit/includes/GlobalFunctions/wfExpandUrl.php)22
-rw-r--r--tests/phpunit/includes/GlobalFunctions/wfRemoveDotSegmentsTest.php90
-rw-r--r--tests/phpunit/includes/GlobalFunctions/wfShorthandToIntegerTest.php28
-rw-r--r--tests/phpunit/includes/GlobalFunctions/wfTimestampTest.php134
-rw-r--r--tests/phpunit/includes/HtmlTest.php277
-rw-r--r--tests/phpunit/includes/HttpTest.php556
-rw-r--r--tests/phpunit/includes/IPTest.php32
-rw-r--r--tests/phpunit/includes/LocalFileTest.php33
-rw-r--r--tests/phpunit/includes/MWNamespaceTest.php329
-rw-r--r--tests/phpunit/includes/MessageTest.php2
-rw-r--r--tests/phpunit/includes/ParserOptionsTest.php9
-rw-r--r--tests/phpunit/includes/PathRouterTest.php254
-rw-r--r--tests/phpunit/includes/Providers.php4
-rw-r--r--tests/phpunit/includes/ResourceLoaderTest.php2
-rw-r--r--tests/phpunit/includes/SanitizerTest.php43
-rw-r--r--tests/phpunit/includes/SanitizerValidateEmailTest.php (renamed from tests/phpunit/includes/UserIsValidEmailAddrTest.php)4
-rw-r--r--tests/phpunit/includes/SeleniumConfigurationTest.php24
-rw-r--r--tests/phpunit/includes/TemplateCategoriesTest.php38
-rw-r--r--tests/phpunit/includes/TitleMethodsTest.php78
-rw-r--r--tests/phpunit/includes/TitlePermissionTest.php34
-rw-r--r--tests/phpunit/includes/TitleTest.php4
-rw-r--r--tests/phpunit/includes/UserTest.php105
-rw-r--r--tests/phpunit/includes/WebRequestTest.php97
-rw-r--r--tests/phpunit/includes/XmlSelectTest.php4
-rw-r--r--tests/phpunit/includes/XmlTest.php99
-rw-r--r--tests/phpunit/includes/api/ApiBlockTest.php28
-rw-r--r--tests/phpunit/includes/api/ApiPurgeTest.php34
-rw-r--r--tests/phpunit/includes/api/ApiQueryTest.php8
-rw-r--r--tests/phpunit/includes/api/ApiTest.php36
-rw-r--r--tests/phpunit/includes/api/ApiTestCase.php51
-rw-r--r--tests/phpunit/includes/api/ApiTestCaseUpload.php40
-rw-r--r--tests/phpunit/includes/api/ApiTestUser.php2
-rw-r--r--tests/phpunit/includes/api/ApiUploadTest.php197
-rw-r--r--tests/phpunit/includes/api/ApiWatchTest.php5
-rw-r--r--tests/phpunit/includes/api/RandomImageGenerator.php124
-rw-r--r--tests/phpunit/includes/db/DatabaseSqliteTest.php14
-rw-r--r--tests/phpunit/includes/db/DatabaseTest.php32
-rw-r--r--tests/phpunit/includes/debug/MWDebugTest.php63
-rw-r--r--tests/phpunit/includes/filerepo/FileBackendTest.php1358
-rw-r--r--tests/phpunit/includes/filerepo/FileRepoTest.php41
-rw-r--r--tests/phpunit/includes/filerepo/StoreBatchTest.php122
-rw-r--r--tests/phpunit/includes/json/ServicesJsonTest.php93
-rw-r--r--tests/phpunit/includes/libs/JavaScriptMinifierTest.php43
-rw-r--r--tests/phpunit/includes/media/BitmapMetadataHandlerTest.php26
-rw-r--r--tests/phpunit/includes/media/BitmapScalingTest.php25
-rw-r--r--tests/phpunit/includes/media/ExifBitmapTest.php24
-rw-r--r--tests/phpunit/includes/media/ExifRotationTest.php59
-rw-r--r--tests/phpunit/includes/media/ExifTest.php9
-rw-r--r--tests/phpunit/includes/media/FormatMetadataTest.php39
-rw-r--r--tests/phpunit/includes/media/GIFMetadataExtractorTest.php1
-rw-r--r--tests/phpunit/includes/media/GIFTest.php31
-rw-r--r--tests/phpunit/includes/media/JpegMetadataExtractorTest.php10
-rw-r--r--tests/phpunit/includes/media/JpegTest.php18
-rw-r--r--tests/phpunit/includes/media/MediaHandlerTest.php (renamed from tests/phpunit/includes/ImageFunctionsTest.php)8
-rw-r--r--tests/phpunit/includes/media/PNGTest.php30
-rw-r--r--tests/phpunit/includes/media/SVGMetadataExtractorTest.php24
-rw-r--r--tests/phpunit/includes/media/TiffTest.php7
-rw-r--r--tests/phpunit/includes/media/XMPTest.php9
-rw-r--r--tests/phpunit/includes/media/XMPValidateTest.php47
-rw-r--r--tests/phpunit/includes/parser/MagicVariableTest.php10
-rw-r--r--tests/phpunit/includes/parser/MediaWikiParserTest.php12
-rw-r--r--tests/phpunit/includes/parser/NewParserTest.php414
-rw-r--r--tests/phpunit/includes/parser/ParserHelpers.php136
-rw-r--r--tests/phpunit/includes/parser/ParserPreloadTest.php67
-rw-r--r--tests/phpunit/includes/parser/PreprocessorTest.php102
-rw-r--r--tests/phpunit/includes/parser/TagHooksTest.php (renamed from tests/phpunit/includes/parser/TagHooks.php)0
-rw-r--r--tests/phpunit/includes/search/SearchEngineTest.php16
-rw-r--r--tests/phpunit/includes/specials/QueryAllSpecialPagesTest.php79
-rw-r--r--tests/phpunit/includes/specials/SpecialRecentchangesTest.php (renamed from tests/phpunit/includes/specials/SpecialRecentchanges.php)16
-rw-r--r--tests/phpunit/includes/specials/SpecialSearchTest.php108
-rw-r--r--tests/phpunit/includes/upload/UploadFromUrlTest.php19
-rw-r--r--tests/phpunit/includes/upload/UploadStashTest.php53
-rw-r--r--tests/phpunit/includes/upload/UploadTest.php87
-rw-r--r--tests/phpunit/languages/LanguageAmTest.php33
-rw-r--r--tests/phpunit/languages/LanguageArTest.php78
-rw-r--r--tests/phpunit/languages/LanguageBeTest.php40
-rw-r--r--tests/phpunit/languages/LanguageBe_taraskTest.php35
-rw-r--r--tests/phpunit/languages/LanguageBhTest.php34
-rw-r--r--tests/phpunit/languages/LanguageBsTest.php41
-rw-r--r--tests/phpunit/languages/LanguageCsTest.php40
-rw-r--r--tests/phpunit/languages/LanguageCuTest.php41
-rw-r--r--tests/phpunit/languages/LanguageCyTest.php42
-rw-r--r--tests/phpunit/languages/LanguageDsbTest.php40
-rw-r--r--tests/phpunit/languages/LanguageFrTest.php34
-rw-r--r--tests/phpunit/languages/LanguageGaTest.php34
-rw-r--r--tests/phpunit/languages/LanguageGdTest.php38
-rw-r--r--tests/phpunit/languages/LanguageGvTest.php39
-rw-r--r--tests/phpunit/languages/LanguageHeTest.php48
-rw-r--r--tests/phpunit/languages/LanguageHiTest.php34
-rw-r--r--tests/phpunit/languages/LanguageHrTest.php41
-rw-r--r--tests/phpunit/languages/LanguageHsbTest.php40
-rw-r--r--tests/phpunit/languages/LanguageHyTest.php34
-rw-r--r--tests/phpunit/languages/LanguageKshTest.php34
-rw-r--r--tests/phpunit/languages/LanguageLnTest.php34
-rw-r--r--tests/phpunit/languages/LanguageLtTest.php53
-rw-r--r--tests/phpunit/languages/LanguageLvTest.php39
-rw-r--r--tests/phpunit/languages/LanguageMgTest.php35
-rw-r--r--tests/phpunit/languages/LanguageMkTest.php41
-rw-r--r--tests/phpunit/languages/LanguageMlTest.php43
-rw-r--r--tests/phpunit/languages/LanguageMoTest.php43
-rw-r--r--tests/phpunit/languages/LanguageMtTest.php72
-rw-r--r--tests/phpunit/languages/LanguageNlTest.php28
-rw-r--r--tests/phpunit/languages/LanguageNsoTest.php32
-rw-r--r--tests/phpunit/languages/LanguagePlTest.php72
-rw-r--r--tests/phpunit/languages/LanguageRoTest.php43
-rw-r--r--tests/phpunit/languages/LanguageRuTest.php54
-rw-r--r--tests/phpunit/languages/LanguageSeTest.php48
-rw-r--r--tests/phpunit/languages/LanguageSgsTest.php66
-rw-r--r--tests/phpunit/languages/LanguageShTest.php32
-rw-r--r--tests/phpunit/languages/LanguageSkTest.php40
-rw-r--r--tests/phpunit/languages/LanguageSlTest.php42
-rw-r--r--tests/phpunit/languages/LanguageSmaTest.php48
-rw-r--r--tests/phpunit/languages/LanguageSrTest.php199
-rw-r--r--tests/phpunit/languages/LanguageTest.php591
-rw-r--r--tests/phpunit/languages/LanguageTiTest.php32
-rw-r--r--tests/phpunit/languages/LanguageTlTest.php32
-rw-r--r--tests/phpunit/languages/LanguageTrTest.php9
-rw-r--r--tests/phpunit/languages/LanguageUkTest.php54
-rw-r--r--tests/phpunit/languages/LanguageWaTest.php32
-rw-r--r--tests/phpunit/phpunit.php4
-rw-r--r--tests/phpunit/skins/SideBarTest.php9
-rw-r--r--tests/phpunit/suite.xml19
-rw-r--r--tests/phpunit/suites/UploadFromUrlTestSuite.php74
-rw-r--r--tests/qunit/QUnitTestResources.php52
-rw-r--r--tests/qunit/data/qunitOkCall.js2
-rw-r--r--tests/qunit/data/testrunner.js130
-rw-r--r--tests/qunit/index.html79
-rw-r--r--tests/qunit/suites/resources/jquery/jquery.autoEllipsis.test.js (renamed from tests/qunit/suites/resources/jquery/jquery.autoEllipsis.js)13
-rw-r--r--tests/qunit/suites/resources/jquery/jquery.byteLength.test.js (renamed from tests/qunit/suites/resources/jquery/jquery.byteLength.js)4
-rw-r--r--tests/qunit/suites/resources/jquery/jquery.byteLimit.test.js (renamed from tests/qunit/suites/resources/jquery/jquery.byteLimit.js)136
-rw-r--r--tests/qunit/suites/resources/jquery/jquery.client.test.js (renamed from tests/qunit/suites/resources/jquery/jquery.client.js)164
-rw-r--r--tests/qunit/suites/resources/jquery/jquery.colorUtil.test.js (renamed from tests/qunit/suites/resources/jquery/jquery.colorUtil.js)2
-rw-r--r--tests/qunit/suites/resources/jquery/jquery.delayedBind.test.js4
-rw-r--r--tests/qunit/suites/resources/jquery/jquery.getAttrs.test.js (renamed from tests/qunit/suites/resources/jquery/jquery.getAttrs.js)2
-rw-r--r--tests/qunit/suites/resources/jquery/jquery.highlightText.test.js239
-rw-r--r--tests/qunit/suites/resources/jquery/jquery.localize.test.js (renamed from tests/qunit/suites/resources/jquery/jquery.localize.js)2
-rw-r--r--tests/qunit/suites/resources/jquery/jquery.mwExtension.test.js (renamed from tests/qunit/suites/resources/jquery/jquery.mwPrototypes.js)6
-rw-r--r--tests/qunit/suites/resources/jquery/jquery.tabIndex.test.js (renamed from tests/qunit/suites/resources/jquery/jquery.tabIndex.js)12
-rw-r--r--tests/qunit/suites/resources/jquery/jquery.tablesorter.test.js227
-rw-r--r--tests/qunit/suites/resources/jquery/jquery.textSelection.test.js279
-rw-r--r--tests/qunit/suites/resources/mediawiki.special/mediawiki.special.recentchanges.test.js (renamed from tests/qunit/suites/resources/mediawiki.special/mediawiki.special.recentchanges.js)36
-rw-r--r--tests/qunit/suites/resources/mediawiki/mediawiki.Title.test.js201
-rw-r--r--tests/qunit/suites/resources/mediawiki/mediawiki.jqueryMsg.test.js43
-rw-r--r--tests/qunit/suites/resources/mediawiki/mediawiki.jscompat.test.js29
-rw-r--r--tests/qunit/suites/resources/mediawiki/mediawiki.test.js (renamed from tests/qunit/suites/resources/mediawiki/mediawiki.js)130
-rw-r--r--tests/qunit/suites/resources/mediawiki/mediawiki.user.test.js (renamed from tests/qunit/suites/resources/mediawiki/mediawiki.user.js)12
-rw-r--r--tests/qunit/suites/resources/mediawiki/mediawiki.util.test.js (renamed from tests/qunit/suites/resources/mediawiki/mediawiki.util.js)56
-rw-r--r--tests/selenium/SeleniumConfig.php16
-rw-r--r--tests/selenium/data/SimpleSeleniumTestDB.sql1
-rw-r--r--tests/selenium/data/mediawiki118_fresh_installation.sql15
-rw-r--r--tests/selenium/installer/MediaWikiButtonsAvailabilityTestCase.php2
-rw-r--r--tests/selenium/installer/MediaWikiDifferentDatabaseAccountTestCase.php2
-rw-r--r--tests/selenium/installer/MediaWikiDifferntDatabasePrefixTestCase.php2
-rw-r--r--tests/selenium/installer/MediaWikiErrorsConnectToDatabasePageTestCase.php2
-rw-r--r--tests/selenium/installer/MediaWikiErrorsNamepageTestCase.php2
-rw-r--r--tests/selenium/installer/MediaWikiHelpFieldHintTestCase.php2
-rw-r--r--tests/selenium/installer/MediaWikiInstallationConfig.php4
-rw-r--r--tests/selenium/installer/MediaWikiMySQLDataBaseTestCase.php2
-rw-r--r--tests/selenium/installer/MediaWikiMySQLiteDataBaseTestCase.php2
-rw-r--r--tests/selenium/installer/MediaWikiOnAlreadyInstalledTestCase.php2
-rw-r--r--tests/selenium/installer/MediaWikiRestartInstallationTestCase.php2
-rw-r--r--tests/selenium/installer/MediaWikiRightFrameworkLinksTestCase.php2
-rw-r--r--tests/selenium/installer/MediaWikiUpgradeExistingDatabaseTestCase.php2
-rw-r--r--tests/selenium/installer/MediaWikiUserInterfaceTestCase.php2
-rw-r--r--tests/selenium/suites/MediawikiCoreSmokeTestCase.php40
-rw-r--r--tests/selenium/suites/MediawikiCoreSmokeTestSuite.php2
-rw-r--r--tests/selenium/suites/SimpleSeleniumTestCase.php14
-rw-r--r--tests/selenium/suites/SimpleSeleniumTestSuite.php2
-rw-r--r--tests/testHelpers.inc278
217 files changed, 15042 insertions, 2702 deletions
diff --git a/tests/TestsAutoLoader.php b/tests/TestsAutoLoader.php
index cccf7bf2..41bddfcd 100644
--- a/tests/TestsAutoLoader.php
+++ b/tests/TestsAutoLoader.php
@@ -21,9 +21,6 @@ $wgAutoloadClasses += array(
'RandomImageGenerator' => "$testFolder/phpunit/includes/api/RandomImageGenerator.php",
'UserWrapper' => "$testFolder/phpunit/includes/api/ApiTestCase.php",
- //Parser
- 'ParserTestFileIterator' => "$testFolder/phpunit/includes/parser/NewParserHelpers.php",
-
//Selenium
'SeleniumTestConstants' => "$testFolder/selenium/SeleniumTestConstants.php",
diff --git a/tests/jasmine/.htaccess b/tests/jasmine/.htaccess
new file mode 100644
index 00000000..605d2f4c
--- /dev/null
+++ b/tests/jasmine/.htaccess
@@ -0,0 +1 @@
+Allow from all
diff --git a/tests/jasmine/SpecRunner.html b/tests/jasmine/SpecRunner.html
new file mode 100644
index 00000000..6af9b0c3
--- /dev/null
+++ b/tests/jasmine/SpecRunner.html
@@ -0,0 +1,42 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+<head>
+ <title>Jasmine Test Runner</title>
+ <link rel="stylesheet" type="text/css" href="lib/jasmine-1.0.1/jasmine.css">
+ <script type="text/javascript" src="lib/jasmine-1.0.1/jasmine.js"></script>
+ <script type="text/javascript" src="lib/jasmine-1.0.1/jasmine-html.js"></script>
+
+ <!-- include source files here... -->
+ <script type="text/javascript" src="../../load.php?debug=true&lang=en&modules=jquery%7Cmediawiki&only=scripts&skin=vector"></script>
+
+ <script type="text/javascript" src="../../resources/mediawiki/mediawiki.js"></script>
+
+ <script type="text/javascript" src="../../resources/mediawiki.language/mediawiki.language.js"></script>
+ <script type="text/javascript" src="../../resources/mediawiki/mediawiki.jqueryMsg.js"></script>
+ <script type="text/javascript" src="../../resources/mediawiki/mediawiki.Uri.js"></script>
+<!--
+ <script type="text/javascript" src="../../resources/mediawiki/mediawiki.api.js"></script>
+ <script type="text/javascript" src="../../resources/mediawiki/mediawiki.api.edit.js"></script>
+-->
+
+ <!-- insert test data files here -->
+ <script type="text/javascript" src="spec/mediawiki.jqueryMsg.spec.data.js"></script>
+
+ <!-- include spec files here... -->
+ <script type="text/javascript" src="spec/mediawiki.Uri.spec.js"></script>
+ <!--
+ <script type="text/javascript" src="spec/mw.Api.spec.js"></script>
+ <script type="text/javascript" src="spec/mw.Api.edit.spec.js"></script>
+ -->
+ <script type="text/javascript" src="spec/mediawiki.jqueryMsg.spec.js"></script>
+
+</head>
+<body>
+<script type="text/javascript">
+ 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
new file mode 100644
index 00000000..1eb9b49e
--- /dev/null
+++ b/tests/jasmine/lib/jasmine-1.0.1/MIT.LICENSE
@@ -0,0 +1,20 @@
+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
new file mode 100644
index 00000000..81402b9c
--- /dev/null
+++ b/tests/jasmine/lib/jasmine-1.0.1/jasmine-html.js
@@ -0,0 +1,188 @@
+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
new file mode 100644
index 00000000..6583fe7c
--- /dev/null
+++ b/tests/jasmine/lib/jasmine-1.0.1/jasmine.css
@@ -0,0 +1,166 @@
+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
new file mode 100644
index 00000000..964f99ed
--- /dev/null
+++ b/tests/jasmine/lib/jasmine-1.0.1/jasmine.js
@@ -0,0 +1,2421 @@
+/**
+ * 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, '&amp;')
+ .replace(/</g, '&lt;')
+ .replace(/>/g, '&gt;');
+};
+
+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.Uri.spec.js b/tests/jasmine/spec/mediawiki.Uri.spec.js
new file mode 100644
index 00000000..721ccb38
--- /dev/null
+++ b/tests/jasmine/spec/mediawiki.Uri.spec.js
@@ -0,0 +1,307 @@
+( function() {
+
+ // ensure we have a generic URI parser if not running in a browser
+ if ( !mw.Uri ) {
+ mw.Uri = mw.UriRelative( 'http://example.com/' );
+ }
+
+ describe( "mw.Uri", function() {
+
+ describe( "should work well in loose and strict mode", function() {
+
+ function basicTests( strict ) {
+
+ describe( "should parse a simple HTTP URI correctly", function() {
+
+ var uriString = 'http://www.ietf.org/rfc/rfc2396.txt';
+ var uri;
+ if ( strict ) {
+ uri = new mw.Uri( uriString, strict );
+ } else {
+ uri = new mw.Uri( uriString );
+ }
+
+ it( "should have basic object properties", function() {
+ expect( uri.protocol ).toEqual( 'http' );
+ expect( uri.host ).toEqual( 'www.ietf.org' );
+ expect( uri.port ).not.toBeDefined();
+ expect( uri.path ).toEqual( '/rfc/rfc2396.txt' );
+ expect( uri.query ).toEqual( {} );
+ expect( uri.fragment ).not.toBeDefined();
+ } );
+
+ describe( "should construct composite components of URI on request", function() {
+ it( "should have empty userinfo", function() {
+ expect( uri.getUserInfo() ).toEqual( '' );
+ } );
+
+ it( "should have authority equal to host", function() {
+ expect( uri.getAuthority() ).toEqual( 'www.ietf.org' );
+ } );
+
+ it( "should have hostport equal to host", function() {
+ expect( uri.getHostPort() ).toEqual( 'www.ietf.org' );
+ } );
+
+ it( "should have empty string as query string", function() {
+ expect( uri.getQueryString() ).toEqual( '' );
+ } );
+
+ it( "should have path as relative path", function() {
+ expect( uri.getRelativePath() ).toEqual( '/rfc/rfc2396.txt' );
+ } );
+
+ it( "should return a uri string equivalent to original", function() {
+ expect( uri.toString() ).toEqual( uriString );
+ } );
+ } );
+ } );
+ }
+
+ describe( "should work in loose mode", function() {
+ basicTests( false );
+ } );
+
+ describe( "should work in strict mode", function() {
+ basicTests( true );
+ } );
+
+ } );
+
+ it( "should parse a simple ftp URI correctly with user and password", function() {
+ var uri = new mw.Uri( 'ftp://usr:pwd@192.0.2.16/' );
+ expect( uri.protocol ).toEqual( 'ftp' );
+ expect( uri.user ).toEqual( 'usr' );
+ expect( uri.password ).toEqual( 'pwd' );
+ expect( uri.host ).toEqual( '192.0.2.16' );
+ expect( uri.port ).not.toBeDefined();
+ expect( uri.path ).toEqual( '/' );
+ expect( uri.query ).toEqual( {} );
+ expect( uri.fragment ).not.toBeDefined();
+ } );
+
+ it( "should parse a simple querystring", function() {
+ var uri = new mw.Uri( 'http://www.google.com/?q=uri' );
+ expect( uri.protocol ).toEqual( 'http' );
+ expect( uri.host ).toEqual( 'www.google.com' );
+ expect( uri.port ).not.toBeDefined();
+ expect( uri.path ).toEqual( '/' );
+ expect( uri.query ).toBeDefined();
+ expect( uri.query ).toEqual( { q: 'uri' } );
+ expect( uri.fragment ).not.toBeDefined();
+ expect( uri.getQueryString() ).toEqual( 'q=uri' );
+ } );
+
+ describe( "should handle multiple value query args (overrideKeys on)", function() {
+ var uri = new mw.Uri( 'http://www.example.com/dir/?m=foo&m=bar&n=1', { overrideKeys: true } );
+ it ( "should parse with multiple values", function() {
+ expect( uri.query.m ).toEqual( 'bar' );
+ expect( uri.query.n ).toEqual( '1' );
+ } );
+ it ( "should accept multiple values", function() {
+ uri.query.n = [ "x", "y", "z" ];
+ expect( uri.toString() ).toContain( 'm=bar' );
+ expect( uri.toString() ).toContain( 'n=x&n=y&n=z' );
+ expect( uri.toString().length ).toEqual( 'http://www.example.com/dir/?m=bar&n=x&n=y&n=z'.length );
+ } );
+ } );
+
+ describe( "should handle multiple value query args (overrideKeys off)", function() {
+ var uri = new mw.Uri( 'http://www.example.com/dir/?m=foo&m=bar&n=1', { overrideKeys: false } );
+ it ( "should parse with multiple values", function() {
+ expect( uri.query.m.length ).toEqual( 2 );
+ expect( uri.query.m[0] ).toEqual( 'foo' );
+ expect( uri.query.m[1] ).toEqual( 'bar' );
+ expect( uri.query.n ).toEqual( '1' );
+ } );
+ it ( "should accept multiple values", function() {
+ uri.query.n = [ "x", "y", "z" ];
+ expect( uri.toString() ).toContain( 'm=foo&m=bar' );
+ expect( uri.toString() ).toContain( 'n=x&n=y&n=z' );
+ expect( uri.toString().length ).toEqual( 'http://www.example.com/dir/?m=foo&m=bar&n=x&n=y&n=z'.length );
+ } );
+ it ( "should be okay with removing values", function() {
+ uri.query.m.splice( 0, 1 );
+ delete uri.query.n;
+ expect( uri.toString() ).toEqual( 'http://www.example.com/dir/?m=bar' );
+ uri.query.m.splice( 0, 1 );
+ expect( uri.toString() ).toEqual( 'http://www.example.com/dir/' );
+ } );
+ } );
+
+ describe( "should deal with an all-dressed URI with everything", function() {
+ var uri = new mw.Uri( 'http://auth@www.example.com:81/dir/dir.2/index.htm?q1=0&&test1&test2=value+%28escaped%29#top' );
+
+ it( "should have basic object properties", function() {
+ expect( uri.protocol ).toEqual( 'http' );
+ expect( uri.user ).toEqual( 'auth' );
+ expect( uri.password ).not.toBeDefined();
+ expect( uri.host ).toEqual( 'www.example.com' );
+ expect( uri.port ).toEqual( '81' );
+ expect( uri.path ).toEqual( '/dir/dir.2/index.htm' );
+ expect( uri.query ).toEqual( { q1: '0', test1: null, test2: 'value (escaped)' } );
+ expect( uri.fragment ).toEqual( 'top' );
+ } );
+
+ describe( "should construct composite components of URI on request", function() {
+ it( "should have userinfo", function() {
+ expect( uri.getUserInfo() ).toEqual( 'auth' );
+ } );
+
+ it( "should have authority equal to auth@hostport", function() {
+ expect( uri.getAuthority() ).toEqual( 'auth@www.example.com:81' );
+ } );
+
+ it( "should have hostport equal to host:port", function() {
+ expect( uri.getHostPort() ).toEqual( 'www.example.com:81' );
+ } );
+
+ it( "should have query string which contains all components", function() {
+ var queryString = uri.getQueryString();
+ expect( queryString ).toContain( 'q1=0' );
+ expect( queryString ).toContain( 'test1' );
+ expect( queryString ).not.toContain( 'test1=' );
+ expect( queryString ).toContain( 'test2=value+%28escaped%29' );
+ } );
+
+ it( "should have path as relative path", function() {
+ expect( uri.getRelativePath() ).toContain( uri.path );
+ expect( uri.getRelativePath() ).toContain( uri.getQueryString() );
+ expect( uri.getRelativePath() ).toContain( uri.fragment );
+ } );
+
+ } );
+ } );
+
+ describe( "should be able to clone itself", function() {
+ var original = new mw.Uri( 'http://en.wiki.local/w/api.php?action=query&foo=bar' );
+ var clone = original.clone();
+
+ it( "should make clones equivalent", function() {
+ expect( original ).toEqual( clone );
+ expect( original.toString() ).toEqual( clone.toString() );
+ } );
+
+ it( "should be able to manipulate clones independently", function() {
+ // but they are still different objects
+ expect( original ).not.toBe( clone );
+ // and can diverge
+ clone.host = 'fr.wiki.local';
+ expect( original.host ).not.toEqual( clone.host );
+ expect( original.toString() ).not.toEqual( clone.toString() );
+ } );
+ } );
+
+ describe( "should be able to construct URL from object", function() {
+ it ( "should construct given basic arguments", function() {
+ var uri = new mw.Uri( { protocol: 'http', host: 'www.foo.local', path: '/this' } );
+ expect( uri.toString() ).toEqual( 'http://www.foo.local/this' );
+ } );
+
+ it ( "should construct given more complex arguments", function() {
+ var uri = new mw.Uri( {
+ protocol: 'http',
+ host: 'www.foo.local',
+ path: '/this',
+ query: { hi: 'there' },
+ fragment: 'blah'
+ } );
+ expect( uri.toString() ).toEqual( 'http://www.foo.local/this?hi=there#blah' );
+ } );
+
+ it ( "should fail to construct without required properties", function() {
+ expect( function() {
+ var uri = new mw.Uri( { protocol: 'http', host: 'www.foo.local' } );
+ } ).toThrow( "Bad constructor arguments" );
+ } );
+ } );
+
+ describe( "should be able to manipulate properties", function() {
+ var uri;
+
+ beforeEach( function() {
+ uri = new mw.Uri( 'http://en.wiki.local/w/api.php' );
+ } );
+
+ it( "can add a fragment", function() {
+ uri.fragment = 'frag';
+ expect( uri.toString() ).toEqual( 'http://en.wiki.local/w/api.php#frag' );
+ } );
+
+ it( "can change host and port", function() {
+ uri.host = 'fr.wiki.local';
+ uri.port = '8080';
+ expect( uri.toString() ).toEqual( 'http://fr.wiki.local:8080/w/api.php' );
+ } );
+
+ it ( "can add query arguments", function() {
+ uri.query.foo = 'bar';
+ expect( uri.toString() ).toEqual( 'http://en.wiki.local/w/api.php?foo=bar' );
+ } );
+
+ it ( "can extend query arguments", function() {
+ uri.query.foo = 'bar';
+ expect( uri.toString() ).toEqual( 'http://en.wiki.local/w/api.php?foo=bar' );
+ uri.extend( { foo: 'quux', pif: 'paf' } );
+ expect( uri.toString() ).toContain( 'foo=quux' );
+ expect( uri.toString() ).not.toContain( 'foo=bar' );
+ expect( uri.toString() ).toContain( 'pif=paf' );
+ } );
+
+ it ( "can remove query arguments", function() {
+ uri.query.foo = 'bar';
+ expect( uri.toString() ).toEqual( 'http://en.wiki.local/w/api.php?foo=bar' );
+ delete( uri.query.foo );
+ expect( uri.toString() ).toEqual( 'http://en.wiki.local/w/api.php' );
+ } );
+
+ } );
+
+ describe( "should handle protocol-relative URLs", function() {
+
+ it ( "should create protocol-relative URLs with same protocol as document", function() {
+ var uriRel = mw.UriRelative( 'glork://en.wiki.local/foo.php' );
+ var uri = new uriRel( '//en.wiki.local/w/api.php' );
+ expect( uri.protocol ).toEqual( 'glork' );
+ } );
+
+ } );
+
+ it( "should throw error on no arguments to constructor", function() {
+ expect( function() {
+ var uri = new mw.Uri();
+ } ).toThrow( "Bad constructor arguments" );
+ } );
+
+ it( "should throw error on empty string as argument to constructor", function() {
+ expect( function() {
+ var uri = new mw.Uri( '' );
+ } ).toThrow( "Bad constructor arguments" );
+ } );
+
+ it( "should throw error on non-URI as argument to constructor", function() {
+ expect( function() {
+ var uri = new mw.Uri( 'glaswegian penguins' );
+ } ).toThrow( "Bad constructor arguments" );
+ } );
+
+ it( "should throw error on improper URI as argument to constructor", function() {
+ expect( function() {
+ var uri = new mw.Uri( 'http:/foo.com' );
+ } ).toThrow( "Bad constructor arguments" );
+ } );
+
+ it( "should throw error on URI without protocol or // in strict mode", function() {
+ expect( function() {
+ var uri = new mw.Uri( 'foo.com/bar/baz', true );
+ } ).toThrow( "Bad constructor arguments" );
+ } );
+
+ it( "should normalize URI without protocol or // in loose mode", function() {
+ var uri = new mw.Uri( 'foo.com/bar/baz', false );
+ expect( uri.toString() ).toEqual( 'http://foo.com/bar/baz' );
+ } );
+
+ } );
+
+} )();
diff --git a/tests/jasmine/spec/mediawiki.jqueryMsg.spec.data.js b/tests/jasmine/spec/mediawiki.jqueryMsg.spec.data.js
new file mode 100644
index 00000000..a867f72c
--- /dev/null
+++ b/tests/jasmine/spec/mediawiki.jqueryMsg.spec.data.js
@@ -0,0 +1,488 @@
+// 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
new file mode 100644
index 00000000..46dcaa80
--- /dev/null
+++ b/tests/jasmine/spec/mediawiki.jqueryMsg.spec.js
@@ -0,0 +1,389 @@
+/* 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 &lt;p&gt;x&lt;/p&gt; 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': '٠', // &#x0660;
+ '1': '١', // &#x0661;
+ '2': '٢', // &#x0662;
+ '3': '٣', // &#x0663;
+ '4': '٤', // &#x0664;
+ '5': '٥', // &#x0665;
+ '6': '٦', // &#x0666;
+ '7': '٧', // &#x0667;
+ '8': '٨', // &#x0668;
+ '9': '٩', // &#x0669;
+ '.': '٫', // &#x066b; wrong table ?
+ ',': '٬' // &#x066c;
+ }
+
+ },
+ 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
new file mode 100644
index 00000000..1ac8dcba
--- /dev/null
+++ b/tests/jasmine/spec_makers/makeJqueryMsgSpec.php
@@ -0,0 +1,114 @@
+<?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( dirname( dirname( __FILE__ ) ) ) ) ) . '/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();
+ $wfMsgExtOptions = array( 'parsemag' );
+ foreach ( array( 'en', 'fr', 'ar', 'jp', 'zh' ) as $languageCode ) {
+ $wfMsgExtOptions['language'] = $languageCode;
+ foreach ( self::$keyToTestArgs as $key => $testArgs ) {
+ foreach ($testArgs as $args) {
+ // get the raw template, without any transformations
+ $template = wfMsgGetKey( $key, /* useDb */ true, $languageCode, /* transform */ false );
+
+ // get the magic-parsed version with args
+ $wfMsgExtArgs = array_merge( array( $key, $wfMsgExtOptions ), $args );
+ $result = call_user_func_array( 'wfMsgExt', $wfMsgExtArgs );
+
+ // 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/README b/tests/parser/README
new file mode 100644
index 00000000..8b413376
--- /dev/null
+++ b/tests/parser/README
@@ -0,0 +1,8 @@
+Parser tests are run using our PHPUnit test suite in tests/phpunit:
+
+ $ cd tests/phpunit
+ ./phpunit.php --group Parser
+
+You can optionally filter by title using --regex. I.e. :
+
+ ./phpunit.php --group Parser --regex="Bug 6200"
diff --git a/tests/parser/parserTest.inc b/tests/parser/parserTest.inc
index 0ce7c997..30e451b3 100644
--- a/tests/parser/parserTest.inc
+++ b/tests/parser/parserTest.inc
@@ -105,6 +105,9 @@ class ParserTest {
$this->showOutput = isset( $options['show-output'] );
+ if ( isset( $options['filter'] ) ) {
+ $options['regex'] = $options['filter'];
+ }
if ( isset( $options['regex'] ) ) {
if ( isset( $options['record'] ) ) {
@@ -132,45 +135,65 @@ class ParserTest {
}
static function setUp() {
- global $wgParser, $wgParserConf, $IP, $messageMemc, $wgMemc, $wgDeferredUpdateList,
+ global $wgParser, $wgParserConf, $IP, $messageMemc, $wgMemc,
$wgUser, $wgLang, $wgOut, $wgRequest, $wgStyleDirectory, $wgEnableParserCache,
$wgNamespaceAliases, $wgNamespaceProtection, $wgLocalFileRepo,
$parserMemc, $wgThumbnailScriptPath, $wgScriptPath,
- $wgArticlePath, $wgStyleSheetPath, $wgScript, $wgStylePath;
+ $wgArticlePath, $wgStyleSheetPath, $wgScript, $wgStylePath, $wgExtensionAssetsPath,
+ $wgMainCacheType, $wgMessageCacheType, $wgParserCacheType;
$wgScript = '/index.php';
$wgScriptPath = '/';
$wgArticlePath = '/wiki/$1';
$wgStyleSheetPath = '/skins';
$wgStylePath = '/skins';
+ $wgExtensionAssetsPath = '/extensions';
$wgThumbnailScriptPath = false;
$wgLocalFileRepo = array(
- 'class' => 'LocalRepo',
- 'name' => 'local',
- 'directory' => wfTempDir() . '/test-repo',
- 'url' => 'http://example.com/images',
- 'deletedDir' => wfTempDir() . '/test-repo/delete',
- 'hashLevels' => 2,
+ 'class' => 'LocalRepo',
+ 'name' => 'local',
+ 'url' => 'http://example.com/images',
+ 'hashLevels' => 2,
'transformVia404' => false,
+ '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-deleted' => wfTempDir() . '/test-repo/deleted',
+ )
+ ) )
);
$wgNamespaceProtection[NS_MEDIAWIKI] = 'editinterface';
$wgNamespaceAliases['Image'] = NS_FILE;
$wgNamespaceAliases['Image_talk'] = NS_FILE_TALK;
+ // XXX: tests won't run without this (for CACHE_DB)
+ if ( $wgMainCacheType === CACHE_DB ) {
+ $wgMainCacheType = CACHE_NONE;
+ }
+ if ( $wgMessageCacheType === CACHE_DB ) {
+ $wgMessageCacheType = CACHE_NONE;
+ }
+ if ( $wgParserCacheType === CACHE_DB ) {
+ $wgParserCacheType = CACHE_NONE;
+ }
$wgEnableParserCache = false;
- $wgDeferredUpdateList = array();
- $wgMemc = wfGetMainCache();
+ DeferredUpdates::clearPendingUpdates();
+ $wgMemc = wfGetMainCache(); // checks $wgMainCacheType
$messageMemc = wfGetMessageCacheStorage();
$parserMemc = wfGetParserCacheStorage();
// $wgContLang = new StubContLang;
$wgUser = new User;
$context = new RequestContext;
- $wgLang = $context->getLang();
+ $wgLang = $context->getLanguage();
$wgOut = $context->getOutput();
$wgParser = new StubObject( 'wgParser', $wgParserConf['class'], array( $wgParserConf ) );
- $wgRequest = new WebRequest;
+ $wgRequest = $context->getRequest();
if ( $wgStyleDirectory === false ) {
$wgStyleDirectory = "$IP/skins";
@@ -423,10 +446,10 @@ class ParserTest {
}
$opts = $this->parseOptions( $opts );
- $this->setupGlobals( $opts, $config );
+ $context = $this->setupGlobals( $opts, $config );
- $user = new User();
- $options = ParserOptions::newFromUser( $user );
+ $user = $context->getUser();
+ $options = ParserOptions::newFromContext( $context );
if ( isset( $opts['title'] ) ) {
$titleText = $opts['title'];
@@ -452,8 +475,7 @@ class ParserTest {
$replace = $opts['replace'][1];
$out = $parser->replaceSection( $input, $section, $replace );
} elseif ( isset( $opts['comment'] ) ) {
- $linker = $user->getSkin();
- $out = $linker->formatComment( $input, $title, $local );
+ $out = Linker::formatComment( $input, $title, $local );
} elseif ( isset( $opts['preload'] ) ) {
$out = $parser->getpreloadText( $input, $title, $options );
} else {
@@ -471,10 +493,9 @@ class ParserTest {
if ( isset( $opts['ill'] ) ) {
$out = $this->tidy( implode( ' ', $output->getLanguageLinks() ) );
} elseif ( isset( $opts['cat'] ) ) {
- global $wgOut;
-
- $wgOut->addCategoryLinks( $output->getCategories() );
- $cats = $wgOut->getCategoryLinks();
+ $outputPage = $context->getOutput();
+ $outputPage->addCategoryLinks( $output->getCategories() );
+ $cats = $outputPage->getCategoryLinks();
if ( isset( $cats['normal'] ) ) {
$out = $this->tidy( implode( ' ', $cats['normal'] ) );
@@ -609,10 +630,19 @@ class ParserTest {
'wgLocalFileRepo' => array(
'class' => 'LocalRepo',
'name' => 'local',
- 'directory' => $this->uploadDir,
'url' => 'http://example.com/images',
'hashLevels' => 2,
'transformVia404' => false,
+ '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-deleted' => $this->uploadDir . '/delete',
+ )
+ ) )
),
'wgEnableUploads' => self::getOptionValue( 'wgEnableUploads', $opts, true ),
'wgStylePath' => '/skins',
@@ -650,6 +680,7 @@ class ParserTest {
'wgExternalLinkTarget' => false,
'wgAlwaysUseTidy' => false,
'wgHtml5' => true,
+ 'wgCleanupPresentationalAttributes' => true,
'wgWellFormedXml' => true,
'wgAllowMicrodataAttributes' => true,
'wgAdaptiveMessageCache' => true,
@@ -681,7 +712,7 @@ class ParserTest {
$GLOBALS['wgMemc'] = new EmptyBagOStuff;
$context = new RequestContext();
- $GLOBALS['wgLang'] = $context->getLang();
+ $GLOBALS['wgLang'] = $context->getLanguage();
$GLOBALS['wgOut'] = $context->getOutput();
$GLOBALS['wgUser'] = new User();
@@ -689,10 +720,11 @@ class ParserTest {
global $wgHooks;
$wgHooks['ParserTestParser'][] = 'ParserTestParserHook::setup';
- $wgHooks['ParserTestParser'][] = 'ParserTestStaticParserHook::setup';
$wgHooks['ParserGetVariableValueTs'][] = 'ParserTest::getFakeTimestamp';
MagicWord::clearCache();
+
+ return $context;
}
/**
@@ -700,7 +732,7 @@ class ParserTest {
* Some of these probably aren't necessary.
*/
private function listTables() {
- $tables = array( 'user', 'user_properties', 'page', 'page_restrictions',
+ $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',
@@ -709,8 +741,9 @@ class ParserTest {
'archive', 'user_groups', 'page_props', 'category', 'msg_resource', 'msg_resource_links'
);
- if ( in_array( $this->db->getType(), array( 'mysql', 'sqlite', 'oracle' ) ) )
+ if ( in_array( $this->db->getType(), array( 'mysql', 'sqlite', 'oracle' ) ) ) {
array_push( $tables, 'searchindex' );
+ }
// Allow extensions to add to the list of tables to duplicate;
// may be necessary if they hook into page save or other code
@@ -753,17 +786,14 @@ class ParserTest {
}
$temporary = $this->useTemporaryTables || $dbType == 'postgres';
- $tables = $this->listTables();
$prefix = $dbType != 'oracle' ? 'parsertest_' : 'pt_';
$this->dbClone = new CloneDatabase( $this->db, $this->listTables(), $prefix );
$this->dbClone->useTemporaryTables( $temporary );
$this->dbClone->cloneTableStructure();
- if ( $dbType == 'oracle' )
- $this->db->query( 'BEGIN FILL_WIKI_INFO; END;' );
-
if ( $dbType == 'oracle' ) {
+ $this->db->query( 'BEGIN FILL_WIKI_INFO; END;' );
# Insert 0 user to prevent FK violations
# Anonymous user
@@ -807,7 +837,6 @@ class ParserTest {
'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 ) );
@@ -908,9 +937,9 @@ class ParserTest {
return $dir;
}
- wfMkdirParents( $dir . '/3/3a' );
+ wfMkdirParents( $dir . '/3/3a', null, __METHOD__ );
copy( "$IP/skins/monobook/headbg.jpg", "$dir/3/3a/Foobar.jpg" );
- wfMkdirParents( $dir . '/0/09' );
+ wfMkdirParents( $dir . '/0/09', null, __METHOD__ );
copy( "$IP/skins/monobook/headbg.jpg", "$dir/0/09/Bad.jpg" );
return $dir;
@@ -1077,7 +1106,9 @@ class ParserTest {
$shellInfile = wfEscapeShellArg($infile);
$shellOutfile = wfEscapeShellArg($outfile);
- $diff = wfIsWindows()
+ global $wgDiff3;
+ // we assume that people with diff3 also have usual diff
+ $diff = ( wfIsWindows() && !$wgDiff3 )
? `fc $shellInfile $shellOutfile`
: `diff -au $shellInfile $shellOutfile`;
unlink( $infile );
@@ -1130,30 +1161,35 @@ class ParserTest {
* @param $name String: the title, including any prefix
* @param $text String: the article text
* @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' ) {
+ static public function addArticle( $name, $text, $line = 'unknown', $ignoreDuplicate = '' ) {
global $wgCapitalLinks;
- $text = self::chomp($text);
-
$oldCapitalLinks = $wgCapitalLinks;
$wgCapitalLinks = true; // We only need this from SetupGlobals() See r70917#c8637
+ $text = self::chomp( $text );
$name = self::chomp( $name );
+
$title = Title::newFromText( $name );
if ( is_null( $title ) ) {
- throw new MWException( "invalid title ('$name' => '$title') at line $line\n" );
+ throw new MWException( "invalid title '$name' at line $line\n" );
}
- $aid = $title->getArticleID( Title::GAID_FOR_UPDATE );
+ $page = WikiPage::factory( $title );
+ $page->loadPageData( 'fromdbmaster' );
- if ( $aid != 0 ) {
- throw new MWException( "duplicate article '$name' at line $line\n" );
+ if ( $page->exists() ) {
+ if ( $ignoreDuplicate == 'ignoreduplicate' ) {
+ return;
+ } else {
+ throw new MWException( "duplicate article '$name' at line $line\n" );
+ }
}
- $art = new Article( $title );
- $art->doEdit( $text, '', EDIT_NEW );
+ $page->doEdit( $text, '', EDIT_NEW );
$wgCapitalLinks = $oldCapitalLinks;
}
@@ -1204,7 +1240,7 @@ class ParserTest {
return true;
}
- /*
+ /**
* Run the "tidy" command on text if the $wgUseTidy
* global is true
*
diff --git a/tests/parser/parserTests.txt b/tests/parser/parserTests.txt
index 999cd717..d304b19c 100644
--- a/tests/parser/parserTests.txt
+++ b/tests/parser/parserTests.txt
@@ -615,6 +615,26 @@ disabled
</dl>
!! end
+!! test
+Definition and unordered list using wiki syntax nested in unordered list using html tags.
+!! input
+<ul><li>
+; term : description
+* unordered
+</li>
+</ul>
+!! result
+<ul><li>
+<dl><dt> term&#160;</dt><dd> description
+</dd></dl>
+<ul><li> unordered
+</li></ul>
+</li>
+</ul>
+
+!! end
+
+
###
### External links
###
@@ -1050,8 +1070,10 @@ http://www.example.com/?title=AT%26T
</p>
!! end
+# According to http://dev.w3.org/html5/spec/Overview.html#parsing-urls a plain
+# % is actually legal in HTML5. Any change in output would need testing though.
!! test
-Bug 4781, 5267: %26 in URL
+Bug 4781, 5267: %25 in URL
!! input
http://www.example.com/?title=100%25_Bran
!! result
@@ -1169,6 +1191,29 @@ URL-encoding in URL functions (multiple parameters)
</p>
!! end
+!! test
+Brackets in urls
+!! input
+http://example.com/index.php?foozoid%5B%5D=bar
+
+http://example.com/index.php?foozoid&#x5B;&#x5D;=bar
+!! result
+<p><a rel="nofollow" class="external free" href="http://example.com/index.php?foozoid%5B%5D=bar">http://example.com/index.php?foozoid%5B%5D=bar</a>
+</p><p><a rel="nofollow" class="external free" href="http://example.com/index.php?foozoid%5B%5D=bar">http://example.com/index.php?foozoid%5B%5D=bar</a>
+</p>
+!! end
+
+!! test
+IPv6 urls (bug 21261)
+!! options
+disabled
+!! input
+http://[2404:130:0:1000::187:2]/index.php
+!! result
+<p><a rel="nofollow" class="external free" href="http://[2404:130:0:1000::187:2]/index.php">http://[2404:130:0:1000::187:2]/index.php</a>
+</p>
+!! end
+
###
### Quotes
###
@@ -1346,7 +1391,7 @@ Multiplication table
!! test
Table rowspan
!! input
-{| align=right border=1
+{| border=1
| Cell 1, row 1
|rowspan=2| Cell 2, row 1 (and 2)
| Cell 3, row 1
@@ -1355,7 +1400,7 @@ Table rowspan
| Cell 3, row 2
|}
!! result
-<table align="right" border="1">
+<table border="1">
<tr>
<td> Cell 1, row 1
</td>
@@ -1435,6 +1480,28 @@ Table security: embedded pipes (http://lists.wikimedia.org/mailman/htdig/wikitec
!! end
+!! test
+Indented table markup mixed with indented pre content (proposed in bug 6200)
+!! input
+ <table>
+ <tr>
+ <td>
+ Text that should be rendered preformatted
+ </td>
+ </tr>
+ </table>
+!! result
+ <table>
+ <tr>
+ <td>
+<pre>Text that should be rendered preformatted
+</pre>
+ </td>
+ </tr>
+ </table>
+
+!! end
+
###
### Internal links
@@ -1872,7 +1939,7 @@ Inline interwiki link with empty title (bug 2372)
!! input
[[MeatBall:]]
!! result
-<p><a href="http://www.usemod.com/cgi-bin/mb.pl?" class="extiw" title="meatball:">MeatBall:</a>
+<p><a href="http://www.usemod.com/cgi-bin/mb.pl" class="extiw" title="meatball:">MeatBall:</a>
</p>
!! end
@@ -1969,13 +2036,13 @@ Incorrecly removing closing slashes from correctly formed XHTML
!! test
Failing to transform badly formed HTML into correct XHTML
!! input
-<br clear=left>
-<br clear=right>
-<br clear=all>
+<br style="clear: left;">
+<br style="clear: right;">
+<br style="clear: both;">
!! result
-<p><br clear="left" />
-<br clear="right" />
-<br clear="all" />
+<p><br style="clear: left;" />
+<br style="clear: right;" />
+<br style="clear: both;" />
</p>
!!end
@@ -3027,35 +3094,6 @@ section=1
!! result
==Section 1==
!! end
-!! article
-Template:Top-level template
-!! text
-{{Nested template}}
-!! endarticle
-
-!! article
-Template:Nested template
-!! text
-*Item 1
-*Item 2
-!! endarticle
-
-!! test
-Line-start flag in a nested template call
-!! input
-*Item A
-*Item B
-
-{{Top-level template}}
-!! result
-<ul><li>Item A
-</li><li>Item B
-</li></ul>
-<ul><li>Item 1
-</li><li>Item 2
-</li></ul>
-
-!! end
###
### Pre-save transform tests
@@ -3421,6 +3459,66 @@ pst title=[[Ns:Somearticle (IGNORED), Context]]
!! end
!! test
+pre-save transform: context links ("pipe trick") with full-width parens and no space (Japanese and Chinese style, bug 30149)
+!! options
+pst
+!! input
+[[Article(context)|]]
+[[Bar:Article(context)|]]
+[[:Bar:Article(context)|]]
+[[|Article(context)]]
+[[Bar:X (Y) Z|]]
+[[:Bar:X (Y) Z|]]
+!! result
+[[Article(context)|Article]]
+[[Bar:Article(context)|Article]]
+[[:Bar:Article(context)|Article]]
+[[Article(context)]]
+[[Bar:X (Y) Z|X (Y) Z]]
+[[:Bar:X (Y) Z|X (Y) Z]]
+!! end
+
+!! test
+pre-save transform: context links ("pipe trick") with full-width parens and space (Japanese and Chinese style, bug 30149)
+!! options
+pst
+!! input
+[[Article (context)|]]
+[[Bar:Article (context)|]]
+[[:Bar:Article (context)|]]
+[[|Article (context)]]
+[[Bar:X (Y) Z|]]
+[[:Bar:X (Y) Z|]]
+!! result
+[[Article (context)|Article]]
+[[Bar:Article (context)|Article]]
+[[:Bar:Article (context)|Article]]
+[[Article (context)]]
+[[Bar:X (Y) Z|X (Y) Z]]
+[[:Bar:X (Y) Z|X (Y) Z]]
+!! end
+
+!! test
+pre-save transform: context links ("pipe trick") with parens and no space (Korean style, bug 30149)
+!! options
+pst
+!! input
+[[Article(context)|]]
+[[Bar:Article(context)|]]
+[[:Bar:Article(context)|]]
+[[|Article(context)]]
+[[Bar:X(Y)Z|]]
+[[:Bar:X(Y)Z|]]
+!! result
+[[Article(context)|Article]]
+[[Bar:Article(context)|Article]]
+[[:Bar:Article(context)|Article]]
+[[Article(context)]]
+[[Bar:X(Y)Z|X(Y)Z]]
+[[:Bar:X(Y)Z|X(Y)Z]]
+!! end
+
+!! test
pre-save transform: trim trailing empty lines
!! options
pst
@@ -4487,9 +4585,9 @@ div with unquoted attribute
!! test
div with illegal double attributes
!! input
-<div align="center" align="right">HTML rocks</div>
+<div id="a" id="b">HTML rocks</div>
!! result
-<div align="right">HTML rocks</div>
+<div id="b">HTML rocks</div>
!!end
@@ -4519,9 +4617,9 @@ Table multiple attributes correction
!! test
DIV IN UPPERCASE
!! input
-<DIV ALIGN="center">HTML ROCKS</DIV>
+<DIV ID="x">HTML ROCKS</DIV>
!! result
-<div align="center">HTML ROCKS</div>
+<div id="x">HTML ROCKS</div>
!!end
@@ -8381,7 +8479,17 @@ comment title=[[Main Page]]
!! input
/* External links */ removed bogus entries
!! result
-<span class="autocomment"><a href="/wiki/Main_Page#External_links" title="Main Page">→</a>External links: </span> removed bogus entries
+<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
+Edit comment with section link and text before it (non-local, eg in history list)
+!! options
+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>
!!end
!! test
@@ -8391,7 +8499,7 @@ comment local title=[[Main Page]]
!! input
/* External links */ removed bogus entries
!! result
-<span class="autocomment"><a href="#External_links">→</a>External links: </span> removed bogus entries
+<a href="#External_links">→</a>‎<span dir="auto"><span class="autocomment">External links: </span> removed bogus entries</span>
!!end
!! test
@@ -8478,7 +8586,7 @@ title=[[Main Page]]
!!input
/* __hello__world__ */
!! result
-<span class="autocomment"><a href="/wiki/Main_Page#hello_world" title="Main Page">→</a>__hello__world__</span>
+<a href="/wiki/Main_Page#hello_world" title="Main Page">→</a>‎<span dir="auto"><span class="autocomment">__hello__world__</span></span>
!! end
!! test
@@ -8493,6 +8601,8 @@ comment
!! test
Bad images - basic functionality
+!! options
+disabled
!! input
[[File:Bad.jpg]]
!! result
@@ -8500,6 +8610,8 @@ Bad images - basic functionality
!! test
Bad images - bug 16039: text after bad image disappears
+!! options
+disabled
!! input
Foo bar
[[File:Bad.jpg]]
@@ -8713,6 +8825,33 @@ Text&apos;s been normalized?
</p>
!! end
+!! test
+Bug 19052 U+3000 IDEOGRAPHIC SPACE should terminate free external links
+!! input
+http://www.example.org/ <-- U+3000 (vim: ^Vu3000)
+!! result
+<p><a rel="nofollow" class="external free" href="http://www.example.org/">http://www.example.org/</a> &lt;-- U+3000 (vim: ^Vu3000)
+</p>
+!! end
+
+!! test
+Bug 19052 U+3000 IDEOGRAPHIC SPACE should terminate bracketed external links
+!! input
+[http://www.example.org/ ideograms]
+!! result
+<p><a rel="nofollow" class="external text" href="http://www.example.org/">ideograms</a>
+</p>
+!! end
+
+!! test
+Bug 19052 U+3000 IDEOGRAPHIC SPACE should terminate external images links
+!! input
+http://www.example.org/pic.png <-- U+3000 (vim: ^Vu3000)
+!! result
+<p><img src="http://www.example.org/pic.png" alt="pic.png" /> &lt;-- U+3000 (vim: ^Vu3000)
+</p>
+!! end
+
!! article
Mediawiki:loop1
!! text
@@ -8743,6 +8882,22 @@ Bug 31098 Template which includes system messages which includes the template
!! end
!! test
+Deprecated presentational attributes are converted to css
+!! input
+{|
+| valign=top align=left width=100 height=25% | Asdf
+|}
+<ul type="disc"></ul>
+!! result
+<table>
+<tr>
+<td style="text-align: left; height: 25%; vertical-align: top; width: 100px;"> Asdf
+</td></tr></table>
+<ul style="list-style-type: disc;"></ul>
+
+!! end
+
+!! test
Bug31490 Turkish: ucfirst 'blah'
!! options
language=tr
@@ -8797,6 +8952,132 @@ language=en
</p>
!! end
+!! test
+Bug 26375: TOC with italics
+!! options
+title=[[Main Page]]
+!! input
+__TOC__
+== ''Lost'' episodes ==
+!! result
+<table id="toc" class="toc"><tr><td><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&amp;action=edit&amp;section=1" title="Edit section: Lost episodes">edit</a>]</span> <span class="mw-headline" id="Lost_episodes"> <i>Lost</i> episodes </span></h2>
+
+!! end
+
+!! test
+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>
+<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&amp;action=edit&amp;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>
+
+!! end
+
+!! test
+Bug 33845: Headings become cursive in TOC when they contain an image
+!! options
+title=[[Main Page]]
+!! input
+__TOC__
+== Image [[Image:foobar.jpg]] ==
+!! result
+<table id="toc" class="toc"><tr><td><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&amp;action=edit&amp;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>
+
+!! end
+
+!! test
+Bug 33845 (2): Headings become bold in TOC when they contain a blockquote
+!! options
+title=[[Main Page]]
+!! input
+__TOC__
+== <blockquote>Quote</blockquote> ==
+!! result
+<table id="toc" class="toc"><tr><td><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&amp;action=edit&amp;section=1" title="Edit section: Quote">edit</a>]</span> <span class="mw-headline" id="Quote"> <blockquote>Quote</blockquote> </span></h2>
+
+!! end
+
+!! test
+Unclosed tags in TOC
+!! options
+title=[[Main Page]]
+!! input
+__TOC__
+== Proof: 2 < 3 ==
+<small>Hanc marginis exiguitas non caperet.</small>
+QED
+!! result
+<table id="toc" class="toc"><tr><td><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 &lt; 3</span></a></li>
+</ul>
+</td></tr></table>
+<h2><span class="editsection">[<a href="/index.php?title=Main_Page&amp;action=edit&amp;section=1" title="Edit section: Proof: 2 &lt; 3">edit</a>]</span> <span class="mw-headline" id="Proof:_2_.3C_3"> Proof: 2 &lt; 3 </span></h2>
+<p><small>Hanc marginis exiguitas non caperet.</small>
+QED
+</p>
+!! end
+
+!! test
+Multiple tags in TOC
+!! input
+__TOC__
+== <i>Foo</i> <b>Bar</b> ==
+
+== <i>Foo</i> <blockquote>Bar</blockquote> ==
+!! result
+<table id="toc" class="toc"><tr><td><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&amp;action=edit&amp;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&amp;action=edit&amp;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>
+
+!! end
+
+!! test
+Tags with parameters in TOC
+!! input
+__TOC__
+== <sup class="in-h2">Hello</sup> ==
+
+== <sup class="a > b">Evilbye</sup> ==
+!! result
+<table id="toc" class="toc"><tr><td><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"&gt;Evilbye</sup></span></a></li>
+</ul>
+</td></tr></table>
+<h2><span class="editsection">[<a href="/index.php?title=Parser_test&amp;action=edit&amp;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&amp;action=edit&amp;section=2" title="Edit section: b&quot;>Evilbye">edit</a>]</span> <span class="mw-headline" id="b.22.3EEvilbye"> <sup> b"&gt;Evilbye</sup> </span></h2>
+
+!! end
!! article
MediaWiki:Bug32057
@@ -8895,6 +9176,15 @@ Strip marker in anchorencode
</p>
!! end
+!! test
+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&amp;action=edit&amp;section=1" title="Edit section: xyz">edit</a>]</span> <span class="mw-headline" id="xyz"><a href="/index.php?title=Foo&amp;action=edit&amp;redlink=1" class="new" title="Foo (page does not exist)">xyz</a></span></h2>
+
+!! end
+
TODO:
more images
diff --git a/tests/parser/parserTestsParserHook.php b/tests/parser/parserTestsParserHook.php
index 324b8e5c..24d852c5 100644
--- a/tests/parser/parserTestsParserHook.php
+++ b/tests/parser/parserTestsParserHook.php
@@ -29,7 +29,7 @@ class ParserTestParserHook {
static function setup( &$parser ) {
$parser->setHook( 'tag', array( __CLASS__, 'dumpHook' ) );
-
+ $parser->setHook( 'statictag', array( __CLASS__, 'staticTagHook' ) );
return true;
}
@@ -43,4 +43,28 @@ class ParserTestParserHook {
return "<pre>\n$ret</pre>";
}
+
+ static function staticTagHook( $in, $argv, $parser ) {
+ if ( ! count( $argv ) ) {
+ $parser->static_tag_buf = $in;
+ return '';
+ } elseif ( count( $argv ) === 1 && isset( $argv['action'] )
+ && $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;
+ } else {
+ $tmp = '';
+ }
+ $parser->static_tag_buf = null;
+ return $tmp;
+ } 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/parserTestsStaticParserHook.php b/tests/parser/parserTestsStaticParserHook.php
deleted file mode 100644
index e82f7f3f..00000000
--- a/tests/parser/parserTestsStaticParserHook.php
+++ /dev/null
@@ -1,58 +0,0 @@
-<?php
-/**
- * A basic extension that's used by the parser tests to test whether the parser
- * calls extensions when they're called inside comments, it shouldn't do that
- *
- * Copyright © 2005, 2006 Ævar Arnfjörð Bjarmason
- *
- * 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
- * @author Ævar Arnfjörð Bjarmason <avarab@gmail.com>
- */
-
-class ParserTestStaticParserHook {
- static function setup( &$parser ) {
- $parser->setHook( 'statictag', array( __CLASS__, 'hook' ) );
-
- return true;
- }
-
- static function hook( $in, $argv, $parser ) {
- if ( ! count( $argv ) ) {
- $parser->static_tag_buf = $in;
- return '';
- } elseif ( count( $argv ) === 1 && isset( $argv['action'] )
- && $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;
- } else {
- $tmp = '';
- }
- $parser->static_tag_buf = null;
- return $tmp;
- } 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 96d4569b..897c5fb0 100644
--- a/tests/parser/preprocess/All_system_messages.expected
+++ b/tests/parser/preprocess/All_system_messages.expected
@@ -1,4 +1,4 @@
-<root><template lineStart="1"><title>int:allmessagestext</title></template>
+<root><template><title>int:allmessagestext</title></template>
&lt;table border=1 width=100%&gt;&lt;tr&gt;&lt;td&gt;
'''Name'''
diff --git a/tests/parser/preprocess/Factorial.expected b/tests/parser/preprocess/Factorial.expected
index 099029c0..a10fd6ca 100644
--- a/tests/parser/preprocess/Factorial.expected
+++ b/tests/parser/preprocess/Factorial.expected
@@ -1,4 +1,4 @@
-<root><template lineStart="1"><title>#expr:<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=00</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>01<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=01</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*01<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=02</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*02<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=03</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*03<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=04</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*04<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=05</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*05<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=06</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*06<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=07</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*07<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=08</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*08<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=09</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*09<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=10</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*10<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=11</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*11<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=12</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*12<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=13</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*13<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=14</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*14<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=15</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*15<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=16</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*16<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=17</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*17<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=18</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*18<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=19</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*19<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=20</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*20<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=21</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*21<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=22</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*22<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=23</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*23<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=24</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*24<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=25</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*25<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=26</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*26<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=27</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*27<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=28</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*28<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=29</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*29<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=30</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*30<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=31</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*31<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=32</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*32<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=33</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*33<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=34</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*34<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=35</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*35<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=36</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*36<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=37</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*37<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=38</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*38<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=39</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*39<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=40</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*40<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=41</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*41<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=42</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*42<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=43</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*43<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=44</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*44<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=45</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*45<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=46</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*46<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=47</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*47<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=48</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*48<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=49</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*49<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=50</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*50<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=51</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*51<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=52</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*52<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=53</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*53<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=54</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*54<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=55</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*55<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=56</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*56<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=57</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*57<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=58</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*58<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=59</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*59<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=60</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*60<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=61</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*61<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=62</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*62<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=63</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*63<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=64</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*64<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=65</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*65<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=66</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*66<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=67</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*67<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=68</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*68<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=69</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*69<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=70</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*70<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=71</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*71<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=72</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*72<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=73</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*73<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=74</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*74<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=75</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*75<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=76</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*76<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=77</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*77<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=78</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*78<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=79</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*79<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=80</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*80<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=81</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*81<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=82</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*82<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=83</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*83<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=84</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*84<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=85</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*85<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=86</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*86<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=87</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*87<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=88</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*88<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=89</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*89<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=90</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*90<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=91</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*91<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=92</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*92<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=93</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*93<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=94</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*94<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=95</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*95<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=96</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*96<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=97</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*97<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=98</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*98<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=99</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*99</value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></title></template><ignore>&lt;noinclude&gt;</ignore>
+<root><template><title>#expr:<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=00</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>01<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=01</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*01<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=02</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*02<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=03</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*03<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=04</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*04<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=05</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*05<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=06</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*06<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=07</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*07<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=08</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*08<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=09</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*09<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=10</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*10<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=11</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*11<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=12</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*12<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=13</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*13<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=14</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*14<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=15</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*15<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=16</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*16<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=17</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*17<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=18</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*18<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=19</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*19<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=20</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*20<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=21</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*21<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=22</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*22<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=23</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*23<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=24</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*24<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=25</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*25<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=26</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*26<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=27</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*27<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=28</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*28<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=29</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*29<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=30</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*30<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=31</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*31<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=32</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*32<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=33</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*33<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=34</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*34<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=35</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*35<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=36</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*36<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=37</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*37<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=38</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*38<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=39</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*39<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=40</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*40<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=41</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*41<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=42</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*42<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=43</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*43<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=44</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*44<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=45</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*45<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=46</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*46<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=47</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*47<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=48</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*48<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=49</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*49<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=50</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*50<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=51</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*51<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=52</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*52<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=53</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*53<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=54</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*54<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=55</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*55<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=56</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*56<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=57</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*57<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=58</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*58<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=59</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*59<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=60</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*60<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=61</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*61<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=62</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*62<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=63</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*63<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=64</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*64<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=65</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*65<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=66</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*66<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=67</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*67<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=68</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*68<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=69</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*69<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=70</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*70<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=71</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*71<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=72</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*72<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=73</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*73<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=74</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*74<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=75</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*75<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=76</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*76<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=77</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*77<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=78</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*78<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=79</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*79<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=80</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*80<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=81</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*81<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=82</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*82<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=83</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*83<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=84</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*84<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=85</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*85<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=86</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*86<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=87</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*87<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=88</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*88<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=89</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*89<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=90</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*90<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=91</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*91<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=92</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*92<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=93</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*93<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=94</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*94<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=95</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*95<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=96</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*96<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=97</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*97<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=98</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*98<template><title>#ifeq:<template><title>#expr:<tplarg><title>1</title></tplarg>&gt;=99</title></template></title><part><name index="1" /><value>1</value></part><part><name index="2" /><value>*99</value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></value></part></template></title></template><ignore>&lt;noinclude&gt;</ignore>
<template lineStart="1"><title>Template documentation</title></template>
This template finds the [[factorial]] of a number. To use it, enter:&lt;br /&gt;
&lt;code&gt;&lt;nowiki&gt;<template><title>factorial</title><part><name index="1" /><value>input</value></part></template>&lt;/nowiki&gt;&lt;/code&gt;&lt;br /&gt;
diff --git a/tests/parserTests.php b/tests/parserTests.php
index 99ea2ed4..d930ac5a 100644
--- a/tests/parserTests.php
+++ b/tests/parserTests.php
@@ -24,8 +24,8 @@
* @ingroup Testing
*/
-$options = array( 'quick', 'color', 'quiet', 'help', 'show-output', 'record', 'run-disabled' );
-$optionsWithArgs = array( 'regex', 'seed', 'setversion' );
+$otions = array( 'quick', 'color', 'quiet', 'help', 'show-output', 'record', 'run-disabled' );
+$optionsWithArgs = array( 'regex', 'filter', 'seed', 'setversion' );
require_once( dirname( __FILE__ ) . '/../maintenance/commandLine.inc' );
@@ -39,13 +39,14 @@ Options:
--quiet Suppress notification of passed tests (shows only failed tests)
--show-output Show expected and actual output
--color[=yes|no] Override terminal detection and force color output on or off
- use wgCommandLineDarkBg = true; if your term is dark
+ use wgCommandLineDarkBg = true; if your term is dark
--regex Only run tests whose descriptions which match given regex
+ --filter Alias for --regex
--file=<testfile> Run test cases from a custom file instead of parserTests.txt
--record Record tests in database
--compare Compare with recorded results, without updating the database.
--setversion When using --record, set the version string to use (useful
- with git-svn so that you can get the exact revision)
+ with git-svn so that you can get the exact revision)
--keep-uploads Re-use the same upload directory for each test, don't delete it
--fuzz Do a fuzz test instead of a normal test
--seed <n> Start the fuzz test from the specified seed
diff --git a/tests/phpunit/Makefile b/tests/phpunit/Makefile
index 24536efc..8a55dae0 100644
--- a/tests/phpunit/Makefile
+++ b/tests/phpunit/Makefile
@@ -46,17 +46,26 @@ coverage:
parser:
${PU} --group Parser
+parserfuzz:
+ @echo "******************************************************************"
+ @echo "* This WILL kill your computer by eating all memory AND all swap *"
+ @echo "* *"
+ @echo "* If you are on a production machine. ABORT NOW!! *"
+ @echo "* Press control+C to stop *"
+ @echo "* *"
+ @echo "******************************************************************"
+ ${PU} --group Parser,ParserFuzz
noparser:
- ${PU} --exclude-group Parser,Broken,Stub
+ ${PU} --exclude-group Parser,Broken,ParserFuzz,Stub
safe:
- ${PU} --exclude-group Broken,Destructive,Stub
+ ${PU} --exclude-group Broken,ParserFuzz,Destructive,Stub
databaseless:
- ${PU} --exclude-group Broken,Destructive,Database,Stub
+ ${PU} --exclude-group Broken,ParserFuzz,Destructive,Database,Stub
database:
- ${PU} --exclude-group Broken,Destructive,Stub --group Database
+ ${PU} --exclude-group Broken,ParserFuzz,Destructive,Stub --group Database
list-groups:
${PU} --list-groups
diff --git a/tests/phpunit/MediaWikiLangTestCase.php b/tests/phpunit/MediaWikiLangTestCase.php
index 1cd6a3ba..783f0315 100644
--- a/tests/phpunit/MediaWikiLangTestCase.php
+++ b/tests/phpunit/MediaWikiLangTestCase.php
@@ -13,7 +13,11 @@ abstract class MediaWikiLangTestCase extends MediaWikiTestCase {
self::$oldLang = $wgLang;
self::$oldContLang = $wgContLang;
- if( $wgLanguageCode != $wgContLang->getCode() ) die("nooo!");
+ 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'
diff --git a/tests/phpunit/MediaWikiPHPUnitCommand.php b/tests/phpunit/MediaWikiPHPUnitCommand.php
index c0d9f363..ea385ad9 100644
--- a/tests/phpunit/MediaWikiPHPUnitCommand.php
+++ b/tests/phpunit/MediaWikiPHPUnitCommand.php
@@ -5,7 +5,10 @@ class MediaWikiPHPUnitCommand extends PHPUnit_TextUI_Command {
static $additionalOptions = array(
'regex=' => false,
'file=' => false,
+ 'use-filebackend=' => false,
'keep-uploads' => false,
+ 'use-normal-tables' => false,
+ 'reuse-db' => false,
);
public function __construct() {
@@ -17,6 +20,28 @@ class MediaWikiPHPUnitCommand extends PHPUnit_TextUI_Command {
public static function main( $exit = true ) {
$command = new self;
+
+ 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.
+ # The below code inject a parameter just like if the user called
+ # phpunit with a --no-color option (which does not exist). It
+ # overrides the suite.xml setting.
+ # Probably fix bug 29226
+ $command->arguments['colors'] = false;
+ }
+
+ # Makes MediaWiki PHPUnit directory includable so the PHPUnit will
+ # be able to resolve relative files inclusion such as suites/*
+ # PHPUnit uses stream_resolve_include_path() internally
+ # See bug 32022
+ set_include_path(
+ dirname( __FILE__ )
+ .PATH_SEPARATOR
+ . get_include_path()
+ );
+
$command->run($_SERVER['argv'], $exit);
}
@@ -40,6 +65,11 @@ ParserTest-specific options:
--keep-uploads Re-use the same upload directory for each test, don't delete it
+Database options:
+ --use-normal-tables Use normal DB tables.
+ --reuse-db Init DB only if tables are missing and keep after finish.
+
+
EOT;
}
diff --git a/tests/phpunit/MediaWikiTestCase.php b/tests/phpunit/MediaWikiTestCase.php
index 64cb486b..6ec8bdc7 100644
--- a/tests/phpunit/MediaWikiTestCase.php
+++ b/tests/phpunit/MediaWikiTestCase.php
@@ -11,6 +11,9 @@ abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase {
protected $db;
protected $oldTablePrefix;
protected $useTemporaryTables = true;
+ protected $reuseDB = false;
+ protected $tablesUsed = array(); // tables with data
+
private static $dbSetup = false;
/**
@@ -22,6 +25,7 @@ abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase {
protected $supportedDBs = array(
'mysql',
'sqlite',
+ 'postgres',
'oracle'
);
@@ -40,8 +44,10 @@ abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase {
ObjectCache::$instances[CACHE_DB] = new HashBagOStuff;
if( $this->needsDB() ) {
-
global $wgDBprefix;
+
+ $this->useTemporaryTables = !$this->getCliArg( 'use-normal-tables' );
+ $this->reuseDB = $this->getCliArg('reuse-db');
$this->db = wfGetDB( DB_MASTER );
@@ -81,6 +87,34 @@ abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase {
function addDBData() {}
private function addCoreDBData() {
+ # disabled for performance
+ #$this->tablesUsed[] = 'page';
+ #$this->tablesUsed[] = 'revision';
+
+ if ( $this->db->getType() == 'oracle' ) {
+
+ # Insert 0 user to prevent FK violations
+ # Anonymous user
+ $this->db->insert( 'user', array(
+ 'user_id' => 0,
+ 'user_name' => 'Anonymous' ), __METHOD__, array( 'IGNORE' ) );
+
+ # Insert 0 page to prevent FK violations
+ # Blank page
+ $this->db->insert( 'page', array(
+ 'page_id' => 0,
+ 'page_namespace' => 0,
+ 'page_title' => ' ',
+ 'page_restrictions' => NULL,
+ 'page_counter' => 0,
+ 'page_is_redirect' => 0,
+ 'page_is_new' => 0,
+ 'page_random' => 0,
+ 'page_touched' => $this->db->timestamp(),
+ 'page_latest' => 0,
+ 'page_len' => 0 ), __METHOD__, array( 'IGNORE' ) );
+
+ }
User::resetIdByNameCache();
@@ -98,12 +132,14 @@ abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase {
//Make 1 page with 1 revision
- $article = new Article( Title::newFromText( 'UTPage' ) );
- $article->doEdit( 'UTContent',
+ $page = WikiPage::factory( Title::newFromText( 'UTPage' ) );
+ if ( !$page->getId() == 0 ) {
+ $page->doEdit( 'UTContent',
'UTPageSummary',
EDIT_NEW,
false,
User::newFromName( 'UTSysop' ) );
+ }
}
private function initDB() {
@@ -112,18 +148,20 @@ abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase {
throw new MWException( 'Cannot run unit tests, the database prefix is already "unittest_"' );
}
- $dbClone = new CloneDatabase( $this->db, $this->listTables(), $this->dbPrefix() );
+ $tablesCloned = $this->listTables();
+ $dbClone = new CloneDatabase( $this->db, $tablesCloned, $this->dbPrefix() );
$dbClone->useTemporaryTables( $this->useTemporaryTables );
- $dbClone->cloneTableStructure();
+
+ 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;' );
-
- # Insert 0 user to prevent FK violations
- # Anonymous user
- $this->db->insert( 'user', array(
- 'user_id' => 0,
- 'user_name' => 'Anonymous' ) );
}
}
@@ -132,35 +170,25 @@ abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase {
*/
private function resetDB() {
if( $this->db ) {
- foreach( $this->listTables() as $tbl ) {
- if( $tbl == 'interwiki' || $tbl == 'user' ) continue;
- $this->db->delete( $tbl, '*', __METHOD__ );
+ if ( $this->db->getType() == 'oracle' ) {
+ if ( $this->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__ );
+ }
+ }
+ } else {
+ foreach( $this->tablesUsed as $tbl ) {
+ if( $tbl == 'interwiki' || $tbl == 'user' ) continue;
+ $this->db->delete( $tbl, '*', __METHOD__ );
+ }
}
}
}
- protected function destroyDB() {
- if ( $this->useTemporaryTables || is_null( $this->db ) ) {
- # Don't need to do anything
- return;
- }
-
- $tables = $this->db->listTables( $this->dbPrefix(), __METHOD__ );
-
- foreach ( $tables as $table ) {
- try {
- $sql = $this->db->getType() == 'oracle' ? "DROP TABLE $table CASCADE CONSTRAINTS PURGE" : "DROP TABLE `$table`";
- $this->db->query( $sql, __METHOD__ );
- } catch( MWException $mwe ) {}
- }
-
- if ( $this->db->getType() == 'oracle' )
- $this->db->query( 'BEGIN FILL_WIKI_INFO; END;', __METHOD__ );
-
- CloneDatabase::changePrefix( $this->oldTablePrefix );
- }
-
-
function __call( $func, $args ) {
static $compatibility = array(
'assertInternalType' => 'assertType',
@@ -235,5 +263,16 @@ abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase {
public static function disableInterwikis( $prefix, &$data ) {
return false;
}
-}
+ /**
+ * Don't throw a warning if $function is deprecated and called later
+ *
+ * @param $function String
+ * @return null
+ */
+ function hideDeprecated( $function ) {
+ wfSuppressWarnings();
+ wfDeprecated( $function );
+ wfRestoreWarnings();
+ }
+}
diff --git a/tests/phpunit/StructureTest.php b/tests/phpunit/StructureTest.php
new file mode 100644
index 00000000..f967c18d
--- /dev/null
+++ b/tests/phpunit/StructureTest.php
@@ -0,0 +1,56 @@
+<?php
+/**
+ * The tests here verify the structure of the code. This is for outright bugs,
+ * not just style issues.
+ */
+
+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.
+ */
+ public function testUnitTestFileNamesEndWithTest() {
+ if ( wfIsWindows() ) {
+ $this->markTestSkipped( 'This test does not work on Windows' );
+ }
+ $rootPath = escapeshellarg( __DIR__ );
+ $testClassRegex = implode( '|', array(
+ 'ApiFormatTestBase',
+ 'ApiTestCase',
+ 'MediaWikiLangTestCase',
+ 'MediaWikiTestCase',
+ 'PHPUnit_Framework_TestCase',
+ ) );
+ $testClassRegex = "^class .* extends ($testClassRegex)";
+ $finder = "find $rootPath -name '*.php' '!' -name '*Test.php'" .
+ " | xargs grep -El '$testClassRegex|function suite\('";
+
+ $results = null;
+ $exitCode = null;
+ exec($finder, $results, $exitCode);
+
+ $this->assertEquals(
+ 0,
+ $exitCode,
+ 'Verify find/grep command succeeds.'
+ );
+
+ $results = array_filter(
+ $results,
+ array( $this, 'filterSuites' )
+ );
+
+ $this->assertEquals(
+ array(),
+ $results,
+ 'Unit test file names must end with Test.'
+ );
+ }
+
+ /**
+ * Filter to remove testUnitTestFileNamesEndWithTest false positives.
+ */
+ public function filterSuites( $filename ) {
+ return strpos( $filename, __DIR__ . '/suites/' ) !== 0;
+ }
+}
diff --git a/tests/phpunit/data/db/mysql/functions.sql b/tests/phpunit/data/db/mysql/functions.sql
new file mode 100644
index 00000000..9e5e470f
--- /dev/null
+++ b/tests/phpunit/data/db/mysql/functions.sql
@@ -0,0 +1,12 @@
+-- MySQL test file for DatabaseTest::testStoredFunctions()
+
+DELIMITER //
+
+CREATE FUNCTION mw_test_function()
+RETURNS int DETERMINISTIC
+BEGIN
+ SET @foo = 21;
+ RETURN @foo * 2;
+END//
+
+DELIMITER //
diff --git a/tests/phpunit/data/db/postgres/functions.sql b/tests/phpunit/data/db/postgres/functions.sql
new file mode 100644
index 00000000..3086d4d5
--- /dev/null
+++ b/tests/phpunit/data/db/postgres/functions.sql
@@ -0,0 +1,12 @@
+-- Postgres test file for DatabaseTest::testStoredFunctions()
+
+CREATE FUNCTION mw_test_function()
+RETURNS INTEGER
+LANGUAGE plpgsql AS
+$mw$
+DECLARE foo INTEGER;
+BEGIN
+ foo := 21;
+ RETURN foo * 2;
+END
+$mw$;
diff --git a/tests/phpunit/includes/db/sqlite/tables-1.13.sql b/tests/phpunit/data/db/sqlite/tables-1.13.sql
index a0dcb553..66847ab1 100644
--- a/tests/phpunit/includes/db/sqlite/tables-1.13.sql
+++ b/tests/phpunit/data/db/sqlite/tables-1.13.sql
@@ -123,7 +123,7 @@ CREATE TABLE /*$wgDBprefix*/site_stats (
ss_images INTEGER default '0') /*$wgDBTableOptions*/;
CREATE TABLE /*$wgDBprefix*/hitcounter (
- hc_id INTEGER
+ hc_id INTEGER
) ;
CREATE TABLE /*$wgDBprefix*/ipblocks (
diff --git a/tests/phpunit/includes/db/sqlite/tables-1.15.sql b/tests/phpunit/data/db/sqlite/tables-1.15.sql
index 901bac52..6b3a628e 100644
--- a/tests/phpunit/includes/db/sqlite/tables-1.15.sql
+++ b/tests/phpunit/data/db/sqlite/tables-1.15.sql
@@ -141,7 +141,7 @@ CREATE INDEX /*i*/el_to ON /*_*/externallinks (el_to(60), el_from);
CREATE INDEX /*i*/el_index ON /*_*/externallinks (el_index(60));
CREATE TABLE /*_*/langlinks (
ll_from int unsigned NOT NULL default 0,
-
+
ll_lang varbinary(20) NOT NULL default '',
ll_title varchar(255) binary NOT NULL default ''
) /*$wgDBTableOptions*/;
@@ -181,7 +181,7 @@ CREATE TABLE /*_*/ipblocks (
ipb_block_email bool NOT NULL default 0,
ipb_allow_usertalk bool NOT NULL default 0
) /*$wgDBTableOptions*/;
-
+
CREATE UNIQUE INDEX /*i*/ipb_address ON /*_*/ipblocks (ipb_address(255), ipb_user, ipb_auto, ipb_anon_only);
CREATE INDEX /*i*/ipb_user ON /*_*/ipblocks (ipb_user);
CREATE INDEX /*i*/ipb_range ON /*_*/ipblocks (ipb_range_start(8), ipb_range_end(8));
diff --git a/tests/phpunit/includes/db/sqlite/tables-1.16.sql b/tests/phpunit/data/db/sqlite/tables-1.16.sql
index 6e56add2..6e56add2 100644
--- a/tests/phpunit/includes/db/sqlite/tables-1.16.sql
+++ b/tests/phpunit/data/db/sqlite/tables-1.16.sql
diff --git a/tests/phpunit/includes/db/sqlite/tables-1.17.sql b/tests/phpunit/data/db/sqlite/tables-1.17.sql
index 69ae3764..69ae3764 100644
--- a/tests/phpunit/includes/db/sqlite/tables-1.17.sql
+++ b/tests/phpunit/data/db/sqlite/tables-1.17.sql
diff --git a/tests/phpunit/data/db/sqlite/tables-1.18.sql b/tests/phpunit/data/db/sqlite/tables-1.18.sql
new file mode 100644
index 00000000..bedf6c33
--- /dev/null
+++ b/tests/phpunit/data/db/sqlite/tables-1.18.sql
@@ -0,0 +1,535 @@
+-- This is a copy of MediaWiki 1.18 schema shared by MySQL and SQLite.
+-- It is used for updater testing. Comments are stripped to decrease
+-- file size, as we don't need to maintain it.
+
+CREATE TABLE /*_*/user (
+ user_id int unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT,
+ user_name varchar(255) binary NOT NULL default '',
+ user_real_name varchar(255) binary NOT NULL default '',
+ user_password tinyblob NOT NULL,
+ user_newpassword tinyblob NOT NULL,
+ user_newpass_time binary(14),
+ user_email tinytext NOT NULL,
+ user_options blob NOT NULL,
+ user_touched binary(14) NOT NULL default '',
+ user_token binary(32) NOT NULL default '',
+ user_email_authenticated binary(14),
+ user_email_token binary(32),
+ user_email_token_expires binary(14),
+ user_registration binary(14),
+ user_editcount int
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/user_name ON /*_*/user (user_name);
+CREATE INDEX /*i*/user_email_token ON /*_*/user (user_email_token);
+CREATE INDEX /*i*/user_email ON /*_*/user (user_email(50));
+CREATE TABLE /*_*/user_groups (
+ ug_user int unsigned NOT NULL default 0,
+ ug_group varbinary(16) NOT NULL default ''
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/ug_user_group ON /*_*/user_groups (ug_user,ug_group);
+CREATE INDEX /*i*/ug_group ON /*_*/user_groups (ug_group);
+CREATE TABLE /*_*/user_former_groups (
+ ufg_user int unsigned NOT NULL default 0,
+ ufg_group varbinary(16) NOT NULL default ''
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/ufg_user_group ON /*_*/user_former_groups (ufg_user,ufg_group);
+CREATE TABLE /*_*/user_newtalk (
+ user_id int NOT NULL default 0,
+ user_ip varbinary(40) NOT NULL default '',
+ user_last_timestamp varbinary(14) NULL default NULL
+) /*$wgDBTableOptions*/;
+CREATE INDEX /*i*/un_user_id ON /*_*/user_newtalk (user_id);
+CREATE INDEX /*i*/un_user_ip ON /*_*/user_newtalk (user_ip);
+CREATE TABLE /*_*/user_properties (
+ up_user int NOT NULL,
+ up_property varbinary(255) NOT NULL,
+ up_value blob
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/user_properties_user_property ON /*_*/user_properties (up_user,up_property);
+CREATE INDEX /*i*/user_properties_property ON /*_*/user_properties (up_property);
+CREATE TABLE /*_*/page (
+ page_id int unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT,
+ page_namespace int NOT NULL,
+ page_title varchar(255) binary NOT NULL,
+ page_restrictions tinyblob NOT NULL,
+ page_counter bigint unsigned NOT NULL default 0,
+ page_is_redirect tinyint unsigned NOT NULL default 0,
+ page_is_new tinyint unsigned NOT NULL default 0,
+ page_random real unsigned NOT NULL,
+ page_touched binary(14) NOT NULL default '',
+ page_latest int unsigned NOT NULL,
+ page_len int unsigned NOT NULL
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/name_title ON /*_*/page (page_namespace,page_title);
+CREATE INDEX /*i*/page_random ON /*_*/page (page_random);
+CREATE INDEX /*i*/page_len ON /*_*/page (page_len);
+CREATE TABLE /*_*/revision (
+ rev_id int unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT,
+ rev_page int unsigned NOT NULL,
+ rev_text_id int unsigned NOT NULL,
+ rev_comment tinyblob NOT NULL,
+ rev_user int unsigned NOT NULL default 0,
+ rev_user_text varchar(255) binary NOT NULL default '',
+ rev_timestamp binary(14) NOT NULL default '',
+ rev_minor_edit tinyint unsigned NOT NULL default 0,
+ rev_deleted tinyint unsigned NOT NULL default 0,
+ rev_len int unsigned,
+ rev_parent_id int unsigned default NULL
+) /*$wgDBTableOptions*/ MAX_ROWS=10000000 AVG_ROW_LENGTH=1024;
+CREATE UNIQUE INDEX /*i*/rev_page_id ON /*_*/revision (rev_page, rev_id);
+CREATE INDEX /*i*/rev_timestamp ON /*_*/revision (rev_timestamp);
+CREATE INDEX /*i*/page_timestamp ON /*_*/revision (rev_page,rev_timestamp);
+CREATE INDEX /*i*/user_timestamp ON /*_*/revision (rev_user,rev_timestamp);
+CREATE INDEX /*i*/usertext_timestamp ON /*_*/revision (rev_user_text,rev_timestamp);
+CREATE TABLE /*_*/text (
+ old_id int unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT,
+ old_text mediumblob NOT NULL,
+ old_flags tinyblob NOT NULL
+) /*$wgDBTableOptions*/ MAX_ROWS=10000000 AVG_ROW_LENGTH=10240;
+CREATE TABLE /*_*/archive (
+ ar_namespace int NOT NULL default 0,
+ ar_title varchar(255) binary NOT NULL default '',
+ ar_text mediumblob NOT NULL,
+ ar_comment tinyblob NOT NULL,
+ ar_user int unsigned NOT NULL default 0,
+ ar_user_text varchar(255) binary NOT NULL,
+ ar_timestamp binary(14) NOT NULL default '',
+ ar_minor_edit tinyint NOT NULL default 0,
+ ar_flags tinyblob NOT NULL,
+ ar_rev_id int unsigned,
+ ar_text_id int unsigned,
+ ar_deleted tinyint unsigned NOT NULL default 0,
+ ar_len int unsigned,
+ ar_page_id int unsigned,
+ ar_parent_id int unsigned default NULL
+) /*$wgDBTableOptions*/;
+CREATE INDEX /*i*/name_title_timestamp ON /*_*/archive (ar_namespace,ar_title,ar_timestamp);
+CREATE INDEX /*i*/ar_usertext_timestamp ON /*_*/archive (ar_user_text,ar_timestamp);
+CREATE INDEX /*i*/ar_revid ON /*_*/archive (ar_rev_id);
+CREATE TABLE /*_*/pagelinks (
+ pl_from int unsigned NOT NULL default 0,
+ pl_namespace int NOT NULL default 0,
+ pl_title varchar(255) binary NOT NULL default ''
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/pl_from ON /*_*/pagelinks (pl_from,pl_namespace,pl_title);
+CREATE UNIQUE INDEX /*i*/pl_namespace ON /*_*/pagelinks (pl_namespace,pl_title,pl_from);
+CREATE TABLE /*_*/templatelinks (
+ tl_from int unsigned NOT NULL default 0,
+ tl_namespace int NOT NULL default 0,
+ tl_title varchar(255) binary NOT NULL default ''
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/tl_from ON /*_*/templatelinks (tl_from,tl_namespace,tl_title);
+CREATE UNIQUE INDEX /*i*/tl_namespace ON /*_*/templatelinks (tl_namespace,tl_title,tl_from);
+CREATE TABLE /*_*/imagelinks (
+ il_from int unsigned NOT NULL default 0,
+ il_to varchar(255) binary NOT NULL default ''
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/il_from ON /*_*/imagelinks (il_from,il_to);
+CREATE UNIQUE INDEX /*i*/il_to ON /*_*/imagelinks (il_to,il_from);
+CREATE TABLE /*_*/categorylinks (
+ cl_from int unsigned NOT NULL default 0,
+ cl_to varchar(255) binary NOT NULL default '',
+ cl_sortkey varbinary(230) NOT NULL default '',
+ cl_sortkey_prefix varchar(255) binary NOT NULL default '',
+ cl_timestamp timestamp NOT NULL,
+ cl_collation varbinary(32) NOT NULL default '',
+ cl_type ENUM('page', 'subcat', 'file') NOT NULL default 'page'
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/cl_from ON /*_*/categorylinks (cl_from,cl_to);
+CREATE INDEX /*i*/cl_sortkey ON /*_*/categorylinks (cl_to,cl_type,cl_sortkey,cl_from);
+CREATE INDEX /*i*/cl_timestamp ON /*_*/categorylinks (cl_to,cl_timestamp);
+CREATE INDEX /*i*/cl_collation ON /*_*/categorylinks (cl_collation);
+CREATE TABLE /*_*/category (
+ cat_id int unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT,
+ cat_title varchar(255) binary NOT NULL,
+ cat_pages int signed NOT NULL default 0,
+ cat_subcats int signed NOT NULL default 0,
+ cat_files int signed NOT NULL default 0,
+ cat_hidden tinyint unsigned NOT NULL default 0
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/cat_title ON /*_*/category (cat_title);
+CREATE INDEX /*i*/cat_pages ON /*_*/category (cat_pages);
+CREATE TABLE /*_*/externallinks (
+ el_from int unsigned NOT NULL default 0,
+ el_to blob NOT NULL,
+ el_index blob NOT NULL
+) /*$wgDBTableOptions*/;
+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 '',
+ ll_title varchar(255) binary NOT NULL default ''
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/ll_from ON /*_*/langlinks (ll_from, ll_lang);
+CREATE INDEX /*i*/ll_lang ON /*_*/langlinks (ll_lang, ll_title);
+CREATE TABLE /*_*/iwlinks (
+ iwl_from int unsigned NOT NULL default 0,
+ iwl_prefix varbinary(20) NOT NULL default '',
+ iwl_title varchar(255) binary NOT NULL default ''
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/iwl_from ON /*_*/iwlinks (iwl_from, iwl_prefix, iwl_title);
+CREATE UNIQUE INDEX /*i*/iwl_prefix_title_from ON /*_*/iwlinks (iwl_prefix, iwl_title, iwl_from);
+CREATE TABLE /*_*/site_stats (
+ ss_row_id int unsigned NOT NULL,
+ ss_total_views bigint unsigned default 0,
+ ss_total_edits bigint unsigned default 0,
+ ss_good_articles bigint unsigned default 0,
+ ss_total_pages bigint default '-1',
+ ss_users bigint default '-1',
+ ss_active_users bigint default '-1',
+ ss_admins int default '-1',
+ ss_images int default 0
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/ss_row_id ON /*_*/site_stats (ss_row_id);
+CREATE TABLE /*_*/hitcounter (
+ hc_id int unsigned NOT NULL
+) ENGINE=HEAP MAX_ROWS=25000;
+CREATE TABLE /*_*/ipblocks (
+ ipb_id int NOT NULL PRIMARY KEY AUTO_INCREMENT,
+ ipb_address tinyblob NOT NULL,
+ ipb_user int unsigned NOT NULL default 0,
+ ipb_by int unsigned NOT NULL default 0,
+ ipb_by_text varchar(255) binary NOT NULL default '',
+ ipb_reason tinyblob NOT NULL,
+ ipb_timestamp binary(14) NOT NULL default '',
+ ipb_auto bool NOT NULL default 0,
+ ipb_anon_only bool NOT NULL default 0,
+ ipb_create_account bool NOT NULL default 1,
+ ipb_enable_autoblock bool 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 bool NOT NULL default 0,
+ ipb_block_email bool NOT NULL default 0,
+ ipb_allow_usertalk bool NOT NULL default 0
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/ipb_address ON /*_*/ipblocks (ipb_address(255), ipb_user, ipb_auto, ipb_anon_only);
+CREATE INDEX /*i*/ipb_user ON /*_*/ipblocks (ipb_user);
+CREATE INDEX /*i*/ipb_range ON /*_*/ipblocks (ipb_range_start(8), ipb_range_end(8));
+CREATE INDEX /*i*/ipb_timestamp ON /*_*/ipblocks (ipb_timestamp);
+CREATE INDEX /*i*/ipb_expiry ON /*_*/ipblocks (ipb_expiry);
+CREATE TABLE /*_*/image (
+ img_name varchar(255) binary NOT NULL default '' PRIMARY KEY,
+ img_size int unsigned NOT NULL default 0,
+ img_width int NOT NULL default 0,
+ img_height int NOT NULL default 0,
+ img_metadata mediumblob NOT NULL,
+ img_bits int 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 unsigned NOT NULL default 0,
+ img_user_text varchar(255) binary NOT NULL,
+ img_timestamp varbinary(14) NOT NULL default '',
+ img_sha1 varbinary(32) NOT NULL default ''
+) /*$wgDBTableOptions*/;
+CREATE INDEX /*i*/img_usertext_timestamp ON /*_*/image (img_user_text,img_timestamp);
+CREATE INDEX /*i*/img_size ON /*_*/image (img_size);
+CREATE INDEX /*i*/img_timestamp ON /*_*/image (img_timestamp);
+CREATE INDEX /*i*/img_sha1 ON /*_*/image (img_sha1);
+CREATE TABLE /*_*/oldimage (
+ oi_name varchar(255) binary NOT NULL default '',
+ oi_archive_name varchar(255) binary NOT NULL default '',
+ oi_size int unsigned NOT NULL default 0,
+ oi_width int NOT NULL default 0,
+ oi_height int NOT NULL default 0,
+ oi_bits int NOT NULL default 0,
+ oi_description tinyblob NOT NULL,
+ oi_user int unsigned NOT NULL default 0,
+ oi_user_text varchar(255) binary NOT NULL,
+ oi_timestamp binary(14) NOT NULL default '',
+ 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 unsigned NOT NULL default 0,
+ oi_sha1 varbinary(32) NOT NULL default ''
+) /*$wgDBTableOptions*/;
+CREATE INDEX /*i*/oi_usertext_timestamp ON /*_*/oldimage (oi_user_text,oi_timestamp);
+CREATE INDEX /*i*/oi_name_timestamp ON /*_*/oldimage (oi_name,oi_timestamp);
+CREATE INDEX /*i*/oi_name_archive_name ON /*_*/oldimage (oi_name,oi_archive_name(14));
+CREATE INDEX /*i*/oi_sha1 ON /*_*/oldimage (oi_sha1);
+CREATE TABLE /*_*/filearchive (
+ fa_id int NOT NULL PRIMARY KEY AUTO_INCREMENT,
+ fa_name varchar(255) binary NOT NULL default '',
+ fa_archive_name varchar(255) binary default '',
+ fa_storage_group varbinary(16),
+ fa_storage_key varbinary(64) default '',
+ fa_deleted_user int,
+ fa_deleted_timestamp binary(14) default '',
+ fa_deleted_reason text,
+ fa_size int unsigned default 0,
+ fa_width int default 0,
+ fa_height int default 0,
+ fa_metadata mediumblob,
+ fa_bits int 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 unsigned default 0,
+ fa_user_text varchar(255) binary,
+ fa_timestamp binary(14) default '',
+ fa_deleted tinyint unsigned NOT NULL default 0
+) /*$wgDBTableOptions*/;
+CREATE INDEX /*i*/fa_name ON /*_*/filearchive (fa_name, fa_timestamp);
+CREATE INDEX /*i*/fa_storage_group ON /*_*/filearchive (fa_storage_group, fa_storage_key);
+CREATE INDEX /*i*/fa_deleted_timestamp ON /*_*/filearchive (fa_deleted_timestamp);
+CREATE INDEX /*i*/fa_user_timestamp ON /*_*/filearchive (fa_user_text,fa_timestamp);
+CREATE TABLE /*_*/uploadstash (
+ us_id int unsigned NOT NULL PRIMARY KEY auto_increment,
+ us_user int unsigned NOT NULL,
+ us_key varchar(255) NOT NULL,
+ us_orig_path varchar(255) NOT NULL,
+ us_path varchar(255) NOT NULL,
+ us_source_type varchar(50),
+ us_timestamp varbinary(14) not null,
+ us_status varchar(50) not null,
+ 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_image_width int unsigned,
+ us_image_height int unsigned,
+ us_image_bits smallint unsigned
+) /*$wgDBTableOptions*/;
+CREATE INDEX /*i*/us_user ON /*_*/uploadstash (us_user);
+CREATE UNIQUE INDEX /*i*/us_key ON /*_*/uploadstash (us_key);
+CREATE INDEX /*i*/us_timestamp ON /*_*/uploadstash (us_timestamp);
+CREATE TABLE /*_*/recentchanges (
+ rc_id int NOT NULL PRIMARY KEY AUTO_INCREMENT,
+ rc_timestamp varbinary(14) NOT NULL default '',
+ rc_cur_time varbinary(14) NOT NULL default '',
+ rc_user int unsigned NOT NULL default 0,
+ rc_user_text varchar(255) binary NOT NULL,
+ rc_namespace int NOT NULL default 0,
+ rc_title varchar(255) binary NOT NULL default '',
+ rc_comment varchar(255) binary NOT NULL default '',
+ rc_minor tinyint unsigned NOT NULL default 0,
+ rc_bot tinyint unsigned NOT NULL default 0,
+ rc_new tinyint unsigned NOT NULL default 0,
+ rc_cur_id int unsigned NOT NULL default 0,
+ rc_this_oldid int unsigned NOT NULL default 0,
+ rc_last_oldid int unsigned NOT NULL default 0,
+ rc_type tinyint unsigned NOT NULL default 0,
+ rc_moved_to_ns tinyint unsigned NOT NULL default 0,
+ rc_moved_to_title varchar(255) binary NOT NULL default '',
+ rc_patrolled tinyint unsigned NOT NULL default 0,
+ rc_ip varbinary(40) NOT NULL default '',
+ rc_old_len int,
+ rc_new_len int,
+ rc_deleted tinyint unsigned NOT NULL default 0,
+ rc_logid int unsigned NOT NULL default 0,
+ rc_log_type varbinary(255) NULL default NULL,
+ rc_log_action varbinary(255) NULL default NULL,
+ rc_params blob NULL
+) /*$wgDBTableOptions*/;
+CREATE INDEX /*i*/rc_timestamp ON /*_*/recentchanges (rc_timestamp);
+CREATE INDEX /*i*/rc_namespace_title ON /*_*/recentchanges (rc_namespace, rc_title);
+CREATE INDEX /*i*/rc_cur_id ON /*_*/recentchanges (rc_cur_id);
+CREATE INDEX /*i*/new_name_timestamp ON /*_*/recentchanges (rc_new,rc_namespace,rc_timestamp);
+CREATE INDEX /*i*/rc_ip ON /*_*/recentchanges (rc_ip);
+CREATE INDEX /*i*/rc_ns_usertext ON /*_*/recentchanges (rc_namespace, rc_user_text);
+CREATE INDEX /*i*/rc_user_text ON /*_*/recentchanges (rc_user_text, rc_timestamp);
+CREATE TABLE /*_*/watchlist (
+ wl_user int unsigned NOT NULL,
+ wl_namespace int NOT NULL default 0,
+ wl_title varchar(255) binary NOT NULL default '',
+ wl_notificationtimestamp varbinary(14)
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/wl_user ON /*_*/watchlist (wl_user, wl_namespace, wl_title);
+CREATE INDEX /*i*/namespace_title ON /*_*/watchlist (wl_namespace, wl_title);
+CREATE TABLE /*_*/searchindex (
+ si_page int unsigned NOT NULL,
+ si_title varchar(255) NOT NULL default '',
+ si_text mediumtext NOT NULL
+) ENGINE=MyISAM;
+CREATE UNIQUE INDEX /*i*/si_page ON /*_*/searchindex (si_page);
+CREATE FULLTEXT INDEX /*i*/si_title ON /*_*/searchindex (si_title);
+CREATE FULLTEXT INDEX /*i*/si_text ON /*_*/searchindex (si_text);
+CREATE TABLE /*_*/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 bool NOT NULL,
+ iw_trans tinyint NOT NULL default 0
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/iw_prefix ON /*_*/interwiki (iw_prefix);
+CREATE TABLE /*_*/querycache (
+ qc_type varbinary(32) NOT NULL,
+ qc_value int unsigned NOT NULL default 0,
+ qc_namespace int NOT NULL default 0,
+ qc_title varchar(255) binary NOT NULL default ''
+) /*$wgDBTableOptions*/;
+CREATE INDEX /*i*/qc_type ON /*_*/querycache (qc_type,qc_value);
+CREATE TABLE /*_*/objectcache (
+ keyname varbinary(255) NOT NULL default '' PRIMARY KEY,
+ value mediumblob,
+ exptime datetime
+) /*$wgDBTableOptions*/;
+CREATE INDEX /*i*/exptime ON /*_*/objectcache (exptime);
+CREATE TABLE /*_*/transcache (
+ tc_url varbinary(255) NOT NULL,
+ tc_contents text,
+ tc_time binary(14) NOT NULL
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/tc_url_idx ON /*_*/transcache (tc_url);
+CREATE TABLE /*_*/logging (
+ log_id int unsigned NOT NULL PRIMARY KEY 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 unsigned NOT NULL default 0,
+ log_user_text varchar(255) binary NOT NULL default '',
+ log_namespace int NOT NULL default 0,
+ log_title varchar(255) binary NOT NULL default '',
+ log_page int unsigned NULL,
+ log_comment varchar(255) NOT NULL default '',
+ log_params blob NOT NULL,
+ log_deleted tinyint unsigned NOT NULL default 0
+) /*$wgDBTableOptions*/;
+CREATE INDEX /*i*/type_time ON /*_*/logging (log_type, log_timestamp);
+CREATE INDEX /*i*/user_time ON /*_*/logging (log_user, log_timestamp);
+CREATE INDEX /*i*/page_time ON /*_*/logging (log_namespace, log_title, log_timestamp);
+CREATE INDEX /*i*/times ON /*_*/logging (log_timestamp);
+CREATE INDEX /*i*/log_user_type_time ON /*_*/logging (log_user, log_type, log_timestamp);
+CREATE INDEX /*i*/log_page_id_time ON /*_*/logging (log_page,log_timestamp);
+CREATE TABLE /*_*/log_search (
+ ls_field varbinary(32) NOT NULL,
+ ls_value varchar(255) NOT NULL,
+ ls_log_id int unsigned NOT NULL default 0
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/ls_field_val ON /*_*/log_search (ls_field,ls_value,ls_log_id);
+CREATE INDEX /*i*/ls_log_id ON /*_*/log_search (ls_log_id);
+CREATE TABLE /*_*/trackbacks (
+ tb_id int PRIMARY KEY AUTO_INCREMENT,
+ tb_page int REFERENCES /*_*/page(page_id) ON DELETE CASCADE,
+ tb_title varchar(255) NOT NULL,
+ tb_url blob NOT NULL,
+ tb_ex text,
+ tb_name varchar(255)
+) /*$wgDBTableOptions*/;
+CREATE INDEX /*i*/tb_page ON /*_*/trackbacks (tb_page);
+CREATE TABLE /*_*/job (
+ job_id int unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT,
+ job_cmd varbinary(60) NOT NULL default '',
+ job_namespace int NOT NULL,
+ job_title varchar(255) binary NOT NULL,
+ job_params blob NOT NULL
+) /*$wgDBTableOptions*/;
+CREATE INDEX /*i*/job_cmd ON /*_*/job (job_cmd, job_namespace, job_title, job_params(128));
+CREATE TABLE /*_*/querycache_info (
+ qci_type varbinary(32) NOT NULL default '',
+ qci_timestamp binary(14) NOT NULL default '19700101000000'
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/qci_type ON /*_*/querycache_info (qci_type);
+CREATE TABLE /*_*/redirect (
+ rd_from int unsigned NOT NULL default 0 PRIMARY KEY,
+ rd_namespace int NOT NULL default 0,
+ rd_title varchar(255) binary NOT NULL default '',
+ rd_interwiki varchar(32) default NULL,
+ rd_fragment varchar(255) binary default NULL
+) /*$wgDBTableOptions*/;
+CREATE INDEX /*i*/rd_ns_title ON /*_*/redirect (rd_namespace,rd_title,rd_from);
+CREATE TABLE /*_*/querycachetwo (
+ qcc_type varbinary(32) NOT NULL,
+ qcc_value int unsigned NOT NULL default 0,
+ qcc_namespace int NOT NULL default 0,
+ qcc_title varchar(255) binary NOT NULL default '',
+ qcc_namespacetwo int NOT NULL default 0,
+ qcc_titletwo varchar(255) binary NOT NULL default ''
+) /*$wgDBTableOptions*/;
+CREATE INDEX /*i*/qcc_type ON /*_*/querycachetwo (qcc_type,qcc_value);
+CREATE INDEX /*i*/qcc_title ON /*_*/querycachetwo (qcc_type,qcc_namespace,qcc_title);
+CREATE INDEX /*i*/qcc_titletwo ON /*_*/querycachetwo (qcc_type,qcc_namespacetwo,qcc_titletwo);
+CREATE TABLE /*_*/page_restrictions (
+ pr_page int NOT NULL,
+ pr_type varbinary(60) NOT NULL,
+ pr_level varbinary(60) NOT NULL,
+ pr_cascade tinyint NOT NULL,
+ pr_user int NULL,
+ pr_expiry varbinary(14) NULL,
+ pr_id int unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/pr_pagetype ON /*_*/page_restrictions (pr_page,pr_type);
+CREATE INDEX /*i*/pr_typelevel ON /*_*/page_restrictions (pr_type,pr_level);
+CREATE INDEX /*i*/pr_level ON /*_*/page_restrictions (pr_level);
+CREATE INDEX /*i*/pr_cascade ON /*_*/page_restrictions (pr_cascade);
+CREATE TABLE /*_*/protected_titles (
+ pt_namespace int NOT NULL,
+ pt_title varchar(255) binary NOT NULL,
+ pt_user int 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
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/pt_namespace_title ON /*_*/protected_titles (pt_namespace,pt_title);
+CREATE INDEX /*i*/pt_timestamp ON /*_*/protected_titles (pt_timestamp);
+CREATE TABLE /*_*/page_props (
+ pp_page int NOT NULL,
+ pp_propname varbinary(60) NOT NULL,
+ pp_value blob NOT NULL
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/pp_page_propname ON /*_*/page_props (pp_page,pp_propname);
+CREATE TABLE /*_*/updatelog (
+ ul_key varchar(255) NOT NULL PRIMARY KEY,
+ ul_value blob
+) /*$wgDBTableOptions*/;
+CREATE TABLE /*_*/change_tag (
+ ct_rc_id int NULL,
+ ct_log_id int NULL,
+ ct_rev_id int NULL,
+ ct_tag varchar(255) NOT NULL,
+ ct_params blob NULL
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/change_tag_rc_tag ON /*_*/change_tag (ct_rc_id,ct_tag);
+CREATE UNIQUE INDEX /*i*/change_tag_log_tag ON /*_*/change_tag (ct_log_id,ct_tag);
+CREATE UNIQUE INDEX /*i*/change_tag_rev_tag ON /*_*/change_tag (ct_rev_id,ct_tag);
+CREATE INDEX /*i*/change_tag_tag_id ON /*_*/change_tag (ct_tag,ct_rc_id,ct_rev_id,ct_log_id);
+CREATE TABLE /*_*/tag_summary (
+ ts_rc_id int NULL,
+ ts_log_id int NULL,
+ ts_rev_id int NULL,
+ ts_tags blob NOT NULL
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/tag_summary_rc_id ON /*_*/tag_summary (ts_rc_id);
+CREATE UNIQUE INDEX /*i*/tag_summary_log_id ON /*_*/tag_summary (ts_log_id);
+CREATE UNIQUE INDEX /*i*/tag_summary_rev_id ON /*_*/tag_summary (ts_rev_id);
+CREATE TABLE /*_*/valid_tag (
+ vt_tag varchar(255) NOT NULL PRIMARY KEY
+) /*$wgDBTableOptions*/;
+CREATE TABLE /*_*/l10n_cache (
+ lc_lang varbinary(32) NOT NULL,
+ lc_key varchar(255) NOT NULL,
+ lc_value mediumblob NOT NULL
+) /*$wgDBTableOptions*/;
+CREATE INDEX /*i*/lc_lang_key ON /*_*/l10n_cache (lc_lang, lc_key);
+CREATE TABLE /*_*/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
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/mr_resource_lang ON /*_*/msg_resource (mr_resource, mr_lang);
+CREATE TABLE /*_*/msg_resource_links (
+ mrl_resource varbinary(255) NOT NULL,
+ mrl_message varbinary(255) NOT NULL
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/mrl_message_resource ON /*_*/msg_resource_links (mrl_message, mrl_resource);
+CREATE TABLE /*_*/module_deps (
+ md_module varbinary(255) NOT NULL,
+ md_skin varbinary(32) NOT NULL,
+ md_deps mediumblob NOT NULL
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/md_module_skin ON /*_*/module_deps (md_module, md_skin);
+
diff --git a/tests/phpunit/data/media/80x60-2layers.xcf b/tests/phpunit/data/media/80x60-2layers.xcf
new file mode 100644
index 00000000..c51e980c
--- /dev/null
+++ b/tests/phpunit/data/media/80x60-2layers.xcf
Binary files differ
diff --git a/tests/phpunit/data/media/80x60-Greyscale.xcf b/tests/phpunit/data/media/80x60-Greyscale.xcf
new file mode 100644
index 00000000..84bf3e67
--- /dev/null
+++ b/tests/phpunit/data/media/80x60-Greyscale.xcf
Binary files differ
diff --git a/tests/phpunit/data/media/80x60-RGB.xcf b/tests/phpunit/data/media/80x60-RGB.xcf
new file mode 100644
index 00000000..1d58f16d
--- /dev/null
+++ b/tests/phpunit/data/media/80x60-RGB.xcf
Binary files differ
diff --git a/tests/phpunit/data/media/Toll_Texas_1.svg b/tests/phpunit/data/media/Toll_Texas_1.svg
new file mode 100644
index 00000000..73004e3e
--- /dev/null
+++ b/tests/phpunit/data/media/Toll_Texas_1.svg
@@ -0,0 +1,150 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 12.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 51448) -->
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [
+ <!ENTITY ns_svg "http://www.w3.org/2000/svg">
+ <!ENTITY ns_xlink "http://www.w3.org/1999/xlink">
+]>
+<svg version="1.1" id="Layer_1" xmlns="&ns_svg;" xmlns:xlink="&ns_xlink;" width="385" height="385.0004883"
+ viewBox="0 0 385 385.0004883" overflow="visible" enable-background="new 0 0 385 385.0004883" xml:space="preserve">
+<g>
+ <g>
+ <g>
+ <path fill="#FFFFFF" d="M0.5,24.5c0-13.2548828,10.7451172-24,24-24h336c13.2548828,0,24,10.7451172,24,24v336.0004883
+ c0,13.2548828-10.7451172,24-24,24h-336c-13.2548828,0-24-10.7451172-24-24V24.5L0.5,24.5z"/>
+ <path fill="#FFFFFF" d="M192.5,192.5004883"/>
+ </g>
+ <g>
+ <path fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="3.863693" d="M0.5,24.5
+ c0-13.2548828,10.7451172-24,24-24h336c13.2548828,0,24,10.7451172,24,24v336.0004883c0,13.2548828-10.7451172,24-24,24h-336
+ c-13.2548828,0-24-10.7451172-24-24V24.5L0.5,24.5z"/>
+ <path fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="3.863693" d="
+ M192.5,192.5004883"/>
+ </g>
+ </g>
+ <g>
+ <path fill="#003882" d="M24.5,0.5h336c13.2548828,0,24,10.7451172,24,24v232.0004883H0.5V24.5
+ C0.5,11.2451172,11.2451172,0.5,24.5,0.5z"/>
+ </g>
+ <g>
+ <path fill="#FFFFFF" d="M10.5,24.5c0-7.7319336,6.2680664-14,14-14h336c7.7324219,0,14,6.2680664,14,14v222.0004883h-364V24.5z"/>
+ </g>
+ <g>
+ <polygon fill-rule="evenodd" clip-rule="evenodd" fill="#003882" points="93.809082,348.2397461 91.6787109,347.8666992
+ 89.5478516,347.7368164 85.2929688,347.7368164 83.1640625,347.8666992 78.9042969,348.6157227 76.7763672,349.1166992
+ 72.7666016,350.3706055 70.7631836,351.246582 68.8837891,352.121582 67.0053711,353.1254883 65.1254883,354.253418
+ 63.3740234,355.5063477 60.1210938,358.2602539 58.4926758,359.762207 57.1132813,361.2641602 55.7338867,362.8959961
+ 54.3603516,364.6459961 21.2949219,301.3999023 21.168457,301.3999023 22.5478516,299.6469727 23.9248047,298.0200195
+ 25.3022461,296.5180664 26.9296875,295.0141602 30.1875,292.2592773 31.9404297,291.0073242 33.8188477,289.8793945
+ 35.6972656,288.8774414 37.5761719,288.0004883 39.5791016,287.1245117 43.5878906,285.8706055 45.7148438,285.3706055
+ 49.9755859,284.6176758 52.1020508,284.4946289 56.3632813,284.4946289 58.4926758,284.6176758 60.6201172,284.9946289
+ 60.6201172,284.8696289 "/>
+ </g>
+ <g>
+ <polygon fill-rule="evenodd" clip-rule="evenodd" fill="#FFFFFF" points="32.8154297,319.559082 45.0898438,312.4233398
+ 42.2080078,298.5209961 52.7299805,308.0375977 65.1254883,300.9008789 59.2421875,313.9243164 69.8867188,323.4428711
+ 55.7338867,321.9389648 49.8476563,334.965332 46.9677734,321.0629883 "/>
+ </g>
+ <g>
+ <polygon fill-rule="evenodd" clip-rule="evenodd" fill="#B01C2E" points="132.0053711,306.6606445 148.5385742,338.2211914
+ 147.4101563,339.7241211 146.1577148,341.2270508 144.9052734,342.6020508 143.5302734,343.9848633 142.1494141,345.2358398
+ 140.6484375,346.4868164 139.2705078,347.6157227 137.765625,348.6157227 136.1381836,349.6176758 134.6357422,350.621582
+ 133.0083008,351.3696289 131.3798828,352.246582 129.625,352.8745117 128,353.5024414 126.2441406,354.0004883
+ 124.4921875,354.5043945 122.737793,354.8774414 120.9853516,355.1293945 117.4785156,355.3793945 113.9707031,355.3793945
+ 112.09375,355.2543945 110.3408203,355.003418 108.5854492,354.6274414 106.9589844,354.253418 103.4501953,353.2485352
+ 101.8232422,352.6254883 100.0688477,351.871582 98.4428711,351.1235352 96.9389648,350.1176758 95.3095703,349.2426758
+ 93.809082,348.2397461 93.809082,348.1147461 93.809082,348.2397461 77.1523438,316.4282227 77.2753906,316.4282227
+ 77.2753906,316.5551758 78.78125,317.5581055 80.4057617,318.4350586 81.9116211,319.4360352 83.5395508,320.1860352
+ 85.2929688,320.9399414 86.9208984,321.5649414 90.4257813,322.5668945 92.0551758,322.9418945 93.809082,323.3188477
+ 95.5620117,323.5688477 97.4423828,323.6948242 100.9462891,323.6948242 102.6992188,323.5688477 104.4521484,323.4428711
+ 106.2060547,323.1928711 107.9609375,322.8168945 111.4682617,321.8168945 113.0947266,321.1889648 114.8481445,320.5639648
+ 116.4765625,319.6879883 118.1035156,318.9350586 119.6083984,317.934082 121.2363281,316.9301758 122.737793,315.9282227
+ 124.1152344,314.8012695 125.6196289,313.5483398 126.9960938,312.2983398 128.3759766,310.9194336 129.625,309.5424805
+ 130.8789063,308.0375977 132.0053711,306.5366211 132.1328125,306.5366211 "/>
+ </g>
+ <g>
+
+ <polyline fill="none" stroke="#003882" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="3.863693" points="
+ 60.7441406,285.1196289 63,286.4956055 65.2529297,287.7485352 67.5068359,288.8774414 69.8867188,289.8793945
+ 72.3916016,290.6313477 74.8955078,291.3813477 77.4008789,291.7583008 80.0302734,292.1333008 82.5366211,292.2592773
+ 85.1655273,292.2592773 87.6708984,292.0083008 90.3022461,291.6313477 92.8066406,291.1313477 95.3095703,290.3793945
+ 97.6904297,289.5024414 100.0688477,288.5004883 102.3256836,287.2504883 104.578125,285.8706055 106.7070313,284.4946289
+ 108.7124023,282.8637695 110.5888672,281.1108398 112.3427734,279.2329102 114.0986328,277.2290039 115.5976563,275.1010742 "/>
+ </g>
+ <g>
+
+ <line fill="none" stroke="#003882" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="3.863693" x1="115.4746094" y1="275.1010742" x2="131.8793945" y2="306.2836914"/>
+ </g>
+ <g>
+ <polygon fill-rule="evenodd" clip-rule="evenodd" fill="#003882" points="215.1650391,349.1166992 213.1601563,348.2397461
+ 211.2832031,347.237793 207.7773438,344.7329102 206.1503906,343.2319336 204.6435547,341.7270508 203.2685547,340.0991211
+ 202.0166016,338.347168 200.8867188,336.5942383 199.8857422,334.590332 199.0097656,332.7114258 198.2578125,330.7075195
+ 197.6328125,328.5776367 197.1289063,326.5756836 196.7548828,324.4458008 196.6318359,322.3168945 196.6318359,320.0629883
+ 196.7548828,317.934082 197.0058594,315.8041992 197.3818359,313.6743164 198.6357422,309.6665039 199.5107422,307.6635742
+ 200.5126953,305.7827148 201.6386719,303.9067383 202.890625,302.152832 204.2685547,300.3989258 205.6464844,298.8969727
+ 207.2744141,297.3920898 208.9023438,296.0161133 210.6572266,294.887207 212.5361328,293.7602539 214.4130859,292.7602539
+ 216.4179688,291.8833008 218.5449219,291.0073242 220.6767578,290.2543945 222.9306641,289.6274414 227.4384766,288.8774414
+ 229.6933594,288.6254883 231.9482422,288.5004883 234.2011719,288.5004883 236.4541016,288.6254883 238.7119141,288.8774414
+ 240.9638672,289.253418 243.21875,289.7543945 245.3476563,290.3793945 247.6025391,291.1313477 249.6054688,292.0083008
+ 251.7363281,293.0083008 251.7363281,292.8852539 253.6132813,294.137207 255.4931641,295.5151367 257.2460938,297.0161133
+ 258.8740234,298.6459961 260.5019531,300.3989258 261.8808594,302.277832 263.1328125,304.1577148 264.2578125,306.2836914
+ 266.0117188,310.543457 266.6386719,312.7983398 267.390625,317.3051758 267.5136719,319.6879883 267.390625,321.9418945
+ 267.265625,324.3188477 266.2626953,328.8286133 265.5117188,330.9575195 264.6347656,333.2114258 263.6318359,335.2163086
+ 262.5058594,337.2192383 261.1298828,339.0981445 259.625,340.9770508 258.1240234,342.6020508 256.3701172,344.1079102
+ 254.4902344,345.6098633 252.6142578,346.8618164 250.609375,347.9926758 248.4785156,348.9926758 246.3496094,349.8696289
+ 244.2216797,350.621582 242.0927734,351.246582 239.8359375,351.7485352 237.5830078,352.121582 235.3291016,352.3754883
+ 232.9501953,352.4985352 230.6933594,352.4985352 228.4414063,352.3754883 226.1865234,352.121582 223.9296875,351.7485352
+ 221.6777344,351.246582 219.421875,350.746582 217.2949219,349.9946289 "/>
+ </g>
+ <g>
+ <polygon fill-rule="evenodd" clip-rule="evenodd" fill="#FFFFFF" points="219.9238281,303.6547852 221.9287109,301.5258789
+ 224.4335938,299.8999023 227.1904297,298.8969727 230.1943359,298.2700195 233.0742188,298.3959961 235.9550781,299.0219727
+ 238.7119141,300.2739258 241.0898438,301.9018555 243.0957031,304.1577148 244.5957031,306.5366211 245.9746094,308.9145508
+ 246.9765625,311.4204102 247.8535156,314.0512695 248.4785156,316.8051758 248.8535156,319.559082 248.9804688,322.3168945
+ 248.9804688,325.0727539 248.6035156,327.8256836 248.1035156,330.5805664 247.3496094,333.2114258 246.3496094,335.7172852
+ 244.9726563,337.8442383 243.34375,339.6000977 241.3408203,341.1020508 239.2109375,342.355957 236.8330078,343.2319336
+ 234.4511719,343.6049805 231.9482422,343.7319336 229.4414063,343.3579102 227.1904297,342.6020508 224.9326172,341.4770508
+ 222.9306641,340.0991211 221.1757813,338.347168 219.6748047,336.3422852 218.5449219,334.2114258 217.7949219,331.8334961
+ 217.0449219,329.7036133 216.0419922,325.4477539 215.7910156,323.1928711 215.6660156,320.9399414 215.6660156,318.684082
+ 215.9169922,316.4282227 216.1669922,314.300293 216.6679688,312.0454102 217.2949219,309.918457 218.0458984,307.7895508
+ 218.9208984,305.7827148 219.9238281,303.7817383 "/>
+ </g>
+ <g>
+ <polygon fill-rule="evenodd" clip-rule="evenodd" fill="#003882" points="150.4169922,290.0063477 196.3789063,289.7543945
+ 192.7470703,304.4067383 192.6220703,304.5307617 192.1210938,303.7817383 191.4960938,303.1567383 190.8681641,302.5288086
+ 190.1162109,302.027832 189.2421875,301.652832 188.4887695,301.2768555 187.6113281,301.0258789 186.7353516,300.7758789
+ 179.9741211,300.7758789 179.8481445,345.1098633 179.8481445,345.2358398 180.0966797,346.1118164 180.3486328,347.1157227
+ 180.7246094,347.9926758 181.1005859,348.7407227 181.6015625,349.6176758 182.8549805,351.1235352 183.6054688,351.7485352
+ 158.5556641,351.7485352 158.5556641,351.871582 159.3095703,351.246582 160.0595703,350.4956055 160.6845703,349.7426758
+ 161.1875,348.9926758 161.6879883,347.9926758 161.9384766,347.1157227 162.1894531,346.1118164 162.3144531,345.1098633
+ 162.4375,300.9008789 156.5527344,300.9008789 155.1767578,301.0258789 153.9248047,301.2768555 152.671875,301.5258789
+ 151.4204102,301.9018555 150.1660156,302.4008789 148.9135742,303.0297852 146.6601563,304.2797852 146.6601563,304.4067383 "/>
+ </g>
+ <g>
+ <polygon fill-rule="evenodd" clip-rule="evenodd" fill="#003882" points="275.7822266,344.3598633 276.03125,298.1450195
+ 275.90625,297.769043 275.90625,297.894043 275.7822266,296.7661133 275.5302734,295.7670898 275.15625,294.6391602
+ 274.7792969,293.637207 272.8984375,291.0073242 272.0234375,290.2543945 272.1484375,290.2543945 297.4492188,290.1313477
+ 296.4453125,290.8803711 295.5683594,291.6313477 294.6904297,292.5083008 294.0673828,293.5102539 293.5644531,294.6391602
+ 293.1904297,295.7670898 293.0644531,297.0161133 293.0644531,298.2700195 293.0644531,298.1450195 293.1904297,298.1450195
+ 293.0644531,340.7260742 292.9394531,340.7260742 294.8183594,341.3540039 296.8222656,341.8540039 298.7011719,342.1040039
+ 300.7050781,342.355957 302.7070313,342.4799805 304.5878906,342.355957 306.5917969,342.2299805 308.46875,341.8540039
+ 311.4746094,340.7260742 312.4765625,340.2231445 313.3535156,339.7241211 314.3535156,339.2241211 316.109375,337.972168
+ 311.8505859,351.621582 271.3984375,351.7485352 272.3994141,351.1235352 273.2753906,350.3706055 274.0292969,349.6176758
+ 275.2802734,347.6157227 275.53125,346.4868164 275.7822266,345.4858398 275.90625,344.2348633 "/>
+ </g>
+ <g>
+ <polygon fill-rule="evenodd" clip-rule="evenodd" fill="#003882" points="327.5058594,344.3598633 327.7539063,298.1450195
+ 327.6308594,297.769043 327.6308594,297.894043 327.5058594,296.7661133 327.2539063,295.7670898 326.8769531,294.6391602
+ 326.5029297,293.637207 324.6259766,291.0073242 323.7480469,290.2543945 323.8740234,290.2543945 349.171875,290.1313477
+ 348.1708984,290.8803711 347.2929688,291.6313477 346.4179688,292.5083008 345.7890625,293.5102539 345.2871094,294.6391602
+ 344.9121094,295.7670898 344.7890625,297.0161133 344.7890625,298.2700195 344.7890625,298.1450195 344.9121094,298.1450195
+ 344.7890625,340.7260742 344.6640625,340.7260742 346.5410156,341.3540039 348.5458984,341.8540039 350.4238281,342.1040039
+ 352.4277344,342.355957 354.4316406,342.4799805 356.3105469,342.355957 358.3144531,342.2299805 360.1933594,341.8540039
+ 361.1933594,341.4770508 363.1992188,340.7260742 364.2011719,340.2231445 365.078125,339.7241211 366.0820313,339.2241211
+ 367.8320313,337.972168 363.5751953,351.621582 323.1220703,351.7485352 324.125,351.1235352 325.0019531,350.3706055
+ 325.7519531,349.6176758 327.0058594,347.6157227 327.2539063,346.4868164 327.5058594,345.4858398 327.6308594,344.2348633 "/>
+ </g>
+</g>
+<path fill-rule="evenodd" clip-rule="evenodd" fill="#003882" d="M188.4228516,211.0395508V89.7011719h-23.2304688V66.4711914
+ c7.4140625-0.3291016,13.8393555-2.3886719,19.2763672-6.1782227c5.4365234-3.7890625,9.3085938-8.7314453,11.6152344-14.8276367
+ h23.7236328v165.5742188H188.4228516z"/>
+</svg>
diff --git a/tests/phpunit/data/media/iptc-invalid-psir.jpg b/tests/phpunit/data/media/iptc-invalid-psir.jpg
new file mode 100644
index 00000000..01b9acf3
--- /dev/null
+++ b/tests/phpunit/data/media/iptc-invalid-psir.jpg
Binary files differ
diff --git a/tests/phpunit/includes/ArticleTablesTest.php b/tests/phpunit/includes/ArticleTablesTest.php
index 01776c95..02571b55 100644
--- a/tests/phpunit/includes/ArticleTablesTest.php
+++ b/tests/phpunit/includes/ArticleTablesTest.php
@@ -6,29 +6,28 @@
class ArticleTablesTest extends MediaWikiLangTestCase {
function testbug14404() {
- global $wgUser, $wgContLang, $wgLanguageCode, $wgLang;
-
- $title = Title::newFromText("Bug 14404");
- $article = new Article( $title );
- $wgUser = new User();
- $wgUser->mRights = array( 'createpage', 'edit', 'purge' );
+ global $wgContLang, $wgLanguageCode, $wgLang;
+
+ $title = Title::newFromText( 'Bug 14404' );
+ $page = WikiPage::factory( $title );
+ $user = new User();
+ $user->mRights = array( 'createpage', 'edit', 'purge' );
$wgLanguageCode = 'es';
$wgContLang = Language::factory( 'es' );
-
+
$wgLang = Language::factory( 'fr' );
- $status = $article->doEdit( '{{:{{int:history}}}}', 'Test code for bug 14404', 0 );
- $templates1 = $article->getUsedTemplates();
+ $status = $page->doEdit( '{{:{{int:history}}}}', 'Test code for bug 14404', 0, false, $user );
+ $templates1 = $page->getUsedTemplates();
$wgLang = Language::factory( 'de' );
- $article->mParserOptions = null; // Let it pick the new user language
- $article->mPreparedEdit = false; // In order to force the rerendering of the same wikitext
-
+ $page->mPreparedEdit = false; // In order to force the rerendering of the same wikitext
+
// We need an edit, a purge is not enough to regenerate the tables
- $status = $article->doEdit( '{{:{{int:history}}}}', 'Test code for bug 14404', EDIT_UPDATE );
- $templates2 = $article->getUsedTemplates();
-
+ $status = $page->doEdit( '{{:{{int:history}}}}', 'Test code for bug 14404', EDIT_UPDATE, false, $user );
+ $templates2 = $page->getUsedTemplates();
+
$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 285efee9..846d2b86 100644
--- a/tests/phpunit/includes/ArticleTest.php
+++ b/tests/phpunit/includes/ArticleTest.php
@@ -19,25 +19,25 @@ class ArticleTest extends MediaWikiTestCase {
}
- function testImplementsGetMagic() {
- $this->assertEquals( -1, $this->article->mCounter, "Article __get magic" );
+ function testImplementsGetMagic() {
+ $this->assertEquals( false, $this->article->mLatest, "Article __get magic" );
}
/**
* @depends testImplementsGetMagic
*/
function testImplementsSetMagic() {
-
- $this->article->mCounter = 2;
- $this->assertEquals( 2, $this->article->mCounter, "Article __set magic" );
+ $this->article->mLatest = 2;
+ $this->assertEquals( 2, $this->article->mLatest, "Article __set magic" );
}
/**
* @depends testImplementsSetMagic
*/
function testImplementsCallMagic() {
- $this->article->mCounter = 33;
- $this->assertEquals( 33, $this->article->getCount(), "Article __call magic" );
+ $this->article->mLatest = 33;
+ $this->article->mDataLoaded = true;
+ $this->assertEquals( 33, $this->article->getLatest(), "Article __call magic" );
}
function testGetOrSetOnNewProperty() {
diff --git a/tests/phpunit/includes/BlockTest.php b/tests/phpunit/includes/BlockTest.php
index 2f224ba8..749f40b4 100644
--- a/tests/phpunit/includes/BlockTest.php
+++ b/tests/phpunit/includes/BlockTest.php
@@ -2,11 +2,10 @@
/**
* @group Database
+ * @group Blocking
*/
class BlockTest extends MediaWikiLangTestCase {
- const REASON = "Some reason";
-
private $block, $madeAt;
/* variable used to save up the blockID we insert in this test suite */
@@ -36,8 +35,8 @@ class BlockTest extends MediaWikiLangTestCase {
$oldBlock->delete();
}
- $this->block = new Block( 'UTBlockee', 1, 0,
- self::REASON
+ $this->block = new Block( 'UTBlockee', $user->getID(), 0,
+ 'Parce que', 0, false, time() + 100500
);
$this->madeAt = wfTimestamp( TS_MW );
@@ -68,7 +67,7 @@ class BlockTest extends MediaWikiLangTestCase {
// $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");
}
@@ -77,8 +76,9 @@ class BlockTest extends MediaWikiLangTestCase {
* per bug 26425
*/
function testBug26425BlockTimestampDefaultsToTime() {
-
- $this->assertEquals( $this->madeAt, $this->block->mTimestamp, "If no timestamp is specified, the block is recorded as time()");
+ // 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()");
}
@@ -91,6 +91,8 @@ class BlockTest extends MediaWikiLangTestCase {
* @dataProvider dataBug29116
*/
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' );
@@ -121,4 +123,3 @@ class BlockTest extends MediaWikiLangTestCase {
);
}
}
-
diff --git a/tests/phpunit/includes/EditPageTest.php b/tests/phpunit/includes/EditPageTest.php
new file mode 100644
index 00000000..e98e9707
--- /dev/null
+++ b/tests/phpunit/includes/EditPageTest.php
@@ -0,0 +1,33 @@
+<?php
+
+class EditPageTest extends MediaWikiTestCase {
+
+ /**
+ * @dataProvider dataExtractSectionTitle
+ */
+ function testExtractSectionTitle( $section, $title ) {
+ $extracted = EditPage::extractSectionTitle( $section );
+ $this->assertEquals( $title, $extracted );
+ }
+
+ function dataExtractSectionTitle() {
+ return array(
+ array(
+ "== Test ==\n\nJust a test section.",
+ "Test"
+ ),
+ array(
+ "An initial section, no header.",
+ false
+ ),
+ array(
+ "An initial section with a fake heder (bug 32617)\n\n== Test == ??\nwtf",
+ false
+ ),
+ array(
+ "== Section ==\nfollowed by a fake == Non-section == ??\nnoooo",
+ "Section"
+ )
+ );
+ }
+}
diff --git a/tests/phpunit/includes/ExtraParserTest.php b/tests/phpunit/includes/ExtraParserTest.php
index 5b0aa98b..a9088cb2 100644
--- a/tests/phpunit/includes/ExtraParserTest.php
+++ b/tests/phpunit/includes/ExtraParserTest.php
@@ -10,11 +10,13 @@ class ExtraParserTest extends MediaWikiTestCase {
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;
$this->options->setTemplateCallback( array( __CLASS__, 'statelessFetchTemplate' ) );
@@ -61,20 +63,48 @@ class ExtraParserTest extends MediaWikiTestCase {
* cleanSig() makes all templates substs and removes tildes
*/
function testCleanSig() {
+ global $wgCleanSignatures;
+ $oldCleanSignature = $wgCleanSignatures;
+ $wgCleanSignatures = true;
+
$title = Title::newFromText( __FUNCTION__ );
$outputText = $this->parser->cleanSig( "{{Foo}} ~~~~" );
+
+ $wgCleanSignatures = $oldCleanSignature;
$this->assertEquals( "{{SUBST:Foo}} ", $outputText );
}
-
+
/**
- * cleanSigInSig() just removes tildes
+ * cleanSig() should do nothing if disabled
*/
- function testCleanSigInSig() {
+ function testCleanSigDisabled() {
+ global $wgCleanSignatures;
+ $oldCleanSignature = $wgCleanSignatures;
+ $wgCleanSignatures = false;
+
$title = Title::newFromText( __FUNCTION__ );
- $outputText = $this->parser->cleanSigInSig( "{{Foo}} ~~~~" );
+ $outputText = $this->parser->cleanSig( "{{Foo}} ~~~~" );
+
+ $wgCleanSignatures = $oldCleanSignature;
- $this->assertEquals( "{{Foo}} ", $outputText );
+ $this->assertEquals( "{{Foo}} ~~~~", $outputText );
+ }
+
+ /**
+ * cleanSigInSig() just removes tildes
+ * @dataProvider provideStringsForCleanSigInSig
+ */
+ function testCleanSigInSig( $in, $out ) {
+ $this->assertEquals( Parser::cleanSigInSig( $in), $out );
+ }
+
+ function provideStringsForCleanSigInSig() {
+ return array(
+ array( "{{Foo}} ~~~~", "{{Foo}} " ),
+ array( "~~~", "" ),
+ array( "~~~~~", "" ),
+ );
}
function testGetSection() {
@@ -110,4 +140,28 @@ class ExtraParserTest extends MediaWikiTestCase {
'finalTitle' => $title,
'deps' => $deps );
}
+
+ /**
+ * @group Database
+ */
+ function testTrackingCategory() {
+ $title = Title::newFromText( __FUNCTION__ );
+ $catName = wfMsgForContent( 'broken-file-category' );
+ $cat = Title::makeTitleSafe( NS_CATEGORY, $catName );
+ $expected = array( $cat->getDBkey() );
+ $parserOutput = $this->parser->parse( "[[file:nonexistent]]" , $title, $this->options );
+ $result = $parserOutput->getCategoryLinks();
+ $this->assertEquals( $expected, $result );
+ }
+
+ /**
+ * @group Database
+ */
+ function testTrackingCategorySpecial() {
+ // Special pages shouldn't have tracking cats.
+ $title = SpecialPage::getTitleFor( 'Contributions' );
+ $parserOutput = $this->parser->parse( "[[file:nonexistent]]" , $title, $this->options );
+ $result = $parserOutput->getCategoryLinks();
+ $this->assertEmpty( $result );
+ }
}
diff --git a/tests/phpunit/includes/FormOptionsInitializationTest.php b/tests/phpunit/includes/FormOptionsInitializationTest.php
index 85d76271..d86c95d7 100644
--- a/tests/phpunit/includes/FormOptionsInitializationTest.php
+++ b/tests/phpunit/includes/FormOptionsInitializationTest.php
@@ -25,9 +25,9 @@ class FormOptionsExposed extends FormOptions {
*
* Generated by PHPUnit on 2011-02-28 at 20:46:27.
*
- * Copyright © 2011, Ashar Voultoiz
+ * Copyright © 2011, Antoine Musso
*
- * @author Ashar Voultoiz
+ * @author Antoine Musso
*/
class FormOptionsInitializationTest extends MediaWikiTestCase {
/**
diff --git a/tests/phpunit/includes/FormOptionsTest.php b/tests/phpunit/includes/FormOptionsTest.php
index 86618d93..749343ec 100644
--- a/tests/phpunit/includes/FormOptionsTest.php
+++ b/tests/phpunit/includes/FormOptionsTest.php
@@ -12,9 +12,9 @@
* Test class for FormOptions methods.
* Generated by PHPUnit on 2011-02-28 at 20:46:27.
*
- * Copyright © 2011, Ashar Voultoiz
+ * Copyright © 2011, Antoine Musso
*
- * @author Ashar Voultoiz
+ * @author Antoine Musso
*/
class FormOptionsTest extends MediaWikiTestCase {
/**
diff --git a/tests/phpunit/includes/GlobalFunctions/GlobalTest.php b/tests/phpunit/includes/GlobalFunctions/GlobalTest.php
index 3d157d0a..3cb42f12 100644
--- a/tests/phpunit/includes/GlobalFunctions/GlobalTest.php
+++ b/tests/phpunit/includes/GlobalFunctions/GlobalTest.php
@@ -94,24 +94,80 @@ class GlobalTest extends MediaWikiTestCase {
$this->assertTrue( $end > $start, "Time is running backwards!" );
}
- function testArrayToCGI() {
+ function dataArrayToCGI() {
+ return array(
+ array( array(), '' ), // empty
+ array( array( 'foo' => 'bar' ), 'foo=bar' ), // string test
+ array( array( 'foo' => '' ), 'foo=' ), // empty string test
+ array( array( 'foo' => 1 ), 'foo=1' ), // number test
+ array( array( 'foo' => true ), 'foo=1' ), // true test
+ array( array( 'foo' => false ), '' ), // false test
+ array( array( 'foo' => null ), '' ), // null test
+ array( array( 'foo' => 'A&B=5+6@!"\'' ), 'foo=A%26B%3D5%2B6%40%21%22%27' ), // urlencoding test
+ array( array( 'foo' => 'bar', 'baz' => 'is', 'asdf' => 'qwerty' ), 'foo=bar&baz=is&asdf=qwerty' ), // multi-item test
+ array( array( 'foo' => array( 'bar' => 'baz' ) ), 'foo%5Bbar%5D=baz' ),
+ array( array( 'foo' => array( 'bar' => 'baz', 'qwerty' => 'asdf' ) ), 'foo%5Bbar%5D=baz&foo%5Bqwerty%5D=asdf' ),
+ array( array( 'foo' => array( 'bar', 'baz' ) ), 'foo%5B0%5D=bar&foo%5B1%5D=baz' ),
+ array( array( 'foo' => array( 'bar' => array( 'bar' => 'baz' ) ) ), 'foo%5Bbar%5D%5Bbar%5D=baz' ),
+ );
+ }
+
+ /**
+ * @dataProvider dataArrayToCGI
+ */
+ function testArrayToCGI( $array, $result ) {
+ $this->assertEquals( $result, wfArrayToCGI( $array ) );
+ }
+
+
+ function testArrayToCGI2() {
$this->assertEquals(
- "baz=AT%26T&foo=bar",
+ "baz=bar&foo=bar",
wfArrayToCGI(
- array( 'baz' => 'AT&T', 'ignore' => '' ),
+ array( 'baz' => 'bar' ),
array( 'foo' => 'bar', 'baz' => 'overridden value' ) ) );
- $this->assertEquals(
- "path%5B0%5D=wiki&path%5B1%5D=test&cfg%5Bservers%5D%5Bhttp%5D=localhost",
- wfArrayToCGI( array(
- 'path' => array( 'wiki', 'test' ),
- 'cfg' => array( 'servers' => array( 'http' => 'localhost' ) ) ) ) );
}
- function testCgiToArray() {
- $this->assertEquals(
- array( 'path' => array( 'wiki', 'test' ),
- 'cfg' => array( 'servers' => array( 'http' => 'localhost' ) ) ),
- wfCgiToArray( 'path%5B0%5D=wiki&path%5B1%5D=test&cfg%5Bservers%5D%5Bhttp%5D=localhost' ) );
+ function dataCgiToArray() {
+ return array(
+ array( '', array() ), // empty
+ array( 'foo=bar', array( 'foo' => 'bar' ) ), // string
+ array( 'foo=', array( 'foo' => '' ) ), // empty string
+ array( 'foo', array( 'foo' => '' ) ), // missing =
+ array( 'foo=bar&qwerty=asdf', array( 'foo' => 'bar', 'qwerty' => 'asdf' ) ), // multiple value
+ array( 'foo=A%26B%3D5%2B6%40%21%22%27', array( 'foo' => 'A&B=5+6@!"\'' ) ), // urldecoding test
+ array( 'foo%5Bbar%5D=baz', array( 'foo' => array( 'bar' => 'baz' ) ) ),
+ array( 'foo%5Bbar%5D=baz&foo%5Bqwerty%5D=asdf', array( 'foo' => array( 'bar' => 'baz', 'qwerty' => 'asdf' ) ) ),
+ array( 'foo%5B0%5D=bar&foo%5B1%5D=baz', array( 'foo' => array( 0 => 'bar', 1 => 'baz' ) ) ),
+ array( 'foo%5Bbar%5D%5Bbar%5D=baz', array( 'foo' => array( 'bar' => array( 'bar' => 'baz' ) ) ) ),
+ );
+ }
+
+ /**
+ * @dataProvider dataCgiToArray
+ */
+ function testCgiToArray( $cgi, $result ) {
+ $this->assertEquals( $result, wfCgiToArray( $cgi ) );
+ }
+
+ function dataCgiRoundTrip() {
+ return array(
+ array( '' ),
+ array( 'foo=bar' ),
+ array( 'foo=' ),
+ array( 'foo=bar&baz=biz' ),
+ array( 'foo=A%26B%3D5%2B6%40%21%22%27' ),
+ array( 'foo%5Bbar%5D=baz' ),
+ array( 'foo%5B0%5D=bar&foo%5B1%5D=baz' ),
+ array( 'foo%5Bbar%5D%5Bbar%5D=baz' ),
+ );
+ }
+
+ /**
+ * @dataProvider dataCgiRoundTrip
+ */
+ function testCgiRoundTrip( $cgi ) {
+ $this->assertEquals( $cgi, wfArrayToCGI( wfCgiToArray( $cgi ) ) );
}
function testMimeTypeMatch() {
@@ -176,263 +232,6 @@ class GlobalTest extends MediaWikiTestCase {
array( 'text/*' => 1.0 ),
array( 'application/xhtml+xml' => 1.0 ) ) );
}
-
- function testTimestamp() {
- $t = gmmktime( 12, 34, 56, 1, 15, 2001 );
- $this->assertEquals(
- '20010115123456',
- wfTimestamp( TS_MW, $t ),
- 'TS_UNIX to TS_MW' );
- $this->assertEquals(
- '19690115123456',
- wfTimestamp( TS_MW, -30281104 ),
- 'Negative TS_UNIX to TS_MW' );
- $this->assertEquals(
- 979562096,
- wfTimestamp( TS_UNIX, $t ),
- 'TS_UNIX to TS_UNIX' );
- $this->assertEquals(
- '2001-01-15 12:34:56',
- wfTimestamp( TS_DB, $t ),
- 'TS_UNIX to TS_DB' );
- $this->assertEquals(
- '20010115T123456Z',
- wfTimestamp( TS_ISO_8601_BASIC, $t ),
- 'TS_ISO_8601_BASIC to TS_DB' );
-
- $this->assertEquals(
- '20010115123456',
- wfTimestamp( TS_MW, '20010115123456' ),
- 'TS_MW to TS_MW' );
- $this->assertEquals(
- 979562096,
- wfTimestamp( TS_UNIX, '20010115123456' ),
- 'TS_MW to TS_UNIX' );
- $this->assertEquals(
- '2001-01-15 12:34:56',
- wfTimestamp( TS_DB, '20010115123456' ),
- 'TS_MW to TS_DB' );
- $this->assertEquals(
- '20010115T123456Z',
- wfTimestamp( TS_ISO_8601_BASIC, '20010115123456' ),
- 'TS_MW to TS_ISO_8601_BASIC' );
-
- $this->assertEquals(
- '20010115123456',
- wfTimestamp( TS_MW, '2001-01-15 12:34:56' ),
- 'TS_DB to TS_MW' );
- $this->assertEquals(
- 979562096,
- wfTimestamp( TS_UNIX, '2001-01-15 12:34:56' ),
- 'TS_DB to TS_UNIX' );
- $this->assertEquals(
- '2001-01-15 12:34:56',
- wfTimestamp( TS_DB, '2001-01-15 12:34:56' ),
- 'TS_DB to TS_DB' );
- $this->assertEquals(
- '20010115T123456Z',
- wfTimestamp( TS_ISO_8601_BASIC, '2001-01-15 12:34:56' ),
- 'TS_DB to TS_ISO_8601_BASIC' );
-
- # rfc2822 section 3.3
-
- $this->assertEquals(
- 'Mon, 15 Jan 2001 12:34:56 GMT',
- wfTimestamp( TS_RFC2822, '20010115123456' ),
- 'TS_MW to TS_RFC2822' );
-
- $this->assertEquals(
- '20010115123456',
- wfTimestamp( TS_MW, 'Mon, 15 Jan 2001 12:34:56 GMT' ),
- 'TS_RFC2822 to TS_MW' );
-
- $this->assertEquals(
- '20010115123456',
- wfTimestamp( TS_MW, ' Mon, 15 Jan 2001 12:34:56 GMT' ),
- 'TS_RFC2822 with leading space to TS_MW' );
-
- $this->assertEquals(
- '20010115123456',
- wfTimestamp( TS_MW, '15 Jan 2001 12:34:56 GMT' ),
- 'TS_RFC2822 without optional day-of-week to TS_MW' );
-
- # FWS = ([*WSP CRLF] 1*WSP) / obs-FWS ; Folding white space
- # obs-FWS = 1*WSP *(CRLF 1*WSP) ; Section 4.2
- $this->assertEquals(
- '20010115123456',
- wfTimestamp( TS_MW, 'Mon, 15 Jan 2001 12:34:56 GMT' ),
- 'TS_RFC2822 to TS_MW' );
-
- # WSP = SP / HTAB ; rfc2234
- $this->assertEquals(
- '20010115123456',
- wfTimestamp( TS_MW, "Mon, 15 Jan\x092001 12:34:56 GMT" ),
- 'TS_RFC2822 with HTAB to TS_MW' );
-
- $this->assertEquals(
- '20010115123456',
- wfTimestamp( TS_MW, "Mon, 15 Jan\x09 \x09 2001 12:34:56 GMT" ),
- 'TS_RFC2822 with HTAB and SP to TS_MW' );
-
- $this->assertEquals(
- '19941106084937',
- wfTimestamp( TS_MW, "Sun, 6 Nov 94 08:49:37 GMT" ),
- 'TS_RFC2822 with obsolete year to TS_MW' );
- }
-
- /**
- * This test checks wfTimestamp() with values outside.
- * It needs PHP 64 bits or PHP > 5.1.
- * See r74778 and bug 25451
- */
- function testOldTimestamps() {
- $this->assertEquals( 'Fri, 13 Dec 1901 20:45:54 GMT',
- wfTimestamp( TS_RFC2822, '19011213204554' ),
- 'Earliest time according to php documentation' );
-
- $this->assertEquals( 'Tue, 19 Jan 2038 03:14:07 GMT',
- wfTimestamp( TS_RFC2822, '20380119031407' ),
- 'Latest 32 bit time' );
-
- $this->assertEquals( '-2147483648',
- wfTimestamp( TS_UNIX, '19011213204552' ),
- 'Earliest 32 bit unix time' );
-
- $this->assertEquals( '2147483647',
- wfTimestamp( TS_UNIX, '20380119031407' ),
- 'Latest 32 bit unix time' );
-
- $this->assertEquals( 'Fri, 13 Dec 1901 20:45:52 GMT',
- wfTimestamp( TS_RFC2822, '19011213204552' ),
- 'Earliest 32 bit time' );
-
- $this->assertEquals( 'Fri, 13 Dec 1901 20:45:51 GMT',
- wfTimestamp( TS_RFC2822, '19011213204551' ),
- 'Earliest 32 bit time - 1' );
-
- $this->assertEquals( 'Tue, 19 Jan 2038 03:14:08 GMT',
- wfTimestamp( TS_RFC2822, '20380119031408' ),
- 'Latest 32 bit time + 1' );
-
- $this->assertEquals( '19011212000000',
- wfTimestamp(TS_MW, '19011212000000'),
- 'Convert to itself r74778#c10645' );
-
- $this->assertEquals( '-2147483649',
- wfTimestamp( TS_UNIX, '19011213204551' ),
- 'Earliest 32 bit unix time - 1' );
-
- $this->assertEquals( '2147483648',
- wfTimestamp( TS_UNIX, '20380119031408' ),
- 'Latest 32 bit unix time + 1' );
-
- $this->assertEquals( '19011213204551',
- wfTimestamp( TS_MW, '-2147483649' ),
- '1901 negative unix time to MediaWiki' );
-
- $this->assertEquals( '18010115123456',
- wfTimestamp( TS_MW, '-5331871504' ),
- '1801 negative unix time to MediaWiki' );
-
- $this->assertEquals( 'Tue, 09 Aug 0117 12:34:56 GMT',
- wfTimestamp( TS_RFC2822, '0117-08-09 12:34:56'),
- 'Death of Roman Emperor [[Trajan]]');
-
- /* @todo FIXME: 00 to 101 years are taken as being in [1970-2069] */
-
- $this->assertEquals( 'Sun, 01 Jan 0101 00:00:00 GMT',
- wfTimestamp( TS_RFC2822, '-58979923200'),
- '1/1/101');
-
- $this->assertEquals( 'Mon, 01 Jan 0001 00:00:00 GMT',
- wfTimestamp( TS_RFC2822, '-62135596800'),
- 'Year 1');
-
- /* It is not clear if we should generate a year 0 or not
- * We are completely off RFC2822 requirement of year being
- * 1900 or later.
- */
- $this->assertEquals( 'Wed, 18 Oct 0000 00:00:00 GMT',
- wfTimestamp( TS_RFC2822, '-62142076800'),
- 'ISO 8601:2004 [[year 0]], also called [[1 BC]]');
- }
-
- function testHttpDate() {
- # The Resource Loader uses wfTimestamp() to convert timestamps
- # from If-Modified-Since header.
- # Thus it must be able to parse all rfc2616 date formats
- # http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1
-
- $this->assertEquals(
- '19941106084937',
- wfTimestamp( TS_MW, 'Sun, 06 Nov 1994 08:49:37 GMT' ),
- 'RFC 822 date' );
-
- $this->assertEquals(
- '19941106084937',
- wfTimestamp( TS_MW, 'Sunday, 06-Nov-94 08:49:37 GMT' ),
- 'RFC 850 date' );
-
- $this->assertEquals(
- '19941106084937',
- wfTimestamp( TS_MW, 'Sun Nov 6 08:49:37 1994' ),
- "ANSI C's asctime() format" );
-
- // See http://www.squid-cache.org/mail-archive/squid-users/200307/0122.html and r77171
- $this->assertEquals(
- '20101122141242',
- wfTimestamp( TS_MW, 'Mon, 22 Nov 2010 14:12:42 GMT; length=52626' ),
- "Netscape extension to HTTP/1.0" );
-
- }
-
- function testTimestampParameter() {
- // 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
-
- $now = wfTimestamp( TS_UNIX );
- // 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 );
- $this->assertNotEquals( false, $zero );
- $this->assertLessThan( 5, $zero - $now );
-
- $empty = wfTimestamp( TS_UNIX, '' );
- $this->assertNotEquals( false, $empty );
- $this->assertLessThan( 5, $empty - $now );
-
- $null = wfTimestamp( TS_UNIX, null );
- $this->assertNotEquals( false, $null );
- $this->assertLessThan( 5, $null - $now );
- }
-
- function testBasename() {
- $sets = array(
- '' => '',
- '/' => '',
- '\\' => '',
- '//' => '',
- '\\\\' => '',
- 'a' => 'a',
- 'aaaa' => 'aaaa',
- '/a' => 'a',
- '\\a' => 'a',
- '/aaaa' => 'aaaa',
- '\\aaaa' => 'aaaa',
- '/aaaa/' => 'aaaa',
- '\\aaaa\\' => 'aaaa',
- '\\aaaa\\' => 'aaaa',
- '/mnt/upload3/wikipedia/en/thumb/8/8b/Zork_Grand_Inquisitor_box_cover.jpg/93px-Zork_Grand_Inquisitor_box_cover.jpg' => '93px-Zork_Grand_Inquisitor_box_cover.jpg',
- 'C:\\Progra~1\\Wikime~1\\Wikipe~1\\VIEWER.EXE' => 'VIEWER.EXE',
- 'Östergötland_coat_of_arms.png' => 'Östergötland_coat_of_arms.png',
- );
- foreach ( $sets as $from => $to ) {
- $this->assertEquals( $to, wfBaseName( $from ),
- "wfBaseName('$from') => '$to'" );
- }
- }
-
function testFallbackMbstringFunctions() {
@@ -701,135 +500,6 @@ class GlobalTest 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
- * all upper cases language code.
- *
- * @see http://tools.ietf.org/html/bcp47
- * @dataProvider provideLanguageCodes()
- */
- function testBCP47( $code, $expected ) {
- $code = strtolower( $code );
- $this->assertEquals( $expected, wfBCP47($code),
- "Applying BCP47 standard to lower case '$code'"
- );
-
- $code = strtoupper( $code );
- $this->assertEquals( $expected, wfBCP47($code),
- "Applying BCP47 standard to upper case '$code'"
- );
- }
-
- /**
- * Array format is ($code, $expected)
- */
- 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( '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( 'de-ch-1996', 'de-CH-1996' ),
-
- # 2.2.6
- array(
- 'en-latn-gb-boont-r-extended-sequence-x-private',
- 'en-Latn-GB-boont-r-extended-sequence-x-private'
- ),
-
- // Examples from BCP47 Appendix A
- # Simple language subtag:
- array( 'DE', 'de' ),
- array( 'fR', 'fr' ),
- array( 'ja', 'ja' ),
-
- # Language subtag plus script subtag:
- 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' ),
-
- # 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-biske', 'sl-rozaj-biske' ),
- array( 'sl-nedis' , 'sl-nedis' ),
-
- # Language-Region-Variant:
- 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'),
-
- # Private use subtags:
- 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:
- * az-Arab-x-AZE-derbend
- * AZE being private, it should be lower case, hence the test above
- * should probably be:
- #array( 'az-arab-x-aze-derbend', 'az-Arab-x-AZE-derbend' ),
- */
-
- # 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( 'sr-latn-qm', 'sr-Latn-QM' ),
- array( 'sr-qaaa-rs', 'sr-Qaaa-RS' ),
-
- # Tags that use extensions
- array( 'en-us-u-islamcal', 'en-US-u-islamcal' ),
- array( 'zh-cn-a-myext-x-private', 'zh-CN-a-myext-x-private' ),
- array( 'en-a-myext-b-another', 'en-a-myext-b-another' ),
-
- # Invalid:
- // 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)
- */);
- }
-
/**
* @dataProvider provideMakeUrlIndexes()
*/
@@ -886,7 +556,63 @@ class GlobalTest extends MediaWikiTestCase {
),
);
}
+
+ /**
+ * @dataProvider provideWfMatchesDomainList
+ */
+ function testWfMatchesDomainList( $url, $domains, $expected, $description ) {
+ $actual = wfMatchesDomainList( $url, $domains );
+ $this->assertEquals( $expected, $actual, $description );
+ }
+
+ function provideWfMatchesDomainList() {
+ $a = array();
+ $protocols = array( 'HTTP' => 'http:', 'HTTPS' => 'https:', 'protocol-relative' => '' );
+ foreach ( $protocols as $pDesc => $p ) {
+ $a = array_merge( $a, array(
+ array( "$p//www.example.com", array(), false, "No matches for empty domains array, $pDesc URL" ),
+ array( "$p//www.example.com", array( 'www.example.com' ), true, "Exact match in domains array, $pDesc URL" ),
+ array( "$p//www.example.com", array( 'example.com' ), true, "Match without subdomain in domains array, $pDesc URL" ),
+ 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" ),
+ ) );
+ }
+ return $a;
+ }
+ /**
+ * @dataProvider provideWfShellMaintenanceCmdList
+ */
+ function testWfShellMaintenanceCmd( $script, $parameters, $options, $expected, $description ) {
+ if( wfIsWindows() ) {
+ // Approximation that's good enough for our purposes just now
+ $expected = str_replace( "'", '"', $expected );
+ }
+ $actual = wfShellMaintenanceCmd( $script, $parameters, $options );
+ $this->assertEquals( $expected, $actual, $description );
+ }
+
+ 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'),
+ "'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'),
+ "'$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'),
+ "'php5' 'MWScript.php' 'eval.php' '--help' '--test' 'y'",
+ "Called eval.php --help --test with wrapper and php option" ),
+ );
+ }
/* TODO: many more! */
}
diff --git a/tests/phpunit/includes/GlobalFunctions/GlobalWithDBTest.php b/tests/phpunit/includes/GlobalFunctions/GlobalWithDBTest.php
new file mode 100644
index 00000000..4879a38d
--- /dev/null
+++ b/tests/phpunit/includes/GlobalFunctions/GlobalWithDBTest.php
@@ -0,0 +1,29 @@
+<?php
+
+/**
+ * @group Database
+ */
+class GlobalWithDBTest extends MediaWikiTestCase {
+ /**
+ * @dataProvider provideWfIsBadImageList
+ */
+ function testWfIsBadImage( $name, $title, $blacklist, $expected, $desc ) {
+ $this->assertEquals( $expected, wfIsBadImage( $name, $title, $blacklist ), $desc );
+ }
+
+ function provideWfIsBadImageList() {
+ $blacklist = '* [[File:Bad.jpg]] except [[Nasty page]]';
+ return array(
+ array( 'Bad.jpg', false, $blacklist, true,
+ 'Called on a bad image' ),
+ array( 'Bad.jpg', Title::makeTitle( NS_MAIN, 'A page' ), $blacklist, true,
+ 'Called on a bad image' ),
+ array( 'NotBad.jpg', false, $blacklist, false,
+ 'Called on a non-bad image' ),
+ array( 'Bad.jpg', Title::makeTitle( NS_MAIN, 'Nasty page' ), $blacklist, false,
+ 'Called on a bad image but is on a whitelisted page' ),
+ array( 'File:Bad.jpg', false, $blacklist, false,
+ 'Called on a bad image with File:' ),
+ );
+ }
+}
diff --git a/tests/phpunit/includes/GlobalFunctions/wfAssembleUrlTest.php b/tests/phpunit/includes/GlobalFunctions/wfAssembleUrlTest.php
new file mode 100644
index 00000000..be6c99e7
--- /dev/null
+++ b/tests/phpunit/includes/GlobalFunctions/wfAssembleUrlTest.php
@@ -0,0 +1,111 @@
+<?php
+/**
+ * Unit tests for wfAssembleUrl()
+ */
+
+class wfAssembleUrl extends MediaWikiTestCase {
+ /** @dataProvider provideURLParts */
+ public function testWfAssembleUrl( $parts, $output ) {
+ $partsDump = print_r( $parts, true );
+ $this->assertEquals(
+ $output,
+ wfAssembleUrl( $parts ),
+ "Testing $partsDump assembles to $output"
+ );
+ }
+
+ /**
+ * Provider of URL parts for testing wfAssembleUrl()
+ *
+ * @return array
+ */
+ public function provideURLParts() {
+ $schemes = array(
+ '' => array(),
+ '//' => array(
+ 'delimiter' => '//',
+ ),
+ 'http://' => array(
+ 'scheme' => 'http',
+ 'delimiter' => '://',
+ ),
+ );
+
+ $hosts = array(
+ '' => array(),
+ 'example.com' => array(
+ 'host' => 'example.com',
+ ),
+ 'example.com:123' => array(
+ 'host' => 'example.com',
+ 'port' => 123,
+ ),
+ 'id@example.com' => array(
+ 'user' => 'id',
+ 'host' => 'example.com',
+ ),
+ 'id@example.com:123' => array(
+ 'user' => 'id',
+ 'host' => 'example.com',
+ 'port' => 123,
+ ),
+ 'id:key@example.com' => array(
+ 'user' => 'id',
+ 'pass' => 'key',
+ 'host' => 'example.com',
+ ),
+ 'id:key@example.com:123' => array(
+ 'user' => 'id',
+ 'pass' => 'key',
+ 'host' => 'example.com',
+ 'port' => 123,
+ ),
+ );
+
+ $cases = array();
+ foreach ( $schemes as $scheme => $schemeParts ) {
+ foreach ( $hosts as $host => $hostParts ) {
+ foreach ( array( '', '/path' ) as $path ) {
+ foreach ( array( '', 'query' ) as $query ) {
+ foreach ( array( '', 'fragment' ) as $fragment ) {
+ $parts = array_merge(
+ $schemeParts,
+ $hostParts
+ );
+ $url = $scheme .
+ $host .
+ $path;
+
+ if ( $path ) {
+ $parts['path'] = $path;
+ }
+ if ( $query ) {
+ $parts['query'] = $query;
+ $url .= '?' . $query;
+ }
+ if( $fragment ) {
+ $parts['fragment'] = $fragment;
+ $url .= '#' . $fragment;
+ }
+
+
+ $cases[] = array(
+ $parts,
+ $url,
+ );
+ }
+ }
+ }
+ }
+ }
+
+ $complexURL = 'http://id:key@example.org:321' .
+ '/over/there?name=ferret&foo=bar#nose';
+ $cases[] = array(
+ wfParseUrl( $complexURL ),
+ $complexURL,
+ );
+
+ return $cases;
+ }
+}
diff --git a/tests/phpunit/includes/GlobalFunctions/wfBCP47Test.php b/tests/phpunit/includes/GlobalFunctions/wfBCP47Test.php
new file mode 100644
index 00000000..f4ec7a5f
--- /dev/null
+++ b/tests/phpunit/includes/GlobalFunctions/wfBCP47Test.php
@@ -0,0 +1,133 @@
+<?php
+/**
+ * Unit tests for wfBCP47()
+ */
+class wfBCP47 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
+ * all upper cases language code.
+ *
+ * @see http://tools.ietf.org/html/bcp47
+ * @dataProvider provideLanguageCodes()
+ */
+ function testBCP47( $code, $expected ) {
+ $code = strtolower( $code );
+ $this->assertEquals( $expected, wfBCP47($code),
+ "Applying BCP47 standard to lower case '$code'"
+ );
+
+ $code = strtoupper( $code );
+ $this->assertEquals( $expected, wfBCP47($code),
+ "Applying BCP47 standard to upper case '$code'"
+ );
+ }
+
+ /**
+ * Array format is ($code, $expected)
+ */
+ 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( '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( 'de-ch-1996', 'de-CH-1996' ),
+
+ # 2.2.6
+ array(
+ 'en-latn-gb-boont-r-extended-sequence-x-private',
+ 'en-Latn-GB-boont-r-extended-sequence-x-private'
+ ),
+
+ // Examples from BCP47 Appendix A
+ # Simple language subtag:
+ array( 'DE', 'de' ),
+ array( 'fR', 'fr' ),
+ array( 'ja', 'ja' ),
+
+ # Language subtag plus script subtag:
+ 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' ),
+
+ # 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-biske', 'sl-rozaj-biske' ),
+ array( 'sl-nedis' , 'sl-nedis' ),
+
+ # Language-Region-Variant:
+ 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'),
+
+ # Private use subtags:
+ 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:
+ * az-Arab-x-AZE-derbend
+ * AZE being private, it should be lower case, hence the test above
+ * should probably be:
+ #array( 'az-arab-x-aze-derbend', 'az-Arab-x-AZE-derbend' ),
+ */
+
+ # 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( 'sr-latn-qm', 'sr-Latn-QM' ),
+ array( 'sr-qaaa-rs', 'sr-Qaaa-RS' ),
+
+ # Tags that use extensions
+ array( 'en-us-u-islamcal', 'en-US-u-islamcal' ),
+ array( 'zh-cn-a-myext-x-private', 'zh-CN-a-myext-x-private' ),
+ array( 'en-a-myext-b-another', 'en-a-myext-b-another' ),
+
+ # Invalid:
+ // 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/wfBaseNameTest.php b/tests/phpunit/includes/GlobalFunctions/wfBaseNameTest.php
new file mode 100644
index 00000000..59954b23
--- /dev/null
+++ b/tests/phpunit/includes/GlobalFunctions/wfBaseNameTest.php
@@ -0,0 +1,36 @@
+<?php
+/**
+ * Tests for wfBaseName()
+ */
+class wfBaseName extends MediaWikiTestCase {
+ /**
+ * @dataProvider providePaths
+ */
+ function testBaseName( $fullpath, $basename ) {
+ $this->assertEquals( $basename, wfBaseName( $fullpath ),
+ "wfBaseName('$fullpath') => '$basename'" );
+ }
+
+ function providePaths() {
+ return array(
+ array( '', '' ),
+ array( '/', '' ),
+ array( '\\', '' ),
+ array( '//', '' ),
+ array( '\\\\', '' ),
+ array( 'a', 'a' ),
+ array( 'aaaa', 'aaaa' ),
+ array( '/a', 'a' ),
+ array( '\\a', 'a' ),
+ array( '/aaaa', 'aaaa' ),
+ array( '\\aaaa', 'aaaa' ),
+ array( '/aaaa/', 'aaaa' ),
+ array( '\\aaaa\\', 'aaaa' ),
+ array( '\\aaaa\\', 'aaaa' ),
+ array( '/mnt/upload3/wikipedia/en/thumb/8/8b/Zork_Grand_Inquisitor_box_cover.jpg/93px-Zork_Grand_Inquisitor_box_cover.jpg',
+ '93px-Zork_Grand_Inquisitor_box_cover.jpg' ),
+ array( 'C:\\Progra~1\\Wikime~1\\Wikipe~1\\VIEWER.EXE', 'VIEWER.EXE' ),
+ array( 'Östergötland_coat_of_arms.png', 'Östergötland_coat_of_arms.png' ),
+ );
+ }
+}
diff --git a/tests/phpunit/includes/GlobalFunctions/wfExpandUrl.php b/tests/phpunit/includes/GlobalFunctions/wfExpandUrlTest.php
index b388b266..192689f8 100644
--- a/tests/phpunit/includes/GlobalFunctions/wfExpandUrl.php
+++ b/tests/phpunit/includes/GlobalFunctions/wfExpandUrlTest.php
@@ -1,5 +1,5 @@
<?php
-/*
+/**
* Unit tests for wfExpandUrl()
*/
@@ -12,16 +12,16 @@ class wfExpandUrl extends MediaWikiTestCase {
$oldCanServer = $wgCanonicalServer;
$wgServer = $server;
$wgCanonicalServer = $canServer;
-
+
// Fake $_SERVER['HTTPS'] if needed
if ( $httpsMode ) {
$_SERVER['HTTPS'] = 'on';
} else {
unset( $_SERVER['HTTPS'] );
}
-
+
$this->assertEquals( $fullUrl, wfExpandUrl( $shortUrl, $defaultProto ), $message );
-
+
// Restore $wgServer and $wgCanonicalServer
$wgServer = $oldServer;
$wgCanonicalServer = $oldCanServer;
@@ -29,12 +29,14 @@ class wfExpandUrl extends MediaWikiTestCase {
/**
* Provider of URL examples for testing wfExpandUrl()
+ *
+ * @return array
*/
public 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 );
-
+
$retval = array();
foreach ( $modes as $mode ) {
$httpsMode = $mode == 'https';
@@ -46,14 +48,14 @@ class wfExpandUrl extends MediaWikiTestCase {
$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 )" );
-
+
// Determine expected protocol
$p = $protoDesc . ':'; // default case
if ( $protoDesc == 'protocol-relative' ) {
$p = '';
- } else if ( $protoDesc == 'current' ) {
+ } elseif ( $protoDesc == 'current' ) {
$p = "$mode:";
- } else if ( $protoDesc == 'canonical' ) {
+ } elseif ( $protoDesc == 'canonical' ) {
$p = "$canServerMode:";
} else {
$p = $protoDesc . ':';
@@ -61,12 +63,12 @@ class wfExpandUrl extends MediaWikiTestCase {
// Determine expected server name
if ( $protoDesc == 'canonical' ) {
$srv = $canServer;
- } else if ( $serverDesc == 'protocol-relative' ) {
+ } elseif ( $serverDesc == 'protocol-relative' ) {
$srv = $p . $server;
} else {
$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 )" );
}
diff --git a/tests/phpunit/includes/GlobalFunctions/wfRemoveDotSegmentsTest.php b/tests/phpunit/includes/GlobalFunctions/wfRemoveDotSegmentsTest.php
new file mode 100644
index 00000000..1cf0e0fb
--- /dev/null
+++ b/tests/phpunit/includes/GlobalFunctions/wfRemoveDotSegmentsTest.php
@@ -0,0 +1,90 @@
+<?php
+/**
+ * Unit tests for wfRemoveDotSegments()
+ */
+
+class wfRemoveDotSegments extends MediaWikiTestCase {
+ /** @dataProvider providePaths */
+ public function testWfRemoveDotSegments( $inputPath, $outputPath ) {
+ $this->assertEquals(
+ $outputPath,
+ wfRemoveDotSegments( $inputPath ),
+ "Testing $inputPath expands to $outputPath"
+ );
+ }
+
+ /**
+ * Provider of URL paths for testing wfRemoveDotSegments()
+ *
+ * @return array
+ */
+ public function providePaths() {
+ return array(
+ array( '/a/b/c/./../../g', '/a/g' ),
+ array( 'mid/content=5/../6', 'mid/6' ),
+ array( '/a//../b', '/a/b' ),
+ array( '/.../a', '/.../a' ),
+ array( '.../a', '.../a' ),
+ array( '', '' ),
+ array( '/', '/' ),
+ array( '//', '//' ),
+ array( '.', '' ),
+ array( '..', '' ),
+ array( '...', '...' ),
+ array( '/.', '/' ),
+ array( '/..', '/' ),
+ array( './', '' ),
+ array( '../', '' ),
+ array( './a', 'a' ),
+ array( '../a', 'a' ),
+ array( '../../a', 'a' ),
+ array( '.././a', 'a' ),
+ array( './../a', 'a' ),
+ array( '././a', 'a' ),
+ array( '../../', '' ),
+ array( '.././', '' ),
+ array( './../', '' ),
+ array( '././', '' ),
+ array( '../..', '' ),
+ array( '../.', '' ),
+ array( './..', '' ),
+ array( './.', '' ),
+ array( '/../../a', '/a' ),
+ array( '/.././a', '/a' ),
+ array( '/./../a', '/a' ),
+ array( '/././a', '/a' ),
+ array( '/../../', '/' ),
+ array( '/.././', '/' ),
+ array( '/./../', '/' ),
+ array( '/././', '/' ),
+ array( '/../..', '/' ),
+ array( '/../.', '/' ),
+ array( '/./..', '/' ),
+ array( '/./.', '/' ),
+ array( 'b/../../a', '/a' ),
+ array( 'b/.././a', '/a' ),
+ array( 'b/./../a', '/a' ),
+ array( 'b/././a', 'b/a' ),
+ array( 'b/../../', '/' ),
+ array( 'b/.././', '/' ),
+ array( 'b/./../', '/' ),
+ array( 'b/././', 'b/' ),
+ array( 'b/../..', '/' ),
+ array( 'b/../.', '/' ),
+ array( 'b/./..', '/' ),
+ array( 'b/./.', 'b/' ),
+ array( '/b/../../a', '/a' ),
+ array( '/b/.././a', '/a' ),
+ array( '/b/./../a', '/a' ),
+ array( '/b/././a', '/b/a' ),
+ array( '/b/../../', '/' ),
+ array( '/b/.././', '/' ),
+ array( '/b/./../', '/' ),
+ array( '/b/././', '/b/' ),
+ array( '/b/../..', '/' ),
+ array( '/b/../.', '/' ),
+ array( '/b/./..', '/' ),
+ array( '/b/./.', '/b/' ),
+ );
+ }
+}
diff --git a/tests/phpunit/includes/GlobalFunctions/wfShorthandToIntegerTest.php b/tests/phpunit/includes/GlobalFunctions/wfShorthandToIntegerTest.php
new file mode 100644
index 00000000..1df26d2c
--- /dev/null
+++ b/tests/phpunit/includes/GlobalFunctions/wfShorthandToIntegerTest.php
@@ -0,0 +1,28 @@
+<?php
+
+class wfShorthandToIntegerTest extends MediaWikiTestCase {
+ /**
+ * @dataProvider provideABunchOfShorthands
+ */
+ function testWfShorthandToInteger( $input, $output, $description ) {
+ $this->assertEquals(
+ wfShorthandToInteger( $input ),
+ $output,
+ $description
+ );
+ }
+
+ function provideABunchOfShorthands() {
+ return array(
+ array( '', -1, 'Empty string' ),
+ array( ' ', -1, 'String of spaces' ),
+ array( '1G', 1024 * 1024 * 1024, 'One gig uppercased' ),
+ array( '1g', 1024 * 1024 * 1024, 'One gig lowercased' ),
+ array( '1M', 1024 * 1024, 'One meg uppercased' ),
+ array( '1m', 1024 * 1024, 'One meg lowercased' ),
+ array( '1K', 1024, 'One kb uppercased' ),
+ array( '1k', 1024, 'One kb lowercased' ),
+ );
+ }
+
+}
diff --git a/tests/phpunit/includes/GlobalFunctions/wfTimestampTest.php b/tests/phpunit/includes/GlobalFunctions/wfTimestampTest.php
new file mode 100644
index 00000000..505c28c7
--- /dev/null
+++ b/tests/phpunit/includes/GlobalFunctions/wfTimestampTest.php
@@ -0,0 +1,134 @@
+<?php
+
+/*
+ * Tests for wfTimestamp()
+ */
+class wfTimestamp extends MediaWikiTestCase {
+ /**
+ * @dataProvider provideNormalTimestamps
+ */
+ function testNormalTimestamps( $input, $format, $output, $desc ) {
+ $this->assertEquals( $output, wfTimestamp( $format, $input ), $desc );
+ }
+
+ function provideNormalTimestamps() {
+ $t = gmmktime( 12, 34, 56, 1, 15, 2001 );
+ 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' ),
+ array( $t, TS_UNIX, 979562096, 'TS_UNIX to TS_UNIX' ),
+ 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' ),
+ array( '2001-01-15 12:34:56', TS_DB, '2001-01-15 12:34:56', 'TS_DB to TS_DB' ),
+ array( '2001-01-15 12:34:56', TS_ISO_8601_BASIC, '20010115T123456Z', 'TS_DB to TS_ISO_8601_BASIC' ),
+
+ # rfc2822 section 3.3
+ array( '20010115123456', TS_RFC2822, 'Mon, 15 Jan 2001 12:34:56 GMT', 'TS_MW to TS_RFC2822' ),
+ array( 'Mon, 15 Jan 2001 12:34:56 GMT', TS_MW, '20010115123456', 'TS_RFC2822 to TS_MW' ),
+ array( ' Mon, 15 Jan 2001 12:34:56 GMT', TS_MW, '20010115123456', 'TS_RFC2822 with leading space to TS_MW' ),
+ array( '15 Jan 2001 12:34:56 GMT', TS_MW, '20010115123456', 'TS_RFC2822 without optional day-of-week to TS_MW' ),
+
+ # FWS = ([*WSP CRLF] 1*WSP) / obs-FWS ; Folding white space
+ # obs-FWS = 1*WSP *(CRLF 1*WSP) ; Section 4.2
+ array( 'Mon, 15 Jan 2001 12:34:56 GMT', TS_MW, '20010115123456', 'TS_RFC2822 to TS_MW' ),
+
+ # WSP = SP / HTAB ; rfc2234
+ array( "Mon, 15 Jan\x092001 12:34:56 GMT", TS_MW, '20010115123456', 'TS_RFC2822 with HTAB to TS_MW' ),
+ array( "Mon, 15 Jan\x09 \x09 2001 12:34:56 GMT", TS_MW, '20010115123456', 'TS_RFC2822 with HTAB and SP to TS_MW' ),
+ array( 'Sun, 6 Nov 94 08:49:37 GMT', TS_MW, '19941106084937', 'TS_RFC2822 with obsolete year to TS_MW' ),
+ );
+ }
+
+ /**
+ * This test checks wfTimestamp() with values outside.
+ * It needs PHP 64 bits or PHP > 5.1.
+ * See r74778 and bug 25451
+ * @dataProvider provideOldTimestamps
+ */
+ function testOldTimestamps( $input, $format, $output, $desc ) {
+ $this->assertEquals( $output, wfTimestamp( $format, $input ), $desc );
+ }
+
+ 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' ),
+ array( '20380119031407', TS_UNIX, '2147483647', 'Latest 32 bit unix time' ),
+ array( '19011213204552', TS_RFC2822, 'Fri, 13 Dec 1901 20:45:52 GMT', 'Earliest 32 bit time' ),
+ array( '19011213204551', TS_RFC2822, 'Fri, 13 Dec 1901 20:45:51 GMT', 'Earliest 32 bit time - 1' ),
+ array( '20380119031408', TS_RFC2822, 'Tue, 19 Jan 2038 03:14:08 GMT', 'Latest 32 bit time + 1' ),
+ array( '19011212000000', TS_MW, '19011212000000', 'Convert to itself r74778#c10645' ),
+ array( '19011213204551', TS_UNIX, '-2147483649', 'Earliest 32 bit unix time - 1' ),
+ array( '20380119031408', TS_UNIX, '2147483648', 'Latest 32 bit unix time + 1' ),
+ array( '-2147483649', TS_MW, '19011213204551', '1901 negative unix time to MediaWiki' ),
+ array( '-5331871504', TS_MW, '18010115123456', '1801 negative unix time to MediaWiki' ),
+ array( '0117-08-09 12:34:56', TS_RFC2822, 'Tue, 09 Aug 0117 12:34:56 GMT', 'Death of Roman Emperor [[Trajan]]' ),
+
+ /* @todo FIXME: 00 to 101 years are taken as being in [1970-2069] */
+ array( '-58979923200', TS_RFC2822, 'Sun, 01 Jan 0101 00:00:00 GMT', '1/1/101' ),
+ array( '-62135596800', TS_RFC2822, 'Mon, 01 Jan 0001 00:00:00 GMT', 'Year 1' ),
+
+ /* It is not clear if we should generate a year 0 or not
+ * We are completely off RFC2822 requirement of year being
+ * 1900 or later.
+ */
+ array( '-62142076800', TS_RFC2822, 'Wed, 18 Oct 0000 00:00:00 GMT', 'ISO 8601:2004 [[year 0]], also called [[1 BC]]' ),
+ );
+ }
+
+ /**
+ * The Resource Loader uses wfTimestamp() to convert timestamps
+ * from If-Modified-Since header. Thus it must be able to parse all
+ * rfc2616 date formats
+ * @see http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1
+ * @dataProvider provideHttpDates
+ */
+ function testHttpDate( $input, $output, $desc ) {
+ $this->assertEquals( $output, wfTimestamp( TS_MW, $input ), $desc );
+ }
+
+ 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' ),
+ array( 'Sun Nov 6 08:49:37 1994', '19941106084937', "ANSI C's asctime() format" ),
+ // See http://www.squid-cache.org/mail-archive/squid-users/200307/0122.html and r77171
+ array( 'Mon, 22 Nov 2010 14:12:42 GMT; length=52626', '20101122141242', 'Netscape extension to HTTP/1.0' ),
+ );
+ }
+
+ /**
+ * 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() {
+ $now = wfTimestamp( TS_UNIX );
+ // 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 );
+ $this->assertNotEquals( false, $zero );
+ $this->assertLessThan( 5, $zero - $now );
+
+ $empty = wfTimestamp( TS_UNIX, '' );
+ $this->assertNotEquals( false, $empty );
+ $this->assertLessThan( 5, $empty - $now );
+
+ $null = wfTimestamp( TS_UNIX, null );
+ $this->assertNotEquals( false, $null );
+ $this->assertLessThan( 5, $null - $now );
+ }
+}
diff --git a/tests/phpunit/includes/HtmlTest.php b/tests/phpunit/includes/HtmlTest.php
index 96bb1803..67b60d32 100644
--- a/tests/phpunit/includes/HtmlTest.php
+++ b/tests/phpunit/includes/HtmlTest.php
@@ -3,58 +3,90 @@
class HtmlTest extends MediaWikiTestCase {
private static $oldLang;
+ private static $oldContLang;
+ private static $oldLanguageCode;
+ private static $oldNamespaces;
public function setUp() {
- global $wgLang, $wgLanguageCode;
+ global $wgLang, $wgContLang, $wgLanguageCode;
self::$oldLang = $wgLang;
+ self::$oldContLang = $wgContLang;
+ self::$oldNamespaces = $wgContLang->getNamespaces();
+ self::$oldLanguageCode = $wgLanguageCode;
+
$wgLanguageCode = 'en';
- $wgLang = Language::factory( $wgLanguageCode );
+ $wgContLang = $wgLang = Language::factory( $wgLanguageCode );
+
+ // Hardcode namespaces during test runs,
+ // so that html output based on existing namespaces
+ // can be properly evaluated.
+ $wgContLang->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',
+ ) );
}
public function tearDown() {
- global $wgLang, $wgLanguageCode;
+ global $wgLang, $wgContLang, $wgLanguageCode;
+
+ $wgContLang->setNamespaces( self::$oldNamespaces );
$wgLang = self::$oldLang;
- $wgLanguageCode = $wgLang->getCode();
+ $wgContLang = self::$oldContLang;
+ $wgLanguageCode = self::$oldLanguageCode;
}
public function testExpandAttributesSkipsNullAndFalse() {
### EMPTY ########
$this->AssertEmpty(
- Html::expandAttributes( array( 'foo'=>null) ),
+ Html::expandAttributes( array( 'foo' => null ) ),
'skip keys with null value'
);
$this->AssertEmpty(
- Html::expandAttributes( array( 'foo'=>false) ),
+ Html::expandAttributes( array( 'foo' => false ) ),
'skip keys with false value'
);
$this->AssertNotEmpty(
- Html::expandAttributes( array( 'foo'=>'') ),
+ Html::expandAttributes( array( 'foo' => '' ) ),
'keep keys with an empty string'
);
}
public function testExpandAttributesForBooleans() {
+ global $wgHtml5;
$this->AssertEquals(
'',
- Html::expandAttributes( array( 'selected'=>false) ),
+ Html::expandAttributes( array( 'selected' => false ) ),
'Boolean attributes do not generates output when value is false'
);
$this->AssertEquals(
'',
- Html::expandAttributes( array( 'selected'=>null) ),
+ Html::expandAttributes( array( 'selected' => null ) ),
'Boolean attributes do not generates output when value is null'
);
- ### FIXME: maybe they should just output 'selected'
$this->AssertEquals(
- ' selected=""',
- Html::expandAttributes( array( 'selected'=>true ) ),
+ $wgHtml5 ? ' selected=""' : ' selected="selected"',
+ Html::expandAttributes( array( 'selected' => true ) ),
'Boolean attributes skip value output'
);
$this->AssertEquals(
- ' selected=""',
+ $wgHtml5 ? ' selected=""' : ' selected="selected"',
Html::expandAttributes( array( 'selected' ) ),
'Boolean attributes (ex: selected) do not need a value'
);
@@ -68,23 +100,234 @@ class HtmlTest extends MediaWikiTestCase {
### NOT EMPTY ####
$this->AssertEquals(
' empty_string=""',
- Html::expandAttributes( array( 'empty_string'=>'') ),
+ Html::expandAttributes( array( 'empty_string' => '' ) ),
'Value with an empty string'
);
$this->AssertEquals(
' key="value"',
- Html::expandAttributes( array( 'key'=>'value') ),
+ Html::expandAttributes( array( 'key' => 'value' ) ),
'Value is a string'
);
$this->AssertEquals(
' one="1"',
- Html::expandAttributes( array( 'one'=>1) ),
+ Html::expandAttributes( array( 'one' => 1 ) ),
'Value is a numeric one'
);
$this->AssertEquals(
' zero="0"',
- Html::expandAttributes( array( 'zero'=>0) ),
+ Html::expandAttributes( array( 'zero' => 0 ) ),
'Value is a numeric zero'
);
}
+
+ /**
+ * Html::expandAttributes has special features for HTML
+ * attributes that use space separated lists and also
+ * allows arrays to be used as values.
+ */
+ public function testExpandAttributesListValueAttributes() {
+ ### STRING VALUES
+ $this->AssertEquals(
+ ' class="redundant spaces here"',
+ Html::expandAttributes( array( 'class' => ' redundant spaces here ' ) ),
+ 'Normalization should strip redundant spaces'
+ );
+ $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(
+ ' class=""',
+ Html::expandAttributes( array( 'class' => array() ) ),
+ 'Value with an empty array'
+ );
+ $this->AssertEquals(
+ ' class=""',
+ Html::expandAttributes( array( 'class' => array( null, '', ' ', ' ' ) ) ),
+ 'Array with null, empty string and spaces'
+ );
+ ### NON-EMPTY ARRAY VALUES
+ $this->AssertEquals(
+ ' class="foo bar"',
+ Html::expandAttributes( array( 'class' => array(
+ 'foo',
+ 'bar',
+ 'foo',
+ 'bar',
+ 'bar',
+ ) ) ),
+ 'Normalization should remove duplicates in the array'
+ );
+ $this->AssertEquals(
+ ' class="foo bar"',
+ Html::expandAttributes( array( 'class' => array(
+ 'foo bar',
+ 'bar foo',
+ 'foo',
+ 'bar bar',
+ ) ) ),
+ 'Normalization should remove duplicates in string-lists in the array'
+ );
+ }
+
+ /**
+ * Test feature added by r96188, let pass attributes values as
+ * a PHP array. Restricted to class,rel, accesskey.
+ */
+ function testExpandAttributesSpaceSeparatedAttributesWithBoolean() {
+ $this->assertEquals(
+ ' class="booltrue one"',
+ Html::expandAttributes( array( 'class' => array(
+ 'booltrue' => true,
+ 'one' => 1,
+
+ # Method use isset() internally, make sure we do discard
+ # attributes values which have been assigned well known values
+ 'emptystring' => '',
+ 'boolfalse' => false,
+ 'zero' => 0,
+ 'null' => null,
+ )))
+ );
+ }
+
+ /**
+ * How do we handle duplicate keys in HTML attributes expansion?
+ * We could pass a "class" the values: 'GREEN' and array( 'GREEN' => false )
+ * The later will take precedence.
+ *
+ * Feature added by r96188
+ */
+ function testValueIsAuthoritativeInSpaceSeparatedAttributesArrays() {
+ $this->assertEquals(
+ ' class=""',
+ Html::expandAttributes( array( 'class' => array(
+ 'GREEN',
+ 'GREEN' => false,
+ 'GREEN',
+ )))
+ );
+ }
+
+ 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="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>&#160;' .
+'<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="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' )
+ ),
+ 'Basic namespace selector with custom values'
+ );
+ }
+
+ function testNamespaceSelectorIdAndNameDefaultsAttributes() {
+
+ $this->assertNsSelectorIdAndName(
+ 'namespace', 'namespace',
+ Html::namespaceSelector( array(), array(
+ # neither 'id' nor 'name' key given
+ )),
+ "Neither 'id' nor 'name' key given"
+ );
+
+ $this->assertNsSelectorIdAndName(
+ 'namespace', 'select_name',
+ Html::namespaceSelector( array(), array(
+ 'name' => 'select_name',
+ # no 'id' key given
+ )),
+ "No 'id' key given, 'name' given"
+ );
+
+ $this->assertNsSelectorIdAndName(
+ 'select_id', 'namespace',
+ Html::namespaceSelector( array(), array(
+ 'id' => 'select_id',
+ # no 'name' key given
+ )),
+ "'id' given, no 'name' key given"
+ );
+
+ $this->assertNsSelectorIdAndName(
+ 'select_id', 'select_name',
+ Html::namespaceSelector( array(), array(
+ 'id' => 'select_id',
+ 'name' => 'select_name',
+ )),
+ "Both 'id' and 'name' given"
+ );
+ }
+
+ /**
+ * Helper to verify <select> attributes generated by Html::namespaceSelector()
+ * This helper expect the Html method to use 'namespace' as a default value for
+ * both 'id' and 'name' attributes.
+ *
+ * @param String $expectedId <select> id attribute value
+ * @param String $expectedName <select> name attribute value
+ * @param String $html Output of a call to Html::namespaceSelector()
+ * @param String $msg Optional message (default: '')
+ */
+ function assertNsSelectorIdAndName( $expectedId, $expectedName, $html, $msg = '' ) {
+ $actualId = 'namespace';
+ if( 1 === preg_match( '/id="(.+?)"/', $html, $m ) ) {
+ $actualId = $m[1];
+ }
+
+ $actualName = 'namespace';
+ if( 1 === preg_match( '/name="(.+?)"/', $html, $m ) ) {
+ $actualName = $m[1];
+ }
+ $this->assertEquals(
+ array( #expected
+ 'id' => $expectedId,
+ 'name' => $expectedName,
+ ),
+ array( #actual
+ 'id' => $actualId,
+ 'name' => $actualName,
+ ),
+ 'Html::namespaceSelector() got wrong id and/or name attribute(s). ' . $msg
+ );
+ }
+
}
diff --git a/tests/phpunit/includes/HttpTest.php b/tests/phpunit/includes/HttpTest.php
index 1a99af7d..263383f1 100644
--- a/tests/phpunit/includes/HttpTest.php
+++ b/tests/phpunit/includes/HttpTest.php
@@ -1,325 +1,12 @@
<?php
-
-class MockCookie extends Cookie {
- public function canServeDomain( $arg ) { return parent::canServeDomain( $arg ); }
- public function canServePath( $arg ) { return parent::canServePath( $arg ); }
- public function isUnExpired() { return parent::isUnExpired(); }
-}
-
/**
* @group Broken
*/
class HttpTest extends MediaWikiTestCase {
- static $content;
- static $headers;
- static $has_curl;
- static $has_fopen;
- static $has_proxy = false;
- static $proxy = "http://hulk:8080/";
- var $test_geturl = array(
- "http://en.wikipedia.org/robots.txt",
- "https://secure.wikimedia.org/",
- "http://pecl.php.net/feeds/pkg_apc.rss",
- "http://meta.wikimedia.org/w/index.php?title=Interwiki_map&action=raw",
- "http://www.mediawiki.org/w/api.php?action=query&list=categorymembers&cmtitle=Category:MediaWiki_hooks&format=php",
- );
- var $test_requesturl = array( "http://en.wikipedia.org/wiki/Special:Export/User:MarkAHershberger" );
-
- var $test_posturl = array( "http://www.comp.leeds.ac.uk/cgi-bin/Perl/environment-example" => "review=test" );
-
- function setUp() {
- putenv( "http_proxy" ); /* Remove any proxy env var, so curl doesn't get confused */
- if ( is_array( self::$content ) ) {
- return;
- }
- self::$has_curl = function_exists( 'curl_init' );
- self::$has_fopen = wfIniGetBool( 'allow_url_fopen' );
-
- if ( !file_exists( "/usr/bin/curl" ) ) {
- $this->markTestIncomplete( "This test requires the curl binary at /usr/bin/curl. If you have curl, please file a bug on this test, or, better yet, provide a patch." );
- }
-
- $content = tempnam( wfTempDir(), "" );
- $headers = tempnam( wfTempDir(), "" );
- if ( !$content && !$headers ) {
- die( "Couldn't create temp file!" );
- }
-
- // This probably isn't the best test for a proxy, but it works on my system!
- system( "curl -0 -o $content -s " . self::$proxy );
- $out = file_get_contents( $content );
- if ( $out ) {
- self::$has_proxy = true;
- }
-
- /* Maybe use wget instead of curl here ... just to use a different codebase? */
- foreach ( $this->test_geturl as $u ) {
- system( "curl -0 -s -D $headers '$u' -o $content" );
- self::$content["GET $u"] = file_get_contents( $content );
- self::$headers["GET $u"] = file_get_contents( $headers );
- }
- foreach ( $this->test_requesturl as $u ) {
- system( "curl -0 -s -X POST -H 'Content-Length: 0' -D $headers '$u' -o $content" );
- self::$content["POST $u"] = file_get_contents( $content );
- self::$headers["POST $u"] = file_get_contents( $headers );
- }
- foreach ( $this->test_posturl as $u => $postData ) {
- system( "curl -0 -s -X POST -d '$postData' -D $headers '$u' -o $content" );
- self::$content["POST $u => $postData"] = file_get_contents( $content );
- self::$headers["POST $u => $postData"] = file_get_contents( $headers );
- }
- unlink( $content );
- unlink( $headers );
- }
-
-
- function testInstantiation() {
- Http::$httpEngine = false;
-
- $r = MWHttpRequest::factory( "http://www.example.com/" );
- if ( self::$has_curl ) {
- $this->assertThat( $r, $this->isInstanceOf( 'CurlHttpRequest' ) );
- } else {
- $this->assertThat( $r, $this->isInstanceOf( 'PhpHttpRequest' ) );
- }
- unset( $r );
-
- if ( !self::$has_fopen ) {
- $this->setExpectedException( 'MWException' );
- }
- Http::$httpEngine = 'php';
- $r = MWHttpRequest::factory( "http://www.example.com/" );
- $this->assertThat( $r, $this->isInstanceOf( 'PhpHttpRequest' ) );
- unset( $r );
-
- if ( !self::$has_curl ) {
- $this->setExpectedException( 'MWException' );
- }
- Http::$httpEngine = 'curl';
- $r = MWHttpRequest::factory( "http://www.example.com/" );
- if ( self::$has_curl ) {
- $this->assertThat( $r, $this->isInstanceOf( 'CurlHttpRequest' ) );
- }
- }
-
- function runHTTPFailureChecks() {
- // Each of the following requests should result in a failure.
-
- $timeout = 1;
- $start_time = time();
- $r = Http::get( "http://www.example.com:1/", $timeout );
- $end_time = time();
- $this->assertLessThan( $timeout + 2, $end_time - $start_time,
- "Request took less than {$timeout}s via " . Http::$httpEngine );
- $this->assertEquals( $r, false, "false -- what we get on error from Http::get()" );
-
- $r = Http::get( "http://www.mediawiki.org/xml/made-up-url", $timeout );
- $this->assertFalse( $r, "False on 404s" );
-
-
- $r = MWHttpRequest::factory( "http://www.mediawiki.org/xml/made-up-url" );
- $er = $r->execute();
- if ( $r instanceof PhpHttpRequest && version_compare( '5.2.10', phpversion(), '>' ) ) {
- $this->assertRegexp( "/HTTP request failed/", $er->getWikiText() );
- } else {
- $this->assertRegexp( "/404 Not Found/", $er->getWikiText() );
- }
- }
-
- function testFailureDefault() {
- Http::$httpEngine = false;
- $this->runHTTPFailureChecks();
- }
-
- function testFailurePhp() {
- if ( !self::$has_fopen ) {
- $this->markTestIncomplete( "This test requires allow_url_fopen=true." );
- }
-
- Http::$httpEngine = "php";
- $this->runHTTPFailureChecks();
- }
-
- function testFailureCurl() {
- if ( !self::$has_curl ) {
- $this->markTestIncomplete( "This test requires curl." );
- }
-
- Http::$httpEngine = "curl";
- $this->runHTTPFailureChecks();
- }
-
- /* ./phase3/includes/Import.php:1108: $data = Http::request( $method, $url ); */
- /* ./includes/Import.php:1124: $link = Title::newFromText( "$interwiki:Special:Export/$page" ); */
- /* ./includes/Import.php:1134: return ImportStreamSource::newFromURL( $url, "POST" ); */
- function runHTTPRequests( $proxy = null ) {
- $opt = array();
-
- if ( $proxy ) {
- $opt['proxy'] = $proxy;
- } elseif ( $proxy === false ) {
- $opt['noProxy'] = true;
- }
-
- /* no postData here because the only request I could find in code so far didn't have any */
- foreach ( $this->test_requesturl as $u ) {
- $r = Http::request( "POST", $u, $opt );
- $this->assertEquals( self::$content["POST $u"], "$r", "POST $u with " . Http::$httpEngine );
- }
- }
-
- function testRequestDefault() {
- Http::$httpEngine = false;
- $this->runHTTPRequests();
- }
-
- function testRequestPhp() {
- if ( !self::$has_fopen ) {
- $this->markTestIncomplete( "This test requires allow_url_fopen=true." );
- }
-
- Http::$httpEngine = "php";
- $this->runHTTPRequests();
- }
-
- function testRequestCurl() {
- if ( !self::$has_curl ) {
- $this->markTestIncomplete( "This test requires curl." );
- }
-
- Http::$httpEngine = "curl";
- $this->runHTTPRequests();
- }
-
- function runHTTPGets( $proxy = null ) {
- $opt = array();
-
- if ( $proxy ) {
- $opt['proxy'] = $proxy;
- } elseif ( $proxy === false ) {
- $opt['noProxy'] = true;
- }
-
- foreach ( $this->test_geturl as $u ) {
- $r = Http::get( $u, 30, $opt ); /* timeout of 30s */
- $this->assertEquals( self::$content["GET $u"], "$r", "Get $u with " . Http::$httpEngine );
- }
- }
-
- function testGetDefault() {
- Http::$httpEngine = false;
- $this->runHTTPGets();
- }
-
- function testGetPhp() {
- if ( !self::$has_fopen ) {
- $this->markTestIncomplete( "This test requires allow_url_fopen=true." );
- }
-
- Http::$httpEngine = "php";
- $this->runHTTPGets();
- }
-
- function testGetCurl() {
- if ( !self::$has_curl ) {
- $this->markTestIncomplete( "This test requires curl." );
- }
-
- Http::$httpEngine = "curl";
- $this->runHTTPGets();
- }
-
- function runHTTPPosts( $proxy = null ) {
- $opt = array();
-
- if ( $proxy ) {
- $opt['proxy'] = $proxy;
- } elseif ( $proxy === false ) {
- $opt['noProxy'] = true;
- }
-
- foreach ( $this->test_posturl as $u => $postData ) {
- $opt['postData'] = $postData;
- $r = Http::post( $u, $opt );
- $this->assertEquals( self::$content["POST $u => $postData"], "$r",
- "POST $u (postData=$postData) with " . Http::$httpEngine );
- }
- }
-
- function testPostDefault() {
- Http::$httpEngine = false;
- $this->runHTTPPosts();
- }
-
- function testPostPhp() {
- if ( !self::$has_fopen ) {
- $this->markTestIncomplete( "This test requires allow_url_fopen=true." );
- }
-
- Http::$httpEngine = "php";
- $this->runHTTPPosts();
- }
-
- function testPostCurl() {
- if ( !self::$has_curl ) {
- $this->markTestIncomplete( "This test requires curl." );
- }
-
- Http::$httpEngine = "curl";
- $this->runHTTPPosts();
- }
-
- function runProxyRequests() {
- if ( !self::$has_proxy ) {
- $this->markTestIncomplete( "This test requires a proxy." );
- }
- $this->runHTTPGets( self::$proxy );
- $this->runHTTPPosts( self::$proxy );
- $this->runHTTPRequests( self::$proxy );
-
- // Set false here to do noProxy
- $this->runHTTPGets( false );
- $this->runHTTPPosts( false );
- $this->runHTTPRequests( false );
- }
-
- function testProxyDefault() {
- Http::$httpEngine = false;
- $this->runProxyRequests();
- }
-
- function testProxyPhp() {
- if ( !self::$has_fopen ) {
- $this->markTestIncomplete( "This test requires allow_url_fopen=true." );
- }
-
- Http::$httpEngine = 'php';
- $this->runProxyRequests();
- }
-
- function testProxyCurl() {
- if ( !self::$has_curl ) {
- $this->markTestIncomplete( "This test requires curl." );
- }
-
- Http::$httpEngine = 'curl';
- $this->runProxyRequests();
- }
-
- function testIsLocalUrl() {
- }
-
- /* ./extensions/DonationInterface/payflowpro_gateway/payflowpro_gateway.body.php:559: $user_agent = Http::userAgent(); */
- function testUserAgent() {
- }
-
- function testIsValidUrl() {
- }
-
/**
* @dataProvider cookieDomains
*/
- function testValidateCookieDomain( $expected, $domain, $origin=null ) {
+ function testValidateCookieDomain( $expected, $domain, $origin = null ) {
if ( $origin ) {
$ok = Cookie::validateCookieDomain( $domain, $origin );
$msg = "$domain against origin $origin";
@@ -329,7 +16,7 @@ class HttpTest extends MediaWikiTestCase {
}
$this->assertEquals( $expected, $ok, $msg );
}
-
+
function cookieDomains() {
return array(
array( false, "org"),
@@ -359,189 +46,11 @@ class HttpTest extends MediaWikiTestCase {
);
}
- function testSetCooke() {
- $c = new MockCookie( "name", "value",
- array(
- "domain" => "ac.th",
- "path" => "/path/",
- ) );
- $this->assertFalse( $c->canServeDomain( "ac.th" ) );
-
- $c = new MockCookie( "name", "value",
- array(
- "domain" => "example.com",
- "path" => "/path/",
- ) );
-
- $this->assertTrue( $c->canServeDomain( "example.com" ) );
- $this->assertFalse( $c->canServeDomain( "www.example.com" ) );
-
- $c = new MockCookie( "name", "value",
- array(
- "domain" => ".example.com",
- "path" => "/path/",
- ) );
-
- $this->assertFalse( $c->canServeDomain( "www.example.net" ) );
- $this->assertFalse( $c->canServeDomain( "example.com" ) );
- $this->assertTrue( $c->canServeDomain( "www.example.com" ) );
-
- $this->assertFalse( $c->canServePath( "/" ) );
- $this->assertFalse( $c->canServePath( "/bogus/path/" ) );
- $this->assertFalse( $c->canServePath( "/path" ) );
- $this->assertTrue( $c->canServePath( "/path/" ) );
-
- $this->assertTrue( $c->isUnExpired() );
-
- $this->assertEquals( "", $c->serializeToHttpRequest( "/path/", "www.example.net" ) );
- $this->assertEquals( "", $c->serializeToHttpRequest( "/", "www.example.com" ) );
- $this->assertEquals( "name=value", $c->serializeToHttpRequest( "/path/", "www.example.com" ) );
-
- $c = new MockCookie( "name", "value",
- array(
- "domain" => "www.example.com",
- "path" => "/path/",
- ) );
- $this->assertFalse( $c->canServeDomain( "example.com" ) );
- $this->assertFalse( $c->canServeDomain( "www.example.net" ) );
- $this->assertTrue( $c->canServeDomain( "www.example.com" ) );
-
- $c = new MockCookie( "name", "value",
- array(
- "domain" => ".example.com",
- "path" => "/path/",
- "expires" => "-1 day",
- ) );
- $this->assertFalse( $c->isUnExpired() );
- $this->assertEquals( "", $c->serializeToHttpRequest( "/path/", "www.example.com" ) );
-
- $c = new MockCookie( "name", "value",
- array(
- "domain" => ".example.com",
- "path" => "/path/",
- "expires" => "+1 day",
- ) );
- $this->assertTrue( $c->isUnExpired() );
- $this->assertEquals( "name=value", $c->serializeToHttpRequest( "/path/", "www.example.com" ) );
- }
-
- function testCookieJarSetCookie() {
- $cj = new CookieJar;
- $cj->setCookie( "name", "value",
- array(
- "domain" => ".example.com",
- "path" => "/path/",
- ) );
- $cj->setCookie( "name2", "value",
- array(
- "domain" => ".example.com",
- "path" => "/path/sub",
- ) );
- $cj->setCookie( "name3", "value",
- array(
- "domain" => ".example.com",
- "path" => "/",
- ) );
- $cj->setCookie( "name4", "value",
- array(
- "domain" => ".example.net",
- "path" => "/path/",
- ) );
- $cj->setCookie( "name5", "value",
- array(
- "domain" => ".example.net",
- "path" => "/path/",
- "expires" => "-1 day",
- ) );
-
- $this->assertEquals( "name4=value", $cj->serializeToHttpRequest( "/path/", "www.example.net" ) );
- $this->assertEquals( "name3=value", $cj->serializeToHttpRequest( "/", "www.example.com" ) );
- $this->assertEquals( "name=value; name3=value", $cj->serializeToHttpRequest( "/path/", "www.example.com" ) );
-
- $cj->setCookie( "name5", "value",
- array(
- "domain" => ".example.net",
- "path" => "/path/",
- "expires" => "+1 day",
- ) );
- $this->assertEquals( "name4=value; name5=value", $cj->serializeToHttpRequest( "/path/", "www.example.net" ) );
-
- $cj->setCookie( "name4", "value",
- array(
- "domain" => ".example.net",
- "path" => "/path/",
- "expires" => "-1 day",
- ) );
- $this->assertEquals( "name5=value", $cj->serializeToHttpRequest( "/path/", "www.example.net" ) );
- }
-
- function testParseResponseHeader() {
- $cj = new CookieJar;
-
- $h[] = "Set-Cookie: name4=value; domain=.example.com; path=/; expires=Mon, 09-Dec-2029 13:46:00 GMT";
- $cj->parseCookieResponseHeader( $h[0], "www.example.com" );
- $this->assertEquals( "name4=value", $cj->serializeToHttpRequest( "/", "www.example.com" ) );
-
- $h[] = "name4=value2; domain=.example.com; path=/path/; expires=Mon, 09-Dec-2029 13:46:00 GMT";
- $cj->parseCookieResponseHeader( $h[1], "www.example.com" );
- $this->assertEquals( "", $cj->serializeToHttpRequest( "/", "www.example.com" ) );
- $this->assertEquals( "name4=value2", $cj->serializeToHttpRequest( "/path/", "www.example.com" ) );
-
- $h[] = "name5=value3; domain=.example.com; path=/path/; expires=Mon, 09-Dec-2029 13:46:00 GMT";
- $cj->parseCookieResponseHeader( $h[2], "www.example.com" );
- $this->assertEquals( "name4=value2; name5=value3", $cj->serializeToHttpRequest( "/path/", "www.example.com" ) );
-
- $h[] = "name6=value3; domain=.example.net; path=/path/; expires=Mon, 09-Dec-2029 13:46:00 GMT";
- $cj->parseCookieResponseHeader( $h[3], "www.example.com" );
- $this->assertEquals( "", $cj->serializeToHttpRequest( "/path/", "www.example.net" ) );
-
- $h[] = "name6=value0; domain=.example.net; path=/path/; expires=Mon, 09-Dec-1999 13:46:00 GMT";
- $cj->parseCookieResponseHeader( $h[4], "www.example.net" );
- $this->assertEquals( "", $cj->serializeToHttpRequest( "/path/", "www.example.net" ) );
-
- $h[] = "name6=value4; domain=.example.net; path=/path/; expires=Mon, 09-Dec-2029 13:46:00 GMT";
- $cj->parseCookieResponseHeader( $h[5], "www.example.net" );
- $this->assertEquals( "name6=value4", $cj->serializeToHttpRequest( "/path/", "www.example.net" ) );
- }
-
- function runCookieRequests() {
- $r = MWHttpRequest::factory( "http://www.php.net/manual", array( 'followRedirects' => true ) );
- $r->execute();
-
- $jar = $r->getCookieJar();
- $this->assertThat( $jar, $this->isInstanceOf( 'CookieJar' ) );
-
- $serialized = $jar->serializeToHttpRequest( "/search?q=test", "www.php.net" );
- $this->assertRegExp( '/\bCOUNTRY=[^=;]+/', $serialized );
- $this->assertRegExp( '/\bLAST_LANG=[^=;]+/', $serialized );
- $this->assertEquals( '', $jar->serializeToHttpRequest( "/search?q=test", "www.php.com" ) );
- }
-
- function testCookieRequestDefault() {
- Http::$httpEngine = false;
- $this->runCookieRequests();
- }
- function testCookieRequestPhp() {
- if ( !self::$has_fopen ) {
- $this->markTestIncomplete( "This test requires allow_url_fopen=true." );
- }
-
- Http::$httpEngine = 'php';
- $this->runCookieRequests();
- }
- function testCookieRequestCurl() {
- if ( !self::$has_curl ) {
- $this->markTestIncomplete( "This test requires curl." );
- }
-
- Http::$httpEngine = 'curl';
- $this->runCookieRequests();
- }
-
/**
* Test Http::isValidURI()
- * @bug 27854 : Http::isValidURI is to lax
- *@dataProvider provideURI */
+ * @bug 27854 : Http::isValidURI is too lax
+ * @dataProvider provideURI
+ */
function testIsValidUri( $expect, $URI, $message = '' ) {
$this->assertEquals(
$expect,
@@ -567,7 +76,7 @@ class HttpTest extends MediaWikiTestCase {
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' ),
@@ -615,4 +124,57 @@ 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' );
+ # Forge a Location header
+ $h->setRespHeaders( 'location', array(
+ 'http://newsite/file.ext',
+ '/newfile.ext',
+ )
+ );
+ # Verify we correctly fix the Location
+ $this->assertEquals(
+ 'http://newsite/newfile.ext',
+ $h->getFinalUrl(),
+ "Relative file path Location: interpreted as full URL"
+ );
+
+ $h->setRespHeaders( 'location', array(
+ 'https://oldsite/file.ext'
+ )
+ );
+ $this->assertEquals(
+ 'https://oldsite/file.ext',
+ $h->getFinalUrl(),
+ "Location to the HTTPS version of the site"
+ );
+
+ $h->setRespHeaders( 'location', array(
+ '/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!")
+ );
+ }
+}
+
+/**
+ * Class to let us overwrite MWHttpREquest respHeaders variable
+ */
+class MWHttpRequestTester extends MWHttpRequest {
+ function setRespHeaders( $name, $value ) {
+ $this->respHeaders[$name] = $value ;
+ }
}
diff --git a/tests/phpunit/includes/IPTest.php b/tests/phpunit/includes/IPTest.php
index c77dd852..4397b879 100644
--- a/tests/phpunit/includes/IPTest.php
+++ b/tests/phpunit/includes/IPTest.php
@@ -1,5 +1,5 @@
<?php
-/*
+/**
* Tests for IP validity functions. Ported from /t/inc/IP.t by avar.
*/
@@ -43,20 +43,20 @@ class IPTest extends MediaWikiTestCase {
$this->assertFalse( IP::isIPv6( 'fc:100:::' ), 'IPv6 ending with a ":::"' );
$this->assertFalse( IP::isIPv6( 'fc:300' ), 'IPv6 with only 2 words' );
$this->assertFalse( IP::isIPv6( 'fc:100:300' ), 'IPv6 with only 3 words' );
-
+
$this->assertTrue( IP::isIPv6( 'fc:100::' ) );
$this->assertTrue( IP::isIPv6( 'fc:100:a::' ) );
$this->assertTrue( IP::isIPv6( 'fc:100:a:d::' ) );
$this->assertTrue( IP::isIPv6( 'fc:100:a:d:1::' ) );
$this->assertTrue( IP::isIPv6( 'fc:100:a:d:1:e::' ) );
$this->assertTrue( IP::isIPv6( 'fc:100:a:d:1:e:ac::' ) );
-
+
$this->assertFalse( IP::isIPv6( 'fc:100:a:d:1:e:ac:0::' ), 'IPv6 with 8 words ending with "::"' );
$this->assertFalse( IP::isIPv6( 'fc:100:a:d:1:e:ac:0:1::' ), 'IPv6 with 9 words ending with "::"' );
$this->assertFalse( IP::isIPv6( ':::' ) );
$this->assertFalse( IP::isIPv6( '::0:' ), 'IPv6 ending in a lone ":"' );
-
+
$this->assertTrue( IP::isIPv6( '::' ), 'IPv6 zero address' );
$this->assertTrue( IP::isIPv6( '::0' ) );
$this->assertTrue( IP::isIPv6( '::fc' ) );
@@ -66,14 +66,14 @@ class IPTest extends MediaWikiTestCase {
$this->assertTrue( IP::isIPv6( '::fc:100:a:d:1' ) );
$this->assertTrue( IP::isIPv6( '::fc:100:a:d:1:e' ) );
$this->assertTrue( IP::isIPv6( '::fc:100:a:d:1:e:ac' ) );
-
+
$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' );
$this->assertFalse( IP::isIPv6( ':fc::100' ), 'IPv6 starting with lone ":"' );
$this->assertFalse( IP::isIPv6( 'fc::100:' ), 'IPv6 ending with lone ":"' );
$this->assertFalse( IP::isIPv6( 'fc:::100' ), 'IPv6 with ":::" in the middle' );
-
+
$this->assertTrue( IP::isIPv6( 'fc::100' ), 'IPv6 with "::" and 2 words' );
$this->assertTrue( IP::isIPv6( 'fc::100:a' ), 'IPv6 with "::" and 3 words' );
$this->assertTrue( IP::isIPv6( 'fc::100:a:d', 'IPv6 with "::" and 4 words' ) );
@@ -83,7 +83,7 @@ class IPTest extends MediaWikiTestCase {
$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' );
@@ -135,11 +135,11 @@ class IPTest extends MediaWikiTestCase {
$this->assertFalse( IP::isValid( 'fc:100:::' ), 'IPv6 ending with a ":::"' );
$this->assertFalse( IP::isValid( 'fc:300' ), 'IPv6 with only 2 words' );
$this->assertFalse( IP::isValid( 'fc:100:300' ), 'IPv6 with only 3 words' );
-
+
$this->assertTrue( IP::isValid( 'fc:100::' ) );
$this->assertTrue( IP::isValid( 'fc:100:a:d:1:e::' ) );
$this->assertTrue( IP::isValid( 'fc:100:a:d:1:e:ac::' ) );
-
+
$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' );
@@ -147,7 +147,7 @@ class IPTest extends MediaWikiTestCase {
$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' );
-
+
$this->assertFalse( IP::isValid( 'fc:100:a:d:1:e:ac:0::' ), 'IPv6 with 8 words ending with "::"' );
$this->assertFalse( IP::isValid( 'fc:100:a:d:1:e:ac:0:1::' ), 'IPv6 with 9 words ending with "::"' );
}
@@ -276,7 +276,7 @@ class IPTest extends MediaWikiTestCase {
foreach ( $private as $p ) {
$this->assertFalse( IP::isPublic( $p ), "$p is not a public IP address" );
}
- $public = array( '2001:5c0:1000:a::133', 'fc::3' );
+ $public = array( '2001:5c0:1000:a::133', 'fc::3', '00FC::' );
foreach ( $public as $p ) {
$this->assertTrue( IP::isPublic( $p ), "$p is a public IP address" );
}
@@ -332,7 +332,7 @@ class IPTest extends MediaWikiTestCase {
$this->assertEquals( '0:0:0:0:0:0:FCCF:FAFF', IP::hexToOctet( 'FCCFFAFF' ) );
}
- /*
+ /**
* IP::parseCIDR() returns an array containing a signed IP address
* representing the network mask and the bit mask.
* @covers IP::parseCIDR
@@ -391,7 +391,7 @@ class IPTest extends MediaWikiTestCase {
}
/**
- * Issues there are most probably from IP::toHex() or IP::parseRange()
+ * Issues there are most probably from IP::toHex() or IP::parseRange()
* @covers IP::isInRange
* @dataProvider provideIPsAndRanges
*/
@@ -464,9 +464,9 @@ class IPTest extends MediaWikiTestCase {
*/
function testCombineHostAndPort( $expected, $input, $description ) {
list( $host, $port, $defaultPort ) = $input;
- $this->assertEquals(
- $expected,
- IP::combineHostAndPort( $host, $port, $defaultPort ),
+ $this->assertEquals(
+ $expected,
+ IP::combineHostAndPort( $host, $port, $defaultPort ),
$description );
}
diff --git a/tests/phpunit/includes/LocalFileTest.php b/tests/phpunit/includes/LocalFileTest.php
index e08d4d7e..5b26b89c 100644
--- a/tests/phpunit/includes/LocalFileTest.php
+++ b/tests/phpunit/includes/LocalFileTest.php
@@ -10,12 +10,21 @@ class LocalFileTest extends MediaWikiTestCase {
global $wgCapitalLinks;
$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',
+ 'lockManager' => 'fsLockManager',
+ 'containerPaths' => array(
+ 'cont1' => "/testdir/local-backend/tempimages/cont1",
+ 'cont2' => "/testdir/local-backend/tempimages/cont2"
+ )
+ ) )
);
$this->repo_hl0 = new LocalRepo( array( 'hashLevels' => 0 ) + $info );
$this->repo_hl2 = new LocalRepo( array( 'hashLevels' => 2 ) + $info );
@@ -44,17 +53,17 @@ class LocalFileTest extends MediaWikiTestCase {
}
function testGetArchivePath() {
- $this->assertEquals( '/testdir/archive', $this->file_hl0->getArchivePath() );
- $this->assertEquals( '/testdir/archive/a/a2', $this->file_hl2->getArchivePath() );
- $this->assertEquals( '/testdir/archive/!', $this->file_hl0->getArchivePath( '!' ) );
- $this->assertEquals( '/testdir/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() );
+ $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() {
- $this->assertEquals( '/testdir/thumb/Test!', $this->file_hl0->getThumbPath() );
- $this->assertEquals( '/testdir/thumb/a/a2/Test!', $this->file_hl2->getThumbPath() );
- $this->assertEquals( '/testdir/thumb/Test!/x', $this->file_hl0->getThumbPath( 'x' ) );
- $this->assertEquals( '/testdir/thumb/a/a2/Test!/x', $this->file_hl2->getThumbPath( 'x' ) );
+ $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() {
diff --git a/tests/phpunit/includes/MWNamespaceTest.php b/tests/phpunit/includes/MWNamespaceTest.php
index 462afc24..6b231fc5 100644
--- a/tests/phpunit/includes/MWNamespaceTest.php
+++ b/tests/phpunit/includes/MWNamespaceTest.php
@@ -1,7 +1,7 @@
<?php
/**
- * @author Ashar Voultoiz
- * @copyright Copyright © 2011, Ashar Voultoiz
+ * @author Antoine Musso
+ * @copyright Copyright © 2011, Antoine Musso
* @file
*/
@@ -39,40 +39,55 @@ class MWNamespaceTest extends MediaWikiTestCase {
/**
* Please make sure to change testIsTalk() if you change the assertions below
*/
- public function testIsMain() {
+ public function testIsSubject() {
// Special namespaces
- $this->assertTrue( MWNamespace::isMain( NS_MEDIA ) );
- $this->assertTrue( MWNamespace::isMain( NS_SPECIAL ) );
+ $this->assertIsSubject( NS_MEDIA );
+ $this->assertIsSubject( NS_SPECIAL );
// Subject pages
- $this->assertTrue( MWNamespace::isMain( NS_MAIN ) );
- $this->assertTrue( MWNamespace::isMain( NS_USER ) );
- $this->assertTrue( MWNamespace::isMain( 100 ) ); # user defined
+ $this->assertIsSubject( NS_MAIN );
+ $this->assertIsSubject( NS_USER );
+ $this->assertIsSubject( 100 ); # user defined
// Talk pages
- $this->assertFalse( MWNamespace::isMain( NS_TALK ) );
- $this->assertFalse( MWNamespace::isMain( NS_USER_TALK ) );
- $this->assertFalse( MWNamespace::isMain( 101 ) ); # user defined
+ $this->assertIsNotSubject( NS_TALK );
+ $this->assertIsNotSubject( NS_USER_TALK );
+ $this->assertIsNotSubject( 101 ); # user defined
+
+ // Back compat
+ $this->assertTrue( MWNamespace::isMain( NS_MAIN ) == MWNamespace::isSubject( NS_MAIN ) );
+ $this->assertTrue( MWNamespace::isMain( NS_USER_TALK ) == MWNamespace::isSubject( NS_USER_TALK ) );
}
/**
- * Reverse of testIsMain().
- * Please update testIsMain() if you change assertions below
+ * Reverse of testIsSubject().
+ * Please update testIsSubject() if you change assertions below
*/
public function testIsTalk() {
// Special namespaces
- $this->assertFalse( MWNamespace::isTalk( NS_MEDIA ) );
- $this->assertFalse( MWNamespace::isTalk( NS_SPECIAL ) );
+ $this->assertIsNotTalk( NS_MEDIA );
+ $this->assertIsNotTalk( NS_SPECIAL );
// Subject pages
- $this->assertFalse( MWNamespace::isTalk( NS_MAIN ) );
- $this->assertFalse( MWNamespace::isTalk( NS_USER ) );
- $this->assertFalse( MWNamespace::isTalk( 100 ) ); # user defined
+ $this->assertIsNotTalk( NS_MAIN );
+ $this->assertIsNotTalk( NS_USER );
+ $this->assertIsNotTalk( 100 ); # user defined
// Talk pages
- $this->assertTrue( MWNamespace::isTalk( NS_TALK ) );
- $this->assertTrue( MWNamespace::isTalk( NS_USER_TALK ) );
- $this->assertTrue( MWNamespace::isTalk( 101 ) ); # user defined
+ $this->assertIsTalk( NS_TALK );
+ $this->assertIsTalk( NS_USER_TALK );
+ $this->assertIsTalk( 101 ); # user defined
+ }
+
+ /**
+ */
+ public function testGetSubject() {
+ // Special namespaces are their own subjects
+ $this->assertEquals( NS_MEDIA, MWNamespace::getSubject( NS_MEDIA ) );
+ $this->assertEquals( NS_SPECIAL, MWNamespace::getSubject( NS_SPECIAL ) );
+
+ $this->assertEquals( NS_MAIN, MWNamespace::getSubject( NS_TALK ) );
+ $this->assertEquals( NS_USER, MWNamespace::getSubject( NS_USER_TALK ) );
}
/**
@@ -82,6 +97,9 @@ class MWNamespaceTest extends MediaWikiTestCase {
*/
public function testGetTalk() {
$this->assertEquals( NS_TALK, MWNamespace::getTalk( NS_MAIN ) );
+ $this->assertEquals( NS_TALK, MWNamespace::getTalk( NS_TALK ) );
+ $this->assertEquals( NS_USER_TALK, MWNamespace::getTalk( NS_USER ) );
+ $this->assertEquals( NS_USER_TALK, MWNamespace::getTalk( NS_USER_TALK ) );
}
/**
@@ -93,7 +111,7 @@ class MWNamespaceTest extends MediaWikiTestCase {
$this->assertNull( MWNamespace::getTalk( NS_MEDIA ) );
}
- /**
+ /**
* Exceptions with getTalk()
* NS_SPECIAL does not have talk pages. MediaWiki raise an exception for them.
* @expectedException MWException
@@ -108,7 +126,7 @@ class MWNamespaceTest extends MediaWikiTestCase {
* the function testGetAssociatedExceptions()
*/
public function testGetAssociated() {
- $this->assertEquals( NS_TALK, MWNamespace::getAssociated( NS_MAIN ) );
+ $this->assertEquals( NS_TALK, MWNamespace::getAssociated( NS_MAIN ) );
$this->assertEquals( NS_MAIN, MWNamespace::getAssociated( NS_TALK ) );
}
@@ -131,80 +149,122 @@ class MWNamespaceTest extends MediaWikiTestCase {
}
/**
- */
- public function testGetSubject() {
- // Special namespaces are their own subjects
- $this->assertEquals( NS_MEDIA, MWNamespace::getSubject( NS_MEDIA ) );
- $this->assertEquals( NS_SPECIAL, MWNamespace::getSubject( NS_SPECIAL ) );
-
- $this->assertEquals( NS_MAIN, MWNamespace::getSubject( NS_TALK ) );
- $this->assertEquals( NS_USER, MWNamespace::getSubject( NS_USER_TALK ) );
- }
-
- /**
* @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
+ * Note if we add a namespace registration system with keys like 'MAIN'
+ * we should add tests here for equivilance on things like 'MAIN' == 0
+ * and 'MAIN' == NS_MAIN.
+ */
+ public function testEquals() {
+ $this->assertTrue( MWNamespace::equals( NS_MAIN, NS_MAIN ) );
+ $this->assertTrue( MWNamespace::equals( NS_MAIN, 0 ) ); // In case we make NS_MAIN 'MAIN'
+ $this->assertTrue( MWNamespace::equals( NS_USER, NS_USER ) );
+ $this->assertTrue( MWNamespace::equals( NS_USER, 2 ) );
+ $this->assertTrue( MWNamespace::equals( NS_USER_TALK, NS_USER_TALK ) );
+ $this->assertTrue( MWNamespace::equals( NS_SPECIAL, NS_SPECIAL ) );
+ $this->assertFalse( MWNamespace::equals( NS_MAIN, NS_TALK ) );
+ $this->assertFalse( MWNamespace::equals( NS_USER, NS_USER_TALK ) );
+ $this->assertFalse( MWNamespace::equals( NS_PROJECT, NS_TEMPLATE ) );
+ }
+
+ /**
+ * Test MWNamespace::subjectEquals
+ */
+ public function testSubjectEquals() {
+ $this->assertSameSubject( NS_MAIN, NS_MAIN );
+ $this->assertSameSubject( NS_MAIN, 0 ); // In case we make NS_MAIN 'MAIN'
+ $this->assertSameSubject( NS_USER, NS_USER );
+ $this->assertSameSubject( NS_USER, 2 );
+ $this->assertSameSubject( NS_USER_TALK, NS_USER_TALK );
+ $this->assertSameSubject( NS_SPECIAL, NS_SPECIAL );
+ $this->assertSameSubject( NS_MAIN, NS_TALK );
+ $this->assertSameSubject( NS_USER, NS_USER_TALK );
+
+ $this->assertDifferentSubject( NS_PROJECT, NS_TEMPLATE );
+ $this->assertDifferentSubject( NS_SPECIAL, NS_MAIN );
+ }
+
+ public function testSpecialAndMediaAreDifferentSubjects() {
+ $this->assertDifferentSubject(
+ NS_MEDIA, NS_SPECIAL,
+ "NS_MEDIA and NS_SPECIAL are different subject namespaces"
+ );
+ $this->assertDifferentSubject(
+ 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.'
);
}
-
+*/
/**
* @todo Implement testGetCanonicalIndex().
*/
+/*
public function testGetCanonicalIndex() {
// Remove the following lines when you implement this test.
$this->markTestIncomplete(
'This test has not been implemented yet. Rely on $wgCanonicalNamespaces.'
);
}
-
+*/
/**
* @todo Implement testGetValidNamespaces().
*/
+/*
public function testGetValidNamespaces() {
// Remove the following lines when you implement this test.
$this->markTestIncomplete(
'This test has not been implemented yet. Rely on $wgCanonicalNamespaces.'
);
}
-
+*/
/**
*/
public function testCanTalk() {
- $this->assertFalse( MWNamespace::canTalk( NS_MEDIA ) );
- $this->assertFalse( MWNamespace::canTalk( NS_SPECIAL ) );
+ $this->assertCanNotTalk( NS_MEDIA );
+ $this->assertCanNotTalk( NS_SPECIAL );
- $this->assertTrue( MWNamespace::canTalk( NS_MAIN ) );
- $this->assertTrue( MWNamespace::canTalk( NS_TALK ) );
- $this->assertTrue( MWNamespace::canTalk( NS_USER ) );
- $this->assertTrue( MWNamespace::canTalk( NS_USER_TALK ) );
+ $this->assertCanTalk( NS_MAIN );
+ $this->assertCanTalk( NS_TALK );
+ $this->assertCanTalk( NS_USER );
+ $this->assertCanTalk( NS_USER_TALK );
// User defined namespaces
- $this->assertTrue( MWNamespace::canTalk( 100 ) );
- $this->assertTrue( MWNamespace::canTalk( 101 ) );
+ $this->assertCanTalk( 100 );
+ $this->assertCanTalk( 101 );
}
/**
@@ -212,16 +272,47 @@ class MWNamespaceTest extends MediaWikiTestCase {
public function testIsContent() {
// NS_MAIN is a content namespace per DefaultSettings.php
// and per function definition.
- $this->assertTrue( MWNamespace::isContent( NS_MAIN ) );
+ $this->assertIsContent( NS_MAIN );
+
+ global $wgContentNamespaces;
+
+ $saved = $wgContentNamespaces;
+
+ $wgContentNamespaces[] = NS_MAIN;
+ $this->assertIsContent( NS_MAIN );
// Other namespaces which are not expected to be content
- $this->assertFalse( MWNamespace::isContent( NS_MEDIA ) );
- $this->assertFalse( MWNamespace::isContent( NS_SPECIAL ) );
- $this->assertFalse( MWNamespace::isContent( NS_TALK ) );
- $this->assertFalse( MWNamespace::isContent( NS_USER ) );
- $this->assertFalse( MWNamespace::isContent( NS_CATEGORY ) );
- // User defined namespace:
- $this->assertFalse( MWNamespace::isContent( 100 ) );
+ 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_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;
}
/**
@@ -231,47 +322,47 @@ class MWNamespaceTest extends MediaWikiTestCase {
public function testIsContentWithAdditionsInWgContentNamespaces() {
// NS_MAIN is a content namespace per DefaultSettings.php
// and per function definition.
- $this->assertTrue( MWNamespace::isContent( NS_MAIN ) );
+ $this->assertIsContent( NS_MAIN );
// Tests that user defined namespace #252 is not content:
- $this->assertFalse( MWNamespace::isContent( 252 ) );
+ $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->assertTrue( MWNamespace::isContent( 252 ) );
+ $this->assertIsContent( 252 );
// Makes sure NS_MAIN was not impacted
- $this->assertTrue( MWNamespace::isContent( NS_MAIN ) );
+ $this->assertIsContent( NS_MAIN );
// Restore global
$wgContentNamespaces = $savedGlobal;
// Verify namespaces after global restauration
- $this->assertTrue( MWNamespace::isContent( NS_MAIN ) );
- $this->assertFalse( MWNamespace::isContent( 252 ) );
+ $this->assertIsContent( NS_MAIN );
+ $this->assertIsNotContent( 252 );
}
public function testIsWatchable() {
// Specials namespaces are not watchable
- $this->assertFalse( MWNamespace::isWatchable( NS_MEDIA ) );
- $this->assertFalse( MWNamespace::isWatchable( NS_SPECIAL ) );
+ $this->assertIsNotWatchable( NS_MEDIA );
+ $this->assertIsNotWatchable( NS_SPECIAL );
// Core defined namespaces are watchables
- $this->assertTrue( MWNamespace::isWatchable( NS_MAIN ) );
- $this->assertTrue( MWNamespace::isWatchable( NS_TALK ) );
+ $this->assertIsWatchable( NS_MAIN );
+ $this->assertIsWatchable( NS_TALK );
// Additional, user defined namespaces are watchables
- $this->assertTrue( MWNamespace::isWatchable( 100 ) );
- $this->assertTrue( MWNamespace::isWatchable( 101 ) );
+ $this->assertIsWatchable( 100 );
+ $this->assertIsWatchable( 101 );
}
public function testHasSubpages() {
// Special namespaces:
- $this->assertFalse( MWNamespace::hasSubpages( NS_MEDIA ) );
- $this->assertFalse( MWNamespace::hasSubpages( NS_SPECIAL ) );
+ $this->assertHasNotSubpages( NS_MEDIA );
+ $this->assertHasNotSubpages( NS_SPECIAL );
// namespaces without subpages
# save up global
@@ -282,12 +373,12 @@ class MWNamespaceTest extends MediaWikiTestCase {
unset( $wgNamespacesWithSubpages[NS_MAIN] );
}
- $this->assertFalse( MWNamespace::hasSubpages( NS_MAIN ) );
+ $this->assertHasNotSubpages( NS_MAIN );
$wgNamespacesWithSubpages[NS_MAIN] = true;
- $this->assertTrue( MWNamespace::hasSubpages( NS_MAIN ) );
+ $this->assertHasSubpages( NS_MAIN );
$wgNamespacesWithSubpages[NS_MAIN] = false;
- $this->assertFalse( MWNamespace::hasSubpages( NS_MAIN ) );
+ $this->assertHasNotSubpages( NS_MAIN );
# restore global
if( $saved !== null ) {
@@ -295,9 +386,9 @@ class MWNamespaceTest extends MediaWikiTestCase {
}
// Some namespaces with subpages
- $this->assertTrue( MWNamespace::hasSubpages( NS_TALK ) );
- $this->assertTrue( MWNamespace::hasSubpages( NS_USER ) );
- $this->assertTrue( MWNamespace::hasSubpages( NS_USER_TALK ) );
+ $this->assertHasSubpages( NS_TALK );
+ $this->assertHasSubpages( NS_USER );
+ $this->assertHasSubpages( NS_USER_TALK );
}
/**
@@ -311,6 +402,7 @@ class MWNamespaceTest extends MediaWikiTestCase {
global $wgContentNamespaces;
+ $saved = $wgContentNamespaces;
# test !is_array( $wgcontentNamespaces )
$wgContentNamespaces = '';
$this->assertEquals( NS_MAIN, MWNamespace::getcontentNamespaces() );
@@ -330,7 +422,7 @@ class MWNamespaceTest extends MediaWikiTestCase {
$this->assertEquals(
array( NS_MAIN, NS_USER, NS_CATEGORY ),
MWNamespace::getcontentNamespaces(),
- 'NS_MAIN is forced in wgContentNamespaces even if unwanted'
+ 'NS_MAIN is forced in $wgContentNamespaces even if unwanted'
);
# test other cases, return $wgcontentNamespaces as is
@@ -346,6 +438,7 @@ class MWNamespaceTest extends MediaWikiTestCase {
MWNamespace::getcontentNamespaces()
);
+ $wgContentNamespaces = $saved;
}
/**
@@ -361,14 +454,14 @@ class MWNamespaceTest extends MediaWikiTestCase {
);
// Boths are capitalized by default
- $this->assertTrue( MWNamespace::isCapitalized( NS_MEDIA ) );
- $this->assertTrue( MWNamespace::isCapitalized( NS_FILE ) );
+ $this->assertIsCapitalized( NS_MEDIA );
+ $this->assertIsCapitalized( NS_FILE );
// Always capitalized namespaces
// @see MWNamespace::$alwaysCapitalizedNamespaces
- $this->assertTrue( MWNamespace::isCapitalized( NS_SPECIAL ) );
- $this->assertTrue( MWNamespace::isCapitalized( NS_USER ) );
- $this->assertTrue( MWNamespace::isCapitalized( NS_MEDIAWIKI ) );
+ $this->assertIsCapitalized( NS_SPECIAL );
+ $this->assertIsCapitalized( NS_USER );
+ $this->assertIsCapitalized( NS_MEDIAWIKI );
}
/**
@@ -389,17 +482,17 @@ class MWNamespaceTest extends MediaWikiTestCase {
$savedGlobal = $wgCapitalLinks;
$wgCapitalLinks = true;
- $this->assertTrue( MWNamespace::isCapitalized( NS_PROJECT ) );
- $this->assertTrue( MWNamespace::isCapitalized( NS_PROJECT_TALK ) );
+ $this->assertIsCapitalized( NS_PROJECT );
+ $this->assertIsCapitalized( NS_PROJECT_TALK );
$wgCapitalLinks = false;
// hardcoded namespaces (see above function) are still capitalized:
- $this->assertTrue( MWNamespace::isCapitalized( NS_SPECIAL ) );
- $this->assertTrue( MWNamespace::isCapitalized( NS_USER ) );
- $this->assertTrue( MWNamespace::isCapitalized( NS_MEDIAWIKI ) );
+ $this->assertIsCapitalized( NS_SPECIAL );
+ $this->assertIsCapitalized( NS_USER );
+ $this->assertIsCapitalized( NS_MEDIAWIKI );
// setting is correctly applied
- $this->assertFalse( MWNamespace::isCapitalized( NS_PROJECT ) );
- $this->assertFalse( MWNamespace::isCapitalized( NS_PROJECT_TALK ) );
+ $this->assertIsNotCapitalized( NS_PROJECT );
+ $this->assertIsNotCapitalized( NS_PROJECT_TALK );
// reset global state:
$wgCapitalLinks = $savedGlobal;
@@ -417,28 +510,28 @@ class MWNamespaceTest extends MediaWikiTestCase {
$savedGlobal = $wgCapitalLinkOverrides;
// Test default settings
- $this->assertTrue( MWNamespace::isCapitalized( NS_PROJECT ) );
- $this->assertTrue( MWNamespace::isCapitalized( NS_PROJECT_TALK ) );
+ $this->assertIsCapitalized( NS_PROJECT );
+ $this->assertIsCapitalized( NS_PROJECT_TALK );
// hardcoded namespaces (see above function) are capitalized:
- $this->assertTrue( MWNamespace::isCapitalized( NS_SPECIAL ) );
- $this->assertTrue( MWNamespace::isCapitalized( NS_USER ) );
- $this->assertTrue( MWNamespace::isCapitalized( NS_MEDIAWIKI ) );
+ $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_MEDIAWIKI] = false;
- $this->assertTrue( MWNamespace::isCapitalized( NS_SPECIAL ) );
- $this->assertTrue( MWNamespace::isCapitalized( NS_USER ) );
- $this->assertTrue( MWNamespace::isCapitalized( NS_MEDIAWIKI ) );
+ $this->assertIsCapitalized( NS_SPECIAL );
+ $this->assertIsCapitalized( NS_USER );
+ $this->assertIsCapitalized( NS_MEDIAWIKI );
$wgCapitalLinkOverrides = $savedGlobal;
$wgCapitalLinkOverrides[NS_PROJECT] = false;
- $this->assertFalse( MWNamespace::isCapitalized( NS_PROJECT ) );
+ $this->assertIsNotCapitalized( NS_PROJECT );
$wgCapitalLinkOverrides[NS_PROJECT] = true ;
- $this->assertTrue( MWNamespace::isCapitalized( NS_PROJECT ) );
+ $this->assertIsCapitalized( NS_PROJECT );
unset( $wgCapitalLinkOverrides[NS_PROJECT] );
- $this->assertTrue( MWNamespace::isCapitalized( NS_PROJECT ) );
+ $this->assertIsCapitalized( NS_PROJECT );
// reset global state:
$wgCapitalLinkOverrides = $savedGlobal;
@@ -456,5 +549,45 @@ class MWNamespaceTest extends MediaWikiTestCase {
$this->assertFalse( MWNamespace::hasGenderDistinction( NS_TALK ) );
}
+
+ ####### HELPERS ###########################################################
+ function __call( $method, $args ) {
+ // Call the real method if it exists
+ 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 ) ) {
+ # Interprets arguments:
+ $ns = $args[0];
+ $msg = isset($args[1]) ? $args[1] : " dummy message";
+
+ # Forge the namespace constant name:
+ if( $ns === 0 ) {
+ $ns_name = "NS_MAIN";
+ } else {
+ $ns_name = "NS_" . strtoupper( MWNamespace::getCanonicalName( $ns ) );
+ }
+ # ... and the MWNamespace method name
+ $nsMethod = strtolower( $m[1] ) . $m[3];
+
+ $expect = ($m[2] === '');
+ $expect_name = $expect ? 'TRUE' : 'FALSE';
+
+ return $this->assertEquals( $expect,
+ MWNamespace::$nsMethod( $ns, $msg ),
+ "MWNamespace::$nsMethod( $ns_name ) should returns $expect_name"
+ );
+ }
+
+ throw new Exception( __METHOD__ . " could not find a method named $method\n" );
+ }
+
+ 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 45c02bbe..295b6d74 100644
--- a/tests/phpunit/includes/MessageTest.php
+++ b/tests/phpunit/includes/MessageTest.php
@@ -47,7 +47,7 @@ class MessageTest extends MediaWikiLangTestCase {
$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'] );
diff --git a/tests/phpunit/includes/ParserOptionsTest.php b/tests/phpunit/includes/ParserOptionsTest.php
index 58c89146..59c955fe 100644
--- a/tests/phpunit/includes/ParserOptionsTest.php
+++ b/tests/phpunit/includes/ParserOptionsTest.php
@@ -6,10 +6,9 @@ class ParserOptionsTest extends MediaWikiTestCase {
private $pcache;
function setUp() {
- ParserTest::setUp(); //reuse setup from parser tests
global $wgContLang, $wgUser, $wgLanguageCode;
$wgContLang = Language::factory( $wgLanguageCode );
- $this->popts = new ParserOptions( $wgUser );
+ $this->popts = ParserOptions::newFromUserAndLang( $wgUser, $wgContLang );
$this->pcache = ParserCache::singleton();
}
@@ -26,11 +25,11 @@ class ParserOptionsTest extends MediaWikiTestCase {
$wgUseDynamicDates = true;
$title = Title::newFromText( "Some test article" );
- $article = new Article( $title );
+ $page = WikiPage::factory( $title );
- $pcacheKeyBefore = $this->pcache->getKey( $article, $this->popts );
+ $pcacheKeyBefore = $this->pcache->getKey( $page, $this->popts );
$this->assertNotNull( $this->popts->getDateFormat() );
- $pcacheKeyAfter = $this->pcache->getKey( $article, $this->popts );
+ $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
new file mode 100644
index 00000000..f6274584
--- /dev/null
+++ b/tests/phpunit/includes/PathRouterTest.php
@@ -0,0 +1,254 @@
+<?php
+/**
+ * Tests for the PathRouter parsing
+ */
+
+class PathRouterTest extends MediaWikiTestCase {
+
+ public function setUp() {
+ $router = new PathRouter;
+ $router->add("/wiki/$1");
+ $this->basicRouter = $router;
+ }
+
+ /**
+ * Test basic path parsing
+ */
+ public function testBasic() {
+ $matches = $this->basicRouter->parse( "/wiki/Foo" );
+ $this->assertEquals( $matches, array( 'title' => "Foo" ) );
+ }
+
+ /**
+ * Test loose path auto-$1
+ */
+ public function testLoose() {
+ $router = new PathRouter;
+ $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
+ $matches = $router->parse( "/wiki/Foo" );
+ $this->assertEquals( $matches, array( 'title' => "Foo" ) );
+
+ $router = new PathRouter;
+ $router->add("/wiki/"); # Should be the same as /wiki/$1
+ $matches = $router->parse( "/wiki/Foo" );
+ $this->assertEquals( $matches, array( 'title' => "Foo" ) );
+ }
+
+ /**
+ * Test to ensure that path is based on specifity, not order
+ */
+ public function testOrder() {
+ $router = new PathRouter;
+ $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");
+ $matches = $router->parse( "/a/Foo" );
+ $this->assertEquals( $matches, array( 'title' => "Foo" ) );
+ }
+
+ /**
+ * Test the handling of key based arrays with a url parameter
+ */
+ public function testKeyParameter() {
+ $router = new PathRouter;
+ $router->add( array( 'edit' => "/edit/$1" ), array( 'action' => '$key' ) );
+ $matches = $router->parse( "/edit/Foo" );
+ $this->assertEquals( $matches, array( 'title' => "Foo", 'action' => 'edit' ) );
+ }
+
+ /**
+ * Test the handling of $2 inside paths
+ */
+ public function testAdditionalParameter() {
+ // Basic $2
+ $router = new PathRouter;
+ $router->add( '/$2/$1', array( 'test' => '$2' ) );
+ $matches = $router->parse( "/asdf/Foo" );
+ $this->assertEquals( $matches, array( 'title' => "Foo", 'test' => 'asdf' ) );
+ }
+
+ /**
+ * Test additional restricted value parameter
+ */
+ public function testRestrictedValue() {
+ $router = new PathRouter;
+ $router->add( '/$2/$1',
+ array( 'test' => '$2' ),
+ array( '$2' => array( 'a', 'b' ) )
+ );
+ $router->add( '/$2/$1',
+ array( 'test2' => '$2' ),
+ array( '$2' => 'c' )
+ );
+ $router->add( '/$1' );
+
+ $matches = $router->parse( "/asdf/Foo" );
+ $this->assertEquals( $matches, array( 'title' => "asdf/Foo" ) );
+
+ $matches = $router->parse( "/a/Foo" );
+ $this->assertEquals( $matches, array( 'title' => "Foo", 'test' => 'a' ) );
+
+ $matches = $router->parse( "/c/Foo" );
+ $this->assertEquals( $matches, array( 'title' => "Foo", 'test2' => 'c' ) );
+ }
+
+ public function callbackForTest( &$matches, $data ) {
+ $matches['x'] = $data['$1'];
+ $matches['foo'] = $data['foo'];
+ }
+
+ public function testCallback() {
+ $router = new PathRouter;
+ $router->add( "/$1",
+ array( 'a' => 'b', 'data:foo' => 'bar' ),
+ array( 'callback' => array( $this, 'callbackForTest' ) )
+ );
+ $matches = $router->parse( '/Foo' );
+ $this->assertEquals( $matches, array(
+ 'title' => "Foo",
+ 'x' => 'Foo',
+ 'a' => 'b',
+ 'foo' => 'bar'
+ ) );
+ }
+
+ /**
+ * Test to ensure that matches are not made if a parameter expects nonexistent input
+ */
+ public function testFail() {
+ $router = new PathRouter;
+ $router->add( "/wiki/$1", array( 'title' => "$1$2" ) );
+ $matches = $router->parse( "/wiki/A" );
+ $this->assertEquals( array(), $matches );
+ }
+
+ /**
+ * Test to ensure weight of paths is handled correctly
+ */
+ public function testWeight() {
+ $router = new PathRouter;
+ $router->addStrict( "/Bar", array( 'ping' => 'pong' ) );
+ $router->add( "/asdf-$1", array( 'title' => 'qwerty-$1' ) );
+ $router->add( "/$1" );
+ $router->add( "/qwerty-$1", array( 'title' => 'asdf-$1' ) );
+ $router->addStrict( "/Baz", array( 'marco' => 'polo' ) );
+ $router->add( "/a/$1" );
+ $router->add( "/asdf/$1" );
+ $router->add( "/$2/$1", array( 'unrestricted' => '$2' ) );
+ $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 ) {
+ $this->assertEquals( $router->parse( $path ), $result );
+ }
+ }
+
+ /**
+ * Make sure the router handles titles like Special:Recentchanges correctly
+ */
+ public function testSpecial() {
+ $matches = $this->basicRouter->parse( "/wiki/Special:Recentchanges" );
+ $this->assertEquals( $matches, array( 'title' => "Special:Recentchanges" ) );
+ }
+
+ /**
+ * Make sure the router decodes urlencoding properly
+ */
+ public function testUrlencoding() {
+ $matches = $this->basicRouter->parse( "/wiki/Title_With%20Space" );
+ $this->assertEquals( $matches, array( 'title' => "Title_With Space" ) );
+ }
+
+ public function dataRegexpChars() {
+ return array(
+ array( "$" ),
+ array( "$1" ),
+ array( "\\" ),
+ array( "\\$1" ),
+ );
+ }
+
+ /**
+ * Make sure the router doesn't break on special characters like $ used in regexp replacements
+ * @dataProvider dataRegexpChars
+ */
+ public function testRegexpChars( $char ) {
+ $matches = $this->basicRouter->parse( "/wiki/$char" );
+ $this->assertEquals( $matches, array( 'title' => "$char" ) );
+ }
+
+ /**
+ * Make sure the router handles characters like +&() properly
+ */
+ public function testCharacters() {
+ $matches = $this->basicRouter->parse( "/wiki/Plus+And&Dollar\\Stuff();[]{}*" );
+ $this->assertEquals( $matches, array( 'title' => "Plus+And&Dollar\\Stuff();[]{}*" ) );
+ }
+
+ /**
+ * Make sure the router handles unicode characters correctly
+ * @depends testSpecial
+ * @depends testUrlencoding
+ * @depends testCharacters
+ */
+ public function testUnicode() {
+ $matches = $this->basicRouter->parse( "/wiki/Spécial:Modifications_récentes" );
+ $this->assertEquals( $matches, array( 'title' => "Spécial:Modifications_récentes" ) );
+
+ $matches = $this->basicRouter->parse( "/wiki/Sp%C3%A9cial:Modifications_r%C3%A9centes" );
+ $this->assertEquals( $matches, array( 'title' => "Spécial:Modifications_récentes" ) );
+ }
+
+ /**
+ * Ensure the router doesn't choke on long paths.
+ */
+ public function testLength() {
+ $matches = $this->basicRouter->parse( "/wiki/Lorem_ipsum_dolor_sit_amet,_consectetur_adipisicing_elit,_sed_do_eiusmod_tempor_incididunt_ut_labore_et_dolore_magna_aliqua._Ut_enim_ad_minim_veniam,_quis_nostrud_exercitation_ullamco_laboris_nisi_ut_aliquip_ex_ea_commodo_consequat._Duis_aute_irure_dolor_in_reprehenderit_in_voluptate_velit_esse_cillum_dolore_eu_fugiat_nulla_pariatur._Excepteur_sint_occaecat_cupidatat_non_proident,_sunt_in_culpa_qui_officia_deserunt_mollit_anim_id_est_laborum." );
+ $this->assertEquals( $matches, array( 'title' => "Lorem_ipsum_dolor_sit_amet,_consectetur_adipisicing_elit,_sed_do_eiusmod_tempor_incididunt_ut_labore_et_dolore_magna_aliqua._Ut_enim_ad_minim_veniam,_quis_nostrud_exercitation_ullamco_laboris_nisi_ut_aliquip_ex_ea_commodo_consequat._Duis_aute_irure_dolor_in_reprehenderit_in_voluptate_velit_esse_cillum_dolore_eu_fugiat_nulla_pariatur._Excepteur_sint_occaecat_cupidatat_non_proident,_sunt_in_culpa_qui_officia_deserunt_mollit_anim_id_est_laborum." ) );
+ }
+
+
+ /**
+ * Ensure that the php passed site of parameter values are not urldecoded
+ */
+ public function testPatternUrlencoding() {
+ $router = new PathRouter;
+ $router->add( "/wiki/$1", array( 'title' => '%20:$1' ) );
+ $matches = $router->parse( "/wiki/Foo" );
+ $this->assertEquals( $matches, array( 'title' => '%20:Foo' ) );
+ }
+
+ /**
+ * Ensure that raw parameter values do not have any variable replacements or urldecoding
+ */
+ public function testRawParamValue() {
+ $router = new PathRouter;
+ $router->add( "/wiki/$1", array( 'title' => array( 'value' => 'bar%20$1' ) ) );
+ $matches = $router->parse( "/wiki/Foo" );
+ $this->assertEquals( $matches, array( 'title' => 'bar%20$1' ) );
+ }
+
+}
diff --git a/tests/phpunit/includes/Providers.php b/tests/phpunit/includes/Providers.php
index 02898673..f451f8a0 100644
--- a/tests/phpunit/includes/Providers.php
+++ b/tests/phpunit/includes/Providers.php
@@ -2,8 +2,8 @@
/**
* Generic providers for the MediaWiki PHPUnit test suite
*
- * @author Ashar Voultoiz
- * @copyright Copyright © 2011, Ashar Voultoiz
+ * @author Antoine Musso
+ * @copyright Copyright © 2011, Antoine Musso
* @file
*/
diff --git a/tests/phpunit/includes/ResourceLoaderTest.php b/tests/phpunit/includes/ResourceLoaderTest.php
index 30a69c5e..ab704839 100644
--- a/tests/phpunit/includes/ResourceLoaderTest.php
+++ b/tests/phpunit/includes/ResourceLoaderTest.php
@@ -1,6 +1,6 @@
<?php
-class ResourceLoaderTest extends PHPUnit_Framework_TestCase {
+class ResourceLoaderTest extends MediaWikiTestCase {
protected static $resourceLoaderRegisterModulesHook;
diff --git a/tests/phpunit/includes/SanitizerTest.php b/tests/phpunit/includes/SanitizerTest.php
index 40d6cf77..b76aa5c7 100644
--- a/tests/phpunit/includes/SanitizerTest.php
+++ b/tests/phpunit/includes/SanitizerTest.php
@@ -109,5 +109,48 @@ class SanitizerTest extends MediaWikiTestCase {
$this->assertEquals( Sanitizer::decodeTagAttributes( 'foo=&amp;&quot;' ), array( 'foo' => '&"' ), 'Special chars can be provided as entities' );
$this->assertEquals( Sanitizer::decodeTagAttributes( 'foo=&foobar;' ), array( 'foo' => '&foobar;' ), 'Entity-like items are accepted' );
}
+
+ function testDeprecatedAttributes() {
+ $GLOBALS['wgCleanupPresentationalAttributes'] = true;
+ $this->assertEquals( Sanitizer::fixTagAttributes( 'clear="left"', 'br' ), ' style="clear: left;"', 'Deprecated attributes are converted to styles when enabled.' );
+ $this->assertEquals( Sanitizer::fixTagAttributes( 'clear="all"', 'br' ), ' style="clear: both;"', 'clear=all is converted to clear: both; not clear: all;' );
+ $this->assertEquals( Sanitizer::fixTagAttributes( 'CLEAR="ALL"', 'br' ), ' style="clear: both;"', 'clear=ALL is not treated differently from clear=all' );
+ $this->assertEquals( Sanitizer::fixTagAttributes( 'width="100"', 'td' ), ' style="width: 100px;"', 'Numeric sizes use pixels instead of numbers.' );
+ $this->assertEquals( Sanitizer::fixTagAttributes( 'width="100%"', 'td' ), ' style="width: 100%;"', 'Units are allowed in sizes.' );
+ $this->assertEquals( Sanitizer::fixTagAttributes( 'WIDTH="100%"', 'td' ), ' style="width: 100%;"', 'Uppercase WIDTH is treated as lowercase width.' );
+ $this->assertEquals( Sanitizer::fixTagAttributes( 'WiDTh="100%"', 'td' ), ' style="width: 100%;"', 'Mixed case does not break WiDTh.' );
+ $this->assertEquals( Sanitizer::fixTagAttributes( 'nowrap="true"', 'td' ), ' style="white-space: nowrap;"', 'nowrap attribute is output as white-space: nowrap; not something else.' );
+ $this->assertEquals( Sanitizer::fixTagAttributes( 'nowrap=""', 'td' ), ' style="white-space: nowrap;"', 'nowrap="" is considered true, not false' );
+ $this->assertEquals( Sanitizer::fixTagAttributes( 'NOWRAP="true"', 'td' ), ' style="white-space: nowrap;"', 'nowrap attribute works when uppercase.' );
+ $this->assertEquals( Sanitizer::fixTagAttributes( 'NoWrAp="true"', 'td' ), ' style="white-space: nowrap;"', 'nowrap attribute works when mixed-case.' );
+ $GLOBALS['wgCleanupPresentationalAttributes'] = false;
+ $this->assertEquals( Sanitizer::fixTagAttributes( 'clear="left"', 'br' ), ' clear="left"', 'Deprecated attributes are not converted to styles when enabled.' );
+ }
+
+ /**
+ * @dataProvider provideCssCommentsFixtures
+ */
+ function testCssCommentsChecking( $expected, $css, $message = '' ) {
+ $this->assertEquals(
+ $expected,
+ Sanitizer::checkCss( $css ),
+ $message
+ );
+ }
+
+ function provideCssCommentsFixtures() {
+ /** array( <expected>, <css>, [message] ) */
+ return array(
+ array( ' ', '/**/' ),
+ array( ' ', '/****/' ),
+ array( ' ', '/* comment */' ),
+ array( ' ', "\\2f\\2a foo \\2a\\2f",
+ 'Backslash-escaped comments must be stripped (bug 28450)' ),
+ array( '', '/* unfinished comment structure',
+ 'Remove anything after a comment-start token' ),
+ array( '', "\\2f\\2a unifinished comment'",
+ 'Remove anything after a backslash-escaped comment-start token' ),
+ );
+ }
}
diff --git a/tests/phpunit/includes/UserIsValidEmailAddrTest.php b/tests/phpunit/includes/SanitizerValidateEmailTest.php
index 99bf718e..14d799cf 100644
--- a/tests/phpunit/includes/UserIsValidEmailAddrTest.php
+++ b/tests/phpunit/includes/SanitizerValidateEmailTest.php
@@ -1,12 +1,12 @@
<?php
-class UserIsValidEmailAddrTest extends MediaWikiTestCase {
+class SanitizerValidateEmailTest extends MediaWikiTestCase {
private function checkEmail( $addr, $expected = true, $msg = '') {
if( $msg == '' ) { $msg = "Testing $addr"; }
$this->assertEquals(
$expected,
- User::isValidEmailAddr( $addr ),
+ Sanitizer::validateEmail( $addr ),
$msg
);
}
diff --git a/tests/phpunit/includes/SeleniumConfigurationTest.php b/tests/phpunit/includes/SeleniumConfigurationTest.php
index 750524eb..8589c188 100644
--- a/tests/phpunit/includes/SeleniumConfigurationTest.php
+++ b/tests/phpunit/includes/SeleniumConfigurationTest.php
@@ -2,13 +2,13 @@
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 =
@@ -32,14 +32,14 @@ runAgainstGrid = false
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(
@@ -55,7 +55,7 @@ testSuite[TestSuiteName] = "testSuitePath"
'jUnitLogFile' => null,
'runAgainstGrid' => null
);
- /*
+ /**
* Array of expected testSuites from $testConfig0
*/
private $testSuites0 = array(
@@ -64,7 +64,7 @@ testSuite[TestSuiteName] = "testSuitePath"
);
- /*
+ /**
* Another sample selenium settings file contents
*/
private $testConfig1 =
@@ -73,11 +73,11 @@ testSuite[TestSuiteName] = "testSuitePath"
host = "localhost"
testBrowser = "firefox"
';
- /*
+ /**
* Expected browsers from $testConfig1
*/
private $testBrowsers1 = null;
- /*
+ /**
* Expected selenium settings from $testConfig1
*/
private $testSettings1 = array(
@@ -93,7 +93,7 @@ testBrowser = "firefox"
'jUnitLogFile' => null,
'runAgainstGrid' => null
);
- /*
+ /**
* Expected test suites from $testConfig1
*/
private $testSuites1 = null;
@@ -105,7 +105,7 @@ testBrowser = "firefox"
}
}
- /*
+ /**
* Clean up the temporary file used to store the selenium settings.
*/
public function tearDown() {
@@ -199,7 +199,7 @@ testBrowser = "firefox"
}
- /*
+ /**
* create a temp file and write text to it.
* @param $testToWrite the text to write to the temp file
*/
@@ -210,7 +210,7 @@ testBrowser = "firefox"
fclose($tempFile);
}
- /*
+ /**
* Returns an array containing:
* The contents of the selenium cingiguration ini file
* The expected selenium configuration array that getSeleniumSettings should return
diff --git a/tests/phpunit/includes/TemplateCategoriesTest.php b/tests/phpunit/includes/TemplateCategoriesTest.php
new file mode 100644
index 00000000..de9d6dc6
--- /dev/null
+++ b/tests/phpunit/includes/TemplateCategoriesTest.php
@@ -0,0 +1,38 @@
+<?php
+
+/**
+ * @group Database
+ */
+require dirname( __FILE__ ) . "/../../../maintenance/runJobs.php";
+
+class TemplateCategoriesTest extends MediaWikiLangTestCase {
+
+ function testTemplateCategories() {
+ global $wgUser;
+
+ $title = Title::newFromText( "Categorized from template" );
+ $article = new Article( $title );
+ $wgUser = new User();
+ $wgUser->mRights['*'] = array( 'createpage', 'edit', 'purge' );
+
+ $status = $article->doEdit( '{{Categorising template}}', 'Create a page with a template', 0 );
+ $this->assertEquals(
+ array()
+ , $title->getParentCategories()
+ );
+
+ $template = new Article( Title::newFromText( 'Template:Categorising template' ) );
+ $status = $template->doEdit( '[[Category:Solved bugs]]', 'Add a category through a template', 0 );
+
+ // Run the job queue
+ $jobs = new RunJobs;
+ $jobs->loadParamsAndArgs( null, array( 'quiet' => true ), null );
+ $jobs->execute();
+
+ $this->assertEquals(
+ array( 'Category:Solved_bugs' => $title->getPrefixedText() )
+ , $title->getParentCategories()
+ );
+ }
+
+}
diff --git a/tests/phpunit/includes/TitleMethodsTest.php b/tests/phpunit/includes/TitleMethodsTest.php
new file mode 100644
index 00000000..2f1103e8
--- /dev/null
+++ b/tests/phpunit/includes/TitleMethodsTest.php
@@ -0,0 +1,78 @@
+<?php
+
+class TitleMethodsTest extends MediaWikiTestCase {
+
+ public function dataEquals() {
+ return array(
+ array( 'Main Page', 'Main Page', true ),
+ array( 'Main Page', 'Not The Main Page', false ),
+ array( 'Main Page', 'Project:Main Page', false ),
+ array( 'File:Example.png', 'Image:Example.png', true ),
+ array( 'Special:Version', 'Special:Version', true ),
+ array( 'Special:Version', 'Special:Recentchanges', false ),
+ array( 'Special:Version', 'Main Page', false ),
+ );
+ }
+
+ /**
+ * @dataProvider dataEquals
+ */
+ public function testEquals( $titleA, $titleB, $expectedBool ) {
+ $titleA = Title::newFromText( $titleA );
+ $titleB = Title::newFromText( $titleB );
+
+ $this->assertEquals( $titleA->equals( $titleB ), $expectedBool );
+ $this->assertEquals( $titleB->equals( $titleA ), $expectedBool );
+ }
+
+ public function dataInNamespace() {
+ return array(
+ array( 'Main Page', NS_MAIN, true ),
+ array( 'Main Page', NS_TALK, false ),
+ array( 'Main Page', NS_USER, false ),
+ array( 'User:Foo', NS_USER, true ),
+ array( 'User:Foo', NS_USER_TALK, false ),
+ array( 'User:Foo', NS_TEMPLATE, false ),
+ array( 'User_talk:Foo', NS_USER_TALK, true ),
+ array( 'User_talk:Foo', NS_USER, false ),
+ );
+ }
+
+ /**
+ * @dataProvider dataInNamespace
+ */
+ public function testInNamespace( $title, $ns, $expectedBool ) {
+ $title = Title::newFromText( $title );
+ $this->assertEquals( $title->inNamespace( $ns ), $expectedBool );
+ }
+
+ public function testInNamespaces() {
+ $mainpage = Title::newFromText( 'Main Page' );
+ $this->assertTrue( $mainpage->inNamespaces( NS_MAIN, NS_USER ) );
+ $this->assertTrue( $mainpage->inNamespaces( array( NS_MAIN, NS_USER ) ) );
+ $this->assertTrue( $mainpage->inNamespaces( array( NS_USER, NS_MAIN ) ) );
+ $this->assertFalse( $mainpage->inNamespaces( array( NS_PROJECT, NS_TEMPLATE ) ) );
+ }
+
+ public function dataHasSubjectNamespace() {
+ return array(
+ array( 'Main Page', NS_MAIN, true ),
+ array( 'Main Page', NS_TALK, true ),
+ array( 'Main Page', NS_USER, false ),
+ array( 'User:Foo', NS_USER, true ),
+ array( 'User:Foo', NS_USER_TALK, true ),
+ array( 'User:Foo', NS_TEMPLATE, false ),
+ array( 'User_talk:Foo', NS_USER_TALK, true ),
+ array( 'User_talk:Foo', NS_USER, true ),
+ );
+ }
+
+ /**
+ * @dataProvider dataHasSubjectNamespace
+ */
+ public function testHasSubjectNamespace( $title, $ns, $expectedBool ) {
+ $title = Title::newFromText( $title );
+ $this->assertEquals( $title->hasSubjectNamespace( $ns ), $expectedBool );
+ }
+
+}
diff --git a/tests/phpunit/includes/TitlePermissionTest.php b/tests/phpunit/includes/TitlePermissionTest.php
index 1b179686..f62ac5dd 100644
--- a/tests/phpunit/includes/TitlePermissionTest.php
+++ b/tests/phpunit/includes/TitlePermissionTest.php
@@ -5,12 +5,16 @@
*/
class TitlePermissionTest extends MediaWikiLangTestCase {
protected $title;
- protected $user;
- protected $anonUser;
- protected $userUser;
- protected $altUser;
- protected $userName;
- protected $altUserName;
+
+ /**
+ * @var User
+ */
+ protected $user, $anonUser, $userUser, $altUser;
+
+ /**
+ * @var string
+ */
+ protected $userName, $altUserName;
function setUp() {
global $wgLocaltimezone, $wgLocalTZoffset, $wgMemc, $wgContLang, $wgLang;
@@ -56,6 +60,8 @@ class TitlePermissionTest extends MediaWikiLangTestCase {
}
function setUserPerm( $perm ) {
+ // Setting member variables is evil!!!
+
if ( is_array( $perm ) ) {
$this->user->mRights = $perm;
} else {
@@ -299,7 +305,7 @@ class TitlePermissionTest extends MediaWikiLangTestCase {
$this->assertEquals( $check[$action][3],
$this->title->userCan( $action, true ) );
$this->assertEquals( $check[$action][3],
- $this->title->quickUserCan( $action, false ) );
+ $this->title->quickUserCan( $action ) );
# count( User::getGroupsWithPermissions( $action ) ) < 1
}
@@ -451,7 +457,7 @@ class TitlePermissionTest extends MediaWikiLangTestCase {
$this->user ) );
$this->assertEquals( true,
- $this->title->quickUserCan( 'edit', false ) );
+ $this->title->quickUserCan( 'edit' ) );
$this->title->mRestrictions = array( "edit" => array( 'bogus', "sysop", "protect", "" ),
"bogus" => array( 'bogus', "sysop", "protect", "" ) );
@@ -491,9 +497,9 @@ class TitlePermissionTest extends MediaWikiLangTestCase {
$this->user ) );
$this->title->mCascadeRestriction = true;
$this->assertEquals( false,
- $this->title->quickUserCan( 'bogus', false ) );
+ $this->title->quickUserCan( 'bogus' ) );
$this->assertEquals( false,
- $this->title->quickUserCan( 'edit', false ) );
+ $this->title->quickUserCan( 'edit' ) );
$this->assertEquals( array( array( 'badaccess-group0' ),
array( 'protectedpagetext', 'bogus' ),
array( 'protectedpagetext', 'protect' ),
@@ -537,7 +543,7 @@ class TitlePermissionTest extends MediaWikiLangTestCase {
$this->setTitle( NS_MAIN, "test page" );
$this->title->mTitleProtection['pt_create_perm'] = '';
$this->title->mTitleProtection['pt_user'] = $this->user->getID();
- $this->title->mTitleProtection['pt_expiry'] = Block::infinity();
+ $this->title->mTitleProtection['pt_expiry'] = wfGetDB( DB_SLAVE )->getInfinity();
$this->title->mTitleProtection['pt_reason'] = 'test';
$this->title->mCascadeRestriction = false;
@@ -574,7 +580,7 @@ class TitlePermissionTest extends MediaWikiLangTestCase {
$this->title->userCan( 'move' ) );
$this->title->mInterwiki = "no";
- $this->assertEquals( array( array( 'immobile-page' ) ),
+ $this->assertEquals( array( array( 'immobile-source-page' ) ),
$this->title->getUserPermissionsErrors( 'move', $this->user ) );
$this->assertEquals( false,
$this->title->userCan( 'move' ) );
@@ -623,7 +629,7 @@ class TitlePermissionTest extends MediaWikiLangTestCase {
$prev = time();
$now = time() + 120;
$this->user->mBlockedby = $this->user->getId();
- $this->user->mBlock = new Block( '127.0.8.1', $this->user->getId(), $this->user->getId(),
+ $this->user->mBlock = new Block( '127.0.8.1', 0, $this->user->getId(),
'no reason given', $prev + 3600, 1, 0 );
$this->user->mBlock->mTimestamp = 0;
$this->assertEquals( array( array( 'autoblockedtext',
@@ -640,7 +646,7 @@ class TitlePermissionTest extends MediaWikiLangTestCase {
global $wgLocalTZoffset;
$wgLocalTZoffset = -60;
$this->user->mBlockedby = $this->user->getName();
- $this->user->mBlock = new Block( '127.0.8.1', 2, 1, 'no reason given', $now, 0, 10 );
+ $this->user->mBlock = new Block( '127.0.8.1', 0, 1, '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',
diff --git a/tests/phpunit/includes/TitleTest.php b/tests/phpunit/includes/TitleTest.php
index e7bb98ac..1c8be5f9 100644
--- a/tests/phpunit/includes/TitleTest.php
+++ b/tests/phpunit/includes/TitleTest.php
@@ -41,10 +41,10 @@ class TitleTest extends MediaWikiTestCase {
/**
* Auth-less test of Title::isValidMoveOperation
*
+ * @group Database
* @param string $source
* @param string $target
- * @param array|string|true $requiredErrors
- * @group Database
+ * @param array|string|true $expected Required error
* @dataProvider dataTestIsValidMoveOperation
*/
function testIsValidMoveOperation( $source, $target, $expected ) {
diff --git a/tests/phpunit/includes/UserTest.php b/tests/phpunit/includes/UserTest.php
index df91aca8..ef03e835 100644
--- a/tests/phpunit/includes/UserTest.php
+++ b/tests/phpunit/includes/UserTest.php
@@ -1,47 +1,67 @@
<?php
+define( 'NS_UNITTEST', 5600 );
+define( 'NS_UNITTEST_TALK', 5601 );
+
+/**
+ * @group Database
+ */
class UserTest extends MediaWikiTestCase {
protected $savedGroupPermissions, $savedRevokedPermissions;
-
+
+ /**
+ * @var User
+ */
+ protected $user;
+
public function setUp() {
parent::setUp();
-
+
$this->savedGroupPermissions = $GLOBALS['wgGroupPermissions'];
$this->savedRevokedPermissions = $GLOBALS['wgRevokePermissions'];
-
+
$this->setUpPermissionGlobals();
+ $this->setUpUser();
}
private function setUpPermissionGlobals() {
global $wgGroupPermissions, $wgRevokePermissions;
-
+
+ # Data for regular $wgGroupPermissions test
$wgGroupPermissions['unittesters'] = array(
+ 'test' => true,
'runtest' => true,
'writetest' => false,
'nukeworld' => false,
);
$wgGroupPermissions['testwriters'] = array(
+ 'test' => true,
'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;
}
-
+
public function testGroupPermissions() {
$rights = User::getGroupPermissions( array( 'unittesters' ) );
$this->assertContains( 'runtest', $rights );
$this->assertNotContains( 'writetest', $rights );
$this->assertNotContains( 'modifytest', $rights );
$this->assertNotContains( 'nukeworld', $rights );
-
+
$rights = User::getGroupPermissions( array( 'unittesters', 'testwriters' ) );
$this->assertContains( 'runtest', $rights );
$this->assertContains( 'writetest', $rights );
@@ -53,6 +73,71 @@ class UserTest extends MediaWikiTestCase {
$this->assertNotContains( 'runtest', $rights );
$this->assertNotContains( 'writetest', $rights );
$this->assertNotContains( 'modifytest', $rights );
- $this->assertNotContains( 'nukeworld', $rights );
+ $this->assertNotContains( 'nukeworld', $rights );
+ }
+
+ public function testUserPermissions() {
+ $rights = $this->user->getRights();
+ $this->assertContains( 'runtest', $rights );
+ $this->assertNotContains( 'writetest', $rights );
+ $this->assertNotContains( 'modifytest', $rights );
+ $this->assertNotContains( 'nukeworld', $rights );
+ }
+
+ /**
+ * @dataProvider provideGetGroupsWithPermission
+ */
+ public function testGetGroupsWithPermission( $expected, $right ) {
+ $result = User::getGroupsWithPermission( $right );
+ sort( $result );
+ sort( $expected );
+
+ $this->assertEquals( $expected, $result, "Groups with permission $right" );
+ }
+
+ public function provideGetGroupsWithPermission() {
+ return array(
+ array(
+ array( 'unittesters', 'testwriters' ),
+ 'test'
+ ),
+ array(
+ array( 'unittesters' ),
+ 'runtest'
+ ),
+ array(
+ array( 'testwriters' ),
+ 'writetest'
+ ),
+ array(
+ array( 'testwriters' ),
+ 'modifytest'
+ ),
+ );
+ }
+
+ /**
+ * @dataProvider provideUserNames
+ */
+ public function testIsValidUserName( $username, $result, $message ) {
+ $this->assertEquals( $this->user->isValidUserName( $username ), $result, $message );
+ }
+
+ public 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( 'User:Abcd', false, 'Reserved Namespace' ),
+ 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( 'Ab cd', false, ' Ideographic space' ),
+ );
}
-} \ No newline at end of file
+}
diff --git a/tests/phpunit/includes/WebRequestTest.php b/tests/phpunit/includes/WebRequestTest.php
index 1cfbd3fc..e72408f6 100644
--- a/tests/phpunit/includes/WebRequestTest.php
+++ b/tests/phpunit/includes/WebRequestTest.php
@@ -85,4 +85,101 @@ class WebRequestTest extends MediaWikiTestCase {
),
);
}
+
+ /**
+ * @dataProvider provideGetIP
+ */
+ function testGetIP( $expected, $input, $squid, $private, $description ) {
+ global $wgSquidServersNoPurge, $wgUsePrivateIPs;
+ $oldServer = $_SERVER;
+ $_SERVER = $input;
+ $wgSquidServersNoPurge = $squid;
+ $wgUsePrivateIPs = $private;
+ $request = new WebRequest();
+ $result = $request->getIP();
+ $_SERVER = $oldServer;
+ $this->assertEquals( $expected, $result, $description );
+ }
+
+ function provideGetIP() {
+ return array(
+ array(
+ '127.0.0.1',
+ array(
+ 'REMOTE_ADDR' => '127.0.0.1'
+ ),
+ array(),
+ false,
+ 'Simple IPv4'
+ ),
+ array(
+ '::1',
+ array(
+ 'REMOTE_ADDR' => '::1'
+ ),
+ array(),
+ false,
+ 'Simple IPv6'
+ ),
+ 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' ),
+ false,
+ 'With X-Forwaded-For'
+ ),
+ array(
+ '12.0.0.1',
+ array(
+ 'REMOTE_ADDR' => '12.0.0.1',
+ 'HTTP_X_FORWARDED_FOR' => '12.0.0.3, 12.0.0.2'
+ ),
+ array(),
+ false,
+ 'With X-Forwaded-For and disallowed server'
+ ),
+ 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( '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( '12.0.0.1', '12.0.0.2' ),
+ false,
+ 'With X-Forwaded-For and private IP'
+ ),
+ array(
+ '10.0.0.3',
+ array(
+ 'REMOTE_ADDR' => '12.0.0.2',
+ 'HTTP_X_FORWARDED_FOR' => '10.0.0.3, 12.0.0.2'
+ ),
+ array( '12.0.0.1', '12.0.0.2' ),
+ true,
+ 'With X-Forwaded-For and private IP (allowed)'
+ ),
+ );
+ }
+
+ /**
+ * @expectedException MWException
+ */
+ function testGetIpLackOfRemoteAddrThrowAnException() {
+ $request = new WebRequest();
+ # Next call throw an exception about lacking an IP
+ $request->getIP();
+ }
}
diff --git a/tests/phpunit/includes/XmlSelectTest.php b/tests/phpunit/includes/XmlSelectTest.php
index bf761e3d..2407c151 100644
--- a/tests/phpunit/includes/XmlSelectTest.php
+++ b/tests/phpunit/includes/XmlSelectTest.php
@@ -80,7 +80,7 @@ class XmlSelectTest extends MediaWikiTestCase {
$this->select->addOption( 'foo2' );
$this->assertEquals(
'<select><option value="foo1">foo1</option>' . "\n" .
-'<option value="bar1" selected="selected">bar1</option>' . "\n" .
+'<option value="bar1" selected="">bar1</option>' . "\n" .
'<option value="foo2">foo2</option></select>', $this->select->getHTML() );
}
@@ -96,7 +96,7 @@ class XmlSelectTest extends MediaWikiTestCase {
$this->select->setDefault( 'bar1' ); # setting default after adding options
$this->assertEquals(
'<select><option value="foo1">foo1</option>' . "\n" .
-'<option value="bar1" selected="selected">bar1</option>' . "\n" .
+'<option value="bar1" selected="">bar1</option>' . "\n" .
'<option value="foo2">foo2</option></select>', $this->select->getHTML() );
}
diff --git a/tests/phpunit/includes/XmlTest.php b/tests/phpunit/includes/XmlTest.php
index a6058ef6..1d9361f2 100644
--- a/tests/phpunit/includes/XmlTest.php
+++ b/tests/phpunit/includes/XmlTest.php
@@ -2,19 +2,43 @@
class XmlTest extends MediaWikiTestCase {
private static $oldLang;
+ private static $oldNamespaces;
public function setUp() {
- global $wgLang, $wgLanguageCode;
-
+ global $wgLang, $wgContLang;
+
self::$oldLang = $wgLang;
- $wgLanguageCode = 'en';
- $wgLang = Language::factory( $wgLanguageCode );
+ $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(
+ -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',
+ ) );
}
-
+
public function tearDown() {
- global $wgLang, $wgLanguageCode;
+ global $wgLang, $wgContLang;
$wgLang = self::$oldLang;
- $wgLanguageCode = $wgLang->getCode();
+
+ $wgContLang->setNamespaces( self::$oldNamespaces );
}
public function testExpandAttributes() {
@@ -88,6 +112,9 @@ class XmlTest extends MediaWikiTestCase {
$this->assertEquals( '</element>', Xml::closeElement( 'element' ), 'closeElement() shortcut' );
}
+ /**
+ * @group Broken
+ */
public function testDateMenu( ) {
$curYear = intval(gmdate('Y'));
$prevYear = $curYear - 1;
@@ -98,11 +125,10 @@ class XmlTest extends MediaWikiTestCase {
$nextMonth = $curMonth + 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="selected">February</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" .
@@ -139,7 +165,6 @@ class XmlTest extends MediaWikiTestCase {
"Date menu year is the current one when not specified"
);
- $this->markTestIncomplete( "Broken" );
// @todo FIXME: next month can be in the next year
// test failing because it is now december
$this->assertEquals(
@@ -163,11 +188,57 @@ class XmlTest extends MediaWikiTestCase {
'<option value="10">October</option>' . "\n" .
'<option value="11">November</option>' . "\n" .
'<option value="12">December</option></select>',
- Xml::dateMenu( '', ''),
+ Xml::dateMenu( '', '' ),
"Date menu with neither year or month"
);
}
+ function testNamespaceSelector() {
+ $this->assertEquals(
+ '<select class="namespaceselector" 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="100">Custom</option>' . "\n" .
+'<option value="101">Custom talk</option>' . "\n" .
+'</select>',
+ Xml::namespaceSelector(),
+ 'Basic namespace selector without custom options'
+ );
+ $this->assertEquals(
+ '<label for="namespace">Select a namespace:</label>' .
+'&#160;<select class="namespaceselector" id="namespace" name="myname">' . "\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="100">Custom</option>' . "\n" .
+'<option value="101">Custom talk</option>' . "\n" .
+'</select>',
+ Xml::namespaceSelector( $selected = '2', $all = 'all', $element_name = 'myname', $label = 'Select a namespace:' ),
+ 'Basic namespace selector with custom values'
+ );
+ }
+
+
#
# textarea
#
@@ -255,12 +326,12 @@ class XmlTest extends MediaWikiTestCase {
function testEncodeJsVarArray() {
$this->assertEquals(
- '["a", 1]',
+ '["a",1]',
Xml::encodeJsVar( array( 'a', 1 ) ),
'encodeJsVar() with array'
);
$this->assertEquals(
- '{"a": "a", "b": 1}',
+ '{"a":"a","b":1}',
Xml::encodeJsVar( array( 'a' => 'a', 'b' => 1 ) ),
'encodeJsVar() with associative array'
);
@@ -268,7 +339,7 @@ class XmlTest extends MediaWikiTestCase {
function testEncodeJsVarObject() {
$this->assertEquals(
- '{"a": "a", "b": 1}',
+ '{"a":"a","b":1}',
Xml::encodeJsVar( (object)array( 'a' => 'a', 'b' => 1 ) ),
'encodeJsVar() with object'
);
diff --git a/tests/phpunit/includes/api/ApiBlockTest.php b/tests/phpunit/includes/api/ApiBlockTest.php
index 227555eb..b95d8214 100644
--- a/tests/phpunit/includes/api/ApiBlockTest.php
+++ b/tests/phpunit/includes/api/ApiBlockTest.php
@@ -15,24 +15,34 @@ class ApiBlockTest extends ApiTestCase {
}
function addDBData() {
- $user = User::newFromName( 'UTBlockee' );
+ $user = User::newFromName( 'UTApiBlockee' );
if ( $user->getId() == 0 ) {
$user->addToDatabase();
- $user->setPassword( 'UTBlockeePassword' );
+ $user->setPassword( 'UTApiBlockeePassword' );
$user->saveSettings();
}
}
+ /**
+ * This test has probably always been broken and use an invalid token
+ * Bug tracking brokenness is https://bugzilla.wikimedia.org/35646
+ *
+ * Root cause is https://gerrit.wikimedia.org/r/3434
+ * Which made the Block/Unblock API to actually verify the token
+ * previously always considered valid (bug 34212).
+ *
+ * @group Broken
+ */
function testMakeNormalBlock() {
$data = $this->getTokens();
- $user = User::newFromName( 'UTBlockee' );
+ $user = User::newFromName( 'UTApiBlockee' );
if ( !$user->getId() ) {
- $this->markTestIncomplete( "The user UTBlockee does not exist" );
+ $this->markTestIncomplete( "The user UTApiBlockee does not exist" );
}
if( !isset( $data[0]['query']['pages'] ) ) {
@@ -45,15 +55,15 @@ class ApiBlockTest extends ApiTestCase {
$data = $this->doApiRequest( array(
'action' => 'block',
- 'user' => 'UTBlockee',
- 'reason' => BlockTest::REASON,
- 'token' => $pageinfo['blocktoken'] ), $data );
+ 'user' => 'UTApiBlockee',
+ 'reason' => 'Some reason',
+ 'token' => $pageinfo['blocktoken'] ), $data, false, self::$users['sysop']->user );
- $block = Block::newFromTarget('UTBlockee');
+ $block = Block::newFromTarget('UTApiBlockee');
$this->assertTrue( !is_null( $block ), 'Block is valid' );
- $this->assertEquals( 'UTBlockee', (string)$block->getTarget() );
+ $this->assertEquals( 'UTApiBlockee', (string)$block->getTarget() );
$this->assertEquals( 'Some reason', $block->mReason );
$this->assertEquals( 'infinity', $block->mExpiry );
diff --git a/tests/phpunit/includes/api/ApiPurgeTest.php b/tests/phpunit/includes/api/ApiPurgeTest.php
index db1563e9..70c20746 100644
--- a/tests/phpunit/includes/api/ApiPurgeTest.php
+++ b/tests/phpunit/includes/api/ApiPurgeTest.php
@@ -9,33 +9,31 @@ class ApiPurgeTest extends ApiTestCase {
parent::setUp();
$this->doLogin();
}
-
+
+ /**
+ * @group Broken
+ */
function testPurgeMainPage() {
-
if ( !Title::newFromText( 'UTPage' )->exists() ) {
$this->markTestIncomplete( "The article [[UTPage]] does not exist" );
}
-
+
$somePage = mt_rand();
$data = $this->doApiRequest( array(
'action' => 'purge',
'titles' => 'UTPage|' . $somePage . '|%5D' ) );
-
- $this->assertArrayHasKey( 'purge', $data[0] );
-
- $this->assertArrayHasKey( 0, $data[0]['purge'] );
- $this->assertArrayHasKey( 'purged', $data[0]['purge'][0] );
- $this->assertEquals( 'UTPage', $data[0]['purge'][0]['title'] );
-
- $this->assertArrayHasKey( 1, $data[0]['purge'] );
- $this->assertArrayHasKey( 'missing', $data[0]['purge'][1] );
- $this->assertEquals( $somePage, $data[0]['purge'][1]['title'] );
-
- $this->assertArrayHasKey( 2, $data[0]['purge'] );
- $this->assertArrayHasKey( 'invalid', $data[0]['purge'][2] );
- $this->assertEquals( '%5D', $data[0]['purge'][2]['title'] );
-
+
+ $this->assertArrayHasKey( 'purge', $data[0],
+ "Must receive a 'purge' result from API" );
+
+ $this->assertEquals( 3, count( $data[0]['purge'] ),
+ "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 ) {
+ $this->assertArrayHasKey( $pages[$v['title']], $v );
+ }
}
}
diff --git a/tests/phpunit/includes/api/ApiQueryTest.php b/tests/phpunit/includes/api/ApiQueryTest.php
index 114eadf3..ae05a30a 100644
--- a/tests/phpunit/includes/api/ApiQueryTest.php
+++ b/tests/phpunit/includes/api/ApiQueryTest.php
@@ -22,10 +22,13 @@ class ApiQueryTest extends ApiTestCase {
$this->assertArrayHasKey( 'query', $data[0] );
$this->assertArrayHasKey( 'normalized', $data[0]['query'] );
+ // Forge a normalized title
+ $to = Title::newFromText( $wgMetaNamespace.':ArticleA' );
+
$this->assertEquals(
array(
'from' => 'Project:articleA',
- 'to' => $wgMetaNamespace . ':ArticleA'
+ 'to' => $to->getPrefixedText(),
),
$data[0]['query']['normalized'][0]
);
@@ -50,7 +53,6 @@ class ApiQueryTest extends ApiTestCase {
'action' => 'query',
'titles' => $title . '|Talk:' ) );
-
$this->assertArrayHasKey( 'query', $data[0] );
$this->assertArrayHasKey( 'pages', $data[0]['query'] );
$this->assertEquals( 2, count( $data[0]['query']['pages'] ) );
@@ -60,8 +62,6 @@ 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/ApiTest.php b/tests/phpunit/includes/api/ApiTest.php
index a587e6b1..1d9c3238 100644
--- a/tests/phpunit/includes/api/ApiTest.php
+++ b/tests/phpunit/includes/api/ApiTest.php
@@ -96,7 +96,7 @@ class ApiTest extends ApiTestCase {
"lgtoken" => $token,
"lgname" => $user->username,
"lgpassword" => "badnowayinhell",
- )
+ ), $ret[2]
);
$result = $ret[0];
@@ -137,7 +137,7 @@ class ApiTest extends ApiTestCase {
"lgtoken" => $token,
"lgname" => $user->username,
"lgpassword" => $user->password,
- )
+ ), $ret[2]
);
$result = $ret[0];
@@ -148,6 +148,9 @@ class ApiTest extends ApiTestCase {
$this->assertEquals( "Success", $a );
}
+ /**
+ * @group Broken
+ */
function testApiGotCookie() {
$this->markTestIncomplete( "The server can't do external HTTP requests, and the internal one won't give cookies" );
@@ -192,24 +195,23 @@ class ApiTest extends ApiTestCase {
}
/**
- * @depends testApiGotCookie
+ * @todo Finish filling me out...what are we trying to test here?
*/
- function testApiListPages( CookieJar $cj ) {
- $this->markTestIncomplete( "Not done with this yet" );
+ function testApiListPages() {
global $wgServer;
-
- if ( $wgServer == "http://localhost" ) {
+ if ( !isset( $wgServer ) ) {
$this->markTestIncomplete( 'This test needs $wgServer to be set in LocalSettings.php' );
}
- $req = MWHttpRequest::factory( self::$apiUrl . "?action=query&format=xml&prop=revisions&" .
- "titles=Main%20Page&rvprop=timestamp|user|comment|content" );
- $req->setCookieJar( $cj );
- $req->execute();
- libxml_use_internal_errors( true );
- $sxe = simplexml_load_string( $req->getContent() );
- $this->assertNotInternalType( "bool", $sxe );
- $this->assertThat( $sxe, $this->isInstanceOf( "SimpleXMLElement" ) );
- $a = $sxe->query[0]->pages[0]->page[0]->attributes();
+
+ $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() {
@@ -228,7 +230,7 @@ class ApiTest extends ApiTestCase {
'action' => 'login',
"lgtoken" => $token,
"lgname" => $sysopUser->username,
- "lgpassword" => $sysopUser->password ), $data );
+ "lgpassword" => $sysopUser->password ), $data[2] );
$this->assertArrayHasKey( "login", $data[0] );
$this->assertArrayHasKey( "result", $data[0]['login'] );
diff --git a/tests/phpunit/includes/api/ApiTestCase.php b/tests/phpunit/includes/api/ApiTestCase.php
index 2917c880..8801391f 100644
--- a/tests/phpunit/includes/api/ApiTestCase.php
+++ b/tests/phpunit/includes/api/ApiTestCase.php
@@ -7,6 +7,11 @@ abstract class ApiTestCase extends MediaWikiLangTestCase {
public static $users;
protected static $apiUrl;
+ /**
+ * @var ApiTestContext
+ */
+ protected $apiContext;
+
function setUp() {
global $wgContLang, $wgAuth, $wgMemc, $wgRequest, $wgUser, $wgServer;
@@ -21,31 +26,37 @@ abstract class ApiTestCase extends MediaWikiLangTestCase {
'sysop' => new ApiTestUser(
'Apitestsysop',
'Api Test Sysop',
- 'api_test_sysop@sample.com',
+ 'api_test_sysop@example.com',
array( 'sysop' )
),
'uploader' => new ApiTestUser(
'Apitestuser',
'Api Test User',
- 'api_test_user@sample.com',
+ 'api_test_user@example.com',
array()
)
);
$wgUser = self::$users['sysop']->user;
+ $this->apiContext = new ApiTestContext();
+
}
- protected function doApiRequest( $params, $session = null, $appendModule = false ) {
+ protected function doApiRequest( $params, $session = null, $appendModule = false, $user = null ) {
if ( is_null( $session ) ) {
$session = array();
}
- $request = new FauxRequest( $params, true, $session );
- $module = new ApiMain( $request, true );
+ $context = $this->apiContext->newTestContext( $params, $session, $user );
+ $module = new ApiMain( $context, true );
$module->execute();
- $results = array( $module->getResultData(), $request, $request->getSessionArray() );
+ $results = array(
+ $module->getResultData(),
+ $context->getRequest(),
+ $context->getRequest()->getSessionArray()
+ );
if( $appendModule ) {
$results[] = $module;
}
@@ -59,14 +70,15 @@ abstract class ApiTestCase extends MediaWikiLangTestCase {
* request, without actually requesting a "real" edit token
* @param $params: key-value API params
* @param $session: session array
+ * @param $user String|null A User object for the context
*/
- protected function doApiRequestWithToken( $params, $session ) {
+ protected function doApiRequestWithToken( $params, $session, $user = null ) {
if ( $session['wsToken'] ) {
// add edit token to fake session
$session['wsEditToken'] = $session['wsToken'];
// add token to request parameters
$params['token'] = md5( $session['wsToken'] ) . User::EDIT_TOKEN_SUFFIX;
- return $this->doApiRequest( $params, $session );
+ return $this->doApiRequest( $params, $session, false, $user );
} else {
throw new Exception( "request data not in right format" );
}
@@ -91,12 +103,11 @@ abstract class ApiTestCase extends MediaWikiLangTestCase {
}
protected function getTokenList( $user ) {
- $GLOBALS['wgUser'] = $user->user;
$data = $this->doApiRequest( array(
'action' => 'query',
'titles' => 'Main Page',
'intoken' => 'edit|delete|protect|move|block|unblock',
- 'prop' => 'info' ) );
+ 'prop' => 'info' ), false, $user->user );
return $data;
}
}
@@ -137,3 +148,23 @@ class MockApi extends ApiBase {
);
}
}
+
+class ApiTestContext extends RequestContext {
+
+ /**
+ * Returns a DerivativeContext with the request variables in place
+ *
+ * @param $params Array key-value API params
+ * @param $session Array session data
+ * @param $user User or null
+ * @return DerivativeContext
+ */
+ public function newTestContext( $params, $session, $user = null ) {
+ $context = new DerivativeContext( $this );
+ $context->setRequest( new FauxRequest( $params, true, $session ) );
+ 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 e51e7214..39c79547 100644
--- a/tests/phpunit/includes/api/ApiTestCaseUpload.php
+++ b/tests/phpunit/includes/api/ApiTestCaseUpload.php
@@ -19,6 +19,10 @@ abstract class ApiTestCaseUpload extends ApiTestCase {
$this->clearFakeUploads();
}
+ public function tearDown() {
+ $this->clearTempUpload();
+ }
+
/**
* Helper function -- remove files and associated articles by Title
* @param $title Title: title to be removed
@@ -33,8 +37,8 @@ abstract class ApiTestCaseUpload extends ApiTestCase {
if ( !$status->isGood() ) {
return false;
}
- $article = new Article( $title );
- $article->doDeleteArticle( "removing for test" );
+ $page = WikiPage::factory( $title );
+ $page->doDeleteArticle( "removing for test" );
// see if it now doesn't exist; reload
$title = Title::newFromText( $title->getText(), NS_FILE );
@@ -56,7 +60,7 @@ abstract class ApiTestCaseUpload extends ApiTestCase {
* @param $filePath String: path to file on the filesystem
*/
public function deleteFileByContent( $filePath ) {
- $hash = File::sha1Base36( $filePath );
+ $hash = FSFile::getSha1Base36FromPath( $filePath );
$dupes = RepoGroup::singleton()->findBySha1( $hash );
$success = true;
foreach ( $dupes as $dupe ) {
@@ -100,6 +104,36 @@ abstract class ApiTestCaseUpload extends ApiTestCase {
return true;
}
+ function fakeUploadChunk( $fieldName, $fileName, $type, & $chunkData ){
+ $tmpName = tempnam( wfTempDir(), "" );
+ // 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
+ );
+ }
+
+ function clearTempUpload() {
+ if( isset( $_FILES['file']['tmp_name'] ) ) {
+ $tmp = $_FILES['file']['tmp_name'];
+ if( file_exists( $tmp ) ) {
+ unlink( $tmp );
+ }
+ }
+ }
/**
* Remove traces of previous fake uploads
diff --git a/tests/phpunit/includes/api/ApiTestUser.php b/tests/phpunit/includes/api/ApiTestUser.php
index df60682f..8d5f61a7 100644
--- a/tests/phpunit/includes/api/ApiTestUser.php
+++ b/tests/phpunit/includes/api/ApiTestUser.php
@@ -8,7 +8,7 @@ class ApiTestUser {
public $groups;
public $user;
- function __construct( $username, $realname = 'Real Name', $email = 'sample@sample.com', $groups = array() ) {
+ function __construct( $username, $realname = 'Real Name', $email = 'sample@example.com', $groups = array() ) {
$this->username = $username;
$this->realname = $realname;
$this->email = $email;
diff --git a/tests/phpunit/includes/api/ApiUploadTest.php b/tests/phpunit/includes/api/ApiUploadTest.php
index 5c929784..7a700326 100644
--- a/tests/phpunit/includes/api/ApiUploadTest.php
+++ b/tests/phpunit/includes/api/ApiUploadTest.php
@@ -19,6 +19,9 @@ require_once( 'ApiTestCaseUpload.php' );
/**
* @group Database
+ * @group Broken
+ * Broken test, reports false errors from time to time.
+ * See https://bugzilla.wikimedia.org/26169
*
* This is pretty sucky... needs to be prettified.
*/
@@ -54,6 +57,7 @@ class ApiUploadTest extends ApiTestCaseUpload {
$this->assertEquals( "Success", $result['login']['result'] );
$this->assertArrayHasKey( 'lgtoken', $result['login'] );
+ $this->assertNotEmpty( $session, 'API Login must return a session' );
return $session;
}
@@ -78,14 +82,11 @@ class ApiUploadTest extends ApiTestCaseUpload {
* @depends testLogin
*/
public function testUploadMissingParams( $session ) {
- global $wgUser;
- $wgUser = self::$users['uploader']->user;
-
$exception = false;
try {
$this->doApiRequestWithToken( array(
'action' => 'upload',
- ), $session );
+ ), $session, self::$users['uploader']->user );
} catch ( UsageException $e ) {
$exception = true;
$this->assertEquals( "One of the parameters filekey, file, url, statuskey is required",
@@ -99,20 +100,17 @@ class ApiUploadTest extends ApiTestCaseUpload {
* @depends testLogin
*/
public function testUpload( $session ) {
- global $wgUser;
- $wgUser = self::$users['uploader']->user;
-
$extension = 'png';
$mimeType = 'image/png';
try {
$randomImageGenerator = new RandomImageGenerator();
+ $filePaths = $randomImageGenerator->writeImages( 1, $extension, wfTempDir() );
}
catch ( Exception $e ) {
$this->markTestIncomplete( $e->getMessage() );
}
- $filePaths = $randomImageGenerator->writeImages( 1, $extension, wfTempDir() );
$filePath = $filePaths[0];
$fileSize = filesize( $filePath );
$fileName = basename( $filePath );
@@ -135,7 +133,8 @@ class ApiUploadTest extends ApiTestCaseUpload {
$exception = false;
try {
- list( $result, , ) = $this->doApiRequestWithToken( $params, $session );
+ list( $result, , ) = $this->doApiRequestWithToken( $params, $session,
+ self::$users['uploader']->user );
} catch ( UsageException $e ) {
$exception = true;
}
@@ -155,9 +154,6 @@ class ApiUploadTest extends ApiTestCaseUpload {
* @depends testLogin
*/
public function testUploadZeroLength( $session ) {
- global $wgUser;
- $wgUser = self::$users['uploader']->user;
-
$mimeType = 'image/png';
$filePath = tempnam( wfTempDir(), "" );
@@ -179,7 +175,7 @@ class ApiUploadTest extends ApiTestCaseUpload {
$exception = false;
try {
- $this->doApiRequestWithToken( $params, $session );
+ $this->doApiRequestWithToken( $params, $session, self::$users['uploader']->user );
} catch ( UsageException $e ) {
$this->assertContains( 'The file you submitted was empty', $e->getMessage() );
$exception = true;
@@ -196,20 +192,17 @@ class ApiUploadTest extends ApiTestCaseUpload {
* @depends testLogin
*/
public function testUploadSameFileName( $session ) {
- global $wgUser;
- $wgUser = self::$users['uploader']->user;
-
$extension = 'png';
$mimeType = 'image/png';
try {
$randomImageGenerator = new RandomImageGenerator();
+ $filePaths = $randomImageGenerator->writeImages( 2, $extension, wfTempDir() );
}
catch ( Exception $e ) {
$this->markTestIncomplete( $e->getMessage() );
}
- $filePaths = $randomImageGenerator->writeImages( 2, $extension, wfTempDir() );
// we'll reuse this filename
$fileName = basename( $filePaths[0] );
@@ -233,7 +226,8 @@ class ApiUploadTest extends ApiTestCaseUpload {
$exception = false;
try {
- list( $result, , $session ) = $this->doApiRequestWithToken( $params, $session );
+ list( $result, , $session ) = $this->doApiRequestWithToken( $params, $session,
+ self::$users['uploader']->user );
} catch ( UsageException $e ) {
$exception = true;
}
@@ -249,7 +243,8 @@ class ApiUploadTest extends ApiTestCaseUpload {
$exception = false;
try {
- list( $result, , ) = $this->doApiRequestWithToken( $params, $session );
+ list( $result, , ) = $this->doApiRequestWithToken( $params, $session,
+ self::$users['uploader']->user ); // FIXME: leaks a temporary file
} catch ( UsageException $e ) {
$exception = true;
}
@@ -270,19 +265,17 @@ class ApiUploadTest extends ApiTestCaseUpload {
* @depends testLogin
*/
public function testUploadSameContent( $session ) {
- global $wgUser;
- $wgUser = self::$users['uploader']->user;
-
$extension = 'png';
$mimeType = 'image/png';
try {
$randomImageGenerator = new RandomImageGenerator();
+ $filePaths = $randomImageGenerator->writeImages( 1, $extension, wfTempDir() );
}
catch ( Exception $e ) {
$this->markTestIncomplete( $e->getMessage() );
}
- $filePaths = $randomImageGenerator->writeImages( 1, $extension, wfTempDir() );
+
$fileNames[0] = basename( $filePaths[0] );
$fileNames[1] = "SameContentAs" . $fileNames[0];
@@ -307,7 +300,8 @@ class ApiUploadTest extends ApiTestCaseUpload {
$exception = false;
try {
- list( $result, $request, $session ) = $this->doApiRequestWithToken( $params, $session );
+ list( $result, $request, $session ) = $this->doApiRequestWithToken( $params, $session,
+ self::$users['uploader']->user );
} catch ( UsageException $e ) {
$exception = true;
}
@@ -332,7 +326,8 @@ class ApiUploadTest extends ApiTestCaseUpload {
$exception = false;
try {
- list( $result, $request, $session ) = $this->doApiRequestWithToken( $params, $session );
+ list( $result, $request, $session ) = $this->doApiRequestWithToken( $params, $session,
+ self::$users['uploader']->user ); // FIXME: leaks a temporary file
} catch ( UsageException $e ) {
$exception = true;
}
@@ -354,19 +349,19 @@ class ApiUploadTest extends ApiTestCaseUpload {
*/
public function testUploadStash( $session ) {
global $wgUser;
- $wgUser = self::$users['uploader']->user;
+ $wgUser = self::$users['uploader']->user; // @todo FIXME: still used somewhere
$extension = 'png';
$mimeType = 'image/png';
try {
$randomImageGenerator = new RandomImageGenerator();
+ $filePaths = $randomImageGenerator->writeImages( 1, $extension, wfTempDir() );
}
catch ( Exception $e ) {
$this->markTestIncomplete( $e->getMessage() );
}
- $filePaths = $randomImageGenerator->writeImages( 1, $extension, wfTempDir() );
$filePath = $filePaths[0];
$fileSize = filesize( $filePath );
$fileName = basename( $filePath );
@@ -389,7 +384,8 @@ class ApiUploadTest extends ApiTestCaseUpload {
$exception = false;
try {
- list( $result, $request, $session ) = $this->doApiRequestWithToken( $params, $session );
+ list( $result, $request, $session ) = $this->doApiRequestWithToken( $params, $session,
+ self::$users['uploader']->user ); // FIXME: leaks a temporary file
} catch ( UsageException $e ) {
$exception = true;
}
@@ -417,17 +413,156 @@ class ApiUploadTest extends ApiTestCaseUpload {
$this->clearFakeUploads();
$exception = false;
try {
- list( $result, $request, $session ) = $this->doApiRequestWithToken( $params, $session );
+ list( $result, $request, $session ) = $this->doApiRequestWithToken( $params, $session,
+ self::$users['uploader']->user );
} catch ( UsageException $e ) {
$exception = true;
}
$this->assertTrue( isset( $result['upload'] ) );
$this->assertEquals( 'Success', $result['upload']['result'] );
- $this->assertFalse( $exception );
+ $this->assertFalse( $exception, "No UsageException exception." );
// clean up
$this->deleteFileByFilename( $fileName );
unlink( $filePath );
}
-}
+
+
+ /**
+ * @depends testLogin
+ */
+ public function testUploadChunks( $session ) {
+ global $wgUser;
+ $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 )
+ $mimeType = 'image/jpeg';
+ $url = 'http://upload.wikimedia.org/wikipedia/commons/e/ed/Oberaargletscher_from_Oberaar%2C_2010_07.JPG';
+ $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);
+ }
+ }
+ catch ( Exception $e ) {
+ $this->markTestIncomplete( $e->getMessage() );
+ }
+ $fileSize = filesize( $filePath );
+ $fileName = basename( $filePath );
+
+ $this->deleteFileByFileName( $fileName );
+ $this->deleteFileByContent( $filePath );
+
+ // Base upload params:
+ $params = array(
+ 'action' => 'upload',
+ 'stash' => 1,
+ 'filename' => $fileName,
+ 'filesize' => $fileSize,
+ 'offset' => 0,
+ );
+
+ // Upload chunks
+ $chunkSessionKey = false;
+ $resultOffset = 0;
+ // Open the file:
+ $handle = @fopen ($filePath, "r");
+ if( $handle === false ){
+ $this->markTestIncomplete( "could not open file: $filePath" );
+ }
+ 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 ){
+ // Upload fist chunk ( and get the session key )
+ try {
+ list( $result, $request, $session ) = $this->doApiRequestWithToken( $params, $session,
+ self::$users['uploader']->user );
+ } catch ( UsageException $e ) {
+ $this->markTestIncomplete( $e->getMessage() );
+ }
+ // 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'] ) ){
+ $this->markTestIncomplete( "no filekey provided" );
+ }
+ $chunkSessionKey = $result['upload']['filekey'];
+ $this->assertEquals( 'Continue', $result['upload']['result'] );
+ // First chunk should have chunkSize == offset
+ $this->assertEquals( $chunkSize, $result['upload']['offset'] );
+ $resultOffset = $result['upload']['offset'];
+ continue;
+ }
+ // Filekey set to chunk session
+ $params['filekey'] = $chunkSessionKey;
+ // Update the offset ( always add chunkSize for subquent chunks should be in-sync with $result['upload']['offset'] )
+ $params['offset'] += $chunkSize;
+ // Make sure param offset is insync with resultOffset:
+ $this->assertEquals( $resultOffset, $params['offset'] );
+ // Upload current chunk
+ try {
+ list( $result, $request, $session ) = $this->doApiRequestWithToken( $params, $session,
+ self::$users['uploader']->user );
+ } catch ( UsageException $e ) {
+ $this->markTestIncomplete( $e->getMessage() );
+ }
+ // 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 ){
+ $this->assertEquals( 'Success', $result['upload']['result'] );
+ break;
+ } else {
+ $this->assertEquals( 'Continue', $result['upload']['result'] );
+ // update $resultOffset
+ $resultOffset = $result['upload']['offset'];
+ }
+ }
+ fclose ($handle);
+
+ // Check that we got a valid file result:
+ 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'] ) );
+ $filekey = $result['upload']['filekey'];
+
+ // Now we should try to release the file from stash
+ $params = array(
+ 'action' => 'upload',
+ 'filekey' => $filekey,
+ 'filename' => $fileName,
+ 'comment' => 'dummy comment',
+ 'text' => "This is the page text for $fileName, altered",
+ );
+ $this->clearFakeUploads();
+ $exception = false;
+ try {
+ list( $result, $request, $session ) = $this->doApiRequestWithToken( $params, $session,
+ self::$users['uploader']->user );
+ } catch ( UsageException $e ) {
+ $exception = true;
+ }
+ $this->assertTrue( isset( $result['upload'] ) );
+ $this->assertEquals( 'Success', $result['upload']['result'] );
+ $this->assertFalse( $exception );
+
+ // clean up
+ $this->deleteFileByFilename( $fileName );
+ // 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 3c7ff304..b7803746 100644
--- a/tests/phpunit/includes/api/ApiWatchTest.php
+++ b/tests/phpunit/includes/api/ApiWatchTest.php
@@ -41,6 +41,7 @@ class ApiWatchTest extends ApiTestCase {
/**
* @depends testWatchEdit
+ * @group Broken
*/
function testWatchClear() {
@@ -92,7 +93,9 @@ class ApiWatchTest extends ApiTestCase {
$this->assertArrayHasKey( 'edit', $data[0]['protect']['protections'][0] );
}
-
+ /**
+ * @group Broken
+ */
function testGetRollbackToken() {
$data = $this->getTokens();
diff --git a/tests/phpunit/includes/api/RandomImageGenerator.php b/tests/phpunit/includes/api/RandomImageGenerator.php
index ae349978..86c0a828 100644
--- a/tests/phpunit/includes/api/RandomImageGenerator.php
+++ b/tests/phpunit/includes/api/RandomImageGenerator.php
@@ -1,6 +1,6 @@
<?php
-/*
+/**
* RandomImageGenerator -- does what it says on the tin.
* Requires Imagick, the ImageMagick library for PHP, or the command line equivalent (usually 'convert').
*
@@ -27,12 +27,11 @@
class RandomImageGenerator {
private $dictionaryFile;
- private $minWidth = 400;
- private $maxWidth = 800;
- private $minHeight = 400;
- private $maxHeight = 800;
- private $shapesToDraw = 5;
- private $imageWriteMethod;
+ 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
@@ -41,35 +40,35 @@ class RandomImageGenerator {
* (we also would need a non-symmetric shape for the images to test those, like a letter F)
*/
private static $orientations = array(
- array(
- '0thRow' => 'top',
- '0thCol' => 'left',
- 'exifCode' => 1,
- 'counterRotation' => array( array( 1, 0 ), array( 0, 1 ) )
+ array(
+ '0thRow' => 'top',
+ '0thCol' => 'left',
+ 'exifCode' => 1,
+ 'counterRotation' => array( array( 1, 0 ), array( 0, 1 ) )
),
- array(
+ array(
'0thRow' => 'bottom',
- '0thCol' => 'right',
- 'exifCode' => 3,
- 'counterRotation' => array( array( -1, 0 ), array( 0, -1 ) )
+ '0thCol' => 'right',
+ 'exifCode' => 3,
+ 'counterRotation' => array( array( -1, 0 ), array( 0, -1 ) )
),
- array(
- '0thRow' => 'right',
- '0thCol' => 'top',
- 'exifCode' => 6,
- 'counterRotation' => array( array( 0, 1 ), array( 1, 0 ) )
+ array(
+ '0thRow' => 'right',
+ '0thCol' => 'top',
+ 'exifCode' => 6,
+ 'counterRotation' => array( array( 0, 1 ), array( 1, 0 ) )
),
- array(
- '0thRow' => 'left',
- '0thCol' => 'bottom',
- 'exifCode' => 8,
- 'counterRotation' => array( array( 0, -1 ), array( -1, 0 ) )
+ array(
+ '0thRow' => 'left',
+ '0thCol' => 'bottom',
+ 'exifCode' => 8,
+ 'counterRotation' => array( array( 0, -1 ), array( -1, 0 ) )
)
);
public function __construct( $options = array() ) {
- foreach ( array( 'dictionaryFile', 'minWidth', 'minHeight', 'maxHeight', 'shapesToDraw' ) as $property ) {
+ foreach ( array( 'dictionaryFile', 'minWidth', 'minHeight', 'maxWidth', 'maxHeight', 'shapesToDraw' ) as $property ) {
if ( isset( $options[$property] ) ) {
$this->$property = $options[$property];
}
@@ -77,10 +76,10 @@ class RandomImageGenerator {
// find the dictionary file, to generate random names
if ( !isset( $this->dictionaryFile ) ) {
- foreach ( array(
- '/usr/share/dict/words',
- '/usr/dict/words',
- dirname( __FILE__ ) . '/words.txt' )
+ foreach ( array(
+ '/usr/share/dict/words',
+ '/usr/dict/words',
+ dirname( __FILE__ ) . '/words.txt' )
as $dictionaryFile ) {
if ( is_file( $dictionaryFile ) and is_readable( $dictionaryFile ) ) {
$this->dictionaryFile = $dictionaryFile;
@@ -91,14 +90,6 @@ class RandomImageGenerator {
if ( !isset( $this->dictionaryFile ) ) {
throw new Exception( "RandomImageGenerator: dictionary file not found or not specified properly" );
}
-
- if ( !class_exists( 'Imagick' ) ) {
- throw new Exception( 'No Imagick extension' );
- }
- global $wgExiv2Command;
- if ( !$wgExiv2Command || !is_executable( $wgExiv2Command ) ) {
- throw new Exception( 'exiv2 not executable or $wgExiv2Command not set' );
- }
}
/**
@@ -125,15 +116,16 @@ class RandomImageGenerator {
*/
function getImageWriteMethod( $format ) {
global $wgUseImageMagick, $wgImageMagickConvertCommand;
- if ( $format === 'svg' ) {
+ if ( $format === 'svg' ) {
return 'writeSvg';
} else {
// figure out how to write images
- if ( class_exists( 'Imagick' ) ) {
+ global $wgExiv2Command;
+ if ( class_exists( 'Imagick' ) && $wgExiv2Command && is_executable( $wgExiv2Command ) ) {
return 'writeImageWithApi';
} elseif ( $wgUseImageMagick && $wgImageMagickConvertCommand && is_executable( $wgImageMagickConvertCommand ) ) {
return 'writeImageWithCommandLine';
- }
+ }
}
throw new Exception( "RandomImageGenerator: could not find a suitable method to write images in '$format' format" );
}
@@ -219,7 +211,7 @@ class RandomImageGenerator {
*/
static function shapePointsToString( $shape ) {
$points = array();
- foreach ( $shape as $point ) {
+ foreach ( $shape as $point ) {
$points[] = $point['x'] . ',' . $point['y'];
}
return join( " ", $points );
@@ -232,16 +224,16 @@ class RandomImageGenerator {
* @param $format: file format to write (which is obviously always svg here)
* @param $filename: filename to write to
*/
- public function writeSvg( $spec, $format, $filename ) {
+ public function writeSvg( $spec, $format, $filename ) {
$svg = new SimpleXmlElement( '<svg/>' );
$svg->addAttribute( 'xmlns', 'http://www.w3.org/2000/svg' );
- $svg->addAttribute( 'version', '1.1' );
- $svg->addAttribute( 'width', $spec['width'] );
- $svg->addAttribute( 'height', $spec['height'] );
+ $svg->addAttribute( 'version', '1.1' );
+ $svg->addAttribute( 'width', $spec['width'] );
+ $svg->addAttribute( 'height', $spec['height'] );
$g = $svg->addChild( 'g' );
foreach ( $spec['draws'] as $drawSpec ) {
$shape = $g->addChild( 'polygon' );
- $shape->addAttribute( 'fill', $drawSpec['fill'] );
+ $shape->addAttribute( 'fill', $drawSpec['fill'] );
$shape->addAttribute( 'points', self::shapePointsToString( $drawSpec['shape'] ) );
};
if ( ! $fh = fopen( $filename, 'w' ) ) {
@@ -260,20 +252,20 @@ class RandomImageGenerator {
* @param $filename: filename to write to
*/
public function writeImageWithApi( $spec, $format, $filename ) {
- // this is a hack because I can't get setImageOrientation() to work. See below.
+ // this is a hack because I can't get setImageOrientation() to work. See below.
global $wgExiv2Command;
$image = new Imagick();
/**
- * If the format is 'jpg', will also add a random orientation -- the image will be drawn rotated with triangle points
+ * If the format is 'jpg', will also add a random orientation -- the image will be drawn rotated with triangle points
* facing in some direction (0, 90, 180 or 270 degrees) and a countering rotation should turn the triangle points upward again
*/
$orientation = self::$orientations[0]; // default is normal orientation
if ( $format == 'jpg' ) {
$orientation = self::$orientations[ array_rand( self::$orientations ) ];
- $spec = self::rotateImageSpec( $spec, $orientation['counterRotation'] );
+ $spec = self::rotateImageSpec( $spec, $orientation['counterRotation'] );
}
-
+
$image->newImage( $spec['width'], $spec['height'], new ImagickPixel( $spec['fill'] ) );
foreach ( $spec['draws'] as $drawSpec ) {
@@ -296,7 +288,7 @@ class RandomImageGenerator {
$cmd = wfEscapeShellArg( $wgExiv2Command )
. " -M "
. wfEscapeShellArg( "set Exif.Image.Orientation " . $orientation['exifCode'] )
- . " "
+ . " "
. wfEscapeShellArg( $filename );
$retval = 0;
@@ -304,15 +296,13 @@ class RandomImageGenerator {
if ( $retval !== 0 ) {
print "Error with $cmd: $retval, $err\n";
}
- }
-
-
+ }
}
/**
* Given an image specification, produce rotated version
* This is used when simulating a rotated image capture with EXIF orientation
- * @param $spec Object returned by getImageSpec
+ * @param $spec Object returned by getImageSpec
* @param $matrix 2x2 transformation matrix
* @return transformed Spec
*/
@@ -323,8 +313,8 @@ class RandomImageGenerator {
$correctionY = 0;
if ( $dims['x'] < 0 ) {
$correctionX = abs( $dims['x'] );
- }
- if ( $dims['y'] < 0 ) {
+ }
+ if ( $dims['y'] < 0 ) {
$correctionY = abs( $dims['y'] );
}
$tSpec['width'] = abs( $dims['x'] );
@@ -332,7 +322,7 @@ class RandomImageGenerator {
$tSpec['fill'] = $spec['fill'];
$tSpec['draws'] = array();
foreach( $spec['draws'] as $draw ) {
- $tDraw = array(
+ $tDraw = array(
'fill' => $draw['fill'],
'shape' => array()
);
@@ -349,13 +339,13 @@ class RandomImageGenerator {
/**
* Given a matrix and a pair of images, return new position
- * @param $matrix: 2x2 rotation matrix
+ * @param $matrix: 2x2 rotation matrix
* @param $x: x-coordinate number
* @param $y: y-coordinate number
- * @return Array transformed with properties x, y
+ * @return Array transformed with properties x, y
*/
private static function matrixMultiply2x2( $matrix, $x, $y ) {
- return array(
+ return array(
'x' => $x * $matrix[0][0] + $y * $matrix[0][1],
'y' => $x * $matrix[1][0] + $y * $matrix[1][1]
);
@@ -366,10 +356,10 @@ class RandomImageGenerator {
* Based on an image specification, write such an image to disk, using the command line ImageMagick program ('convert').
*
* 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(99,123,231) circle 59,39 56,57' \
- * -draw 'fill rgb(240,12,32) circle 50,21 50,3' filename.png
+ * $ 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(99,123,231) circle 59,39 56,57' \
+ * -draw 'fill rgb(240,12,32) circle 50,21 50,3' filename.png
*
* @param $spec: spec describing background and shapes to draw
* @param $format: file format to write (unused by this method but kept so it has the same signature as writeImageWithApi)
diff --git a/tests/phpunit/includes/db/DatabaseSqliteTest.php b/tests/phpunit/includes/db/DatabaseSqliteTest.php
index 914ab27c..067c731a 100644
--- a/tests/phpunit/includes/db/DatabaseSqliteTest.php
+++ b/tests/phpunit/includes/db/DatabaseSqliteTest.php
@@ -19,6 +19,7 @@ class MockDatabaseSqlite extends DatabaseSqliteStandalone {
/**
* @group sqlite
+ * @group Database
*/
class DatabaseSqliteTest extends MediaWikiTestCase {
var $db;
@@ -98,7 +99,7 @@ class DatabaseSqliteTest extends MediaWikiTestCase {
$this->assertEquals( 'sqlite_master', $db->tableName( 'sqlite_master' ) );
$this->assertEquals( 'foobar', $db->tableName( 'bar' ) );
}
-
+
public function testDuplicateTableStructure() {
$db = new DatabaseSqliteStandalone( ':memory:' );
$db->query( 'CREATE TABLE foo(foo, barfoo)' );
@@ -119,7 +120,7 @@ class DatabaseSqliteTest extends MediaWikiTestCase {
'Create a temporary duplicate only'
);
}
-
+
public function testDuplicateTableStructureVirtual() {
$db = new DatabaseSqliteStandalone( ':memory:' );
if ( $db->getFulltextSearchModule() != 'FTS3' ) {
@@ -191,13 +192,14 @@ class DatabaseSqliteTest extends MediaWikiTestCase {
'1.15',
'1.16',
'1.17',
+ '1.18',
);
// Mismatches for these columns we can safely ignore
$ignoredColumns = array(
'user_newtalk.user_last_timestamp', // r84185
);
-
+
$currentDB = new DatabaseSqliteStandalone( ':memory:' );
$currentDB->sourceFile( "$IP/maintenance/tables.sql" );
$currentTables = $this->getTables( $currentDB );
@@ -254,9 +256,10 @@ class DatabaseSqliteTest extends MediaWikiTestCase {
$maint = new FakeMaintenance();
$maint->loadParamsAndArgs( null, array( 'quiet' => 1 ) );
}
-
+
+ global $IP;
$db = new DatabaseSqliteStandalone( ':memory:' );
- $db->sourceFile( dirname( __FILE__ ) . "/sqlite/tables-$version.sql" );
+ $db->sourceFile( "$IP/tests/phpunit/data/db/sqlite/tables-$version.sql" );
$updater = DatabaseUpdater::newForDB( $db, false, $maint );
$updater->doUpdates( array( 'core' ) );
return $db;
@@ -266,6 +269,7 @@ class DatabaseSqliteTest extends MediaWikiTestCase {
$list = array_flip( $db->listTables() );
$excluded = array(
'math', // moved out of core in 1.18
+ 'trackbacks', // removed from core in 1.19
'searchindex',
'searchindex_content',
'searchindex_segments',
diff --git a/tests/phpunit/includes/db/DatabaseTest.php b/tests/phpunit/includes/db/DatabaseTest.php
index d480ac6e..672e6645 100644
--- a/tests/phpunit/includes/db/DatabaseTest.php
+++ b/tests/phpunit/includes/db/DatabaseTest.php
@@ -2,12 +2,20 @@
/**
* @group Database
+ * @group DatabaseBase
*/
class DatabaseTest extends MediaWikiTestCase {
- var $db;
+ var $db, $functionTest = false;
function setUp() {
- $this->db = wfGetDB( DB_SLAVE );
+ $this->db = wfGetDB( DB_MASTER );
+ }
+
+ function tearDown() {
+ if ( $this->functionTest ) {
+ $this->dropFunctions();
+ $this->functionTest = false;
+ }
}
function testAddQuotesNull() {
@@ -90,6 +98,26 @@ class DatabaseTest extends MediaWikiTestCase {
$sql );
}
+ /**
+ * @group Broken
+ */
+ function testStoredFunctions() {
+ if ( !in_array( wfGetDB( DB_MASTER )->getType(), array( 'mysql', 'postgres' ) ) ) {
+ $this->markTestSkipped( 'MySQL or Postgres required' );
+ }
+ global $IP;
+ $this->dropFunctions();
+ $this->functionTest = true;
+ $this->assertTrue( $this->db->sourceFile( "$IP/tests/phpunit/data/db/{$this->db->getType()}/functions.sql" ) );
+ $res = $this->db->query( 'SELECT mw_test_function() AS test', __METHOD__ );
+ $this->assertEquals( 42, $res->fetchObject()->test );
+ }
+
+ private function dropFunctions() {
+ $this->db->query( 'DROP FUNCTION IF EXISTS mw_test_function'
+ . ( $this->db->getType() == 'postgres' ? '()' : '' )
+ );
+ }
}
diff --git a/tests/phpunit/includes/debug/MWDebugTest.php b/tests/phpunit/includes/debug/MWDebugTest.php
new file mode 100644
index 00000000..5a4e66d4
--- /dev/null
+++ b/tests/phpunit/includes/debug/MWDebugTest.php
@@ -0,0 +1,63 @@
+<?php
+
+class MWDebugTest extends MediaWikiTestCase {
+
+
+ function setUp() {
+ // Make sure MWDebug class is enabled
+ static $MWDebugEnabled = false;
+ if( !$MWDebugEnabled ) {
+ MWDebug::init();
+ $MWDebugEnabled = true;
+ }
+ /** Clear log before each test */
+ MWDebug::clearLog();
+ }
+
+ function testAddLog() {
+ MWDebug::log( 'logging a string' );
+ $this->assertEquals( array( array(
+ 'msg' => 'logging a string',
+ 'type' => 'log',
+ 'caller' => __METHOD__ ,
+ ) ),
+ MWDebug::getLog()
+ );
+ }
+
+ function testAddWarning() {
+ MWDebug::warning( 'Warning message' );
+ $this->assertEquals( array( array(
+ 'msg' => 'Warning message',
+ 'type' => 'warn',
+ 'caller' => 'MWDebug::warning',
+ ) ),
+ MWDebug::getLog()
+ );
+ }
+
+ function testAvoidDuplicateDeprecations() {
+ MWDebug::deprecated( 'wfOldFunction', '1.0', 'component' );
+ MWDebug::deprecated( 'wfOldFunction', '1.0', 'component' );
+
+ // assertCount() not available on WMF integration server
+ $this->assertEquals( 1,
+ count( MWDebug::getLog() ),
+ "Only one deprecated warning per function should be kept"
+ );
+ }
+
+ function testAvoidNonConsecutivesDuplicateDeprecations() {
+ MWDebug::deprecated( 'wfOldFunction', '1.0', 'component' );
+ MWDebug::warning( 'some warning' );
+ MWDebug::log( 'we could have logged something too' );
+ // Another deprecation
+ MWDebug::deprecated( 'wfOldFunction', '1.0', 'component' );
+
+ // assertCount() not available on WMF integration server
+ $this->assertEquals( 3,
+ count( MWDebug::getLog() ),
+ "Only one deprecated warning per function should be kept"
+ );
+ }
+}
diff --git a/tests/phpunit/includes/filerepo/FileBackendTest.php b/tests/phpunit/includes/filerepo/FileBackendTest.php
new file mode 100644
index 00000000..da44797a
--- /dev/null
+++ b/tests/phpunit/includes/filerepo/FileBackendTest.php
@@ -0,0 +1,1358 @@
+<?php
+
+/**
+ * @group FileRepo
+ * @group FileBackend
+ */
+class FileBackendTest extends MediaWikiTestCase {
+ private $backend, $multiBackend;
+ private $filesToPrune = array();
+ private $dirsToPrune = array();
+ private static $backendToUse;
+
+ function setUp() {
+ global $wgFileBackends;
+ parent::setUp();
+ $tmpPrefix = wfTempDir() . '/filebackend-unittest-' . time() . '-' . mt_rand();
+ if ( $this->getCliArg( 'use-filebackend=' ) ) {
+ if ( self::$backendToUse ) {
+ $this->singleBackend = self::$backendToUse;
+ } else {
+ $name = $this->getCliArg( 'use-filebackend=' );
+ $useConfig = array();
+ foreach ( $wgFileBackends as $conf ) {
+ if ( $conf['name'] == $name ) {
+ $useConfig = $conf;
+ }
+ }
+ $useConfig['name'] = 'localtesting'; // swap name
+ $class = $conf['class'];
+ self::$backendToUse = new $class( $useConfig );
+ $this->singleBackend = self::$backendToUse;
+ }
+ } else {
+ $this->singleBackend = new FSFileBackend( array(
+ 'name' => 'localtesting',
+ 'lockManager' => 'fsLockManager',
+ 'containerPaths' => array(
+ 'unittest-cont1' => "{$tmpPrefix}-localtesting-cont1",
+ 'unittest-cont2' => "{$tmpPrefix}-localtesting-cont2" )
+ ) );
+ }
+ $this->multiBackend = new FileBackendMultiWrite( array(
+ 'name' => 'localtesting',
+ 'lockManager' => 'fsLockManager',
+ 'backends' => array(
+ array(
+ 'name' => 'localmutlitesting1',
+ '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',
+ 'containerPaths' => array(
+ 'unittest-cont1' => "{$tmpPrefix}-localtestingmulti2-cont1",
+ 'unittest-cont2' => "{$tmpPrefix}-localtestingmulti2-cont2" ),
+ 'isMultiMaster' => true
+ )
+ )
+ ) );
+ $this->filesToPrune = array();
+ }
+
+ private function baseStorePath() {
+ return 'mwstore://localtesting';
+ }
+
+ private function backendClass() {
+ return get_class( $this->backend );
+ }
+
+ /**
+ * @dataProvider provider_testIsStoragePath
+ */
+ public function testIsStoragePath( $path, $isStorePath ) {
+ $this->assertEquals( $isStorePath, FileBackend::isStoragePath( $path ),
+ "FileBackend::isStoragePath on path '$path'" );
+ }
+
+ function provider_testIsStoragePath() {
+ return array(
+ array( 'mwstore://', true ),
+ array( 'mwstore://backend', true ),
+ array( 'mwstore://backend/container', true ),
+ array( 'mwstore://backend/container/', true ),
+ array( 'mwstore://backend/container/path', true ),
+ array( 'mwstore://backend//container/', true ),
+ array( 'mwstore://backend//container//', true ),
+ array( 'mwstore://backend//container//path', true ),
+ array( 'mwstore:///', true ),
+ array( 'mwstore:/', false ),
+ array( 'mwstore:', false ),
+ );
+ }
+
+ /**
+ * @dataProvider provider_testSplitStoragePath
+ */
+ public function testSplitStoragePath( $path, $res ) {
+ $this->assertEquals( $res, FileBackend::splitStoragePath( $path ),
+ "FileBackend::splitStoragePath on path '$path'" );
+ }
+
+ function provider_testSplitStoragePath() {
+ return array(
+ array( 'mwstore://backend/container', array( 'backend', 'container', '' ) ),
+ array( 'mwstore://backend/container/', array( 'backend', 'container', '' ) ),
+ array( 'mwstore://backend/container/path', array( 'backend', 'container', 'path' ) ),
+ array( 'mwstore://backend/container//path', array( 'backend', 'container', '/path' ) ),
+ array( 'mwstore://backend//container/path', array( null, null, null ) ),
+ array( 'mwstore://backend//container//path', array( null, null, null ) ),
+ array( 'mwstore://', array( null, null, null ) ),
+ array( 'mwstore://backend', array( null, null, null ) ),
+ array( 'mwstore:///', array( null, null, null ) ),
+ array( 'mwstore:/', array( null, null, null ) ),
+ array( 'mwstore:', array( null, null, null ) )
+ );
+ }
+
+ /**
+ * @dataProvider provider_normalizeStoragePath
+ */
+ public function testNormalizeStoragePath( $path, $res ) {
+ $this->assertEquals( $res, FileBackend::normalizeStoragePath( $path ),
+ "FileBackend::normalizeStoragePath on path '$path'" );
+ }
+
+ 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://', 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 ), )
+ );
+ }
+
+ /**
+ * @dataProvider provider_testParentStoragePath
+ */
+ public function testParentStoragePath( $path, $res ) {
+ $this->assertEquals( $res, FileBackend::parentStoragePath( $path ),
+ "FileBackend::parentStoragePath on path '$path'" );
+ }
+
+ 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' ),
+ array( 'mwstore://backend/container/path', 'mwstore://backend/container' ),
+ array( 'mwstore://backend/container', null ),
+ array( 'mwstore://backend/container/path/to/obj/', 'mwstore://backend/container/path/to' ),
+ array( 'mwstore://backend/container/path/to/', 'mwstore://backend/container/path' ),
+ array( 'mwstore://backend/container/path/', 'mwstore://backend/container' ),
+ array( 'mwstore://backend/container/', null ),
+ );
+ }
+
+ /**
+ * @dataProvider provider_testExtensionFromPath
+ */
+ public function testExtensionFromPath( $path, $res ) {
+ $this->assertEquals( $res, FileBackend::extensionFromPath( $path ),
+ "FileBackend::extensionFromPath on path '$path'" );
+ }
+
+ function provider_testExtensionFromPath() {
+ return array(
+ array( 'mwstore://backend/container/path.txt', 'txt' ),
+ array( 'mwstore://backend/container/path.svg.png', 'png' ),
+ array( 'mwstore://backend/container/path', '' ),
+ array( 'mwstore://backend/container/path.', '' ),
+ );
+ }
+
+ /**
+ * @dataProvider provider_testStore
+ */
+ public function testStore( $op ) {
+ $this->filesToPrune[] = $op['src'];
+
+ $this->backend = $this->singleBackend;
+ $this->tearDownFiles();
+ $this->doTestStore( $op );
+ $this->tearDownFiles();
+
+ $this->backend = $this->multiBackend;
+ $this->tearDownFiles();
+ $this->doTestStore( $op );
+ $this->filesToPrune[] = $op['src']; # avoid file leaking
+ $this->tearDownFiles();
+ }
+
+ function doTestStore( $op ) {
+ $backendName = $this->backendClass();
+
+ $source = $op['src'];
+ $dest = $op['dst'];
+ $this->prepare( array( 'dir' => dirname( $dest ) ) );
+
+ file_put_contents( $source, "Unit test file" );
+
+ if ( isset( $op['overwrite'] ) || isset( $op['overwriteSame'] ) ) {
+ $this->backend->store( $op );
+ }
+
+ $status = $this->backend->doOperation( $op );
+
+ $this->assertEquals( array(), $status->errors,
+ "Store from $source to $dest succeeded without warnings ($backendName)." );
+ $this->assertEquals( array(), $status->errors,
+ "Store from $source to $dest succeeded ($backendName)." );
+ $this->assertEquals( array( 0 => true ), $status->success,
+ "Store from $source to $dest has proper 'success' field in Status ($backendName)." );
+ $this->assertEquals( true, file_exists( $source ),
+ "Source file $source still exists ($backendName)." );
+ $this->assertEquals( true, $this->backend->fileExists( array( 'src' => $dest ) ),
+ "Destination file $dest exists ($backendName)." );
+
+ $this->assertEquals( filesize( $source ),
+ $this->backend->getFileSize( array( 'src' => $dest ) ),
+ "Destination file $dest has correct size ($backendName)." );
+
+ $props1 = FSFile::getPropsFromPath( $source );
+ $props2 = $this->backend->getFileProps( array( 'src' => $dest ) );
+ $this->assertEquals( $props1, $props2,
+ "Source and destination have the same props ($backendName)." );
+ }
+
+ public function provider_testStore() {
+ $cases = array();
+
+ $tmpName = TempFSFile::factory( "unittests_", 'txt' )->getPath();
+ $toPath = $this->baseStorePath() . '/unittest-cont1/fun/obj1.txt';
+ $op = array( 'op' => 'store', 'src' => $tmpName, 'dst' => $toPath );
+ $cases[] = array(
+ $op, // operation
+ $tmpName, // source
+ $toPath, // dest
+ );
+
+ $op2 = $op;
+ $op2['overwrite'] = true;
+ $cases[] = array(
+ $op2, // operation
+ $tmpName, // source
+ $toPath, // dest
+ );
+
+ $op2 = $op;
+ $op2['overwriteSame'] = true;
+ $cases[] = array(
+ $op2, // operation
+ $tmpName, // source
+ $toPath, // dest
+ );
+
+ return $cases;
+ }
+
+ /**
+ * @dataProvider provider_testCopy
+ */
+ public function testCopy( $op ) {
+ $this->backend = $this->singleBackend;
+ $this->tearDownFiles();
+ $this->doTestCopy( $op );
+ $this->tearDownFiles();
+
+ $this->backend = $this->multiBackend;
+ $this->tearDownFiles();
+ $this->doTestCopy( $op );
+ $this->tearDownFiles();
+ }
+
+ function doTestCopy( $op ) {
+ $backendName = $this->backendClass();
+
+ $source = $op['src'];
+ $dest = $op['dst'];
+ $this->prepare( array( 'dir' => dirname( $source ) ) );
+ $this->prepare( array( 'dir' => dirname( $dest ) ) );
+
+ $status = $this->backend->doOperation(
+ array( 'op' => 'create', 'content' => 'blahblah', 'dst' => $source ) );
+ $this->assertEquals( array(), $status->errors,
+ "Creation of file at $source succeeded ($backendName)." );
+
+ if ( isset( $op['overwrite'] ) || isset( $op['overwriteSame'] ) ) {
+ $this->backend->copy( $op );
+ }
+
+ $status = $this->backend->doOperation( $op );
+
+ $this->assertEquals( array(), $status->errors,
+ "Copy from $source to $dest succeeded without warnings ($backendName)." );
+ $this->assertEquals( true, $status->isOK(),
+ "Copy from $source to $dest succeeded ($backendName)." );
+ $this->assertEquals( array( 0 => true ), $status->success,
+ "Copy from $source to $dest has proper 'success' field in Status ($backendName)." );
+ $this->assertEquals( true, $this->backend->fileExists( array( 'src' => $source ) ),
+ "Source file $source still exists ($backendName)." );
+ $this->assertEquals( true, $this->backend->fileExists( array( 'src' => $dest ) ),
+ "Destination file $dest exists after copy ($backendName)." );
+
+ $this->assertEquals(
+ $this->backend->getFileSize( array( 'src' => $source ) ),
+ $this->backend->getFileSize( array( 'src' => $dest ) ),
+ "Destination file $dest has correct size ($backendName)." );
+
+ $props1 = $this->backend->getFileProps( array( 'src' => $source ) );
+ $props2 = $this->backend->getFileProps( array( 'src' => $dest ) );
+ $this->assertEquals( $props1, $props2,
+ "Source and destination have the same props ($backendName)." );
+ }
+
+ public function provider_testCopy() {
+ $cases = array();
+
+ $source = $this->baseStorePath() . '/unittest-cont1/file.txt';
+ $dest = $this->baseStorePath() . '/unittest-cont2/fileMoved.txt';
+
+ $op = array( 'op' => 'copy', 'src' => $source, 'dst' => $dest );
+ $cases[] = array(
+ $op, // operation
+ $source, // source
+ $dest, // dest
+ );
+
+ $op2 = $op;
+ $op2['overwrite'] = true;
+ $cases[] = array(
+ $op2, // operation
+ $source, // source
+ $dest, // dest
+ );
+
+ $op2 = $op;
+ $op2['overwriteSame'] = true;
+ $cases[] = array(
+ $op2, // operation
+ $source, // source
+ $dest, // dest
+ );
+
+ return $cases;
+ }
+
+ /**
+ * @dataProvider provider_testMove
+ */
+ public function testMove( $op ) {
+ $this->backend = $this->singleBackend;
+ $this->tearDownFiles();
+ $this->doTestMove( $op );
+ $this->tearDownFiles();
+
+ $this->backend = $this->multiBackend;
+ $this->tearDownFiles();
+ $this->doTestMove( $op );
+ $this->tearDownFiles();
+ }
+
+ private function doTestMove( $op ) {
+ $backendName = $this->backendClass();
+
+ $source = $op['src'];
+ $dest = $op['dst'];
+ $this->prepare( array( 'dir' => dirname( $source ) ) );
+ $this->prepare( array( 'dir' => dirname( $dest ) ) );
+
+ $status = $this->backend->doOperation(
+ array( 'op' => 'create', 'content' => 'blahblah', 'dst' => $source ) );
+ $this->assertEquals( array(), $status->errors,
+ "Creation of file at $source succeeded ($backendName)." );
+
+ if ( isset( $op['overwrite'] ) || isset( $op['overwriteSame'] ) ) {
+ $this->backend->copy( $op );
+ }
+
+ $status = $this->backend->doOperation( $op );
+ $this->assertEquals( array(), $status->errors,
+ "Move from $source to $dest succeeded without warnings ($backendName)." );
+ $this->assertEquals( true, $status->isOK(),
+ "Move from $source to $dest succeeded ($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 still exists ($backendName)." );
+ $this->assertEquals( true, $this->backend->fileExists( array( 'src' => $dest ) ),
+ "Destination file $dest exists after move ($backendName)." );
+
+ $this->assertNotEquals(
+ $this->backend->getFileSize( array( 'src' => $source ) ),
+ $this->backend->getFileSize( array( 'src' => $dest ) ),
+ "Destination file $dest has correct size ($backendName)." );
+
+ $props1 = $this->backend->getFileProps( array( 'src' => $source ) );
+ $props2 = $this->backend->getFileProps( array( 'src' => $dest ) );
+ $this->assertEquals( false, $props1['fileExists'],
+ "Source file does not exist accourding to props ($backendName)." );
+ $this->assertEquals( true, $props2['fileExists'],
+ "Destination file exists accourding to props ($backendName)." );
+ }
+
+ public function provider_testMove() {
+ $cases = array();
+
+ $source = $this->baseStorePath() . '/unittest-cont1/file.txt';
+ $dest = $this->baseStorePath() . '/unittest-cont2/fileMoved.txt';
+
+ $op = array( 'op' => 'move', 'src' => $source, 'dst' => $dest );
+ $cases[] = array(
+ $op, // operation
+ $source, // source
+ $dest, // dest
+ );
+
+ $op2 = $op;
+ $op2['overwrite'] = true;
+ $cases[] = array(
+ $op2, // operation
+ $source, // source
+ $dest, // dest
+ );
+
+ $op2 = $op;
+ $op2['overwriteSame'] = true;
+ $cases[] = array(
+ $op2, // operation
+ $source, // source
+ $dest, // dest
+ );
+
+ return $cases;
+ }
+
+ /**
+ * @dataProvider provider_testDelete
+ */
+ public function testDelete( $op, $withSource, $okStatus ) {
+ $this->backend = $this->singleBackend;
+ $this->tearDownFiles();
+ $this->doTestDelete( $op, $withSource, $okStatus );
+ $this->tearDownFiles();
+
+ $this->backend = $this->multiBackend;
+ $this->tearDownFiles();
+ $this->doTestDelete( $op, $withSource, $okStatus );
+ $this->tearDownFiles();
+ }
+
+ private function doTestDelete( $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->assertEquals( array(), $status->errors,
+ "Creation of file at $source succeeded ($backendName)." );
+ }
+
+ $status = $this->backend->doOperation( $op );
+ if ( $okStatus ) {
+ $this->assertEquals( array(), $status->errors,
+ "Deletion of file at $source succeeded without warnings ($backendName)." );
+ $this->assertEquals( true, $status->isOK(),
+ "Deletion of file at $source succeeded ($backendName)." );
+ $this->assertEquals( array( 0 => true ), $status->success,
+ "Deletion of file at $source has proper 'success' field in Status ($backendName)." );
+ } else {
+ $this->assertEquals( false, $status->isOK(),
+ "Deletion of file at $source failed ($backendName)." );
+ }
+
+ $this->assertEquals( false, $this->backend->fileExists( array( 'src' => $source ) ),
+ "Source file $source does not exist after move ($backendName)." );
+
+ $this->assertFalse(
+ $this->backend->getFileSize( array( 'src' => $source ) ),
+ "Source file $source has correct size (false) ($backendName)." );
+
+ $props1 = $this->backend->getFileProps( array( 'src' => $source ) );
+ $this->assertFalse( $props1['fileExists'],
+ "Source file $source does not exist according to props ($backendName)." );
+ }
+
+ public function provider_testDelete() {
+ $cases = array();
+
+ $source = $this->baseStorePath() . '/unittest-cont1/myfacefile.txt';
+
+ $op = array( 'op' => 'delete', 'src' => $source );
+ $cases[] = array(
+ $op, // operation
+ true, // with source
+ true // succeeds
+ );
+
+ $cases[] = array(
+ $op, // operation
+ false, // without source
+ false // fails
+ );
+
+ $op['ignoreMissingSource'] = true;
+ $cases[] = array(
+ $op, // operation
+ false, // without source
+ true // succeeds
+ );
+
+ return $cases;
+ }
+
+ /**
+ * @dataProvider provider_testCreate
+ */
+ public function testCreate( $op, $alreadyExists, $okStatus, $newSize ) {
+ $this->backend = $this->singleBackend;
+ $this->tearDownFiles();
+ $this->doTestCreate( $op, $alreadyExists, $okStatus, $newSize );
+ $this->tearDownFiles();
+
+ $this->backend = $this->multiBackend;
+ $this->tearDownFiles();
+ $this->doTestCreate( $op, $alreadyExists, $okStatus, $newSize );
+ $this->tearDownFiles();
+ }
+
+ private function doTestCreate( $op, $alreadyExists, $okStatus, $newSize ) {
+ $backendName = $this->backendClass();
+
+ $dest = $op['dst'];
+ $this->prepare( array( 'dir' => dirname( $dest ) ) );
+
+ $oldText = 'blah...blah...waahwaah';
+ if ( $alreadyExists ) {
+ $status = $this->backend->doOperation(
+ array( 'op' => 'create', 'content' => $oldText, 'dst' => $dest ) );
+ $this->assertEquals( array(), $status->errors,
+ "Creation of file at $dest succeeded ($backendName)." );
+ }
+
+ $status = $this->backend->doOperation( $op );
+ if ( $okStatus ) {
+ $this->assertEquals( array(), $status->errors,
+ "Creation of file at $dest succeeded without warnings ($backendName)." );
+ $this->assertEquals( true, $status->isOK(),
+ "Creation of file at $dest succeeded ($backendName)." );
+ $this->assertEquals( array( 0 => true ), $status->success,
+ "Creation of file at $dest has proper 'success' field in Status ($backendName)." );
+ } else {
+ $this->assertEquals( false, $status->isOK(),
+ "Creation of file at $dest failed ($backendName)." );
+ }
+
+ $this->assertEquals( true, $this->backend->fileExists( array( 'src' => $dest ) ),
+ "Destination file $dest exists after creation ($backendName)." );
+
+ $props1 = $this->backend->getFileProps( array( 'src' => $dest ) );
+ $this->assertEquals( true, $props1['fileExists'],
+ "Destination file $dest exists according to props ($backendName)." );
+ if ( $okStatus ) { // file content is what we saved
+ $this->assertEquals( $newSize, $props1['size'],
+ "Destination file $dest has expected size according to props ($backendName)." );
+ $this->assertEquals( $newSize,
+ $this->backend->getFileSize( array( 'src' => $dest ) ),
+ "Destination file $dest has correct size ($backendName)." );
+ } else { // file content is some other previous text
+ $this->assertEquals( strlen( $oldText ), $props1['size'],
+ "Destination file $dest has original size according to props ($backendName)." );
+ $this->assertEquals( strlen( $oldText ),
+ $this->backend->getFileSize( array( 'src' => $dest ) ),
+ "Destination file $dest has original size according to props ($backendName)." );
+ }
+ }
+
+ /**
+ * @dataProvider provider_testCreate
+ */
+ public function provider_testCreate() {
+ $cases = array();
+
+ $dest = $this->baseStorePath() . '/unittest-cont2/myspacefile.txt';
+
+ $op = array( 'op' => 'create', 'content' => 'test test testing', 'dst' => $dest );
+ $cases[] = array(
+ $op, // operation
+ false, // no dest already exists
+ true, // succeeds
+ strlen( $op['content'] )
+ );
+
+ $op2 = $op;
+ $op2['content'] = "\n";
+ $cases[] = array(
+ $op2, // operation
+ false, // no dest already exists
+ true, // succeeds
+ strlen( $op2['content'] )
+ );
+
+ $op2 = $op;
+ $op2['content'] = "fsf\n waf 3kt";
+ $cases[] = array(
+ $op2, // operation
+ true, // dest already exists
+ false, // fails
+ strlen( $op2['content'] )
+ );
+
+ $op2 = $op;
+ $op2['content'] = "egm'g gkpe gpqg eqwgwqg";
+ $op2['overwrite'] = true;
+ $cases[] = array(
+ $op2, // operation
+ true, // dest already exists
+ true, // succeeds
+ strlen( $op2['content'] )
+ );
+
+ $op2 = $op;
+ $op2['content'] = "39qjmg3-qg";
+ $op2['overwriteSame'] = true;
+ $cases[] = array(
+ $op2, // operation
+ true, // dest already exists
+ false, // succeeds
+ strlen( $op2['content'] )
+ );
+
+ return $cases;
+ }
+
+ /**
+ * @dataProvider provider_testConcatenate
+ */
+ public function testConcatenate( $op, $srcs, $srcsContent, $alreadyExists, $okStatus ) {
+ $this->filesToPrune[] = $op['dst'];
+
+ $this->backend = $this->singleBackend;
+ $this->tearDownFiles();
+ $this->doTestConcatenate( $op, $srcs, $srcsContent, $alreadyExists, $okStatus );
+ $this->tearDownFiles();
+
+ $this->backend = $this->multiBackend;
+ $this->tearDownFiles();
+ $this->doTestConcatenate( $op, $srcs, $srcsContent, $alreadyExists, $okStatus );
+ $this->filesToPrune[] = $op['dst']; # avoid file leaking
+ $this->tearDownFiles();
+ }
+
+ public function doTestConcatenate( $params, $srcs, $srcsContent, $alreadyExists, $okStatus ) {
+ $backendName = $this->backendClass();
+
+ $expContent = '';
+ // Create sources
+ $ops = array();
+ foreach ( $srcs as $i => $source ) {
+ $this->prepare( array( 'dir' => dirname( $source ) ) );
+ $ops[] = array(
+ 'op' => 'create', // operation
+ 'dst' => $source, // source
+ 'content' => $srcsContent[$i]
+ );
+ $expContent .= $srcsContent[$i];
+ }
+ $status = $this->backend->doOperations( $ops );
+
+ $this->assertEquals( array(), $status->errors,
+ "Creation of source files succeeded ($backendName)." );
+
+ $dest = $params['dst'];
+ if ( $alreadyExists ) {
+ $ok = file_put_contents( $dest, 'blah...blah...waahwaah' ) !== false;
+ $this->assertEquals( true, $ok,
+ "Creation of file at $dest succeeded ($backendName)." );
+ } else {
+ $ok = file_put_contents( $dest, '' ) !== false;
+ $this->assertEquals( true, $ok,
+ "Creation of 0-byte file at $dest succeeded ($backendName)." );
+ }
+
+ // Combine the files into one
+ $status = $this->backend->concatenate( $params );
+ if ( $okStatus ) {
+ $this->assertEquals( array(), $status->errors,
+ "Creation of concat file at $dest succeeded without warnings ($backendName)." );
+ $this->assertEquals( true, $status->isOK(),
+ "Creation of concat file at $dest succeeded ($backendName)." );
+ } else {
+ $this->assertEquals( false, $status->isOK(),
+ "Creation of concat file at $dest failed ($backendName)." );
+ }
+
+ if ( $okStatus ) {
+ $this->assertEquals( true, is_file( $dest ),
+ "Dest concat file $dest exists after creation ($backendName)." );
+ } else {
+ $this->assertEquals( true, is_file( $dest ),
+ "Dest concat file $dest exists after failed creation ($backendName)." );
+ }
+
+ $contents = file_get_contents( $dest );
+ $this->assertNotEquals( false, $contents, "File at $dest exists ($backendName)." );
+
+ if ( $okStatus ) {
+ $this->assertEquals( $expContent, $contents,
+ "Concat file at $dest has correct contents ($backendName)." );
+ } else {
+ $this->assertNotEquals( $expContent, $contents,
+ "Concat file at $dest has correct contents ($backendName)." );
+ }
+ }
+
+ function provider_testConcatenate() {
+ $cases = array();
+
+ $rand = mt_rand( 0, 2000000000 ) . time();
+ $dest = wfTempDir() . "/randomfile!$rand.txt";
+ $srcs = array(
+ $this->baseStorePath() . '/unittest-cont1/file1.txt',
+ $this->baseStorePath() . '/unittest-cont1/file2.txt',
+ $this->baseStorePath() . '/unittest-cont1/file3.txt',
+ $this->baseStorePath() . '/unittest-cont1/file4.txt',
+ $this->baseStorePath() . '/unittest-cont1/file5.txt',
+ $this->baseStorePath() . '/unittest-cont1/file6.txt',
+ $this->baseStorePath() . '/unittest-cont1/file7.txt',
+ $this->baseStorePath() . '/unittest-cont1/file8.txt',
+ $this->baseStorePath() . '/unittest-cont1/file9.txt',
+ $this->baseStorePath() . '/unittest-cont1/file10.txt'
+ );
+ $content = array(
+ 'egfage',
+ 'ageageag',
+ 'rhokohlr',
+ 'shgmslkg',
+ 'kenga',
+ 'owagmal',
+ 'kgmae',
+ 'g eak;g',
+ 'lkaem;a',
+ 'legma'
+ );
+ $params = array( 'srcs' => $srcs, 'dst' => $dest );
+
+ $cases[] = array(
+ $params, // operation
+ $srcs, // sources
+ $content, // content for each source
+ false, // no dest already exists
+ true, // succeeds
+ );
+
+ $cases[] = array(
+ $params, // operation
+ $srcs, // sources
+ $content, // content for each source
+ true, // dest already exists
+ false, // succeeds
+ );
+
+ return $cases;
+ }
+
+ /**
+ * @dataProvider provider_testGetFileStat
+ */
+ public function testGetFileStat( $path, $content, $alreadyExists ) {
+ $this->backend = $this->singleBackend;
+ $this->tearDownFiles();
+ $this->doTestGetFileStat( $path, $content, $alreadyExists );
+ $this->tearDownFiles();
+
+ $this->backend = $this->multiBackend;
+ $this->tearDownFiles();
+ $this->doTestGetFileStat( $path, $content, $alreadyExists );
+ $this->tearDownFiles();
+ }
+
+ private function doTestGetFileStat( $path, $content, $alreadyExists ) {
+ $backendName = $this->backendClass();
+
+ if ( $alreadyExists ) {
+ $this->prepare( array( 'dir' => dirname( $path ) ) );
+ $status = $this->backend->create( array( 'dst' => $path, 'content' => $content ) );
+ $this->assertEquals( array(), $status->errors,
+ "Creation of file at $path succeeded ($backendName)." );
+
+ $size = $this->backend->getFileSize( array( 'src' => $path ) );
+ $time = $this->backend->getFileTimestamp( array( 'src' => $path ) );
+ $stat = $this->backend->getFileStat( array( 'src' => $path ) );
+
+ $this->assertEquals( strlen( $content ), $size,
+ "Correct file size of '$path'" );
+ $this->assertTrue( abs( time() - wfTimestamp( TS_UNIX, $time ) ) < 5,
+ "Correct file timestamp of '$path'" );
+
+ $size = $stat['size'];
+ $time = $stat['mtime'];
+ $this->assertEquals( strlen( $content ), $size,
+ "Correct file size of '$path'" );
+ $this->assertTrue( abs( time() - wfTimestamp( TS_UNIX, $time ) ) < 5,
+ "Correct file timestamp of '$path'" );
+ } else {
+ $size = $this->backend->getFileSize( array( 'src' => $path ) );
+ $time = $this->backend->getFileTimestamp( array( 'src' => $path ) );
+ $stat = $this->backend->getFileStat( array( 'src' => $path ) );
+
+ $this->assertFalse( $size, "Correct file size of '$path'" );
+ $this->assertFalse( $time, "Correct file timestamp of '$path'" );
+ $this->assertFalse( $stat, "Correct file stat of '$path'" );
+ }
+ }
+
+ function provider_testGetFileStat() {
+ $cases = array();
+
+ $base = $this->baseStorePath();
+ $cases[] = array( "$base/unittest-cont1/b/z/some_file.txt", "some file contents", true );
+ $cases[] = array( "$base/unittest-cont1/b/some-other_file.txt", "", true );
+ $cases[] = array( "$base/unittest-cont1/b/some-diff_file.txt", null, false );
+
+ return $cases;
+ }
+
+ /**
+ * @dataProvider provider_testGetFileContents
+ */
+ public function testGetFileContents( $source, $content ) {
+ $this->backend = $this->singleBackend;
+ $this->tearDownFiles();
+ $this->doTestGetFileContents( $source, $content );
+ $this->tearDownFiles();
+
+ $this->backend = $this->multiBackend;
+ $this->tearDownFiles();
+ $this->doTestGetFileContents( $source, $content );
+ $this->tearDownFiles();
+ }
+
+ public 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->assertEquals( array(), $status->errors,
+ "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)." );
+
+ $this->assertEquals( $content, $newContents,
+ "Contents read match data at $source ($backendName)." );
+ }
+
+ function provider_testGetFileContents() {
+ $cases = array();
+
+ $base = $this->baseStorePath();
+ $cases[] = array( "$base/unittest-cont1/b/z/some_file.txt", "some file contents" );
+ $cases[] = array( "$base/unittest-cont1/b/some-other_file.txt", "more file contents" );
+
+ return $cases;
+ }
+
+ /**
+ * @dataProvider provider_testGetLocalCopy
+ */
+ public function testGetLocalCopy( $source, $content ) {
+ $this->backend = $this->singleBackend;
+ $this->tearDownFiles();
+ $this->doTestGetLocalCopy( $source, $content );
+ $this->tearDownFiles();
+
+ $this->backend = $this->multiBackend;
+ $this->tearDownFiles();
+ $this->doTestGetLocalCopy( $source, $content );
+ $this->tearDownFiles();
+ }
+
+ public 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->assertEquals( array(), $status->errors,
+ "Creation of file at $source succeeded ($backendName)." );
+
+ $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)." );
+ }
+
+ function provider_testGetLocalCopy() {
+ $cases = array();
+
+ $base = $this->baseStorePath();
+ $cases[] = array( "$base/unittest-cont1/a/z/some_file.txt", "some file contents" );
+ $cases[] = array( "$base/unittest-cont1/a/some-other_file.txt", "more file contents" );
+
+ return $cases;
+ }
+
+ /**
+ * @dataProvider provider_testGetLocalReference
+ */
+ public function testGetLocalReference( $source, $content ) {
+ $this->backend = $this->singleBackend;
+ $this->tearDownFiles();
+ $this->doTestGetLocalReference( $source, $content );
+ $this->tearDownFiles();
+
+ $this->backend = $this->multiBackend;
+ $this->tearDownFiles();
+ $this->doTestGetLocalReference( $source, $content );
+ $this->tearDownFiles();
+ }
+
+ private function doTestGetLocalReference( $source, $content ) {
+ $backendName = $this->backendClass();
+
+ $this->prepare( array( 'dir' => dirname( $source ) ) );
+
+ $status = $this->backend->doOperation(
+ array( 'op' => 'create', 'content' => $content, 'dst' => $source ) );
+ $this->assertEquals( array(), $status->errors,
+ "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)." );
+
+ $contents = file_get_contents( $tmpFile->getPath() );
+ $this->assertNotEquals( false, $contents, "Local copy of $source exists ($backendName)." );
+ }
+
+ function provider_testGetLocalReference() {
+ $cases = array();
+
+ $base = $this->baseStorePath();
+ $cases[] = array( "$base/unittest-cont1/a/z/some_file.txt", "some file contents" );
+ $cases[] = array( "$base/unittest-cont1/a/some-other_file.txt", "more file contents" );
+
+ return $cases;
+ }
+
+ /**
+ * @dataProvider provider_testPrepareAndClean
+ */
+ public function testPrepareAndClean( $path, $isOK ) {
+ $this->backend = $this->singleBackend;
+ $this->doTestPrepareAndClean( $path, $isOK );
+ $this->tearDownFiles();
+
+ $this->backend = $this->multiBackend;
+ $this->doTestPrepareAndClean( $path, $isOK );
+ $this->tearDownFiles();
+ }
+
+ function provider_testPrepareAndClean() {
+ $base = $this->baseStorePath();
+ return array(
+ array( "$base/unittest-cont1/a/z/some_file1.txt", true ),
+ array( "$base/unittest-cont2/a/z/some_file2.txt", true ),
+ # Specific to FS backend with no basePath field set
+ #array( "$base/unittest-cont3/a/z/some_file3.txt", false ),
+ );
+ }
+
+ function doTestPrepareAndClean( $path, $isOK ) {
+ $backendName = $this->backendClass();
+
+ $status = $this->prepare( array( 'dir' => dirname( $path ) ) );
+ if ( $isOK ) {
+ $this->assertEquals( array(), $status->errors,
+ "Preparing dir $path succeeded without warnings ($backendName)." );
+ $this->assertEquals( true, $status->isOK(),
+ "Preparing dir $path succeeded ($backendName)." );
+ } else {
+ $this->assertEquals( false, $status->isOK(),
+ "Preparing dir $path failed ($backendName)." );
+ }
+
+ $status = $this->backend->clean( array( 'dir' => dirname( $path ) ) );
+ if ( $isOK ) {
+ $this->assertEquals( array(), $status->errors,
+ "Cleaning dir $path succeeded without warnings ($backendName)." );
+ $this->assertEquals( true, $status->isOK(),
+ "Cleaning dir $path succeeded ($backendName)." );
+ } else {
+ $this->assertEquals( false, $status->isOK(),
+ "Cleaning dir $path failed ($backendName)." );
+ }
+ }
+
+ // @TODO: testSecure
+
+ public function testDoOperations() {
+ $this->backend = $this->singleBackend;
+ $this->tearDownFiles();
+ $this->doTestDoOperations();
+ $this->tearDownFiles();
+
+ $this->backend = $this->multiBackend;
+ $this->tearDownFiles();
+ $this->doTestDoOperations();
+ $this->tearDownFiles();
+
+ $this->backend = $this->singleBackend;
+ $this->tearDownFiles();
+ $this->doTestDoOperationsFailing();
+ $this->tearDownFiles();
+
+ $this->backend = $this->multiBackend;
+ $this->tearDownFiles();
+ $this->doTestDoOperationsFailing();
+ $this->tearDownFiles();
+
+ // @TODO: test some cases where the ops should fail
+ }
+
+ function doTestDoOperations() {
+ $base = $this->baseStorePath();
+
+ $fileA = "$base/unittest-cont1/a/b/fileA.txt";
+ $fileAContents = '3tqtmoeatmn4wg4qe-mg3qt3 tq';
+ $fileB = "$base/unittest-cont1/a/b/fileB.txt";
+ $fileBContents = 'g-jmq3gpqgt3qtg q3GT ';
+ $fileC = "$base/unittest-cont1/a/b/fileC.txt";
+ $fileCContents = 'eigna[ogmewt 3qt g3qg flew[ag';
+ $fileD = "$base/unittest-cont1/a/b/fileD.txt";
+
+ $this->prepare( array( 'dir' => dirname( $fileA ) ) );
+ $this->backend->create( array( 'dst' => $fileA, 'content' => $fileAContents ) );
+ $this->prepare( array( 'dir' => dirname( $fileB ) ) );
+ $this->backend->create( array( 'dst' => $fileB, 'content' => $fileBContents ) );
+ $this->prepare( array( 'dir' => dirname( $fileC ) ) );
+ $this->backend->create( array( 'dst' => $fileC, 'content' => $fileCContents ) );
+
+ $status = $this->backend->doOperations( array(
+ 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 ),
+ // Now: A:<A>, B:<B>, C:<A>, D:<empty>
+ array( 'op' => 'move', 'src' => $fileC, 'dst' => $fileD, 'overwrite' => 1 ),
+ // Now: A:<A>, B:<B>, C:<empty>, D:<A>
+ array( 'op' => 'move', 'src' => $fileB, 'dst' => $fileC ),
+ // Now: A:<A>, B:<empty>, C:<B>, D:<A>
+ array( 'op' => 'move', 'src' => $fileD, 'dst' => $fileA, 'overwriteSame' => 1 ),
+ // Now: A:<A>, B:<empty>, C:<B>, D:<empty>
+ array( 'op' => 'move', 'src' => $fileC, 'dst' => $fileA, 'overwrite' => 1 ),
+ // Now: A:<B>, B:<empty>, C:<empty>, D:<empty>
+ array( 'op' => 'copy', 'src' => $fileA, 'dst' => $fileC ),
+ // Now: A:<B>, B:<empty>, C:<B>, D:<empty>
+ array( 'op' => 'move', 'src' => $fileA, 'dst' => $fileC, 'overwriteSame' => 1 ),
+ // Now: A:<empty>, B:<empty>, C:<B>, D:<empty>
+ array( 'op' => 'copy', 'src' => $fileC, 'dst' => $fileC, 'overwrite' => 1 ),
+ // Does nothing
+ array( 'op' => 'copy', 'src' => $fileC, 'dst' => $fileC, 'overwriteSame' => 1 ),
+ // Does nothing
+ array( 'op' => 'move', 'src' => $fileC, 'dst' => $fileC, 'overwrite' => 1 ),
+ // Does nothing
+ array( 'op' => 'move', 'src' => $fileC, 'dst' => $fileC, 'overwriteSame' => 1 ),
+ // Does nothing
+ array( 'op' => 'null' ),
+ // Does nothing
+ ) );
+
+ $this->assertEquals( array(), $status->errors, "Operation batch succeeded" );
+ $this->assertEquals( true, $status->isOK(), "Operation batch succeeded" );
+ $this->assertEquals( 13, count( $status->success ),
+ "Operation batch has correct success array" );
+
+ $this->assertEquals( false, $this->backend->fileExists( array( 'src' => $fileA ) ),
+ "File does not exist at $fileA" );
+ $this->assertEquals( false, $this->backend->fileExists( array( 'src' => $fileB ) ),
+ "File does not exist at $fileB" );
+ $this->assertEquals( false, $this->backend->fileExists( array( 'src' => $fileD ) ),
+ "File does not exist at $fileD" );
+
+ $this->assertEquals( true, $this->backend->fileExists( array( 'src' => $fileC ) ),
+ "File exists at $fileC" );
+ $this->assertEquals( $fileBContents,
+ $this->backend->getFileContents( array( 'src' => $fileC ) ),
+ "Correct file contents of $fileC" );
+ $this->assertEquals( strlen( $fileBContents ),
+ $this->backend->getFileSize( array( 'src' => $fileC ) ),
+ "Correct file size of $fileC" );
+ $this->assertEquals( wfBaseConvert( sha1( $fileBContents ), 16, 36, 31 ),
+ $this->backend->getFileSha1Base36( array( 'src' => $fileC ) ),
+ "Correct file SHA-1 of $fileC" );
+ }
+
+ function doTestDoOperationsFailing() {
+ $base = $this->baseStorePath();
+
+ $fileA = "$base/unittest-cont2/a/b/fileA.txt";
+ $fileAContents = '3tqtmoeatmn4wg4qe-mg3qt3 tq';
+ $fileB = "$base/unittest-cont2/a/b/fileB.txt";
+ $fileBContents = 'g-jmq3gpqgt3qtg q3GT ';
+ $fileC = "$base/unittest-cont2/a/b/fileC.txt";
+ $fileCContents = 'eigna[ogmewt 3qt g3qg flew[ag';
+ $fileD = "$base/unittest-cont2/a/b/fileD.txt";
+
+ $this->prepare( array( 'dir' => dirname( $fileA ) ) );
+ $this->backend->create( array( 'dst' => $fileA, 'content' => $fileAContents ) );
+ $this->prepare( array( 'dir' => dirname( $fileB ) ) );
+ $this->backend->create( array( 'dst' => $fileB, 'content' => $fileBContents ) );
+ $this->prepare( array( 'dir' => dirname( $fileC ) ) );
+ $this->backend->create( array( 'dst' => $fileC, 'content' => $fileCContents ) );
+
+ $status = $this->backend->doOperations( array(
+ 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 ),
+ // Now: A:<A>, B:<B>, C:<A>, D:<empty>
+ array( 'op' => 'copy', 'src' => $fileB, 'dst' => $fileD, 'overwrite' => 1 ),
+ // Now: A:<A>, B:<B>, C:<A>, D:<B>
+ array( 'op' => 'move', 'src' => $fileC, 'dst' => $fileD ),
+ // Now: A:<A>, B:<B>, C:<A>, D:<empty> (failed)
+ array( 'op' => 'move', 'src' => $fileB, 'dst' => $fileC, 'overwriteSame' => 1 ),
+ // Now: A:<A>, B:<B>, C:<A>, D:<empty> (failed)
+ array( 'op' => 'move', 'src' => $fileB, 'dst' => $fileA, 'overwrite' => 1 ),
+ // Now: A:<B>, B:<empty>, C:<A>, D:<empty>
+ array( 'op' => 'delete', 'src' => $fileD ),
+ // Now: A:<B>, B:<empty>, C:<A>, D:<empty>
+ array( 'op' => 'null' ),
+ // Does nothing
+ ), array( 'force' => 1 ) );
+
+ $this->assertNotEquals( array(), $status->errors, "Operation had warnings" );
+ $this->assertEquals( true, $status->isOK(), "Operation batch succeeded" );
+ $this->assertEquals( 8, count( $status->success ),
+ "Operation batch has correct success array" );
+
+ $this->assertEquals( false, $this->backend->fileExists( array( 'src' => $fileB ) ),
+ "File does not exist at $fileB" );
+ $this->assertEquals( false, $this->backend->fileExists( array( 'src' => $fileD ) ),
+ "File does not exist at $fileD" );
+
+ $this->assertEquals( true, $this->backend->fileExists( array( 'src' => $fileA ) ),
+ "File does not exist at $fileA" );
+ $this->assertEquals( true, $this->backend->fileExists( array( 'src' => $fileC ) ),
+ "File exists at $fileC" );
+ $this->assertEquals( $fileBContents,
+ $this->backend->getFileContents( array( 'src' => $fileA ) ),
+ "Correct file contents of $fileA" );
+ $this->assertEquals( strlen( $fileBContents ),
+ $this->backend->getFileSize( array( 'src' => $fileA ) ),
+ "Correct file size of $fileA" );
+ $this->assertEquals( wfBaseConvert( sha1( $fileBContents ), 16, 36, 31 ),
+ $this->backend->getFileSha1Base36( array( 'src' => $fileA ) ),
+ "Correct file SHA-1 of $fileA" );
+ }
+
+ public function testGetFileList() {
+ $this->backend = $this->singleBackend;
+ $this->tearDownFiles();
+ $this->doTestGetFileList();
+ $this->tearDownFiles();
+
+ $this->backend = $this->multiBackend;
+ $this->tearDownFiles();
+ $this->doTestGetFileList();
+ $this->tearDownFiles();
+ }
+
+ private function doTestGetFileList() {
+ $backendName = $this->backendClass();
+
+ $base = $this->baseStorePath();
+ $files = array(
+ "$base/unittest-cont1/test1.txt",
+ "$base/unittest-cont1/test2.txt",
+ "$base/unittest-cont1/test3.txt",
+ "$base/unittest-cont1/subdir1/test1.txt",
+ "$base/unittest-cont1/subdir1/test2.txt",
+ "$base/unittest-cont1/subdir2/test3.txt",
+ "$base/unittest-cont1/subdir2/test4.txt",
+ "$base/unittest-cont1/subdir2/subdir/test1.txt",
+ "$base/unittest-cont1/subdir2/subdir/test2.txt",
+ "$base/unittest-cont1/subdir2/subdir/test3.txt",
+ "$base/unittest-cont1/subdir2/subdir/test4.txt",
+ "$base/unittest-cont1/subdir2/subdir/test5.txt",
+ "$base/unittest-cont1/subdir2/subdir/sub/test0.txt",
+ "$base/unittest-cont1/subdir2/subdir/sub/120-px-file.txt",
+ );
+
+ // Add the files
+ $ops = array();
+ foreach ( $files as $file ) {
+ $this->prepare( array( 'dir' => dirname( $file ) ) );
+ $ops[] = array( 'op' => 'create', 'content' => 'xxy', 'dst' => $file );
+ }
+ $status = $this->backend->doOperations( $ops );
+ $this->assertEquals( array(), $status->errors,
+ "Creation of files succeeded ($backendName)." );
+ $this->assertEquals( true, $status->isOK(),
+ "Creation of files succeeded with OK status ($backendName)." );
+
+ // Expected listing
+ $expected = array(
+ "test1.txt",
+ "test2.txt",
+ "test3.txt",
+ "subdir1/test1.txt",
+ "subdir1/test2.txt",
+ "subdir2/test3.txt",
+ "subdir2/test4.txt",
+ "subdir2/subdir/test1.txt",
+ "subdir2/subdir/test2.txt",
+ "subdir2/subdir/test3.txt",
+ "subdir2/subdir/test4.txt",
+ "subdir2/subdir/test5.txt",
+ "subdir2/subdir/sub/test0.txt",
+ "subdir2/subdir/sub/120-px-file.txt",
+ );
+ sort( $expected );
+
+ // Actual listing (no trailing slash)
+ $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)." );
+
+ // Actual listing (with trailing slash)
+ $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 = array(
+ "test1.txt",
+ "test2.txt",
+ "test3.txt",
+ "test4.txt",
+ "test5.txt",
+ "sub/test0.txt",
+ "sub/120-px-file.txt",
+ );
+ sort( $expected );
+
+ // Actual listing (no trailing slash)
+ $list = array();
+ $iter = $this->backend->getFileList( array( 'dir' => "$base/unittest-cont1/subdir2/subdir" ) );
+ foreach ( $iter as $file ) {
+ $list[] = $file;
+ }
+ sort( $list );
+
+ $this->assertEquals( $expected, $list, "Correct file listing ($backendName)." );
+
+ // Actual listing (with trailing slash)
+ $list = array();
+ $iter = $this->backend->getFileList( array( 'dir' => "$base/unittest-cont1/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;
+ }
+ sort( $list );
+
+ $this->assertEquals( $expected, $list, "Correct file listing ($backendName), second iteration." );
+
+ foreach ( $files as $file ) { // clean up
+ $this->backend->doOperation( array( 'op' => 'delete', 'src' => $file ) );
+ }
+
+ $iter = $this->backend->getFileList( array( 'dir' => "$base/unittest-cont1/not/exists" ) );
+ foreach ( $iter as $iter ) {} // no errors
+ }
+
+ // test helper wrapper for backend prepare() function
+ private function prepare( array $params ) {
+ $this->dirsToPrune[] = $params['dir'];
+ return $this->backend->prepare( $params );
+ }
+
+ function tearDownFiles() {
+ foreach ( $this->filesToPrune as $file ) {
+ @unlink( $file );
+ }
+ $containers = array( 'unittest-cont1', 'unittest-cont2', 'unittest-cont3' );
+ foreach ( $containers as $container ) {
+ $this->deleteFiles( $container );
+ }
+ foreach ( $this->dirsToPrune as $dir ) {
+ $this->recursiveClean( $dir );
+ }
+ $this->filesToPrune = $this->dirsToPrune = array();
+ }
+
+ private function deleteFiles( $container ) {
+ $base = $this->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 ) );
+ }
+ }
+ }
+
+ private function recursiveClean( $dir ) {
+ do {
+ if ( !$this->backend->clean( array( 'dir' => $dir ) )->isOK() ) {
+ break;
+ }
+ } while ( $dir = FileBackend::parentStoragePath( $dir ) );
+ }
+
+ function tearDown() {
+ parent::tearDown();
+ }
+}
diff --git a/tests/phpunit/includes/filerepo/FileRepoTest.php b/tests/phpunit/includes/filerepo/FileRepoTest.php
new file mode 100644
index 00000000..0f023138
--- /dev/null
+++ b/tests/phpunit/includes/filerepo/FileRepoTest.php
@@ -0,0 +1,41 @@
+<?php
+
+class FileRepoTest extends MediaWikiTestCase {
+
+ /**
+ * @expectedException MWException
+ */
+ function testFileRepoConstructionOptionCanNotBeNull() {
+ $f = new FileRepo();
+ }
+ /**
+ * @expectedException MWException
+ */
+ function testFileRepoConstructionOptionCanNotBeAnEmptyArray() {
+ $f = new FileRepo( array() );
+ }
+ /**
+ * @expectedException MWException
+ */
+ function testFileRepoConstructionOptionNeedNameKey() {
+ $f = new FileRepo( array(
+ 'backend' => 'foobar'
+ ) );
+ }
+ /**
+ * @expectedException MWException
+ */
+ function testFileRepoConstructionOptionNeedBackendKey() {
+ $f = new FileRepo( array(
+ 'name' => 'foobar'
+ ) );
+ }
+
+ function testFileRepoConstructionWithRequiredOptions() {
+ $f = new FileRepo( array(
+ 'name' => 'FileRepoTestRepository',
+ 'backend' => 'local-backend',
+ ));
+ $this->assertInstanceOf( 'FileRepo', $f );
+ }
+}
diff --git a/tests/phpunit/includes/filerepo/StoreBatchTest.php b/tests/phpunit/includes/filerepo/StoreBatchTest.php
new file mode 100644
index 00000000..6abceeb3
--- /dev/null
+++ b/tests/phpunit/includes/filerepo/StoreBatchTest.php
@@ -0,0 +1,122 @@
+<?php
+/**
+ * @group FileRepo
+ */
+class StoreBatchTest extends MediaWikiTestCase {
+
+ public function setUp() {
+ global $wgFileBackends;
+ parent::setUp();
+
+ # Forge a FSRepo object to not have to rely on local wiki settings
+ $tmpPrefix = wfTempDir() . '/storebatch-test-' . time() . '-' . mt_rand();
+ if ( $this->getCliArg( 'use-filebackend=' ) ) {
+ $name = $this->getCliArg( 'use-filebackend=' );
+ $useConfig = array();
+ foreach ( $wgFileBackends as $conf ) {
+ if ( $conf['name'] == $name ) {
+ $useConfig = $conf;
+ }
+ }
+ $useConfig['name'] = 'local-testing'; // swap name
+ $class = $useConfig['class'];
+ $backend = new $class( $useConfig );
+ } else {
+ $backend = new FSFileBackend( array(
+ 'name' => 'local-testing',
+ 'lockManager' => 'nullLockManager',
+ 'containerPaths' => array(
+ 'unittests-public' => "{$tmpPrefix}-public",
+ 'unittests-thumb' => "{$tmpPrefix}-thumb",
+ 'unittests-temp' => "{$tmpPrefix}-temp",
+ 'unittests-deleted' => "{$tmpPrefix}-deleted",
+ )
+ ) );
+ }
+ $this->repo = new FileRepo( array(
+ 'name' => 'unittests',
+ 'backend' => $backend
+ ) );
+
+ $this->date = gmdate( "YmdHis" );
+ $this->createdFiles = array();
+ }
+
+ /**
+ * 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().
+ */
+ private function storeit($originalName, $srcPath, $flags) {
+ $hashPath = $this->repo->getHashPath( $originalName );
+ $dstRel = "$hashPath{$this->date}!$originalName";
+ $dstUrlRel = $hashPath . $this->date . '!' . rawurlencode( $originalName );
+
+ $result = $this->repo->store( $srcPath, 'temp', $dstRel, $flags );
+ $result->value = $this->repo->getVirtualUrl( 'temp' ) . '/' . $dstUrlRel;
+ $this->createdFiles[] = $result->value;
+ return $result;
+ }
+
+ /**
+ * Test storing a file using different flags.
+ *
+ * @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.
+ */
+ 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}" );
+ if ( $fromrepo ) {
+ $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 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 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 should fail because we're overwriting different content.
+ if ( $fromrepo ) {
+ $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}" );
+ }
+
+ 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/json/ServicesJsonTest.php b/tests/phpunit/includes/json/ServicesJsonTest.php
new file mode 100644
index 00000000..8f2421a2
--- /dev/null
+++ b/tests/phpunit/includes/json/ServicesJsonTest.php
@@ -0,0 +1,93 @@
+<?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/JavaScriptMinifierTest.php b/tests/phpunit/includes/libs/JavaScriptMinifierTest.php
index aa05500e..d2bfeedf 100644
--- a/tests/phpunit/includes/libs/JavaScriptMinifierTest.php
+++ b/tests/phpunit/includes/libs/JavaScriptMinifierTest.php
@@ -84,6 +84,13 @@ class JavaScriptMinifierTest extends MediaWikiTestCase {
// And also 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={}'),
+
+ /* Some structures that might look invalid at first sight */
+ array( "var a = 5.;", "var a=5.;" ),
+ array( "5.0.toString();", "5.0.toString();" ),
+ array( "5..toString();", "5..toString();" ),
+ array( "5...toString();", false ),
+ array( "5.\n.toString();", '5..toString();' ),
);
}
@@ -102,4 +109,40 @@ class JavaScriptMinifierTest extends MediaWikiTestCase {
$this->assertEquals( $expectedOutput, $minified, "Minified output should be in the form expected." );
}
+
+ /**
+ * @dataProvider provideBug32548
+ */
+ 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.
+ $prefix = 'var longVarName' . str_repeat('_', 973) . '=';
+ $suffix = ',shortVarName=0;';
+
+ $input = $prefix . $num . $suffix;
+ $expected = $prefix . "\n" . $num . $suffix;
+
+ $minified = JavaScriptMinifier::minify( $input );
+
+ $this->assertEquals( $expected, $minified, "Line breaks must not occur in middle of exponent");
+ }
+
+ function provideBug32548() {
+ return array(
+ array(
+ // This one gets interpreted all together by the prior code;
+ // no break at the 'E' happens.
+ '1.23456789E55',
+ ),
+ array(
+ // This one breaks under the bad code; splits between 'E' and '+'
+ '1.23456789E+5',
+ ),
+ array(
+ // This one breaks under the bad code; splits between 'E' and '-'
+ '1.23456789E-5',
+ ),
+ );
+ }
}
diff --git a/tests/phpunit/includes/media/BitmapMetadataHandlerTest.php b/tests/phpunit/includes/media/BitmapMetadataHandlerTest.php
index a0d5cd86..f4f52dd8 100644
--- a/tests/phpunit/includes/media/BitmapMetadataHandlerTest.php
+++ b/tests/phpunit/includes/media/BitmapMetadataHandlerTest.php
@@ -14,10 +14,15 @@ class BitmapMetadataHandlerTest extends MediaWikiTestCase {
* translation (to en) where XMP should win.
*/
public function testMultilingualCascade() {
- global $wgShowEXIF;
- if ( !$wgShowEXIF ) {
- $this->markTestIncomplete( "This test needs the exif extension." );
+ if ( !wfDl( 'exif' ) ) {
+ $this->markTestSkipped( "This test needs the exif extension." );
+ }
+ if ( !wfDl( 'xml' ) ) {
+ $this->markTestSkipped( "This test needs the xml extension." );
}
+ global $wgShowEXIF;
+ $oldExif = $wgShowEXIF;
+ $wgShowEXIF = true;
$meta = BitmapMetadataHandler::Jpeg( $this->filePath .
'/Xmp-exif-multilingual_test.jpg' );
@@ -32,6 +37,8 @@ class BitmapMetadataHandlerTest extends MediaWikiTestCase {
'Did not extract any ImageDescription info?!' );
$this->assertEquals( $expected, $meta['ImageDescription'] );
+
+ $wgShowEXIF = $oldExif;
}
/**
@@ -49,6 +56,16 @@ class BitmapMetadataHandlerTest extends MediaWikiTestCase {
$meta['JPEGFileComment'][0] );
}
+ /**
+ * Make sure a bad iptc block doesn't stop the other metadata
+ * from being extracted.
+ */
+ public function testBadIPTC() {
+ $meta = BitmapMetadataHandler::Jpeg( $this->filePath .
+ 'iptc-invalid-psir.jpg' );
+ $this->assertEquals( 'Created with GIMP', $meta['JPEGFileComment'][0] );
+ }
+
public function testIPTCDates() {
$meta = BitmapMetadataHandler::Jpeg( $this->filePath .
'iptc-timetest.jpg' );
@@ -95,6 +112,9 @@ class BitmapMetadataHandlerTest extends MediaWikiTestCase {
}
public function testPNGXMP() {
+ if ( !wfDl( 'xml' ) ) {
+ $this->markTestSkipped( "This test needs the xml extension." );
+ }
$handler = new BitmapMetadataHandler();
$result = $handler->png( $this->filePath . 'xmp.png' );
$expected = array (
diff --git a/tests/phpunit/includes/media/BitmapScalingTest.php b/tests/phpunit/includes/media/BitmapScalingTest.php
index 5bcd3232..11d9dc47 100644
--- a/tests/phpunit/includes/media/BitmapScalingTest.php
+++ b/tests/phpunit/includes/media/BitmapScalingTest.php
@@ -3,13 +3,16 @@
class BitmapScalingTest extends MediaWikiTestCase {
function setUp() {
- global $wgMaxImageArea;
+ 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;
+ global $wgMaxImageArea, $wgCustomConvertCommand;
$wgMaxImageArea = $this->oldMaxImageArea;
+ $wgCustomConvertCommand = $this->oldCustomConvertCommand;
}
/**
* @dataProvider provideNormaliseParams
@@ -105,14 +108,22 @@ class BitmapScalingTest extends MediaWikiTestCase {
$file = new FakeDimensionFile( array( 4000, 4000 ) );
$handler = new BitmapHandler;
$params = array( 'width' => '3700' ); // Still bigger than max size.
- $this->assertFalse( $handler->normaliseParams( $file, $params ) );
+ $this->assertEquals( 'TransformParameterError',
+ get_class( $handler->doTransform( $file, 'dummy path', '', $params ) ) );
}
function testTooBigMustRenderImage() {
$file = new FakeDimensionFile( array( 4000, 4000 ) );
$file->mustRender = true;
$handler = new BitmapHandler;
$params = array( 'width' => '5000' ); // Still bigger than max size.
- $this->assertFalse( $handler->normaliseParams( $file, $params ) );
+ $this->assertEquals( 'TransformParameterError',
+ get_class( $handler->doTransform( $file, 'dummy path', '', $params ) ) );
+ }
+
+ function testImageArea() {
+ $file = new FakeDimensionFile( array( 7, 9 ) );
+ $handler = new BitmapHandler;
+ $this->assertEquals( 63, $handler->getImageArea( $file ) );
}
}
@@ -120,7 +131,8 @@ class FakeDimensionFile extends File {
public $mustRender = false;
public function __construct( $dimensions ) {
- parent::__construct( Title::makeTitle( NS_FILE, 'Test' ), null );
+ parent::__construct( Title::makeTitle( NS_FILE, 'Test' ),
+ new NullRepo( null ) );
$this->dimensions = $dimensions;
}
@@ -133,4 +145,7 @@ class FakeDimensionFile extends File {
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 4282d3c8..b2f6b7ba 100644
--- a/tests/phpunit/includes/media/ExifBitmapTest.php
+++ b/tests/phpunit/includes/media/ExifBitmapTest.php
@@ -1,4 +1,5 @@
<?php
+
class ExifBitmapTest extends MediaWikiTestCase {
public function setUp() {
@@ -17,42 +18,23 @@ class ExifBitmapTest extends MediaWikiTestCase {
}
public function testIsOldBroken() {
- if ( !wfDl( 'exif' ) ) {
- $this->markTestIncomplete( "This test needs the exif extension." );
- }
$res = $this->handler->isMetadataValid( null, ExifBitmapHandler::OLD_BROKEN_FILE );
$this->assertEquals( ExifBitmapHandler::METADATA_COMPATIBLE, $res );
}
public function testIsBrokenFile() {
- global $wgShowEXIF;
- if ( !$wgShowEXIF ) {
- $this->markTestIncomplete( "This test needs the exif extension." );
- }
$res = $this->handler->isMetadataValid( null, ExifBitmapHandler::BROKEN_FILE );
$this->assertEquals( ExifBitmapHandler::METADATA_GOOD, $res );
}
public function testIsInvalid() {
- global $wgShowEXIF;
- if ( !$wgShowEXIF ) {
- $this->markTestIncomplete( "This test needs the exif extension." );
- }
$res = $this->handler->isMetadataValid( null, 'Something Invalid Here.' );
$this->assertEquals( ExifBitmapHandler::METADATA_BAD, $res );
}
public function testGoodMetadata() {
- global $wgShowEXIF;
- if ( !$wgShowEXIF ) {
- $this->markTestIncomplete( "This test needs the exif extension." );
- }
$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 );
}
public function testIsOldGood() {
- global $wgShowEXIF;
- if ( !$wgShowEXIF ) {
- $this->markTestIncomplete( "This test needs the exif extension." );
- }
$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 );
@@ -60,10 +42,6 @@ class ExifBitmapTest extends MediaWikiTestCase {
// Handle metadata from paged tiff handler (gotten via instant commons)
// gracefully.
public function testPagedTiffHandledGracefully() {
- global $wgShowEXIF;
- if ( !$wgShowEXIF ) {
- $this->markTestIncomplete( "This test needs the exif extension." );
- }
$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 );
diff --git a/tests/phpunit/includes/media/ExifRotationTest.php b/tests/phpunit/includes/media/ExifRotationTest.php
index 639091d0..25149a05 100644
--- a/tests/phpunit/includes/media/ExifRotationTest.php
+++ b/tests/phpunit/includes/media/ExifRotationTest.php
@@ -5,15 +5,26 @@
*/
class ExifRotationTest extends MediaWikiTestCase {
+ /** track directories creations. Content will be deleted. */
+ private $createdDirs = array();
+
function setUp() {
parent::setUp();
- $this->filePath = dirname( __FILE__ ) . '/../../data/media/';
$this->handler = new BitmapHandler();
- $this->repo = new FSRepo(array(
- 'name' => 'temp',
- 'directory' => wfTempDir() . '/exif-test-' . time() . '-' . mt_rand(),
- 'url' => 'http://localhost/thumbtest'
- ));
+ $filePath = dirname( __FILE__ ) . '/../../data/media';
+
+ $tmpDir = wfTempDir() . '/exif-test-' . time() . '-' . mt_rand();
+ $this->createdDirs[] = $tmpDir;
+
+ $this->repo = new FSRepo( array(
+ '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." );
}
@@ -25,10 +36,23 @@ class ExifRotationTest extends MediaWikiTestCase {
$this->oldAuto = $wgEnableAutoRotation;
$wgEnableAutoRotation = true;
}
+
public function tearDown() {
global $wgShowEXIF, $wgEnableAutoRotation;
$wgShowEXIF = $this->show;
$wgEnableAutoRotation = $this->oldAuto;
+
+ $this->tearDownFiles();
+ }
+
+ private function tearDownFiles() {
+ foreach( $this->createdDirs as $dir ) {
+ wfRecursiveRemoveDir( $dir );
+ }
+ }
+
+ function __destruct() {
+ $this->tearDownFiles();
}
/**
@@ -39,7 +63,7 @@ class ExifRotationTest extends MediaWikiTestCase {
if ( !BitmapHandler::canRotate() ) {
$this->markTestSkipped( "This test needs a rasterizer that can auto-rotate." );
}
- $file = UnregisteredLocalFile::newFromPath( $this->filePath . $name, $type );
+ $file = $this->dataFile( $name, $type );
$this->assertEquals( $info['width'], $file->getWidth(), "$name: width check" );
$this->assertEquals( $info['height'], $file->getHeight(), "$name: height check" );
}
@@ -66,13 +90,13 @@ class ExifRotationTest extends MediaWikiTestCase {
throw new MWException('bogus test data format ' . $size);
}
- $file = $this->localFile( $name, $type );
- $thumb = $file->transform( $params, File::RENDER_NOW );
+ $file = $this->dataFile( $name, $type );
+ $thumb = $file->transform( $params, File::RENDER_NOW | File::RENDER_FORCE );
$this->assertEquals( $out[0], $thumb->getWidth(), "$name: thumb reported width check for $size" );
$this->assertEquals( $out[1], $thumb->getHeight(), "$name: thumb reported height check for $size" );
- $gis = getimagesize( $thumb->getPath() );
+ $gis = getimagesize( $thumb->getLocalCopyPath() );
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");
@@ -84,8 +108,9 @@ class ExifRotationTest extends MediaWikiTestCase {
}
}
- private function localFile( $name, $type ) {
- return new UnregisteredLocalFile( false, $this->repo, $this->filePath . $name, $type );
+ private function dataFile( $name, $type ) {
+ return new UnregisteredLocalFile( false, $this->repo,
+ "mwstore://localtesting/data/$name", $type );
}
function providerFiles() {
@@ -129,7 +154,7 @@ class ExifRotationTest extends MediaWikiTestCase {
global $wgEnableAutoRotation;
$wgEnableAutoRotation = false;
- $file = UnregisteredLocalFile::newFromPath( $this->filePath . $name, $type );
+ $file = $this->dataFile( $name, $type );
$this->assertEquals( $info['width'], $file->getWidth(), "$name: width check" );
$this->assertEquals( $info['height'], $file->getHeight(), "$name: height check" );
@@ -158,13 +183,13 @@ class ExifRotationTest extends MediaWikiTestCase {
throw new MWException('bogus test data format ' . $size);
}
- $file = $this->localFile( $name, $type );
- $thumb = $file->transform( $params, File::RENDER_NOW );
+ $file = $this->dataFile( $name, $type );
+ $thumb = $file->transform( $params, File::RENDER_NOW | File::RENDER_FORCE );
$this->assertEquals( $out[0], $thumb->getWidth(), "$name: thumb reported width check for $size" );
$this->assertEquals( $out[1], $thumb->getHeight(), "$name: thumb reported height check for $size" );
- $gis = getimagesize( $thumb->getPath() );
+ $gis = getimagesize( $thumb->getLocalCopyPath() );
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");
@@ -242,7 +267,7 @@ class ExifRotationTest extends MediaWikiTestCase {
array(
270,
array( self::TEST_HEIGHT, self::TEST_WIDTH )
- ),
+ ),
);
}
}
diff --git a/tests/phpunit/includes/media/ExifTest.php b/tests/phpunit/includes/media/ExifTest.php
index 9b490e92..b39c15e4 100644
--- a/tests/phpunit/includes/media/ExifTest.php
+++ b/tests/phpunit/includes/media/ExifTest.php
@@ -4,6 +4,9 @@ class ExifTest extends MediaWikiTestCase {
public function setUp() {
$this->mediaPath = dirname( __FILE__ ) . '/../../data/media/';
+ if ( !wfDl( 'exif' ) ) {
+ $this->markTestSkipped( "This test needs the exif extension." );
+ }
global $wgShowEXIF;
$this->showExif = $wgShowEXIF;
$wgShowEXIF = true;
@@ -14,9 +17,6 @@ class ExifTest extends MediaWikiTestCase {
}
public function testGPSExtraction() {
- if ( !wfDl( 'exif' ) ) {
- $this->markTestIncomplete( "This test needs the exif extension." );
- }
$filename = $this->mediaPath . 'exif-gps.jpg';
$seg = JpegMetadataExtractor::segmentSplitter( $filename );
@@ -32,9 +32,6 @@ class ExifTest extends MediaWikiTestCase {
$this->assertEquals( $expected, $data, '', 0.0000000001 );
}
public function testUnicodeUserComment() {
- if ( !wfDl( 'exif' ) ) {
- $this->markTestIncomplete( "This test needs the exif extension." );
- }
$filename = $this->mediaPath . 'exif-user-comment.jpg';
$seg = JpegMetadataExtractor::segmentSplitter( $filename );
diff --git a/tests/phpunit/includes/media/FormatMetadataTest.php b/tests/phpunit/includes/media/FormatMetadataTest.php
index db36dea3..8a632f52 100644
--- a/tests/phpunit/includes/media/FormatMetadataTest.php
+++ b/tests/phpunit/includes/media/FormatMetadataTest.php
@@ -1,13 +1,31 @@
<?php
class FormatMetadataTest extends MediaWikiTestCase {
- public function testInvalidDate() {
- global $wgShowEXIF;
- if ( !$wgShowEXIF ) {
- $this->markTestIncomplete( "This test needs the exif extension." );
+ public function setUp() {
+ if ( !wfDl( 'exif' ) ) {
+ $this->markTestSkipped( "This test needs the exif extension." );
}
-
- $file = UnregisteredLocalFile::newFromPath( dirname( __FILE__ ) .
- '/../../data/media/broken_exif_date.jpg', 'image/jpeg' );
+ $filePath = dirname( __FILE__ ) . '/../../data/media';
+ $this->backend = new FSFileBackend( array(
+ 'name' => 'localtesting',
+ 'lockManager' => 'nullLockManager',
+ 'containerPaths' => array( 'data' => $filePath )
+ ) );
+ $this->repo = new FSRepo( array(
+ 'name' => 'temp',
+ 'url' => 'http://localhost/thumbtest',
+ 'backend' => $this->backend
+ ) );
+ global $wgShowEXIF;
+ $this->show = $wgShowEXIF;
+ $wgShowEXIF = true;
+ }
+ public function tearDown() {
+ global $wgShowEXIF;
+ $wgShowEXIF = $this->show;
+ }
+
+ public function testInvalidDate() {
+ $file = $this->dataFile( 'broken_exif_date.jpg', 'image/jpeg' );
// Throws an error if bug hit
$meta = $file->formatMetadata();
@@ -26,4 +44,9 @@ class FormatMetadataTest extends MediaWikiTestCase {
$meta['visible'][$dateIndex]['value'],
'File with invalid date metadata (bug 29471)' );
}
-} \ No newline at end of file
+
+ private function dataFile( $name, $type ) {
+ return new UnregisteredLocalFile( false, $this->repo,
+ "mwstore://localtesting/data/$name", $type );
+ }
+}
diff --git a/tests/phpunit/includes/media/GIFMetadataExtractorTest.php b/tests/phpunit/includes/media/GIFMetadataExtractorTest.php
index 59b30441..47fc368b 100644
--- a/tests/phpunit/includes/media/GIFMetadataExtractorTest.php
+++ b/tests/phpunit/includes/media/GIFMetadataExtractorTest.php
@@ -63,6 +63,7 @@ class GIFMetadataExtractorTest extends MediaWikiTestCase {
<?xpacket end='w'?>
EOF;
+ $xmpNugget = str_replace( "\r", '', $xmpNugget ); // Windows compat
return array(
array( 'nonanimated.gif', array(
diff --git a/tests/phpunit/includes/media/GIFTest.php b/tests/phpunit/includes/media/GIFTest.php
index 42c25ca5..36658358 100644
--- a/tests/phpunit/includes/media/GIFTest.php
+++ b/tests/phpunit/includes/media/GIFTest.php
@@ -2,12 +2,22 @@
class GIFHandlerTest extends MediaWikiTestCase {
public function setUp() {
- $this->filePath = dirname( __FILE__ ) . '/../../data/media/';
+ $this->filePath = dirname( __FILE__ ) . '/../../data/media';
+ $this->backend = new FSFileBackend( array(
+ 'name' => 'localtesting',
+ 'lockManager' => 'nullLockManager',
+ 'containerPaths' => array( 'data' => $this->filePath )
+ ) );
+ $this->repo = new FSRepo( array(
+ 'name' => 'temp',
+ 'url' => 'http://localhost/thumbtest',
+ 'backend' => $this->backend
+ ) );
$this->handler = new GIFHandler();
}
public function testInvalidFile() {
- $res = $this->handler->getMetadata( null, $this->filePath . 'README' );
+ $res = $this->handler->getMetadata( null, $this->filePath . '/README' );
$this->assertEquals( GIFHandler::BROKEN_FILE, $res );
}
/**
@@ -16,8 +26,7 @@ class GIFHandlerTest extends MediaWikiTestCase {
* @dataProvider dataIsAnimated
*/
public function testIsAnimanted( $filename, $expected ) {
- $file = UnregisteredLocalFile::newFromPath( $this->filePath . $filename,
- 'image/gif' );
+ $file = $this->dataFile( $filename, 'image/gif' );
$actual = $this->handler->isAnimatedImage( $file );
$this->assertEquals( $expected, $actual );
}
@@ -34,8 +43,7 @@ class GIFHandlerTest extends MediaWikiTestCase {
* @dataProvider dataGetImageArea
*/
public function testGetImageArea( $filename, $expected ) {
- $file = UnregisteredLocalFile::newFromPath( $this->filePath . $filename,
- 'image/gif' );
+ $file = $this->dataFile( $filename, 'image/gif' );
$actual = $this->handler->getImageArea( $file, $file->getWidth(), $file->getHeight() );
$this->assertEquals( $expected, $actual );
}
@@ -71,15 +79,20 @@ class GIFHandlerTest extends MediaWikiTestCase {
* @dataProvider dataGetMetadata
*/
public function testGetMetadata( $filename, $expected ) {
- $file = UnregisteredLocalFile::newFromPath( $this->filePath . $filename,
- 'image/gif' );
- $actual = $this->handler->getMetadata( $file, $this->filePath . $filename );
+ $file = $this->dataFile( $filename, 'image/gif' );
+ $actual = $this->handler->getMetadata( $file, "$this->filePath/$filename" );
$this->assertEquals( unserialize( $expected ), unserialize( $actual ) );
}
+
public function dataGetMetadata() {
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;}}' ),
);
}
+
+ private function dataFile( $name, $type ) {
+ return new UnregisteredLocalFile( false, $this->repo,
+ "mwstore://localtesting/data/$name", $type );
+ }
}
diff --git a/tests/phpunit/includes/media/JpegMetadataExtractorTest.php b/tests/phpunit/includes/media/JpegMetadataExtractorTest.php
index 61fc9c81..f48382a4 100644
--- a/tests/phpunit/includes/media/JpegMetadataExtractorTest.php
+++ b/tests/phpunit/includes/media/JpegMetadataExtractorTest.php
@@ -1,5 +1,5 @@
<?php
-/*
+/**
* @todo Could use a test of extended XMP segments. Hard to find programs that
* create example files, and creating my own in vim propbably wouldn't
* serve as a very good "test". (Adobe photoshop probably creates such files
@@ -59,7 +59,7 @@ class JpegMetadataExtractorTest extends MediaWikiTestCase {
public function testPSIRExtraction() {
$res = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-xmp-psir.jpg' );
$expected = '50686f746f73686f7020332e30003842494d04040000000000181c02190004746573741c02190003666f6f1c020000020004';
- $this->assertEquals( $expected, bin2hex( $res['PSIR'] ) );
+ $this->assertEquals( $expected, bin2hex( $res['PSIR'][0] ) );
}
public function testXMPExtractionAltAppId() {
$res = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-xmp-alt.jpg' );
@@ -70,19 +70,19 @@ class JpegMetadataExtractorTest extends MediaWikiTestCase {
public function testIPTCHashComparisionNoHash() {
$segments = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-xmp-psir.jpg' );
- $res = JpegMetadataExtractor::doPSIR( $segments['PSIR'] );
+ $res = JpegMetadataExtractor::doPSIR( $segments['PSIR'][0] );
$this->assertEquals( 'iptc-no-hash', $res );
}
public function testIPTCHashComparisionBadHash() {
$segments = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-iptc-bad-hash.jpg' );
- $res = JpegMetadataExtractor::doPSIR( $segments['PSIR'] );
+ $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'] );
+ $res = JpegMetadataExtractor::doPSIR( $segments['PSIR'][0] );
$this->assertEquals( 'iptc-good-hash', $res );
}
diff --git a/tests/phpunit/includes/media/JpegTest.php b/tests/phpunit/includes/media/JpegTest.php
index 713a3410..ddabf5b8 100644
--- a/tests/phpunit/includes/media/JpegTest.php
+++ b/tests/phpunit/includes/media/JpegTest.php
@@ -3,22 +3,24 @@ class JpegTest extends MediaWikiTestCase {
public function setUp() {
$this->filePath = dirname( __FILE__ ) . '/../../data/media/';
+ if ( !wfDl( 'exif' ) ) {
+ $this->markTestSkipped( "This test needs the exif extension." );
+ }
+ global $wgShowEXIF;
+ $this->show = $wgShowEXIF;
+ $wgShowEXIF = true;
+ }
+ public function tearDown() {
+ global $wgShowEXIF;
+ $wgShowEXIF = $this->show;
}
public function testInvalidFile() {
- global $wgShowEXIF;
- if ( !$wgShowEXIF ) {
- $this->markTestIncomplete( "This test needs the exif extension." );
- }
$jpeg = new JpegHandler;
$res = $jpeg->getMetadata( null, $this->filePath . 'README' );
$this->assertEquals( ExifBitmapHandler::BROKEN_FILE, $res );
}
public function testJpegMetadataExtraction() {
- global $wgShowEXIF;
- if ( !$wgShowEXIF ) {
- $this->markTestIncomplete( "This test needs the exif extension." );
- }
$h = new JpegHandler;
$res = $h->getMetadata( null, $this->filePath . 'test.jpg' );
$expected = 'a:7:{s:16:"ImageDescription";s:9:"Test file";s:11:"XResolution";s:4:"72/1";s:11:"YResolution";s:4:"72/1";s:14:"ResolutionUnit";i:2;s:16:"YCbCrPositioning";i:1;s:15:"JPEGFileComment";a:1:{i:0;s:17:"Created with GIMP";}s:22:"MEDIAWIKI_EXIF_VERSION";i:2;}';
diff --git a/tests/phpunit/includes/ImageFunctionsTest.php b/tests/phpunit/includes/media/MediaHandlerTest.php
index cb7e67f3..99df4f80 100644
--- a/tests/phpunit/includes/ImageFunctionsTest.php
+++ b/tests/phpunit/includes/media/MediaHandlerTest.php
@@ -1,6 +1,6 @@
<?php
-class ImageFunctionsTest extends MediaWikiTestCase {
+class MediaHandlerTest extends MediaWikiTestCase {
function testFitBoxWidth() {
$vals = array(
array(
@@ -32,10 +32,12 @@ class ImageFunctionsTest extends MediaWikiTestCase {
17 => 4,
18 => 4 ) ) );
foreach ( $vals as $row ) {
- extract( $row );
+ $tests = $row['tests'];
+ $height = $row['height'];
+ $width = $row['width'];
foreach ( $tests as $max => $expected ) {
$y = round( $expected * $height / $width );
- $result = wfFitBoxWidth( $width, $height, $max );
+ $result = MediaHandler::fitBoxWidth( $width, $height, $max );
$y2 = round( $result * $height / $width );
$this->assertEquals( $expected,
$result,
diff --git a/tests/phpunit/includes/media/PNGTest.php b/tests/phpunit/includes/media/PNGTest.php
index b782918c..b6f911fd 100644
--- a/tests/phpunit/includes/media/PNGTest.php
+++ b/tests/phpunit/includes/media/PNGTest.php
@@ -2,12 +2,22 @@
class PNGHandlerTest extends MediaWikiTestCase {
public function setUp() {
- $this->filePath = dirname( __FILE__ ) . '/../../data/media/';
+ $this->filePath = dirname( __FILE__ ) . '/../../data/media';
+ $this->backend = new FSFileBackend( array(
+ 'name' => 'localtesting',
+ 'lockManager' => 'nullLockManager',
+ 'containerPaths' => array( 'data' => $this->filePath )
+ ) );
+ $this->repo = new FSRepo( array(
+ 'name' => 'temp',
+ 'url' => 'http://localhost/thumbtest',
+ 'backend' => $this->backend
+ ) );
$this->handler = new PNGHandler();
}
public function testInvalidFile() {
- $res = $this->handler->getMetadata( null, $this->filePath . 'README' );
+ $res = $this->handler->getMetadata( null, $this->filePath . '/README' );
$this->assertEquals( PNGHandler::BROKEN_FILE, $res );
}
/**
@@ -16,8 +26,7 @@ class PNGHandlerTest extends MediaWikiTestCase {
* @dataProvider dataIsAnimated
*/
public function testIsAnimanted( $filename, $expected ) {
- $file = UnregisteredLocalFile::newFromPath( $this->filePath . $filename,
- 'image/png' );
+ $file = $this->dataFile( $filename, 'image/png' );
$actual = $this->handler->isAnimatedImage( $file );
$this->assertEquals( $expected, $actual );
}
@@ -34,8 +43,7 @@ class PNGHandlerTest extends MediaWikiTestCase {
* @dataProvider dataGetImageArea
*/
public function testGetImageArea( $filename, $expected ) {
- $file = UnregisteredLocalFile::newFromPath( $this->filePath . $filename,
- 'image/png' );
+ $file = $this->dataFile($filename, 'image/png' );
$actual = $this->handler->getImageArea( $file, $file->getWidth(), $file->getHeight() );
$this->assertEquals( $expected, $actual );
}
@@ -73,9 +81,8 @@ class PNGHandlerTest extends MediaWikiTestCase {
* @dataProvider dataGetMetadata
*/
public function testGetMetadata( $filename, $expected ) {
- $file = UnregisteredLocalFile::newFromPath( $this->filePath . $filename,
- 'image/png' );
- $actual = $this->handler->getMetadata( $file, $this->filePath . $filename );
+ $file = $this->dataFile( $filename, 'image/png' );
+ $actual = $this->handler->getMetadata( $file, "$this->filePath/$filename" );
// $this->assertEquals( unserialize( $expected ), unserialize( $actual ) );
$this->assertEquals( ( $expected ), ( $actual ) );
}
@@ -85,4 +92,9 @@ class PNGHandlerTest extends MediaWikiTestCase {
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;}}' ),
);
}
+
+ private function dataFile( $name, $type ) {
+ return new UnregisteredLocalFile( false, $this->repo,
+ "mwstore://localtesting/data/$name", $type );
+ }
}
diff --git a/tests/phpunit/includes/media/SVGMetadataExtractorTest.php b/tests/phpunit/includes/media/SVGMetadataExtractorTest.php
index c2c81b98..526beae8 100644
--- a/tests/phpunit/includes/media/SVGMetadataExtractorTest.php
+++ b/tests/phpunit/includes/media/SVGMetadataExtractorTest.php
@@ -62,23 +62,33 @@ class SVGMetadataExtractorTest extends MediaWikiTestCase {
'height' => 60
)
),
+ array(
+ "$base/Toll_Texas_1.svg",
+ // This file triggered bug 31719, needs entity expansion in the xmlns checks
+ array(
+ 'width' => 385,
+ 'height' => 385
+ )
+ )
);
}
function providerSvgFilesWithXMLMetadata() {
$base = dirname( __FILE__ ) . '/../../data/media';
- return array(
- array(
- "$base/US_states_by_total_state_tax_revenue.svg",
- array(
- 'height' => 593,
- 'metadata' =>
+ $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"/>
</ns4:Work>
- </rdf:RDF>',
+ </rdf:RDF>';
+ $metadata = str_replace( "\r", '', $metadata ); // Windows compat
+ return array(
+ array(
+ "$base/US_states_by_total_state_tax_revenue.svg",
+ array(
+ 'height' => 593,
+ 'metadata' => $metadata,
'width' => 959
)
),
diff --git a/tests/phpunit/includes/media/TiffTest.php b/tests/phpunit/includes/media/TiffTest.php
index 0a7e8e8c..d4cf503b 100644
--- a/tests/phpunit/includes/media/TiffTest.php
+++ b/tests/phpunit/includes/media/TiffTest.php
@@ -15,16 +15,15 @@ class TiffTest extends MediaWikiTestCase {
}
public function testInvalidFile() {
- global $wgShowEXIF;
- if ( !$wgShowEXIF ) {
+ 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 );
}
+
public function testTiffMetadataExtraction() {
- global $wgShowEXIF;
- if ( !$wgShowEXIF ) {
+ if ( !wfDl( 'exif' ) ) {
$this->markTestIncomplete( "This test needs the exif extension." );
}
$res = $this->handler->getMetadata( null, $this->filePath . 'test.tiff' );
diff --git a/tests/phpunit/includes/media/XMPTest.php b/tests/phpunit/includes/media/XMPTest.php
index d1ec4767..c952b23c 100644
--- a/tests/phpunit/includes/media/XMPTest.php
+++ b/tests/phpunit/includes/media/XMPTest.php
@@ -1,6 +1,12 @@
<?php
class XMPTest extends MediaWikiTestCase {
+ function setUp() {
+ if ( !wfDl( 'xml' ) ) {
+ $this->markTestSkipped( 'Requires libxml to do XMP parsing' );
+ }
+ }
+
/**
* Put XMP in, compare what comes out...
*
@@ -11,9 +17,6 @@ class XMPTest extends MediaWikiTestCase {
* @dataProvider dataXMPParse
*/
public function testXMPParse( $xmp, $expected, $info ) {
- if ( !function_exists( 'xml_parser_create_ns' ) ) {
- $this->markIncomplete( 'Requires libxml to do XMP parsing' );
- }
if ( !is_string( $xmp ) || !is_array( $expected ) ) {
throw new Exception( "Invalid data provided to " . __METHOD__ );
}
diff --git a/tests/phpunit/includes/media/XMPValidateTest.php b/tests/phpunit/includes/media/XMPValidateTest.php
new file mode 100644
index 00000000..e2bb8d8d
--- /dev/null
+++ b/tests/phpunit/includes/media/XMPValidateTest.php
@@ -0,0 +1,47 @@
+<?php
+class XMPValidateTest extends MediaWikiTestCase {
+
+ /**
+ * @dataProvider providerDate
+ */
+ function testValidateDate( $value, $expected ) {
+ // The method should modify $value.
+ XMPValidate::validateDate( array(), $value, true );
+ $this->assertEquals( $expected, $value );
+ }
+
+ function providerDate() {
+ /* For reference valid date formats are:
+ * YYYY
+ * YYYY-MM
+ * YYYY-MM-DD
+ * YYYY-MM-DDThh:mmTZD
+ * YYYY-MM-DDThh:mm:ssTZD
+ * YYYY-MM-DDThh:mm:ss.sTZD
+ * (Time zone is optional)
+ */
+ return array(
+ array( '1992', '1992' ),
+ array( '1992-04', '1992:04' ),
+ array( '1992-02-01', '1992:02:01' ),
+ array( '2011-09-29', '2011:09:29' ),
+ array( '1982-12-15T20:12', '1982:12:15 20:12' ),
+ array( '1982-12-15T20:12Z', '1982:12:15 20:12' ),
+ array( '1982-12-15T20:12+02:30', '1982:12:15 22:42' ),
+ array( '1982-12-15T01:12-02:30', '1982:12:14 22:42' ),
+ array( '1982-12-15T20:12:11', '1982:12:15 20:12:11' ),
+ array( '1982-12-15T20:12:11Z', '1982:12:15 20:12:11' ),
+ array( '1982-12-15T20:12:11+01:10', '1982:12:15 21:22:11' ),
+ array( '2045-12-15T20:12:11', '2045:12:15 20:12:11' ),
+ array( '1867-06-01T15:00:00', '1867:06:01 15:00:00' ),
+ /* some invalid ones */
+ array( '2001--12', null ),
+ array( '2001-5-12', null ),
+ array( '2001-5-12TZ', null ),
+ array( '2001-05-12T15', null ),
+ array( '2001-12T15:13', null ),
+ );
+
+ }
+
+}
diff --git a/tests/phpunit/includes/parser/MagicVariableTest.php b/tests/phpunit/includes/parser/MagicVariableTest.php
index a47653e3..31645313 100644
--- a/tests/phpunit/includes/parser/MagicVariableTest.php
+++ b/tests/phpunit/includes/parser/MagicVariableTest.php
@@ -6,8 +6,8 @@
* As of february 2011, it only tests some revisions and date related
* magic variables.
*
- * @author Ashar Voultoiz
- * @copyright Copyright © 2011, Ashar Voultoiz
+ * @author Antoine Musso
+ * @copyright Copyright © 2011, Antoine Musso
* @file
*/
@@ -38,6 +38,12 @@ class MagicVariableTest extends MediaWikiTestCase {
# initialize parser output
$this->testParser->clearState();
+
+ # Needs a title to do magic word stuff
+ $title = Title::newFromText( 'Tests' );
+ $title->mRedirect = false; # Else it needs a db connection just to check if it's a redirect (when deciding the page language)
+
+ $this->testParser->setTitle( $title );
}
/** destroy parser (TODO: is it really neded?)*/
diff --git a/tests/phpunit/includes/parser/MediaWikiParserTest.php b/tests/phpunit/includes/parser/MediaWikiParserTest.php
index 18510d9a..816c017a 100644
--- a/tests/phpunit/includes/parser/MediaWikiParserTest.php
+++ b/tests/phpunit/includes/parser/MediaWikiParserTest.php
@@ -1,10 +1,8 @@
<?php
-
-require_once( dirname( __FILE__ ) . '/ParserHelpers.php' );
require_once( dirname( __FILE__ ) . '/NewParserTest.php' );
/**
- * The UnitTest must be either a class that inherits from PHPUnit_Framework_TestCase
+ * The UnitTest must be either a class that inherits from MediaWikiTestCase
* or a class that provides a public static suite() method which returns
* an PHPUnit_Framework_Test object
*
@@ -20,9 +18,13 @@ class MediaWikiParserTest {
foreach ( $wgParserTestFiles as $filename ) {
$testsName = basename( $filename, '.txt' );
- $className = /*ucfirst( basename( dirname( $filename ) ) ) .*/ ucfirst( basename( $filename, '.txt' ) );
+ /* 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 = \"" . addslashes( $filename ) . "\"; } " );
+ eval( "/** @group Database\n@group Parser\n*/ class $className extends NewParserTest { protected \$file = '" . strtr( $filename, array( "'" => "\\'", '\\' => '\\\\' ) ) . "'; } " );
$parserTester = new $className( $testsName );
$suite->addTestSuite( new ReflectionClass ( $parserTester ) );
diff --git a/tests/phpunit/includes/parser/NewParserTest.php b/tests/phpunit/includes/parser/NewParserTest.php
index f4d5f757..d1221ca8 100644
--- a/tests/phpunit/includes/parser/NewParserTest.php
+++ b/tests/phpunit/includes/parser/NewParserTest.php
@@ -8,13 +8,12 @@
* @group Stub
*/
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
* When running tests from several files, all tests will see all articles.
*/
-
- public $uploadDir;
+ static protected $backendToUse;
+
public $keepUploads = false;
public $runDisabled = false;
public $regex = '';
@@ -31,16 +30,12 @@ class NewParserTest extends MediaWikiTestCase {
public $memoryLimit = 50;
protected $file = false;
-
- /*function __construct($a = null,$b = array(),$c = null ) {
- parent::__construct($a,$b,$c);
- }*/
-
+
function setUp() {
global $wgContLang, $wgNamespaceProtection, $wgNamespaceAliases;
global $wgHooks, $IP;
$wgContLang = Language::factory( 'en' );
-
+
//Setup CLI arguments
if ( $this->getCliArg( 'regex=' ) ) {
$this->regex = $this->getCliArg( 'regex=' );
@@ -48,11 +43,11 @@ class NewParserTest extends MediaWikiTestCase {
# Matches anything
$this->regex = '';
}
-
+
$this->keepUploads = $this->getCliArg( 'keep-uploads' );
-
+
$tmpGlobals = array();
-
+
$tmpGlobals['wgScript'] = '/index.php';
$tmpGlobals['wgScriptPath'] = '/';
$tmpGlobals['wgArticlePath'] = '/wiki/$1';
@@ -60,15 +55,14 @@ class NewParserTest extends MediaWikiTestCase {
$tmpGlobals['wgStylePath'] = '/skins';
$tmpGlobals['wgThumbnailScriptPath'] = false;
$tmpGlobals['wgLocalFileRepo'] = array(
- 'class' => 'LocalRepo',
- 'name' => 'local',
- 'directory' => wfTempDir() . '/test-repo',
- 'url' => 'http://example.com/images',
- 'deletedDir' => wfTempDir() . '/test-repo/delete',
- 'hashLevels' => 2,
+ 'class' => 'LocalRepo',
+ 'name' => 'local',
+ 'url' => 'http://example.com/images',
+ 'hashLevels' => 2,
'transformVia404' => false,
+ 'backend' => 'local-backend'
);
-
+ $tmpGlobals['wgForeignFileRepos'] = array();
$tmpGlobals['wgEnableParserCache'] = false;
$tmpGlobals['wgHooks'] = $wgHooks;
$tmpGlobals['wgDeferredUpdateList'] = array();
@@ -79,16 +73,16 @@ class NewParserTest extends MediaWikiTestCase {
// $tmpGlobals['wgContLang'] = new StubContLang;
$tmpGlobals['wgUser'] = new User;
$context = new RequestContext();
- $tmpGlobals['wgLang'] = $context->getLang();
+ $tmpGlobals['wgLang'] = $context->getLanguage();
$tmpGlobals['wgOut'] = $context->getOutput();
$tmpGlobals['wgParser'] = new StubObject( 'wgParser', $GLOBALS['wgParserConf']['class'], array( $GLOBALS['wgParserConf'] ) );
- $tmpGlobals['wgRequest'] = new WebRequest;
+ $tmpGlobals['wgRequest'] = $context->getRequest();
if ( $GLOBALS['wgStyleDirectory'] === false ) {
$tmpGlobals['wgStyleDirectory'] = "$IP/skins";
}
-
-
+
+
foreach ( $tmpGlobals as $var => $val ) {
if ( array_key_exists( $var, $GLOBALS ) ) {
$this->savedInitialGlobals[$var] = $GLOBALS[$var];
@@ -96,31 +90,38 @@ class NewParserTest extends MediaWikiTestCase {
$GLOBALS[$var] = $val;
}
-
+
$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;
}
-
+
global $wgNamespaceProtection, $wgNamespaceAliases;
-
+
$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();
}
-
+
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(
@@ -158,17 +159,14 @@ class NewParserTest extends MediaWikiTestCase {
* @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' ) );
+ ), __METHOD__, array( 'IGNORE' )
+ );
# Update certain things in site_stats
- $this->db->insert( 'site_stats',
+ $this->db->insert( 'site_stats',
array( 'ss_row_id' => 1, 'ss_images' => 2, 'ss_good_articles' => 1 ),
- __METHOD__,
- /**
- * @todo Fixme! Same as above!
- */
- array( 'IGNORE' )
+ __METHOD__
);
# Reinitialise the LocalisationCache to match the database state
@@ -177,50 +175,66 @@ class NewParserTest extends MediaWikiTestCase {
# Clear the message cache
MessageCache::singleton()->clear();
- $this->uploadDir = $this->setupUploadDir();
-
$user = User::newFromId( 0 );
LinkCache::singleton()->clear(); # Avoids the odd failure at creating the nullRevision
-
+
+ # Upload DB table entries for files.
+ # 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.
$image = wfLocalFile( Title::makeTitle( NS_FILE, 'Foobar.jpg' ) );
- $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 );
+ if ( !$this->db->selectField( 'image', '1', array( 'img_name' => $image->getName() ) ) ) {
+ $image->recordUpload2(
+ '', // archive name
+ '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
+ );
+ }
# 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 );
-
+ if ( !$this->db->selectField( 'image', '1', array( 'img_name' => $image->getName() ) ) ) {
+ $image->recordUpload2(
+ '', // archive name
+ '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
+ );
+ }
}
-
-
-
-
+
+
+
+
//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 = '' ) {
+ global $wgFileBackends;
# Find out values for some special options.
$lang =
self::getOptionValue( 'language', $opts, 'en' );
@@ -231,19 +245,48 @@ class NewParserTest extends MediaWikiTestCase {
$linkHolderBatchSize =
self::getOptionValue( 'wgLinkHolderBatchSize', $opts, 1000 );
+ $uploadDir = $this->getUploadDir();
+ if ( $this->getCliArg( 'use-filebackend=' ) ) {
+ if ( self::$backendToUse ) {
+ $backend = self::$backendToUse;
+ } else {
+ $name = $this->getCliArg( 'use-filebackend=' );
+ $useConfig = array();
+ foreach ( $wgFileBackends as $conf ) {
+ if ( $conf['name'] == $name ) {
+ $useConfig = $conf;
+ }
+ }
+ $useConfig['name'] = 'local-backend'; // swap name
+ $class = $conf['class'];
+ self::$backendToUse = new $class( $useConfig );
+ $backend = self::$backendToUse;
+ }
+ } else {
+ $backend = new FSFileBackend( array(
+ 'name' => 'local-backend',
+ 'lockManager' => 'nullLockManager',
+ 'containerPaths' => array(
+ 'local-public' => "$uploadDir",
+ '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',
- 'directory' => $this->uploadDir,
- 'url' => 'http://example.com/images',
- 'hashLevels' => 2,
+ 'class' => 'LocalRepo',
+ 'name' => 'local',
+ 'url' => 'http://example.com/images',
+ 'hashLevels' => 2,
'transformVia404' => false,
+ 'backend' => $backend
),
'wgEnableUploads' => self::getOptionValue( 'wgEnableUploads', $opts, true ),
'wgStylePath' => '/skins',
@@ -262,7 +305,7 @@ class NewParserTest extends MediaWikiTestCase {
'wgThumbnailScriptPath' => false,
'wgUseImageResize' => false,
'wgUseTeX' => isset( $opts['math'] ),
- 'wgMathDirectory' => $this->uploadDir . '/math',
+ 'wgMathDirectory' => $uploadDir . '/math',
'wgLocaltimezone' => 'UTC',
'wgAllowExternalImages' => true,
'wgUseTidy' => false,
@@ -283,6 +326,7 @@ class NewParserTest extends MediaWikiTestCase {
'wgExternalLinkTarget' => false,
'wgAlwaysUseTidy' => false,
'wgHtml5' => true,
+ 'wgCleanupPresentationalAttributes' => true,
'wgWellFormedXml' => true,
'wgAllowMicrodataAttributes' => true,
'wgAdaptiveMessageCache' => true,
@@ -312,39 +356,41 @@ class NewParserTest extends MediaWikiTestCase {
$langObj = Language::factory( $lang );
$GLOBALS['wgContLang'] = $langObj;
$context = new RequestContext();
- $GLOBALS['wgLang'] = $context->getLang();
+ $GLOBALS['wgLang'] = $context->getLanguage();
$GLOBALS['wgMemc'] = new EmptyBagOStuff;
$GLOBALS['wgOut'] = $context->getOutput();
+ $GLOBALS['wgUser'] = $context->getUser();
global $wgHooks;
$wgHooks['ParserTestParser'][] = 'ParserTestParserHook::setup';
- $wgHooks['ParserTestParser'][] = 'ParserTestStaticParserHook::setup';
$wgHooks['ParserGetVariableValueTs'][] = 'ParserTest::getFakeTimestamp';
MagicWord::clearCache();
+ RepoGroup::destroySingleton();
+ FileBackendGroup::destroySingleton();
+
+ # Create dummy files in storage
+ $this->setupUploads();
# 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::singleton()->destroyInstance();
-
- global $wgUser;
- $wgUser = new User();
+
+ return $context;
}
/**
- * Create a dummy uploads directory which will contain a couple
- * of files in order to pass existence tests.
+ * Get an FS upload directory (only applies to FSFileBackend)
*
* @return String: the directory
*/
- protected function setupUploadDir() {
- global $IP;
-
+ protected function getUploadDir() {
if ( $this->keepUploads ) {
$dir = wfTempDir() . '/mwParser-images';
@@ -361,70 +407,67 @@ class NewParserTest extends MediaWikiTestCase {
return $dir;
}
- wfMkdirParents( $dir . '/3/3a' );
- copy( "$IP/skins/monobook/headbg.jpg", "$dir/3/3a/Foobar.jpg" );
- wfMkdirParents( $dir . '/0/09' );
- copy( "$IP/skins/monobook/headbg.jpg", "$dir/0/09/Bad.jpg" );
-
return $dir;
}
-
+
+ /**
+ * Create a dummy uploads directory which will contain a couple
+ * of files in order to pass existence tests.
+ *
+ * @return String: the directory
+ */
+ protected function setupUploads() {
+ global $IP;
+
+ $base = $this->getBaseDir();
+ $backend = RepoGroup::singleton()->getLocalRepo()->getBackend();
+ $backend->prepare( array( 'dir' => "$base/local-public/3/3a" ) );
+ $backend->store( array(
+ 'src' => "$IP/skins/monobook/headbg.jpg", 'dst' => "$base/local-public/3/3a/Foobar.jpg"
+ ) );
+ $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"
+ ) );
+ }
+
/**
* Restore default values and perform any necessary clean-up
* after each test runs.
*/
protected function teardownGlobals() {
- RepoGroup::destroySingleton();
- LinkCache::singleton()->clear();
+ $this->teardownUploads();
foreach ( $this->savedGlobals as $var => $val ) {
$GLOBALS[$var] = $val;
}
-
- $this->teardownUploadDir( $this->uploadDir );
+
+ RepoGroup::destroySingleton();
+ LinkCache::singleton()->clear();
}
/**
* Remove the dummy uploads directory
*/
- private function teardownUploadDir( $dir ) {
+ private function teardownUploads() {
if ( $this->keepUploads ) {
return;
}
+ $base = $this->getBaseDir();
// delete the files first, then the dirs.
self::deleteFiles(
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/0/09/Bad.jpg",
+ "$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",
- "$dir/math/f/a/5/fa50b8b616463173474302ca3e63586b.png",
- )
- );
+ "$base/local-public/0/09/Bad.jpg",
+ "$base/local-thumb/0/09/Bad.jpg",
- self::deleteDirs(
- array (
- "$dir/3/3a",
- "$dir/3",
- "$dir/thumb/6/65",
- "$dir/thumb/6",
- "$dir/thumb/3/3a/Foobar.jpg",
- "$dir/thumb/3/3a",
- "$dir/thumb/3",
-
- "$dir/0/09/",
- "$dir/0/",
- "$dir/thumb",
- "$dir/math/f/a/5",
- "$dir/math/f/a",
- "$dir/math/f",
- "$dir/math",
- "$dir",
+ "$base/local-public/math/f/a/5/fa50b8b616463173474302ca3e63586b.png",
)
);
}
@@ -434,25 +477,24 @@ class NewParserTest extends MediaWikiTestCase {
* @param $files Array: full paths to files to delete.
*/
private static function deleteFiles( $files ) {
+ $backend = RepoGroup::singleton()->getLocalRepo()->getBackend();
foreach ( $files as $file ) {
- if ( file_exists( $file ) ) {
- unlink( $file );
+ $backend->delete( array( 'src' => $file ), array( 'force' => 1 ) );
+ }
+ foreach ( $files as $file ) {
+ $tmp = $file;
+ while ( $tmp = FileBackend::parentStoragePath( $tmp ) ) {
+ if ( !$backend->clean( array( 'dir' => $tmp ) )->isOK() ) {
+ break;
+ }
}
}
}
- /**
- * Delete the specified directories, if they exist. Must be empty.
- * @param $dirs Array: full paths to directories to delete.
- */
- private static function deleteDirs( $dirs ) {
- foreach ( $dirs as $dir ) {
- if ( is_dir( $dir ) ) {
- rmdir( $dir );
- }
- }
+ protected function getBaseDir() {
+ return 'mwstore://local-backend';
}
-
+
public function parserTestProvider() {
if ( $this->file === false ) {
global $wgParserTestFiles;
@@ -460,25 +502,29 @@ class NewParserTest extends MediaWikiTestCase {
}
return new TestFileIterator( $this->file, $this );
}
-
+
/**
* Set the file from whose tests will be run by this instance
*/
public function setParserTestFile( $filename ) {
$this->file = $filename;
}
-
+
/** @dataProvider parserTestProvider */
public function testParserTest( $desc, $input, $result, $opts, $config ) {
- if ( !preg_match( '/' . $this->regex . '/', $desc ) ) return; //$this->markTestSkipped( 'Filtered out by the user' );
+ if ( $this->regex != '' && !preg_match( '/' . $this->regex . '/', $desc ) ) {
+ $this->assertTrue( true ); // XXX: don't flood output with "test made no assertions"
+ //$this->markTestSkipped( 'Filtered out by the user' );
+ return;
+ }
wfDebug( "Running parser test: $desc\n" );
$opts = $this->parseOptions( $opts );
- $this->setupGlobals( $opts, $config );
+ $context = $this->setupGlobals( $opts, $config );
- $user = new User();
- $options = ParserOptions::newFromUser( $user );
+ $user = $context->getUser();
+ $options = ParserOptions::newFromContext( $context );
if ( isset( $opts['title'] ) ) {
$titleText = $opts['title'];
@@ -490,7 +536,7 @@ class NewParserTest extends MediaWikiTestCase {
$local = isset( $opts['local'] );
$preprocessor = isset( $opts['preprocessor'] ) ? $opts['preprocessor'] : null;
$parser = $this->getParser( $preprocessor );
-
+
$title = Title::newFromText( $titleText );
if ( isset( $opts['pst'] ) ) {
@@ -505,8 +551,7 @@ class NewParserTest extends MediaWikiTestCase {
$replace = $opts['replace'][1];
$out = $parser->replaceSection( $input, $section, $replace );
} elseif ( isset( $opts['comment'] ) ) {
- $linker = $user->getSkin();
- $out = $linker->formatComment( $input, $title, $local );
+ $out = Linker::formatComment( $input, $title, $local );
} elseif ( isset( $opts['preload'] ) ) {
$out = $parser->getpreloadText( $input, $title, $options );
} else {
@@ -524,10 +569,9 @@ class NewParserTest extends MediaWikiTestCase {
if ( isset( $opts['ill'] ) ) {
$out = $this->tidy( implode( ' ', $output->getLanguageLinks() ) );
} elseif ( isset( $opts['cat'] ) ) {
- global $wgOut;
-
- $wgOut->addCategoryLinks( $output->getCategories() );
- $cats = $wgOut->getCategoryLinks();
+ $outputPage = $context->getOutput();
+ $outputPage->addCategoryLinks( $output->getCategories() );
+ $cats = $outputPage->getCategoryLinks();
if ( isset( $cats['normal'] ) ) {
$out = $this->tidy( implode( ' ', $cats['normal'] ) );
@@ -541,38 +585,41 @@ class NewParserTest extends MediaWikiTestCase {
}
$this->teardownGlobals();
-
+
$this->assertEquals( $result, $out, $desc );
}
-
+
/**
* Run a fuzz test series
* Draw input from a set of test files
+ *
+ * @todo @fixme Needs some work to not eat memory until the world explodes
+ *
+ * @group ParserFuzz
*/
function testFuzzTests() {
-
- $this->markTestIncomplete( "Somebody is serializing PDO objects, that's a no-no" );
-
global $wgParserTestFiles;
-
+
$files = $wgParserTestFiles;
-
+
if( $this->getCliArg( 'file=' ) ) {
$files = array( $this->getCliArg( 'file=' ) );
}
-
+
$dict = $this->getFuzzInput( $files );
$dictSize = strlen( $dict );
$logMaxLength = log( $this->maxFuzzTestLength );
+ ini_set( 'memory_limit', $this->memoryLimit * 1048576 );
+
$user = new User;
$opts = ParserOptions::newFromUser( $user );
$title = Title::makeTitle( NS_MAIN, 'Parser_test' );
$id = 1;
-
+
while ( true ) {
-
+
// Generate test input
mt_srand( ++$this->fuzzSeed );
$totalLength = mt_rand( 1, $this->maxFuzzTestLength );
@@ -594,7 +641,7 @@ class NewParserTest extends MediaWikiTestCase {
$this->assertTrue( true, "Test $id, fuzz seed {$this->fuzzSeed}" );
} 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()}" );
}
@@ -611,18 +658,18 @@ class NewParserTest extends MediaWikiTestCase {
foreach ( $memStats as $name => $usage ) {
$ret .= "$name: $usage\n";
}
-
+
throw new MWException( $ret );
}
}
-
+
$id++;
-
+
}
}
//Various getter functions
-
+
/**
* Get an input dictionary from a set of parser test files
*/
@@ -640,7 +687,7 @@ class NewParserTest extends MediaWikiTestCase {
return $dict;
}
-
+
/**
* Get a memory usage breakdown
*/
@@ -675,7 +722,7 @@ class NewParserTest extends MediaWikiTestCase {
return $memStats;
}
-
+
/**
* Get a Parser object
*/
@@ -693,23 +740,20 @@ class NewParserTest extends MediaWikiTestCase {
//Various action functions
public function addArticle( $name, $text, $line ) {
- self::$articles[$name] = $text;
- }
-
+ self::$articles[$name] = array( $text, $line );
+ }
+
public function publishTestArticles() {
if ( empty( self::$articles ) ) {
return;
}
- foreach ( self::$articles as $name => $text ) {
- $title = Title::newFromText( $name );
-
- if ( $title->getArticleID( Title::GAID_FOR_UPDATE ) == 0 ) {
- ParserTest::addArticle( $name, $text );
- }
+ foreach ( self::$articles as $name => $info ) {
+ list( $text, $line ) = $info;
+ ParserTest::addArticle( $name, $text, $line, 'ignoreduplicate' );
}
}
-
+
/**
* Steal a callback function from the primary parser, save it for
* application to our scary parser. If the hook is not installed,
@@ -730,8 +774,8 @@ class NewParserTest extends MediaWikiTestCase {
return isset( $wgParser->mFunctionHooks[$name] );
}
//Various "cleanup" functions
-
- /*
+
+ /**
* Run the "tidy" command on text if the $wgUseTidy
* global is true
*
@@ -747,7 +791,7 @@ class NewParserTest extends MediaWikiTestCase {
return $text;
}
-
+
/**
* Remove last character if it is a newline
*/
@@ -760,12 +804,8 @@ class NewParserTest extends MediaWikiTestCase {
}
}
- public function showRunFile( $file ) {
- /* NOP */
- }
-
//Test options parser functions
-
+
protected function parseOptions( $instring ) {
$opts = array();
// foo
@@ -820,7 +860,7 @@ class NewParserTest extends MediaWikiTestCase {
}
return $opts;
}
-
+
protected function cleanupOption( $opt ) {
if ( substr( $opt, 0, 1 ) == '"' ) {
return substr( $opt, 1, -1 );
@@ -831,7 +871,7 @@ class NewParserTest extends MediaWikiTestCase {
}
return $opt;
}
-
+
/**
* Use a regex to find out the value of an option
* @param $key String: name of option val to retrieve
diff --git a/tests/phpunit/includes/parser/ParserHelpers.php b/tests/phpunit/includes/parser/ParserHelpers.php
deleted file mode 100644
index 4a6ce7c4..00000000
--- a/tests/phpunit/includes/parser/ParserHelpers.php
+++ /dev/null
@@ -1,136 +0,0 @@
-<?php
-
-class PHPUnitParserTest extends ParserTest {
- function showTesting( $desc ) {
- /* Do nothing since we don't want to show info during PHPUnit testing. */
- }
-
- public function showSuccess( $desc ) {
- PHPUnit_Framework_Assert::assertTrue( true, $desc );
- return true;
- }
-
- public function showFailure( $desc, $expected, $got ) {
- PHPUnit_Framework_Assert::assertEquals( $expected, $got, $desc );
- return false;
- }
-
- public function setupRecorder( $options ) {
- $this->recorder = new PHPUnitTestRecorder( $this );
- }
-}
-
-class ParserUnitTest extends MediaWikiTestCase {
- private $test = "";
-
- public function __construct( $suite, $test = null ) {
- parent::__construct();
- $this->test = $test;
- $this->suite = $suite;
- }
-
- function count() { return 1; }
-
- public function run( PHPUnit_Framework_TestResult $result = null ) {
- PHPUnit_Framework_Assert::resetCount();
- if ( $result === NULL ) {
- $result = new PHPUnit_Framework_TestResult;
- }
-
- $this->suite->publishTestArticles(); // Add articles needed by the tests.
- $backend = new ParserTestSuiteBackend;
- $result->startTest( $this );
-
- // Support the transition to PHPUnit 3.5 where PHPUnit_Util_Timer is replaced with PHP_Timer
- if ( class_exists( 'PHP_Timer' ) ) {
- PHP_Timer::start();
- } else {
- PHPUnit_Util_Timer::start();
- }
-
- $r = false;
- try {
- # Run the test.
- # On failure, the subclassed backend will throw an exception with
- # the details.
- $pt = new PHPUnitParserTest;
- $r = $pt->runTest( $this->test['test'], $this->test['input'],
- $this->test['result'], $this->test['options'], $this->test['config']
- );
- }
- catch ( PHPUnit_Framework_AssertionFailedError $e ) {
-
- // PHPUnit_Util_Timer -> PHP_Timer support (see above)
- if ( class_exists( 'PHP_Timer' ) ) {
- $result->addFailure( $this, $e, PHP_Timer::stop() );
- } else {
- $result->addFailure( $this, $e, PHPUnit_Util_Timer::stop() );
- }
- }
- catch ( Exception $e ) {
- // PHPUnit_Util_Timer -> PHP_Timer support (see above)
- if ( class_exists( 'PHP_Timer' ) ) {
- $result->addFailure( $this, $e, PHP_Timer::stop() );
- } else {
- $result->addFailure( $this, $e, PHPUnit_Util_Timer::stop() );
- }
- }
-
- // PHPUnit_Util_Timer -> PHP_Timer support (see above)
- if ( class_exists( 'PHP_Timer' ) ) {
- $result->endTest( $this, PHP_Timer::stop() );
- } else {
- $result->endTest( $this, PHPUnit_Util_Timer::stop() );
- }
-
- $backend->recorder->record( $this->test['test'], $r );
- $this->addToAssertionCount( PHPUnit_Framework_Assert::getCount() );
-
- return $result;
- }
-
- public function toString() {
- return $this->test['test'];
- }
-
-}
-
-class ParserTestSuiteBackend extends PHPUnit_FrameWork_TestSuite {
- public $recorder;
- public $term;
- static $usePHPUnit = false;
-
- function __construct() {
- parent::__construct();
- $this->setupRecorder(null);
- self::$usePHPUnit = method_exists('PHPUnit_Framework_Assert', 'assertEquals');
- }
-
- function showTesting( $desc ) {
- }
-
- function showRunFile( $path ) {
- }
-
- function showTestResult( $desc, $result, $out ) {
- if ( $result === $out ) {
- return self::showSuccess( $desc, $result, $out );
- } else {
- return self::showFailure( $desc, $result, $out );
- }
- }
-
- public function setupRecorder( $options ) {
- $this->recorder = new PHPUnitTestRecorder( $this );
- }
-}
-
-class PHPUnitTestRecorder extends TestRecorder {
- function record( $test, $result ) {
- $this->total++;
- $this->success += $result;
-
- }
-
- function reportPercentage( $success, $total ) { }
-}
diff --git a/tests/phpunit/includes/parser/ParserPreloadTest.php b/tests/phpunit/includes/parser/ParserPreloadTest.php
new file mode 100644
index 00000000..0e8ef530
--- /dev/null
+++ b/tests/phpunit/includes/parser/ParserPreloadTest.php
@@ -0,0 +1,67 @@
+<?php
+/**
+ * Basic tests for Parser::getPreloadText
+ * @author Antoine Musso
+ */
+class ParserPreloadTest extends MediaWikiTestCase {
+ private $testParser;
+ private $testParserOptions;
+ private $title;
+
+ function setUp() {
+ $this->testParserOptions = new ParserOptions();
+
+ $this->testParser = new Parser();
+ $this->testParser->Options( $this->testParserOptions );
+ $this->testParser->clearState();
+
+ $this->title = Title::newFromText( 'Preload Test' );
+ }
+
+ function tearDown() {
+ unset( $this->testParser );
+ unset( $this->title );
+ }
+
+ /**
+ * @covers Parser::getPreloadText
+ */
+ function testPreloadSimpleText() {
+ $this->assertPreloaded( 'simple', 'simple' );
+ }
+
+ /**
+ * @covers Parser::getPreloadText
+ */
+ function testPreloadedPreIsUnstripped() {
+ $this->assertPreloaded(
+ '<pre>monospaced</pre>',
+ '<pre>monospaced</pre>',
+ '<pre> in preloaded text must be unstripped (bug 27467)'
+ );
+ }
+
+ /**
+ * @covers Parser::getPreloadText
+ */
+ function testPreloadedNowikiIsUnstripped() {
+ $this->assertPreloaded(
+ '<nowiki>[[Dummy title]]</nowiki>',
+ '<nowiki>[[Dummy title]]</nowiki>',
+ '<nowiki> in preloaded text must be unstripped (bug 27467)'
+ );
+ }
+
+ function assertPreloaded( $expected, $text, $msg='') {
+ $this->assertEquals(
+ $expected,
+ $this->testParser->getPreloadText(
+ $text,
+ $this->title,
+ $this->testParserOptions
+ ),
+ $msg
+ );
+ }
+
+}
diff --git a/tests/phpunit/includes/parser/PreprocessorTest.php b/tests/phpunit/includes/parser/PreprocessorTest.php
index 7a5948d4..9d3499a0 100644
--- a/tests/phpunit/includes/parser/PreprocessorTest.php
+++ b/tests/phpunit/includes/parser/PreprocessorTest.php
@@ -49,43 +49,45 @@ class PreprocessorTest extends MediaWikiTestCase {
array( "== Foo ==\n== Bar == \n", "<root><h level=\"2\" i=\"1\">== Foo ==</h>\n<h level=\"2\" i=\"2\">== Bar == </h>\n</root>" ),
array( "===========", "<root><h level=\"5\" i=\"1\">===========</h></root>" ),
array( "Foo\n=\n==\n=\n", "<root>Foo\n=\n==\n=\n</root>" ),
- array( "{{Foo}}", "<root><template lineStart=\"1\"><title>Foo</title></template></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 lineStart=\"1\"><title>Foo</title><part><name index=\"1\" /><value>bar</value></part></template></root>" ),
- array( "{{Foo|bar}}a", "<root><template lineStart=\"1\"><title>Foo</title><part><name index=\"1\" /><value>bar</value></part></template>a</root>" ),
- array( "{{Foo|bar|baz}}", "<root><template lineStart=\"1\"><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 lineStart=\"1\"><title>Foo</title><part><name>1</name>=<value>bar</value></part></template></root>" ),
- array( "{{Foo|=bar}}", "<root><template lineStart=\"1\"><title>Foo</title><part><name></name>=<value>bar</value></part></template></root>" ),
- array( "{{Foo|bar=baz}}", "<root><template lineStart=\"1\"><title>Foo</title><part><name>bar</name>=<value>baz</value></part></template></root>" ),
- array( "{{Foo|1=bar|baz}}", "<root><template lineStart=\"1\"><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 lineStart=\"1\"><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 lineStart=\"1\"><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 lineStart=\"1\"><title>1</title></tplarg></root>" ),
- array( "{{{1|}}}", "<root><tplarg lineStart=\"1\"><title>1</title><part><name index=\"1\" /><value></value></part></tplarg></root>" ),
- array( "{{{Foo}}}", "<root><tplarg lineStart=\"1\"><title>Foo</title></tplarg></root>" ),
- array( "{{{Foo|}}}", "<root><tplarg lineStart=\"1\"><title>Foo</title><part><name index=\"1\" /><value></value></part></tplarg></root>" ),
- array( "{{{Foo|bar|baz}}}", "<root><tplarg lineStart=\"1\"><title>Foo</title><part><name index=\"1\" /><value>bar</value></part><part><name index=\"2\" /><value>baz</value></part></tplarg></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><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|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( "{{{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>" ),
+ array( "{{{Foo|}}}", "<root><tplarg><title>Foo</title><part><name index=\"1\" /><value></value></part></tplarg></root>" ),
+ array( "{{{Foo|bar|baz}}}", "<root><tplarg><title>Foo</title><part><name index=\"1\" /><value>bar</value></part><part><name index=\"2\" /><value>baz</value></part></tplarg></root>" ),
array( "{<!-- -->{Foo}}", "<root>{<comment>&lt;!-- --&gt;</comment>{Foo}}</root>" ),
array( "{{{{Foobar}}}}", "<root>{<tplarg><title>Foobar</title></tplarg>}</root>" ),
- array( "{{{ {{Foo}} }}}", "<root><tplarg lineStart=\"1\"><title> <template><title>Foo</title></template> </title></tplarg></root>" ),
- array( "{{ {{{Foo}}} }}", "<root><template lineStart=\"1\"><title> <tplarg><title>Foo</title></tplarg> </title></template></root>" ),
- array( "{{{{{Foo}}}}}", "<root><template lineStart=\"1\"><title><tplarg><title>Foo</title></tplarg></title></template></root>" ),
- array( "{{{{{Foo}} }}}", "<root><tplarg lineStart=\"1\"><title><template><title>Foo</title></template> </title></tplarg></root>" ),
- array( "{{{{{{Foo}}}}}}", "<root><tplarg lineStart=\"1\"><title><tplarg><title>Foo</title></tplarg></title></tplarg></root>" ),
+ array( "{{{ {{Foo}} }}}", "<root><tplarg><title> <template><title>Foo</title></template> </title></tplarg></root>" ),
+ array( "{{ {{{Foo}}} }}", "<root><template><title> <tplarg><title>Foo</title></tplarg> </title></template></root>" ),
+ array( "{{{{{Foo}}}}}", "<root><template><title><tplarg><title>Foo</title></tplarg></title></template></root>" ),
+ array( "{{{{{Foo}} }}}", "<root><tplarg><title><template><title>Foo</title></template> </title></tplarg></root>" ),
+ array( "{{{{{{Foo}}}}}}", "<root><tplarg><title><tplarg><title>Foo</title></tplarg></title></tplarg></root>" ),
array( "{{{{{{Foo}}}}}", "<root>{<template><title><tplarg><title>Foo</title></tplarg></title></template></root>" ),
array( "[[[Foo]]", "<root>[[[Foo]]</root>" ),
- array( "{{Foo|[[[[bar]]|baz]]}}", "<root><template lineStart=\"1\"><title>Foo</title><part><name index=\"1\" /><value>[[[[bar]]|baz]]</value></part></template></root>" ), // This test is important, since it means the difference between having the [[ rule stacked or not
+ array( "{{Foo|[[[[bar]]|baz]]}}", "<root><template><title>Foo</title><part><name index=\"1\" /><value>[[[[bar]]|baz]]</value></part></template></root>" ), // This test is important, since it means the difference between having the [[ rule stacked or not
array( "{{Foo|[[[[bar]|baz]]}}", "<root>{{Foo|[[[[bar]|baz]]}}</root>" ),
array( "{{Foo|Foo [[[[bar]|baz]]}}", "<root>{{Foo|Foo [[[[bar]|baz]]}}</root>" ),
array( "Foo <display map>Bar</display map >Baz", "<root>Foo <ext><name>display map</name><attr></attr><inner>Bar</inner><close>&lt;/display map &gt;</close></ext>Baz</root>" ),
array( "Foo <display map foo>Bar</display map >Baz", "<root>Foo <ext><name>display map</name><attr> foo</attr><inner>Bar</inner><close>&lt;/display map &gt;</close></ext>Baz</root>" ),
array( "Foo <gallery bar=\"baz\" />", "<root>Foo <ext><name>gallery</name><attr> bar=&quot;baz&quot; </attr></ext></root>" ),
+ array( "Foo <gallery bar=\"1\" baz=2 />", "<root>Foo <ext><name>gallery</name><attr> bar=&quot;1&quot; baz=2 </attr></ext></root>" ),
array( "</foo>Foo<//foo>", "<root><ext><name>/foo</name><attr></attr><inner>Foo</inner><close>&lt;//foo&gt;</close></ext></root>" ), # Worth blacklisting IMHO
- array( "{{#ifexpr: ({{{1|1}}} = 2) | Foo | Bar }}", "<root><template lineStart=\"1\"><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 lineStart=\"1\"><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 lineStart=\"1\"><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 lineStart=\"1\"><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 lineStart=\"1\"><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( "{{#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>"),
@@ -97,19 +99,54 @@ class PreprocessorTest extends MediaWikiTestCase {
array( "{{Foo|bar=[[baz]}}", "<root>{{Foo|bar=[[baz]}}</root>"),
array( "{{foo|", "<root>{{foo|</root>"),
array( "{{foo|}", "<root>{{foo|}</root>"),
- array( "{{foo|} }}", "<root><template lineStart=\"1\"><title>foo</title><part><name index=\"1\" /><value>} </value></part></template></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 lineStart=\"1\"><title>Foo</title><part><name>} Bar</name>=<value></value></part></template></root>"),
+ array( "{{Foo|} Bar=}}", "<root><template><title>Foo</title><part><name>} Bar</name>=<value></value></part></template></root>"),
/* array( file_get_contents( dirname( __FILE__ ) . '/QuoteQuran.txt' ), file_get_contents( dirname( __FILE__ ) . '/QuoteQuranExpanded.txt' ) ), */
);
}
/**
+ * Get XML preprocessor tree from the preprocessor (which may not be the
+ * native XML-based one).
+ *
+ * @param string $wikiText
+ * @return string
+ */
+ 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();
+ } else {
+ return $this->normalizeXml( $dom->__toString() );
+ }
+ }
+
+ /**
+ * Normalize XML string to the form that a DOMDocument saves out.
+ *
+ * @param string $xml
+ * @return string
+ */
+ 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
*/
function testPreprocessorOutput( $wikiText, $expectedXml ) {
- $this->assertEquals( $expectedXml, $this->mPreprocessor->preprocessToXml( $wikiText ) );
+ $this->assertEquals( $this->normalizeXml( $expectedXml ), $this->preprocessToXml( $wikiText ) );
}
/**
@@ -130,11 +167,12 @@ class PreprocessorTest extends MediaWikiTestCase {
function testPreprocessorOutputFiles( $filename ) {
$folder = dirname( __FILE__ ) . "/../../../parser/preprocess";
$wikiText = file_get_contents( "$folder/$filename.txt" );
- $output = $this->mPreprocessor->preprocessToXml( $wikiText );
+ $output = $this->preprocessToXml( $wikiText );
$expectedFilename = "$folder/$filename.expected";
if ( file_exists( $expectedFilename ) ) {
- $this->assertStringEqualsFile( $expectedFilename, $output );
+ $expectedXml = $this->normalizeXml( file_get_contents( $expectedFilename ) );
+ $this->assertEquals( $expectedXml, $output );
} else {
$tempFilename = tempnam( $folder, "$filename." );
file_put_contents( $tempFilename, $output );
@@ -189,7 +227,7 @@ class PreprocessorTest extends MediaWikiTestCase {
* @dataProvider provideHeadings
*/
function testHeadings( $wikiText, $expectedXml ) {
- $this->assertEquals( $expectedXml, $this->mPreprocessor->preprocessToXml( $wikiText ) );
+ $this->assertEquals( $this->normalizeXml( $expectedXml ), $this->preprocessToXml( $wikiText ) );
}
}
diff --git a/tests/phpunit/includes/parser/TagHooks.php b/tests/phpunit/includes/parser/TagHooksTest.php
index 713ce846..713ce846 100644
--- a/tests/phpunit/includes/parser/TagHooks.php
+++ b/tests/phpunit/includes/parser/TagHooksTest.php
diff --git a/tests/phpunit/includes/search/SearchEngineTest.php b/tests/phpunit/includes/search/SearchEngineTest.php
index 1a0fcd31..957907c7 100644
--- a/tests/phpunit/includes/search/SearchEngineTest.php
+++ b/tests/phpunit/includes/search/SearchEngineTest.php
@@ -12,7 +12,7 @@ class SearchEngineTest extends MediaWikiTestCase {
unset( $this->search );
}
- /*
+ /**
* Checks for database type & version.
* Will skip current test if DB does not support search.
*/
@@ -64,8 +64,10 @@ class SearchEngineTest extends MediaWikiTestCase {
$this->assertTrue( is_object( $results ) );
$matches = array();
- while ( $row = $results->next() ) {
+ $row = $results->next();
+ while ( $row ) {
$matches[] = $row->getTitle()->getPrefixedText();
+ $row = $results->next();
}
$results->free();
# Search is not guaranteed to return results in a certain order;
@@ -83,20 +85,18 @@ class SearchEngineTest extends MediaWikiTestCase {
* @param $n Integer: unused
*/
function insertPage( $pageName, $text, $ns ) {
- $dbw = $this->db;
$title = Title::newFromText( $pageName );
$user = User::newFromName( 'WikiSysop' );
$comment = 'Search Test';
// avoid memory leak...?
- $linkCache = LinkCache::singleton();
- $linkCache->clear();
+ LinkCache::singleton()->clear();
- $article = new Article( $title );
- $article->doEdit( $text, $comment, 0, false, $user );
+ $page = WikiPage::factory( $title );
+ $page->doEdit( $text, $comment, 0, false, $user );
- $this->pageList[] = array( $title, $article->getId() );
+ $this->pageList[] = array( $title, $page->getId() );
return true;
}
diff --git a/tests/phpunit/includes/specials/QueryAllSpecialPagesTest.php b/tests/phpunit/includes/specials/QueryAllSpecialPagesTest.php
new file mode 100644
index 00000000..a33c7b68
--- /dev/null
+++ b/tests/phpunit/includes/specials/QueryAllSpecialPagesTest.php
@@ -0,0 +1,79 @@
+<?php
+/**
+ * Test class to run the query of most of all our special pages
+ *
+ * Copyright © 2011, Antoine Musso
+ *
+ * @author Antoine Musso
+ * @group Database
+ */
+
+if ( !defined( 'MEDIAWIKI' ) ) {
+ die( 1 );
+}
+
+global $IP;
+require_once "$IP/includes/QueryPage.php"; // Needed to populate $wgQueryPages
+
+class QueryAllSpecialPagesTest extends MediaWikiTestCase {
+
+ /** List query pages that can not be tested automatically */
+ protected $manualTest = array(
+ 'LinkSearchPage'
+ );
+
+ /**
+ * Pages whose query use the same DB table more than once.
+ * This is used to skip testing those pages when run against a MySQL backend
+ * which does not support reopening a temporary table. See upstream bug:
+ * http://bugs.mysql.com/bug.php?id=10327
+ */
+ protected $reopensTempTable = array(
+ 'BrokenRedirects',
+ );
+
+ /**
+ * Initialize all query page objects
+ */
+ function __construct() {
+ parent::__construct();
+
+ global $wgQueryPages;
+ foreach( $wgQueryPages as $page ) {
+ $class = $page[0];
+ if( ! in_array( $class, $this->manualTest ) ) {
+ $this->queryPages[$class] = new $class;
+ }
+ }
+ }
+
+ /**
+ * Test SQL for each of our QueryPages objects
+ * @group Database
+ */
+ function testQuerypageSqlQuery() {
+ global $wgDBtype;
+
+ foreach( $this->queryPages as $page ) {
+
+ // With MySQL, skips special pages reopening a temporary table
+ // See http://bugs.mysql.com/bug.php?id=10327
+ if(
+ $wgDBtype === 'mysql'
+ && in_array( $page->getName(), $this->reopensTempTable )
+ ) {
+ $this->markTestSkipped( "SQL query for page {$page->getName()} can not be tested on MySQL backend (it reopens a temporary table)" );
+ continue;
+ }
+
+ $msg = "SQL query for page {$page->getName()} should give a result wrapper object" ;
+
+ $result = $page->reallyDoQuery( 50 );
+ if( $result instanceof ResultWrapper ) {
+ $this->assertTrue( true, $msg );
+ } else {
+ $this->assertFalse( false, $msg );
+ }
+ }
+ }
+}
diff --git a/tests/phpunit/includes/specials/SpecialRecentchanges.php b/tests/phpunit/includes/specials/SpecialRecentchangesTest.php
index a98e7c1a..2e4f4b09 100644
--- a/tests/phpunit/includes/specials/SpecialRecentchanges.php
+++ b/tests/phpunit/includes/specials/SpecialRecentchangesTest.php
@@ -2,9 +2,10 @@
/**
* Test class for SpecialRecentchanges class
*
- * Copyright © 2011, Ashar Voultoiz
+ * Copyright © 2011, Antoine Musso
*
- * @author Ashar Voultoiz
+ * @author Antoine Musso
+ * @group Database
*/
class SpecialRecentchangesTest extends MediaWikiTestCase {
@@ -18,13 +19,12 @@ class SpecialRecentchangesTest extends MediaWikiTestCase {
/** helper to test SpecialRecentchanges::buildMainQueryConds() */
private function assertConditions( $expected, $requestOptions = null, $message = '' ) {
- global $wgRequest;
- $savedGlobal = $wgRequest;
+ $context = new RequestContext;
+ $context->setRequest( new FauxRequest( $requestOptions ) );
- # Initialize a WebRequest object ...
- $wgRequest = new FauxRequest( $requestOptions );
- # ... then setup the rc object (which use wgRequest internally)
+ # setup the rc object
$this->rc = new SpecialRecentChanges();
+ $this->rc->setContext( $context );
$formOptions = $this->rc->setup( null );
# Filter out rc_timestamp conditions which depends on the test runtime
@@ -40,8 +40,6 @@ class SpecialRecentchangesTest extends MediaWikiTestCase {
$queryConditions,
$message
);
-
- $wgRequest = $savedGlobal;
}
/** return false if condition begin with 'rc_timestamp ' */
diff --git a/tests/phpunit/includes/specials/SpecialSearchTest.php b/tests/phpunit/includes/specials/SpecialSearchTest.php
new file mode 100644
index 00000000..ea9d5533
--- /dev/null
+++ b/tests/phpunit/includes/specials/SpecialSearchTest.php
@@ -0,0 +1,108 @@
+<?php
+/**
+ * Test class for SpecialSearch class
+ * Copyright © 2012, Antoine Musso
+ *
+ * @author Antoine Musso
+ * @group Database
+ */
+
+class SpecialSearchTest extends MediaWikiTestCase {
+ private $search;
+
+ function setUp() { }
+ function tearDown() { }
+
+ /**
+ * @covers SpecialSearch::load
+ * @dataProvider provideSearchOptionsTests
+ * @param $requested Array Request parameters. For example array( 'ns5' => true, 'ns6' => true). NULL to use default options.
+ * @param $userOptions Array User options to test with. For example array('searchNs5' => 1 );. NULL to use default options.
+ * @param $expectedProfile An expected search profile name
+ * @param $expectedNs Array Expected namespaces
+ */
+ function testProfileAndNamespaceLoading(
+ $requested, $userOptions, $expectedProfile, $expectedNS,
+ $message = 'Profile name and namespaces mismatches!'
+ ) {
+ $context = new RequestContext;
+ $context->setUser(
+ $this->newUserWithSearchNS( $userOptions )
+ );
+ /*
+ $context->setRequest( new FauxRequest( array(
+ 'ns5'=>true,
+ 'ns6'=>true,
+ ) ));
+ */
+ $context->setRequest( new FauxRequest( $requested ));
+ $search = new SpecialSearch();
+ $search->setContext( $context );
+ $search->load();
+
+ /**
+ * Verify profile name and namespace in the same assertion to make
+ * sure we will be able to fully compare the above code. PHPUnit stop
+ * after an assertion fail.
+ */
+ $this->assertEquals(
+ array( /** Expected: */
+ 'ProfileName' => $expectedProfile,
+ 'Namespaces' => $expectedNS,
+ )
+ , array( /** Actual: */
+ 'ProfileName' => $search->getProfile(),
+ 'Namespaces' => $search->getNamespaces(),
+ )
+ , $message
+ );
+
+ }
+
+ function provideSearchOptionsTests() {
+ $defaultNS = SearchEngine::defaultNamespaces();
+ $EMPTY_REQUEST = array();
+ $NO_USER_PREF = null;
+
+ return array(
+ /**
+ * Parameters:
+ * <Web Request>, <User options>
+ * Followed by expected values:
+ * <ProfileName>, <NSList>
+ * Then an optional message.
+ */
+ array(
+ $EMPTY_REQUEST, $NO_USER_PREF,
+ 'default', $defaultNS,
+ 'Bug 33270: No request nor user preferences should give default profile'
+ ),
+ array(
+ array( 'ns5' => 1 ), $NO_USER_PREF,
+ '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'
+ ),
+ );
+ }
+
+ /**
+ * Helper to create a new User object with given options
+ * User remains anonymous though
+ */
+ function newUserWithSearchNS( $opt = null ) {
+ $u = User::newFromId(0);
+ if( $opt === null ) {
+ return $u;
+ }
+ foreach($opt as $name => $value) {
+ $u->setOption( $name, $value );
+ }
+ return $u;
+ }
+}
+
diff --git a/tests/phpunit/includes/upload/UploadFromUrlTest.php b/tests/phpunit/includes/upload/UploadFromUrlTest.php
index 4722d408..d56cce31 100644
--- a/tests/phpunit/includes/upload/UploadFromUrlTest.php
+++ b/tests/phpunit/includes/upload/UploadFromUrlTest.php
@@ -20,7 +20,7 @@ class UploadFromUrlTest extends ApiTestCase {
}
}
- protected function doApiRequest( $params, $unused = null, $appendModule = false ) {
+ protected function doApiRequest( $params, $unused = null, $appendModule = false, $user = null ) {
$sessionId = session_id();
session_write_close();
@@ -36,7 +36,10 @@ class UploadFromUrlTest extends ApiTestCase {
* Ensure that the job queue is empty before continuing
*/
public function testClearQueue() {
- while ( $job = Job::pop() ) { }
+ $job = Job::pop();
+ while ( $job ) {
+ $job = Job::pop();
+ }
$this->assertFalse( $job );
}
@@ -73,7 +76,7 @@ class UploadFromUrlTest extends ApiTestCase {
* @depends testClearQueue
*/
public function testSetupUrlDownload( $data ) {
- $token = $this->user->editToken();
+ $token = $this->user->getEditToken();
$exception = false;
try {
@@ -147,7 +150,7 @@ class UploadFromUrlTest extends ApiTestCase {
* @depends testClearQueue
*/
public function testAsyncUpload( $data ) {
- $token = $this->user->editToken();
+ $token = $this->user->getEditToken();
$this->user->addGroup( 'users' );
@@ -166,7 +169,7 @@ class UploadFromUrlTest extends ApiTestCase {
* @depends testClearQueue
*/
public function testAsyncUploadWarning( $data ) {
- $token = $this->user->editToken();
+ $token = $this->user->getEditToken();
$this->user->addGroup( 'users' );
@@ -197,7 +200,7 @@ class UploadFromUrlTest extends ApiTestCase {
* @depends testClearQueue
*/
public function testSyncDownload( $data ) {
- $token = $this->user->editToken();
+ $token = $this->user->getEditToken();
$job = Job::pop();
$this->assertFalse( $job, 'Starting with an empty jobqueue' );
@@ -221,7 +224,7 @@ class UploadFromUrlTest extends ApiTestCase {
}
public function testLeaveMessage() {
- $token = $this->user->user->editToken();
+ $token = $this->user->user->getEditToken();
$talk = $this->user->user->getTalkPage();
if ( $talk->exists() ) {
@@ -274,7 +277,7 @@ class UploadFromUrlTest extends ApiTestCase {
return;
- /**
+ /*
// Broken until using leavemessage with ignorewarnings is supported
$job->run();
diff --git a/tests/phpunit/includes/upload/UploadStashTest.php b/tests/phpunit/includes/upload/UploadStashTest.php
index e644a259..c9dbb138 100644
--- a/tests/phpunit/includes/upload/UploadStashTest.php
+++ b/tests/phpunit/includes/upload/UploadStashTest.php
@@ -1,5 +1,7 @@
<?php
-
+/**
+ * @group Database
+ */
class UploadStashTest extends MediaWikiTestCase {
/**
* @var Array of UploadStashTestUser
@@ -8,49 +10,68 @@ class UploadStashTest extends MediaWikiTestCase {
public function setUp() {
parent::setUp();
-
+
// Setup a file for bug 29408
$this->bug29408File = dirname( __FILE__ ) . '/bug29408';
- file_put_contents( $this->bug29408File, "\x00" );
-
+ file_put_contents( $this->bug29408File, "\x00" );
+
self::$users = array(
'sysop' => new ApiTestUser(
'Uploadstashtestsysop',
'Upload Stash Test Sysop',
- 'upload_stash_test_sysop@sample.com',
+ 'upload_stash_test_sysop@example.com',
array( 'sysop' )
),
'uploader' => new ApiTestUser(
'Uploadstashtestuser',
'Upload Stash Test User',
- 'upload_stash_test_user@sample.com',
+ 'upload_stash_test_user@example.com',
array()
)
);
}
- /**
- * @group Database
- */
public function testBug29408() {
global $wgUser;
$wgUser = self::$users['uploader']->user;
-
+
$repo = RepoGroup::singleton()->getLocalRepo();
$stash = new UploadStash( $repo );
-
+
// Throws exception caught by PHPUnit on failure
$file = $stash->stashFile( $this->bug29408File );
// We'll never reach this point if we hit bug 29408
$this->assertTrue( true, 'Unrecognized file without extension' );
-
+
$stash->removeFile( $file->getFileKey() );
}
-
+
+ public function testValidRequest() {
+ $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( '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', 'wpSessionKey' => 'foo') );
+ $this->assertTrue( UploadFromStash::isValidRequest($request), 'Check key precedence' );
+ }
+
public function tearDown() {
parent::tearDown();
-
- unlink( $this->bug29408File . "." );
-
+
+ if( file_exists( $this->bug29408File . "." ) ) {
+ unlink( $this->bug29408File . "." );
+ }
+
+ if( file_exists( $this->bug29408File ) ) {
+ unlink( $this->bug29408File );
+ }
}
}
diff --git a/tests/phpunit/includes/upload/UploadTest.php b/tests/phpunit/includes/upload/UploadTest.php
index 69c29032..4293d23b 100644
--- a/tests/phpunit/includes/upload/UploadTest.php
+++ b/tests/phpunit/includes/upload/UploadTest.php
@@ -20,53 +20,14 @@ class UploadTest extends MediaWikiTestCase {
$wgHooks = $this->hooks;
}
- /**
- * Test various forms of valid and invalid titles that can be supplied.
- */
- public function testTitleValidation() {
-
-
- /* Test a valid title */
- $this->assertUploadTitleAndCode( 'ValidTitle.jpg',
- 'ValidTitle.jpg', UploadBase::OK,
- 'upload valid title' );
-
- /* A title with a slash */
- $this->assertUploadTitleAndCode( 'A/B.jpg',
- 'B.jpg', UploadBase::OK,
- 'upload title with slash' );
-
- /* A title with illegal char */
- $this->assertUploadTitleAndCode( 'A:B.jpg',
- 'A-B.jpg', UploadBase::OK,
- 'upload title with colon' );
-
- /* Stripping leading File: prefix */
- $this->assertUploadTitleAndCode( 'File:C.jpg',
- 'C.jpg', UploadBase::OK,
- 'upload title with File prefix' );
- /* Test illegal suggested title (r94601) */
- $this->assertUploadTitleAndCode( '%281%29.JPG',
- null, UploadBase::ILLEGAL_FILENAME,
- 'illegal title for upload' );
-
- /* A title without extension */
- $this->assertUploadTitleAndCode( 'A',
- null, UploadBase::FILETYPE_MISSING,
- 'upload title without extension' );
-
- /* A title with no basename */
- $this->assertUploadTitleAndCode( '.jpg',
- null, UploadBase::MIN_LENGTH_PARTNAME,
- 'upload title without basename' );
-
- }
/**
- * Helper function for testTitleValidation. First checks the return code
- * of UploadBase::getTitle() and then the actual returned titl
+ * First checks the return code
+ * of UploadBase::getTitle() and then the actual returned title
+ *
+ * @dataProvider dataTestTitleValidation
*/
- private function assertUploadTitleAndCode( $srcFilename, $dstFilename, $code, $msg ) {
+ public function testTitleValidation( $srcFilename, $dstFilename, $code, $msg ) {
/* Check the result code */
$this->assertEquals( $code,
$this->upload->testTitleValidation( $srcFilename ),
@@ -79,6 +40,41 @@ class UploadTest extends MediaWikiTestCase {
"$msg text" );
}
}
+
+ /**
+ * Test various forms of valid and invalid titles that can be supplied.
+ */
+ public function dataTestTitleValidation() {
+ return array(
+ /* Test a valid title */
+ array( 'ValidTitle.jpg', 'ValidTitle.jpg', UploadBase::OK,
+ 'upload valid title' ),
+ /* A title with a slash */
+ 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,
+ 'upload title with colon' ),
+ /* Stripping leading File: prefix */
+ 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,
+ 'illegal title for upload' ),
+ /* A title without extension */
+ array( 'A', null, UploadBase::FILETYPE_MISSING,
+ 'upload title without extension' ),
+ /* A title with no basename */
+ 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,
+ '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,
+ 'upload title longer than 240 bytes' ),
+ );
+ }
/**
* Test the upload verification functions
@@ -104,7 +100,7 @@ class UploadTest extends MediaWikiTestCase {
}
/**
- * test uploading a 100 bytes file with wgMaxUploadSize = 100
+ * test uploading a 100 bytes file with $wgMaxUploadSize = 100
*
* This method should be abstracted so we can test different settings.
*/
@@ -134,6 +130,7 @@ class UploadTestHandler extends UploadBase {
public function testTitleValidation( $name ) {
$this->mTitle = false;
$this->mDesiredDestName = $name;
+ $this->mTitleError = UploadBase::OK;
$this->getTitle();
return $this->mTitleError;
}
diff --git a/tests/phpunit/languages/LanguageAmTest.php b/tests/phpunit/languages/LanguageAmTest.php
new file mode 100644
index 00000000..3a648ded
--- /dev/null
+++ b/tests/phpunit/languages/LanguageAmTest.php
@@ -0,0 +1,33 @@
+<?php
+/**
+ * @author Santhosh Thottingal
+ * @copyright Copyright © 2012, Santhosh Thottingal
+ * @file
+ */
+
+/** Tests for MediaWiki languages/LanguageAm.php */
+class LanguageAmTest extends MediaWikiTestCase {
+ private $lang;
+
+ function setUp() {
+ $this->lang = Language::factory( 'Am' );
+ }
+ 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/LanguageArTest.php b/tests/phpunit/languages/LanguageArTest.php
new file mode 100644
index 00000000..b23e0534
--- /dev/null
+++ b/tests/phpunit/languages/LanguageArTest.php
@@ -0,0 +1,78 @@
+<?php
+/**
+ * Based on LanguagMlTest
+ * @file
+ */
+
+/** 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 ) );
+ }
+
+ /**
+ * Mostly to test the raw ascii feature.
+ * @dataProvider providerSprintfDate
+ */
+ function testSprintfDate( $format, $date, $expected ) {
+ $this->assertEquals( $expected, $this->lang->sprintfDate( $format, $date ) );
+ }
+
+ function providerSprintfDate() {
+ return array(
+ array(
+ 'xg "vs" g',
+ '20120102030410',
+ 'يناير vs ٣'
+ ),
+ array(
+ 'xmY',
+ '20120102030410',
+ '١٤٣٣'
+ ),
+ array(
+ 'xnxmY',
+ '20120102030410',
+ '1433'
+ ),
+ array(
+ 'xN xmj xmn xN xmY',
+ '20120102030410',
+ ' 7 2 ١٤٣٣'
+ ),
+ );
+ }
+ /** @dataProvider providePlural */
+ function testPlural( $result, $value ) {
+ $forms = array( 'zero', 'one', 'two', 'few', 'many', 'other' );
+ $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) );
+ }
+ function providePlural() {
+ return array (
+ array( 'zero', 0 ),
+ array( 'one', 1 ),
+ array( 'two', 2 ),
+ array( 'few', 3 ),
+ array( 'few', 9 ),
+ array( 'few', 110 ),
+ array( 'many', 11 ),
+ array( 'many', 15 ),
+ array( 'many', 99 ),
+ array( 'many', 9999 ),
+ array( 'other', 100 ),
+ array( 'other', 102 ),
+ array( 'other', 1000 ),
+ array( 'other', 1.7 ),
+ );
+ }
+}
diff --git a/tests/phpunit/languages/LanguageBeTest.php b/tests/phpunit/languages/LanguageBeTest.php
new file mode 100644
index 00000000..735ccc63
--- /dev/null
+++ b/tests/phpunit/languages/LanguageBeTest.php
@@ -0,0 +1,40 @@
+<?php
+/**
+ * @author Santhosh Thottingal
+ * @copyright Copyright © 2012, Santhosh Thottingal
+ * @file
+ */
+
+/** Tests for MediaWiki languages/LanguageBe.php */
+class LanguageBeTest extends MediaWikiTestCase {
+ private $lang;
+
+ function setUp() {
+ $this->lang = Language::factory( 'Be' );
+ }
+ function tearDown() {
+ unset( $this->lang );
+ }
+
+ /** @dataProvider providePlural */
+ function testPlural( $result, $value ) {
+ $forms = array( 'one', 'few', 'many', 'other' );
+ $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) );
+ }
+
+ 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( 'many', 5 ),
+ array( 'many', 15 ),
+ array( 'many', 120 ),
+ );
+ }
+}
diff --git a/tests/phpunit/languages/LanguageBe_taraskTest.php b/tests/phpunit/languages/LanguageBe_taraskTest.php
index e7fdb7ca..765cdb8f 100644
--- a/tests/phpunit/languages/LanguageBe_taraskTest.php
+++ b/tests/phpunit/languages/LanguageBe_taraskTest.php
@@ -27,4 +27,39 @@ class LanguageBeTaraskTest extends MediaWikiTestCase {
function testDoesNotCommafyFourDigitsNumber() {
$this->assertEquals( '1234', $this->lang->commafy( '1234' ) );
}
+ /** @dataProvider providePluralFourForms */
+ function testPluralFourForms( $result, $value ) {
+ $forms = array( 'one', 'few', 'many', 'other' );
+ $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) );
+ }
+
+ function providePluralFourForms() {
+ 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( '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 ) );
+ }
+ function providePluralTwoForms() {
+ return array (
+ array( 'one', 1 ),
+ array( 'several', 11 ),
+ array( 'several', 91 ),
+ array( 'several', 121 ),
+ );
+ }
+
}
diff --git a/tests/phpunit/languages/LanguageBhTest.php b/tests/phpunit/languages/LanguageBhTest.php
new file mode 100644
index 00000000..e1e2a13e
--- /dev/null
+++ b/tests/phpunit/languages/LanguageBhTest.php
@@ -0,0 +1,34 @@
+<?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/LanguageBsTest.php b/tests/phpunit/languages/LanguageBsTest.php
new file mode 100644
index 00000000..b6631c03
--- /dev/null
+++ b/tests/phpunit/languages/LanguageBsTest.php
@@ -0,0 +1,41 @@
+<?php
+/**
+ * @author Santhosh Thottingal
+ * @copyright Copyright © 2012, Santhosh Thottingal
+ * @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 );
+ }
+
+ /** @dataProvider providePlural */
+ function testPlural( $result, $value ) {
+ $forms = array( 'one', 'few', 'many', 'other' );
+ $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) );
+ }
+
+ function providePlural() {
+ return array (
+ array( 'many', 0 ),
+ array( 'one', 1 ),
+ array( 'few', 2 ),
+ array( 'few', 4 ),
+ array( 'many', 5 ),
+ array( 'many', 11 ),
+ array( 'many', 20 ),
+ array( 'one', 21 ),
+ array( 'few', 24 ),
+ array( 'many', 25 ),
+ array( 'many', 200 ),
+ );
+ }
+
+}
diff --git a/tests/phpunit/languages/LanguageCsTest.php b/tests/phpunit/languages/LanguageCsTest.php
new file mode 100644
index 00000000..dda29f9a
--- /dev/null
+++ b/tests/phpunit/languages/LanguageCsTest.php
@@ -0,0 +1,40 @@
+<?php
+/**
+ * @author Santhosh Thottingal
+ * @copyright Copyright © 2012, Santhosh Thottingal
+ * @file
+ */
+
+/** 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 );
+ }
+
+ /** @dataProvider providerPlural */
+ function testPlural( $result, $value ) {
+ $forms = array( 'one', 'few', 'other' );
+ $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) );
+ }
+
+ function providerPlural() {
+ return array (
+ array( 'other', 0 ),
+ array( 'one', 1 ),
+ array( 'few', 2 ),
+ array( 'few', 3 ),
+ array( 'few', 4 ),
+ array( 'other', 5 ),
+ array( 'other', 11 ),
+ array( 'other', 20 ),
+ array( 'other', 25 ),
+ array( 'other', 200 ),
+ );
+ }
+
+}
diff --git a/tests/phpunit/languages/LanguageCuTest.php b/tests/phpunit/languages/LanguageCuTest.php
new file mode 100644
index 00000000..f8186d7b
--- /dev/null
+++ b/tests/phpunit/languages/LanguageCuTest.php
@@ -0,0 +1,41 @@
+<?php
+/**
+ * @author Santhosh Thottingal
+ * @copyright Copyright © 2012, Santhosh Thottingal
+ * @file
+ */
+
+/** Tests for MediaWiki languages/LanguageCu.php */
+class LanguageCuTest extends MediaWikiTestCase {
+ private $lang;
+
+ function setUp() {
+ $this->lang = Language::factory( 'cu' );
+ }
+ function tearDown() {
+ unset( $this->lang );
+ }
+
+ /** @dataProvider providerPlural */
+ function testPlural( $result, $value ) {
+ $forms = array( 'one', 'few', 'many', 'other' );
+ $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) );
+ }
+
+ function providerPlural() {
+ return array (
+ array( 'other', 0 ),
+ array( 'one', 1 ),
+ array( 'few', 2 ),
+ array( 'many', 3 ),
+ array( 'many', 4 ),
+ array( 'other', 5 ),
+ array( 'one', 11 ),
+ array( 'other', 20 ),
+ array( 'few', 22 ),
+ array( 'many', 223 ),
+ array( 'other', 200 ),
+ );
+ }
+
+}
diff --git a/tests/phpunit/languages/LanguageCyTest.php b/tests/phpunit/languages/LanguageCyTest.php
new file mode 100644
index 00000000..e9f9e410
--- /dev/null
+++ b/tests/phpunit/languages/LanguageCyTest.php
@@ -0,0 +1,42 @@
+<?php
+/**
+ * @author Santhosh Thottingal
+ * @copyright Copyright © 2012, Santhosh Thottingal
+ * @file
+ */
+
+/** 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 );
+ }
+
+ /** @dataProvider providerPlural */
+ function testPlural( $result, $value ) {
+ $forms = array( 'zero', 'one', 'two', 'few', 'many', 'other' );
+ $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) );
+ }
+
+ function providerPlural() {
+ return array (
+ array( 'zero', 0 ),
+ array( 'one', 1 ),
+ array( 'two', 2 ),
+ array( 'few', 3 ),
+ array( 'many', 6 ),
+ array( 'other', 4 ),
+ array( 'other', 5 ),
+ array( 'other', 11 ),
+ array( 'other', 20 ),
+ array( 'other', 22 ),
+ array( 'other', 223 ),
+ array( 'other', 200.00 ),
+ );
+ }
+
+}
diff --git a/tests/phpunit/languages/LanguageDsbTest.php b/tests/phpunit/languages/LanguageDsbTest.php
new file mode 100644
index 00000000..ab7f9313
--- /dev/null
+++ b/tests/phpunit/languages/LanguageDsbTest.php
@@ -0,0 +1,40 @@
+<?php
+/**
+ * @author Santhosh Thottingal
+ * @copyright Copyright © 2012, Santhosh Thottingal
+ * @file
+ */
+
+/** 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 );
+ }
+
+ /** @dataProvider providePlural */
+ function testPlural( $result, $value ) {
+ $forms = array( 'one', 'two', 'few', 'other' );
+ $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) );
+ }
+
+ function providePlural() {
+ return array (
+ array( 'other', 0 ),
+ array( 'one', 1 ),
+ array( 'one', 101 ),
+ array( 'one', 90001 ),
+ array( 'two', 2 ),
+ array( 'few', 3 ),
+ array( 'few', 203 ),
+ array( 'few', 4 ),
+ array( 'other', 99 ),
+ array( 'other', 555 ),
+ );
+ }
+
+}
diff --git a/tests/phpunit/languages/LanguageFrTest.php b/tests/phpunit/languages/LanguageFrTest.php
new file mode 100644
index 00000000..8538744e
--- /dev/null
+++ b/tests/phpunit/languages/LanguageFrTest.php
@@ -0,0 +1,34 @@
+<?php
+/**
+ * @author Santhosh Thottingal
+ * @copyright Copyright © 2012, Santhosh Thottingal
+ * @file
+ */
+
+/** 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 );
+ }
+
+ /** @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/LanguageGaTest.php b/tests/phpunit/languages/LanguageGaTest.php
new file mode 100644
index 00000000..fbd9f11d
--- /dev/null
+++ b/tests/phpunit/languages/LanguageGaTest.php
@@ -0,0 +1,34 @@
+<?php
+/**
+ * @author Santhosh Thottingal
+ * @copyright Copyright © 2012, Santhosh Thottingal
+ * @file
+ */
+
+/** 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 );
+ }
+
+ /** @dataProvider providerPlural */
+ function testPlural( $result, $value ) {
+ $forms = array( 'one', 'two', 'other' );
+ $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) );
+ }
+
+ function providerPlural() {
+ 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
new file mode 100644
index 00000000..24574bda
--- /dev/null
+++ b/tests/phpunit/languages/LanguageGdTest.php
@@ -0,0 +1,38 @@
+<?php
+/**
+ * @author Santhosh Thottingal
+ * @copyright Copyright © 2012, Santhosh Thottingal
+ * @file
+ */
+
+/** Tests for MediaWiki languages/classes/LanguageGd.php */
+class LanguageGdTest extends MediaWikiTestCase {
+ private $lang;
+
+ function setUp() {
+ $this->lang = Language::factory( 'gd' );
+ }
+ function tearDown() {
+ unset( $this->lang );
+ }
+
+ /** @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 ) );
+ }
+ 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 ),
+ );
+ }
+
+}
diff --git a/tests/phpunit/languages/LanguageGvTest.php b/tests/phpunit/languages/LanguageGvTest.php
new file mode 100644
index 00000000..3d298b9b
--- /dev/null
+++ b/tests/phpunit/languages/LanguageGvTest.php
@@ -0,0 +1,39 @@
+<?php
+/**
+ * @author Santhosh Thottingal
+ * @copyright Copyright © 2012, 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 );
+ }
+
+ /** @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 ) );
+ }
+ 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 ),
+ );
+ }
+
+}
diff --git a/tests/phpunit/languages/LanguageHeTest.php b/tests/phpunit/languages/LanguageHeTest.php
new file mode 100644
index 00000000..9ac0f952
--- /dev/null
+++ b/tests/phpunit/languages/LanguageHeTest.php
@@ -0,0 +1,48 @@
+<?php
+/**
+ * @author Amir E. Aharoni
+ * @copyright Copyright © 2012, Amir E. Aharoni
+ * @file
+ */
+
+/** Tests for MediaWiki languages/classes/LanguageHe.php */
+class LanguageHeTest extends MediaWikiTestCase {
+ private $lang;
+
+ function setUp() {
+ $this->lang = Language::factory( 'he' );
+ }
+ function tearDown() {
+ unset( $this->lang );
+ }
+
+ /** @dataProvider providerPluralDual */
+ function testPluralDual( $result, $value ) {
+ $forms = array( 'one', 'many', 'two' );
+ $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) );
+ }
+
+ function providerPluralDual() {
+ return array (
+ array( 'many', 0 ), // Zero -> plural
+ array( 'one', 1 ), // Singular
+ array( 'two', 2 ), // Dual
+ array( 'many', 3 ), // Plural
+ );
+ }
+
+ /** @dataProvider providerPlural */
+ function testPlural( $result, $value ) {
+ $forms = array( 'one', 'many' );
+ $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) );
+ }
+
+ function providerPlural() {
+ return array (
+ array( 'many', 0 ), // Zero -> plural
+ array( 'one', 1 ), // Singular
+ array( 'many', 2 ), // Plural, no dual provided
+ array( 'many', 3 ), // Plural
+ );
+ }
+}
diff --git a/tests/phpunit/languages/LanguageHiTest.php b/tests/phpunit/languages/LanguageHiTest.php
new file mode 100644
index 00000000..ead9e020
--- /dev/null
+++ b/tests/phpunit/languages/LanguageHiTest.php
@@ -0,0 +1,34 @@
+<?php
+/**
+ * @author Santhosh Thottingal
+ * @copyright Copyright © 2012, Santhosh Thottingal
+ * @file
+ */
+
+/** Tests for MediaWiki languages/LanguageHi.php */
+class LanguageHiTest extends MediaWikiTestCase {
+ private $lang;
+
+ function setUp() {
+ $this->lang = Language::factory( 'Hi' );
+ }
+ 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/LanguageHrTest.php b/tests/phpunit/languages/LanguageHrTest.php
new file mode 100644
index 00000000..4f1c66bf
--- /dev/null
+++ b/tests/phpunit/languages/LanguageHrTest.php
@@ -0,0 +1,41 @@
+<?php
+/**
+ * @author Santhosh Thottingal
+ * @copyright Copyright © 2012, Santhosh Thottingal
+ * @file
+ */
+
+/** 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 );
+ }
+
+ /** @dataProvider providerPlural */
+ function testPlural( $result, $value ) {
+ $forms = array( 'one', 'few', 'many', 'other' );
+ $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) );
+ }
+
+ function providerPlural() {
+ return array (
+ array( 'many', 0 ),
+ array( 'one', 1 ),
+ array( 'few', 2 ),
+ array( 'few', 4 ),
+ array( 'many', 5 ),
+ array( 'many', 11 ),
+ array( 'many', 20 ),
+ array( 'one', 21 ),
+ array( 'few', 24 ),
+ array( 'many', 25 ),
+ array( 'many', 200 ),
+ );
+ }
+
+}
diff --git a/tests/phpunit/languages/LanguageHsbTest.php b/tests/phpunit/languages/LanguageHsbTest.php
new file mode 100644
index 00000000..803c7721
--- /dev/null
+++ b/tests/phpunit/languages/LanguageHsbTest.php
@@ -0,0 +1,40 @@
+<?php
+/**
+ * @author Santhosh Thottingal
+ * @copyright Copyright © 2012, Santhosh Thottingal
+ * @file
+ */
+
+/** 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 );
+ }
+
+ /** @dataProvider providePlural */
+ function testPlural( $result, $value ) {
+ $forms = array( 'one', 'two', 'few', 'other' );
+ $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) );
+ }
+
+ function providePlural() {
+ return array (
+ array( 'other', 0 ),
+ array( 'one', 1 ),
+ array( 'one', 101 ),
+ array( 'one', 90001 ),
+ array( 'two', 2 ),
+ array( 'few', 3 ),
+ array( 'few', 203 ),
+ array( 'few', 4 ),
+ array( 'other', 99 ),
+ array( 'other', 555 ),
+ );
+ }
+
+}
diff --git a/tests/phpunit/languages/LanguageHyTest.php b/tests/phpunit/languages/LanguageHyTest.php
new file mode 100644
index 00000000..7990bdfc
--- /dev/null
+++ b/tests/phpunit/languages/LanguageHyTest.php
@@ -0,0 +1,34 @@
+<?php
+/**
+ * @author Santhosh Thottingal
+ * @copyright Copyright © 2012, Santhosh Thottingal
+ * @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 );
+ }
+
+ /** @dataProvider providerPlural */
+ function testPlural( $result, $value ) {
+ $forms = array( 'one', 'other' );
+ $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) );
+ }
+
+ function providerPlural() {
+ 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
new file mode 100644
index 00000000..ab889464
--- /dev/null
+++ b/tests/phpunit/languages/LanguageKshTest.php
@@ -0,0 +1,34 @@
+<?php
+/**
+ * @author Santhosh Thottingal
+ * @copyright Copyright © 2012, Santhosh Thottingal
+ * @file
+ */
+
+/** 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 );
+ }
+
+ /** @dataProvider providerPlural */
+ function testPlural( $result, $value ) {
+ $forms = array( 'one', 'other', 'zero' );
+ $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) );
+ }
+
+ function providerPlural() {
+ 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
new file mode 100644
index 00000000..0fd9167e
--- /dev/null
+++ b/tests/phpunit/languages/LanguageLnTest.php
@@ -0,0 +1,34 @@
+<?php
+/**
+ * @author Santhosh Thottingal
+ * @copyright Copyright © 2012, Santhosh Thottingal
+ * @file
+ */
+
+/** 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 );
+ }
+
+ /** @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/LanguageLtTest.php b/tests/phpunit/languages/LanguageLtTest.php
new file mode 100644
index 00000000..0d7c7d3e
--- /dev/null
+++ b/tests/phpunit/languages/LanguageLtTest.php
@@ -0,0 +1,53 @@
+<?php
+/**
+ * @author Santhosh Thottingal
+ * @copyright Copyright © 2012, Santhosh Thottingal
+ * @file
+ */
+
+/** Tests for MediaWiki languages/LanguageLt.php */
+class LanguageLtTest extends MediaWikiTestCase {
+ private $lang;
+
+ function setUp() {
+ $this->lang = Language::factory( 'Lt' );
+ }
+ function tearDown() {
+ unset( $this->lang );
+ }
+
+ /** @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 ) );
+ }
+
+ function provideOneFewOtherCases() {
+ return array (
+ array( 'other', 0 ),
+ array( 'one', 1 ),
+ array( 'few', 2 ),
+ array( 'few', 9 ),
+ array( 'other', 10 ),
+ array( 'other', 11 ),
+ array( 'other', 20 ),
+ array( 'one', 21 ),
+ array( 'few', 32 ),
+ array( 'one', 41 ),
+ array( 'one', 40001 ),
+ );
+ }
+
+ function provideOneFewCases() {
+ return array (
+ array( 'one', 1 ),
+ array( 'few', 15 ),
+ );
+ }
+}
diff --git a/tests/phpunit/languages/LanguageLvTest.php b/tests/phpunit/languages/LanguageLvTest.php
new file mode 100644
index 00000000..0636da5f
--- /dev/null
+++ b/tests/phpunit/languages/LanguageLvTest.php
@@ -0,0 +1,39 @@
+<?php
+/**
+ * @author Santhosh Thottingal
+ * @copyright Copyright © 2012, Santhosh Thottingal
+ * @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 );
+ }
+
+ /** @dataProvider providerPlural */
+ function testPlural( $result, $value ) {
+ $forms = array( 'one', 'other' );
+ $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) );
+ }
+
+ function providerPlural() {
+ return array (
+ array( 'other', 0 ), #this must be zero form as per CLDR
+ array( 'one', 1 ),
+ array( 'other', 11 ),
+ array( 'one', 21 ),
+ array( 'other', 411 ),
+ array( 'other', 12.345 ),
+ array( 'other', 20 ),
+ array( 'one', 31 ),
+ array( 'other', 200 ),
+ );
+ }
+
+}
diff --git a/tests/phpunit/languages/LanguageMgTest.php b/tests/phpunit/languages/LanguageMgTest.php
new file mode 100644
index 00000000..06b56547
--- /dev/null
+++ b/tests/phpunit/languages/LanguageMgTest.php
@@ -0,0 +1,35 @@
+<?php
+/**
+ * @author Santhosh Thottingal
+ * @copyright Copyright © 2012, Santhosh Thottingal
+ * @file
+ */
+
+/** 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 );
+ }
+
+ /** @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 ),
+ array( 'other', 123.3434 ),
+ );
+ }
+
+}
diff --git a/tests/phpunit/languages/LanguageMkTest.php b/tests/phpunit/languages/LanguageMkTest.php
new file mode 100644
index 00000000..cf5ec3d9
--- /dev/null
+++ b/tests/phpunit/languages/LanguageMkTest.php
@@ -0,0 +1,41 @@
+<?php
+/**
+ * @author Santhosh Thottingal
+ * @copyright Copyright © 2012, Santhosh Thottingal
+ * @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 );
+ }
+
+ /** @dataProvider providerPlural */
+ function testPlural( $result, $value ) {
+ $forms = array( 'one', 'other' );
+ $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) );
+ }
+
+
+ function providerPlural() {
+ return array (
+ array( 'other', 0 ),
+ array( 'one', 1 ),
+ array( 'other', 11 ),
+ array( 'one', 21 ),
+ array( 'other', 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
new file mode 100644
index 00000000..8c4b0b2f
--- /dev/null
+++ b/tests/phpunit/languages/LanguageMlTest.php
@@ -0,0 +1,43 @@
+<?php
+/**
+ * @author Santhosh Thottingal
+ * @copyright Copyright © 2011, Santhosh Thottingal
+ * @file
+ */
+
+/** Tests for MediaWiki languages/LanguageMl.php */
+class LanguageMlTest extends MediaWikiTestCase {
+ private $lang;
+
+ 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 ) );
+ }
+
+ function providerFormatNum() {
+ return array(
+ array( '12,34,567', '1234567' ),
+ array( '12,345', '12345' ),
+ array( '1', '1' ),
+ array( '123', '123' ) ,
+ array( '1,234', '1234' ),
+ array( '12,345.56', '12345.56' ),
+ array( '12,34,56,79,81,23,45,678', '12345679812345678' ),
+ array( '.12345', '.12345' ),
+ array( '-12,00,000', '-1200000' ),
+ array( '-98', '-98' ),
+ array( '-98', -98 ),
+ array( '-1,23,45,678', -12345678 ),
+ array( '', '' ),
+ array( '', null ),
+ );
+ }
+}
diff --git a/tests/phpunit/languages/LanguageMoTest.php b/tests/phpunit/languages/LanguageMoTest.php
new file mode 100644
index 00000000..533e590f
--- /dev/null
+++ b/tests/phpunit/languages/LanguageMoTest.php
@@ -0,0 +1,43 @@
+<?php
+/**
+ * @author Santhosh Thottingal
+ * @copyright Copyright © 2012, Santhosh Thottingal
+ * @file
+ */
+
+/** 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 );
+ }
+
+ /** @dataProvider providerPlural */
+ function testPlural( $result, $value ) {
+ $forms = array( 'one', 'few', 'other' );
+ $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) );
+ }
+
+ function providerPlural() {
+ 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( 'other', 120 ),
+ array( 'other', 200 ),
+ array( 'few', 201 ),
+ array( 'few', 219 ),
+ array( 'other', 220 ),
+ );
+ }
+}
diff --git a/tests/phpunit/languages/LanguageMtTest.php b/tests/phpunit/languages/LanguageMtTest.php
new file mode 100644
index 00000000..421bb388
--- /dev/null
+++ b/tests/phpunit/languages/LanguageMtTest.php
@@ -0,0 +1,72 @@
+<?php
+/**
+ * @author Amir E. Aharoni
+ * @copyright Copyright © 2012, Amir E. Aharoni
+ * @file
+ */
+
+/** 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 );
+ }
+
+ /** @dataProvider providerPluralAllForms */
+ function testPluralAllForms( $result, $value ) {
+ $forms = array( 'one', 'few', 'many', 'other' );
+ $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) );
+ }
+
+ function providerPluralAllForms() {
+ 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( 'other', 120 ),
+ array( 'other', 201 ),
+ );
+ }
+
+ /** @dataProvider providerPluralTwoForms */
+ function testPluralTwoForms( $result, $value ) {
+ $forms = array( 'one', 'many' );
+ $this->assertEquals( $result, $this->lang->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 ),
+ );
+ }
+}
diff --git a/tests/phpunit/languages/LanguageNlTest.php b/tests/phpunit/languages/LanguageNlTest.php
new file mode 100644
index 00000000..cf979cd2
--- /dev/null
+++ b/tests/phpunit/languages/LanguageNlTest.php
@@ -0,0 +1,28 @@
+<?php
+/**
+ * @author Santhosh Thottingal
+ * @copyright Copyright © 2011, Santhosh Thottingal
+ * @file
+ */
+
+/** Tests for MediaWiki languages/LanguageNl.php */
+class LanguageNlTest extends MediaWikiTestCase {
+ private $lang;
+
+ 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' ) );
+ }
+}
diff --git a/tests/phpunit/languages/LanguageNsoTest.php b/tests/phpunit/languages/LanguageNsoTest.php
new file mode 100644
index 00000000..ea393628
--- /dev/null
+++ b/tests/phpunit/languages/LanguageNsoTest.php
@@ -0,0 +1,32 @@
+<?php
+/**
+ * @author Amir E. Aharoni
+ * @copyright Copyright © 2012, Amir E. Aharoni
+ * @file
+ */
+
+/** 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 );
+ }
+
+ /** @dataProvider providerPlural */
+ function testPlural( $result, $value ) {
+ $forms = array( 'one', 'many' );
+ $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) );
+ }
+
+ function providerPlural() {
+ return array (
+ array( 'one', 0 ),
+ array( 'one', 1 ),
+ array( 'many', 2 ),
+ );
+ }
+}
diff --git a/tests/phpunit/languages/LanguagePlTest.php b/tests/phpunit/languages/LanguagePlTest.php
new file mode 100644
index 00000000..e56d4b77
--- /dev/null
+++ b/tests/phpunit/languages/LanguagePlTest.php
@@ -0,0 +1,72 @@
+<?php
+/**
+ * @author Amir E. Aharoni
+ * @copyright Copyright © 2012, Amir E. Aharoni
+ * @file
+ */
+
+/** 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 );
+ }
+
+ /** @dataProvider providerPluralFourForms */
+ function testPluralFourForms( $result, $value ) {
+ $forms = array( 'one', 'few', 'many' );
+ $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) );
+ }
+
+ 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 ),
+ );
+ }
+
+ /** @dataProvider providerPlural */
+ function testPlural( $result, $value ) {
+ $forms = array( 'one', 'many' );
+ $this->assertEquals( $result, $this->lang->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 ),
+ );
+ }
+}
diff --git a/tests/phpunit/languages/LanguageRoTest.php b/tests/phpunit/languages/LanguageRoTest.php
new file mode 100644
index 00000000..5270f6fe
--- /dev/null
+++ b/tests/phpunit/languages/LanguageRoTest.php
@@ -0,0 +1,43 @@
+<?php
+/**
+ * @author Amir E. Aharoni
+ * @copyright Copyright © 2012, Amir E. Aharoni
+ * @file
+ */
+
+/** 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 );
+ }
+
+ /** @dataProvider providerPlural */
+ function testPlural( $result, $value ) {
+ $forms = array( 'one', 'few', 'other' );
+ $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) );
+ }
+
+ function providerPlural() {
+ 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( 'other', 120 ),
+ array( 'other', 200 ),
+ array( 'few', 201 ),
+ array( 'few', 219 ),
+ array( 'other', 220 ),
+ );
+ }
+}
diff --git a/tests/phpunit/languages/LanguageRuTest.php b/tests/phpunit/languages/LanguageRuTest.php
new file mode 100644
index 00000000..7a1f193b
--- /dev/null
+++ b/tests/phpunit/languages/LanguageRuTest.php
@@ -0,0 +1,54 @@
+<?php
+/**
+ * @author Amir E. Aharoni
+ * based on LanguageBe_tarask.php
+ * @copyright Copyright © 2012, Amir E. Aharoni
+ * @file
+ */
+
+/** Tests for MediaWiki languages/classes/LanguageRu.php */
+class LanguageRuTest extends MediaWikiTestCase {
+ private $lang;
+
+ function setUp() {
+ $this->lang = Language::factory( 'ru' );
+ }
+ function tearDown() {
+ unset( $this->lang );
+ }
+
+ /** @dataProvider providePluralFourForms */
+ function testPluralFourForms( $result, $value ) {
+ $forms = array( 'one', 'few', 'many', 'other' );
+ $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) );
+ }
+
+ function providePluralFourForms() {
+ 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( '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 ) );
+ }
+ function providePluralTwoForms() {
+ return array (
+ array( 'one', 1 ),
+ array( 'several', 11 ),
+ array( 'several', 91 ),
+ array( 'several', 121 ),
+ );
+ }
+}
diff --git a/tests/phpunit/languages/LanguageSeTest.php b/tests/phpunit/languages/LanguageSeTest.php
new file mode 100644
index 00000000..065ec29e
--- /dev/null
+++ b/tests/phpunit/languages/LanguageSeTest.php
@@ -0,0 +1,48 @@
+<?php
+/**
+ * @author Amir E. Aharoni
+ * @copyright Copyright © 2012, Amir E. Aharoni
+ * @file
+ */
+
+/** 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 );
+ }
+
+ /** @dataProvider providerPluralThreeForms */
+ function testPluralThreeForms( $result, $value ) {
+ $forms = array( 'one', 'two', 'other' );
+ $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) );
+ }
+
+ function providerPluralThreeForms() {
+ return array (
+ array( 'other', 0 ),
+ array( 'one', 1 ),
+ array( 'two', 2 ),
+ array( 'other', 3 ),
+ );
+ }
+
+ /** @dataProvider providerPlural */
+ function testPlural( $result, $value ) {
+ $forms = array( 'one', 'other' );
+ $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) );
+ }
+
+ function providerPlural() {
+ return array (
+ array( 'other', 0 ),
+ array( 'one', 1 ),
+ array( 'other', 2 ),
+ array( 'other', 3 ),
+ );
+ }
+}
diff --git a/tests/phpunit/languages/LanguageSgsTest.php b/tests/phpunit/languages/LanguageSgsTest.php
new file mode 100644
index 00000000..931c82f0
--- /dev/null
+++ b/tests/phpunit/languages/LanguageSgsTest.php
@@ -0,0 +1,66 @@
+<?php
+/**
+ * @author Amir E. Aharoni
+ * @copyright Copyright © 2012, Amir E. Aharoni
+ * @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 );
+ }
+
+ /** @dataProvider providePluralAllForms */
+ function testPluralAllForms( $result, $value ) {
+ $forms = array( 'one', 'few', 'many', 'other' );
+ $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) );
+ }
+
+ function providePluralAllForms() {
+ return array (
+ array( 'many', 0 ),
+ array( 'one', 1 ),
+ array( 'few', 2 ),
+ array( 'other', 3 ),
+ array( 'many', 10 ),
+ array( 'many', 11 ),
+ array( 'many', 12 ),
+ array( 'many', 19 ),
+ array( 'other', 20 ),
+ array( 'many', 100 ),
+ array( 'one', 101 ),
+ array( 'many', 111 ),
+ array( 'many', 112 ),
+ );
+ }
+
+ /** @dataProvider providePluralTwoForms */
+ function testPluralTwoForms( $result, $value ) {
+ $forms = array( 'one', 'other' );
+ $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) );
+ }
+
+ function providePluralTwoForms() {
+ return array (
+ array( 'other', 0 ),
+ array( 'one', 1 ),
+ array( 'other', 2 ),
+ array( 'other', 3 ),
+ array( 'other', 10 ),
+ array( 'other', 11 ),
+ array( 'other', 12 ),
+ array( 'other', 19 ),
+ array( 'other', 20 ),
+ array( 'other', 100 ),
+ array( 'one', 101 ),
+ array( 'other', 111 ),
+ array( 'other', 112 ),
+ );
+ }
+}
diff --git a/tests/phpunit/languages/LanguageShTest.php b/tests/phpunit/languages/LanguageShTest.php
new file mode 100644
index 00000000..b8169aed
--- /dev/null
+++ b/tests/phpunit/languages/LanguageShTest.php
@@ -0,0 +1,32 @@
+<?php
+/**
+ * @author Amir E. Aharoni
+ * @copyright Copyright © 2012, Amir E. Aharoni
+ * @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 );
+ }
+
+ /** @dataProvider providerPlural */
+ function testPlural( $result, $value ) {
+ $forms = array( 'one', 'many' );
+ $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) );
+ }
+
+ function providerPlural() {
+ return array (
+ array( 'many', 0 ),
+ array( 'one', 1 ),
+ array( 'many', 2 ),
+ );
+ }
+}
diff --git a/tests/phpunit/languages/LanguageSkTest.php b/tests/phpunit/languages/LanguageSkTest.php
new file mode 100644
index 00000000..4cfd840e
--- /dev/null
+++ b/tests/phpunit/languages/LanguageSkTest.php
@@ -0,0 +1,40 @@
+<?php
+/**
+ * @author Santhosh Thottingal
+ * @copyright Copyright © 2012, Amir E. Aharoni
+ * based on LanguageSkTest.php
+ * @file
+ */
+
+/** 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 );
+ }
+
+ /** @dataProvider providerPlural */
+ function testPlural( $result, $value ) {
+ $forms = array( 'one', 'few', 'other' );
+ $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) );
+ }
+
+ function providerPlural() {
+ return array (
+ array( 'other', 0 ),
+ array( 'one', 1 ),
+ array( 'few', 2 ),
+ array( 'few', 3 ),
+ array( 'few', 4 ),
+ array( 'other', 5 ),
+ array( 'other', 11 ),
+ array( 'other', 20 ),
+ array( 'other', 25 ),
+ array( 'other', 200 ),
+ );
+ }
+}
diff --git a/tests/phpunit/languages/LanguageSlTest.php b/tests/phpunit/languages/LanguageSlTest.php
new file mode 100644
index 00000000..c1f75691
--- /dev/null
+++ b/tests/phpunit/languages/LanguageSlTest.php
@@ -0,0 +1,42 @@
+<?php
+/**
+ * @author Santhosh Thottingal
+ * @copyright Copyright © 2012, Amir E. Aharoni
+ * based on LanguageSkTest.php
+ * @file
+ */
+
+/** 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 );
+ }
+
+ /** @dataProvider providerPlural */
+ function testPlural( $result, $value ) {
+ $forms = array( 'one', 'two', 'few', 'other', 'zero' );
+ $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) );
+ }
+
+ function providerPlural() {
+ return array (
+ array( 'zero', 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 ),
+ );
+ }
+}
diff --git a/tests/phpunit/languages/LanguageSmaTest.php b/tests/phpunit/languages/LanguageSmaTest.php
new file mode 100644
index 00000000..b7e72e97
--- /dev/null
+++ b/tests/phpunit/languages/LanguageSmaTest.php
@@ -0,0 +1,48 @@
+<?php
+/**
+ * @author Amir E. Aharoni
+ * @copyright Copyright © 2012, Amir E. Aharoni
+ * @file
+ */
+
+/** 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 );
+ }
+
+ /** @dataProvider providerPluralThreeForms */
+ function testPluralThreeForms( $result, $value ) {
+ $forms = array( 'one', 'two', 'other' );
+ $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) );
+ }
+
+ function providerPluralThreeForms() {
+ return array (
+ array( 'other', 0 ),
+ array( 'one', 1 ),
+ array( 'two', 2 ),
+ array( 'other', 3 ),
+ );
+ }
+
+ /** @dataProvider providerPlural */
+ function testPlural( $result, $value ) {
+ $forms = array( 'one', 'other' );
+ $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) );
+ }
+
+ function providerPlural() {
+ return array (
+ array( 'other', 0 ),
+ array( 'one', 1 ),
+ array( 'other', 2 ),
+ array( 'other', 3 ),
+ );
+ }
+}
diff --git a/tests/phpunit/languages/LanguageSrTest.php b/tests/phpunit/languages/LanguageSrTest.php
new file mode 100644
index 00000000..a50547c6
--- /dev/null
+++ b/tests/phpunit/languages/LanguageSrTest.php
@@ -0,0 +1,199 @@
+<?php
+/**
+ * PHPUnit tests for the Serbian language.
+ * The language can be represented using two scripts:
+ * - Latin (SR_el)
+ * - Cyrillic (SR_ec)
+ * Both representations seems to be bijective, hence MediaWiki can convert
+ * from one script to the other.
+ *
+ * @author Antoine Musso <hashar at free dot fr>
+ * @copyright Copyright © 2011, Antoine Musso <hashar at free dot fr>
+ * @file
+ */
+
+require_once dirname( dirname( __FILE__ ) ) . '/bootstrap.php';
+
+/** Tests for MediaWiki languages/LanguageTr.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( ) {
+ $this->assertCyrillic(
+ 'шђчћжШЂЧЋЖ',
+ 'Cyrillic guessing characters'
+ );
+ $this->assertLatin(
+ 'šđč枊ĐČĆŽ',
+ 'Latin guessing characters'
+ );
+ }
+
+ function testMixedConversions() {
+ $this->assertCyrillic(
+ 'шђчћжШЂЧЋЖ - šđčćž',
+ 'Mostly cyrillic characters'
+ );
+ $this->assertLatin(
+ 'šđč枊ĐČĆŽ - шђчћж',
+ 'Mostly latin characters'
+ );
+ }
+
+ function testSameAmountOfLatinAndCyrillicGetConverted() {
+ $this->assertConverted(
+ '4 latin: šđčć | 4 cyrillic: шђчћ',
+ 'sr-ec'
+ );
+ $this->assertConverted(
+ '4 latin: šđčć | 4 cyrillic: шђчћ',
+ 'sr-el'
+ );
+ }
+
+ /**
+ * @author Nikola Smolenski
+ */
+ function testConversionToCyrillic() {
+ $this->assertEquals( 'абвг',
+ $this->convertToCyrillic( 'abvg' )
+ );
+ $this->assertEquals( 'абвг',
+ $this->convertToCyrillic( 'абвг' )
+ );
+ $this->assertEquals( 'abvgшђжчћ',
+ $this->convertToCyrillic( 'abvgшђжчћ' )
+ );
+ $this->assertEquals( 'абвгшђжчћ',
+ $this->convertToCyrillic( 'абвгšđžčć' )
+ );
+ // Roman numerals are not converted
+ $this->assertEquals( 'а I б II в III г IV шђжчћ',
+ $this->convertToCyrillic( 'a I b II v III g IV šđžčć' )
+ );
+ }
+
+ function testConversionToLatin() {
+ $this->assertEquals( 'abcd',
+ $this->convertToLatin( 'abcd' )
+ );
+ $this->assertEquals( 'abcd',
+ $this->convertToLatin( 'абцд' )
+ );
+ $this->assertEquals( 'abcdšđžčć',
+ $this->convertToLatin( 'abcdшђжчћ' )
+ );
+ $this->assertEquals( 'абцдšđžčć',
+ $this->convertToLatin( 'абцдšđžčć' )
+ );
+ }
+
+ /** @dataProvider providePluralFourForms */
+ function testPluralFourForms( $result, $value ) {
+ $forms = array( 'one', 'few', 'many', 'other' );
+ $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) );
+ }
+
+ function providePluralFourForms() {
+ 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( '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 ) );
+ }
+ function providePluralTwoForms() {
+ return array (
+ array( 'one', 1 ),
+ array( 'several', 11 ),
+ array( 'several', 91 ),
+ array( 'several', 121 ),
+ );
+ }
+
+ ##### HELPERS #####################################################
+ /**
+ *Wrapper to verify text stay the same after applying conversion
+ * @param $text string Text to convert
+ * @param $variant string Language variant 'sr-ec' or 'sr-el'
+ * @param $msg string Optional message
+ */
+ 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 = '' ) {
+ $this->assertNotEquals(
+ $text,
+ $this->convertTo( $text, $variant ),
+ $msg
+ );
+ }
+
+ /**
+ * Verifiy the given Cyrillic text is not converted when using
+ * using the cyrillic variant and converted to Latin when using
+ * the Latin variant.
+ */
+ 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 = '' ) {
+ $this->assertUnConverted( $text, 'sr-el', $msg );
+ $this->assertConverted( $text, 'sr-ec', $msg );
+ }
+
+
+ /** Wrapper for converter::convertTo() method*/
+ function convertTo( $text, $variant ) {
+ return $this
+ ->lang
+ ->mConverter
+ ->convertTo(
+ $text, $variant
+ );
+ }
+ function convertToCyrillic( $text ) {
+ return $this->convertTo( $text, 'sr-ec' );
+ }
+ function convertToLatin( $text ) {
+ return $this->convertTo( $text, 'sr-el' );
+ }
+}
diff --git a/tests/phpunit/languages/LanguageTest.php b/tests/phpunit/languages/LanguageTest.php
index aaad9c31..c83e01ea 100644
--- a/tests/phpunit/languages/LanguageTest.php
+++ b/tests/phpunit/languages/LanguageTest.php
@@ -1,6 +1,10 @@
<?php
class LanguageTest extends MediaWikiTestCase {
+
+ /**
+ * @var Language
+ */
private $lang;
function setUp() {
@@ -19,97 +23,46 @@ class LanguageTest extends MediaWikiTestCase {
'convertDoubleWidth() with the full alphabet and digits'
);
}
-
- function testFormatTimePeriod() {
- $this->assertEquals(
- "9.5s",
- $this->lang->formatTimePeriod( 9.45 ),
- 'formatTimePeriod() rounding (<10s)'
- );
-
- $this->assertEquals(
- "10s",
- $this->lang->formatTimePeriod( 9.95 ),
- 'formatTimePeriod() rounding (<10s)'
- );
-
- $this->assertEquals(
- "1m 0s",
- $this->lang->formatTimePeriod( 59.55 ),
- 'formatTimePeriod() rounding (<60s)'
- );
-
- $this->assertEquals(
- "2m 0s",
- $this->lang->formatTimePeriod( 119.55 ),
- 'formatTimePeriod() rounding (<1h)'
- );
-
- $this->assertEquals(
- "1h 0m 0s",
- $this->lang->formatTimePeriod( 3599.55 ),
- 'formatTimePeriod() rounding (<1h)'
- );
-
- $this->assertEquals(
- "2h 0m 0s",
- $this->lang->formatTimePeriod( 7199.55 ),
- 'formatTimePeriod() rounding (>=1h)'
- );
-
- $this->assertEquals(
- "2h 0m",
- $this->lang->formatTimePeriod( 7199.55, 'avoidseconds' ),
- 'formatTimePeriod() rounding (>=1h), avoidseconds'
- );
-
- $this->assertEquals(
- "2h 0m",
- $this->lang->formatTimePeriod( 7199.55, 'avoidminutes' ),
- 'formatTimePeriod() rounding (>=1h), avoidminutes'
- );
-
- $this->assertEquals(
- "48h 0m",
- $this->lang->formatTimePeriod( 172799.55, 'avoidseconds' ),
- 'formatTimePeriod() rounding (=48h), avoidseconds'
- );
-
- $this->assertEquals(
- "3d 0h",
- $this->lang->formatTimePeriod( 259199.55, 'avoidminutes' ),
- 'formatTimePeriod() rounding (>48h), avoidminutes'
- );
-
- $this->assertEquals(
- "2d 1h 0m",
- $this->lang->formatTimePeriod( 176399.55, 'avoidseconds' ),
- 'formatTimePeriod() rounding (>48h), avoidseconds'
- );
-
- $this->assertEquals(
- "2d 1h",
- $this->lang->formatTimePeriod( 176399.55, 'avoidminutes' ),
- 'formatTimePeriod() rounding (>48h), avoidminutes'
- );
-
- $this->assertEquals(
- "3d 0h 0m",
- $this->lang->formatTimePeriod( 259199.55, 'avoidseconds' ),
- 'formatTimePeriod() rounding (>48h), avoidminutes'
- );
-
- $this->assertEquals(
- "2d 0h 0m",
- $this->lang->formatTimePeriod( 172801.55, 'avoidseconds' ),
- 'formatTimePeriod() rounding, (>48h), avoidseconds'
- );
-
- $this->assertEquals(
- "2d 1h 1m 1s",
- $this->lang->formatTimePeriod( 176460.55 ),
- 'formatTimePeriod() rounding, recursion, (>48h)'
+
+ /** @dataProvider provideFormattableTimes */
+ function testFormatTimePeriod( $seconds, $format, $expected, $desc ) {
+ $this->assertEquals( $expected, $this->lang->formatTimePeriod( $seconds, $format ), $desc );
+ }
+
+ function provideFormattableTimes() {
+ return array(
+ array( 9.45, array(), '9.5s', 'formatTimePeriod() rounding (<10s)' ),
+ array( 9.45, array( 'noabbrevs' => true ), '9.5 seconds', 'formatTimePeriod() rounding (<10s)' ),
+ array( 9.95, array(), '10s', 'formatTimePeriod() rounding (<10s)' ),
+ array( 9.95, array( 'noabbrevs' => true ), '10 seconds', 'formatTimePeriod() rounding (<10s)' ),
+ array( 59.55, array(), '1m 0s', 'formatTimePeriod() rounding (<60s)' ),
+ array( 59.55, array( 'noabbrevs' => true ), '1 minute 0 seconds', 'formatTimePeriod() rounding (<60s)' ),
+ array( 119.55, array(), '2m 0s', 'formatTimePeriod() rounding (<1h)' ),
+ array( 119.55, array( 'noabbrevs' => true ), '2 minutes 0 seconds', 'formatTimePeriod() rounding (<1h)' ),
+ array( 3599.55, array(), '1h 0m 0s', 'formatTimePeriod() rounding (<1h)' ),
+ array( 3599.55, array( 'noabbrevs' => true ), '1 hour 0 minutes 0 seconds', 'formatTimePeriod() rounding (<1h)' ),
+ array( 7199.55, array(), '2h 0m 0s', 'formatTimePeriod() rounding (>=1h)' ),
+ array( 7199.55, array( 'noabbrevs' => true ), '2 hours 0 minutes 0 seconds', 'formatTimePeriod() rounding (>=1h)' ),
+ array( 7199.55, 'avoidseconds', '2h 0m', 'formatTimePeriod() rounding (>=1h), avoidseconds' ),
+ array( 7199.55, array( 'avoid' => 'avoidseconds', 'noabbrevs' => true ), '2 hours 0 minutes', 'formatTimePeriod() rounding (>=1h), avoidseconds' ),
+ array( 7199.55, 'avoidminutes', '2h 0m', 'formatTimePeriod() rounding (>=1h), avoidminutes' ),
+ array( 7199.55, array( 'avoid' => 'avoidminutes', 'noabbrevs' => true ), '2 hours 0 minutes', 'formatTimePeriod() rounding (>=1h), avoidminutes' ),
+ array( 172799.55, 'avoidseconds', '48h 0m', 'formatTimePeriod() rounding (=48h), avoidseconds' ),
+ array( 172799.55, array( 'avoid' => 'avoidseconds', 'noabbrevs' => true ), '48 hours 0 minutes', 'formatTimePeriod() rounding (=48h), avoidseconds' ),
+ array( 259199.55, 'avoidminutes', '3d 0h', 'formatTimePeriod() rounding (>48h), avoidminutes' ),
+ array( 259199.55, array( 'avoid' => 'avoidminutes', 'noabbrevs' => true ), '3 days 0 hours', 'formatTimePeriod() rounding (>48h), avoidminutes' ),
+ array( 176399.55, 'avoidseconds', '2d 1h 0m', 'formatTimePeriod() rounding (>48h), avoidseconds' ),
+ array( 176399.55, array( 'avoid' => 'avoidseconds', 'noabbrevs' => true ), '2 days 1 hour 0 minutes', 'formatTimePeriod() rounding (>48h), avoidseconds' ),
+ array( 176399.55, 'avoidminutes', '2d 1h', 'formatTimePeriod() rounding (>48h), avoidminutes' ),
+ array( 176399.55, array( 'avoid' => 'avoidminutes', 'noabbrevs' => true ), '2 days 1 hour', 'formatTimePeriod() rounding (>48h), avoidminutes' ),
+ array( 259199.55, 'avoidseconds', '3d 0h 0m', 'formatTimePeriod() rounding (>48h), avoidseconds' ),
+ array( 259199.55, array( 'avoid' => 'avoidseconds', 'noabbrevs' => true ), '3 days 0 hours 0 minutes', 'formatTimePeriod() rounding (>48h), avoidseconds' ),
+ array( 172801.55, 'avoidseconds', '2d 0h 0m', 'formatTimePeriod() rounding, (>48h), avoidseconds' ),
+ array( 172801.55, array( 'avoid' => 'avoidseconds', 'noabbrevs' => true ), '2 days 0 hours 0 minutes', 'formatTimePeriod() rounding, (>48h), avoidseconds' ),
+ array( 176460.55, array(), '2d 1h 1m 1s', 'formatTimePeriod() rounding, recursion, (>48h)' ),
+ array( 176460.55, array( 'noabbrevs' => true ), '2 days 1 hour 1 minute 1 second', 'formatTimePeriod() rounding, recursion, (>48h)' ),
);
+
}
function testTruncate() {
@@ -243,4 +196,462 @@ class LanguageTest extends MediaWikiTestCase {
array( 'Be-x-old', 'With extension (two dashes)' ),
);
}
+
+ /**
+ * @dataProvider provideSprintfDateSamples
+ */
+ function testSprintfDate( $format, $ts, $expected, $msg ) {
+ $this->assertEquals(
+ $expected,
+ $this->lang->sprintfDate( $format, $ts ),
+ "sprintfDate('$format', '$ts'): $msg"
+ );
+ }
+ /**
+ * bug 33454. sprintfDate should always use UTC.
+ * @dataProvider provideSprintfDateSamples
+ */
+ function testSprintfDateTZ( $format, $ts, $expected, $msg ) {
+ $oldTZ = date_default_timezone_get();
+ $res = date_default_timezone_set( 'Asia/Seoul' );
+ if ( !$res ) {
+ $this->markTestSkipped( "Error setting Timezone" );
+ }
+
+ $this->assertEquals(
+ $expected,
+ $this->lang->sprintfDate( $format, $ts ),
+ "sprintfDate('$format', '$ts'): $msg"
+ );
+
+ date_default_timezone_set( $oldTZ );
+ }
+
+ function provideSprintfDateSamples() {
+ return array(
+ array(
+ 'xiY',
+ '20111212000000',
+ '1390', // note because we're testing English locale we get Latin-standard digits
+ 'Iranian calendar full year'
+ ),
+ array(
+ 'xiy',
+ '20111212000000',
+ '90',
+ 'Iranian calendar short year'
+ ),
+ array(
+ 'o',
+ '20120101235000',
+ '2011',
+ 'ISO 8601 (week) year'
+ ),
+ array(
+ 'W',
+ '20120101235000',
+ '52',
+ 'Week number'
+ ),
+ array(
+ 'W',
+ '20120102235000',
+ '1',
+ 'Week number'
+ ),
+ array(
+ 'o-\\WW-N',
+ '20091231235000',
+ '2009-W53-4',
+ 'leap week'
+ ),
+ // What follows is mostly copied from http://www.mediawiki.org/wiki/Help:Extension:ParserFunctions#.23time
+ array(
+ 'Y',
+ '20120102090705',
+ '2012',
+ 'Full year'
+ ),
+ array(
+ 'y',
+ '20120102090705',
+ '12',
+ '2 digit year'
+ ),
+ array(
+ 'L',
+ '20120102090705',
+ '1',
+ 'Leap year'
+ ),
+ array(
+ 'n',
+ '20120102090705',
+ '1',
+ 'Month index, not zero pad'
+ ),
+ array(
+ 'N',
+ '20120102090705',
+ '01',
+ 'Month index. Zero pad'
+ ),
+ array(
+ 'M',
+ '20120102090705',
+ 'Jan',
+ 'Month abbrev'
+ ),
+ array(
+ 'F',
+ '20120102090705',
+ 'January',
+ 'Full month'
+ ),
+ array(
+ 'xg',
+ '20120102090705',
+ 'January',
+ 'Genitive month name (same in EN)'
+ ),
+ array(
+ 'j',
+ '20120102090705',
+ '2',
+ 'Day of month (not zero pad)'
+ ),
+ array(
+ 'd',
+ '20120102090705',
+ '02',
+ 'Day of month (zero-pad)'
+ ),
+ array(
+ 'z',
+ '20120102090705',
+ '1',
+ 'Day of year (zero-indexed)'
+ ),
+ array(
+ 'D',
+ '20120102090705',
+ 'Mon',
+ 'Day of week (abbrev)'
+ ),
+ array(
+ 'l',
+ '20120102090705',
+ 'Monday',
+ 'Full day of week'
+ ),
+ array(
+ 'N',
+ '20120101090705',
+ '7',
+ 'Day of week (Mon=1, Sun=7)'
+ ),
+ array(
+ 'w',
+ '20120101090705',
+ '0',
+ 'Day of week (Sun=0, Sat=6)'
+ ),
+ array(
+ 'N',
+ '20120102090705',
+ '1',
+ 'Day of week'
+ ),
+ array(
+ 'a',
+ '20120102090705',
+ 'am',
+ 'am vs pm'
+ ),
+ array(
+ 'A',
+ '20120102120000',
+ 'PM',
+ 'AM vs PM'
+ ),
+ array(
+ 'a',
+ '20120102000000',
+ 'am',
+ 'AM vs PM'
+ ),
+ array(
+ 'g',
+ '20120102090705',
+ '9',
+ '12 hour, not Zero'
+ ),
+ array(
+ 'h',
+ '20120102090705',
+ '09',
+ '12 hour, zero padded'
+ ),
+ array(
+ 'G',
+ '20120102090705',
+ '9',
+ '24 hour, not zero'
+ ),
+ array(
+ 'H',
+ '20120102090705',
+ '09',
+ '24 hour, zero'
+ ),
+ array(
+ 'H',
+ '20120102110705',
+ '11',
+ '24 hour, zero'
+ ),
+ array(
+ 'i',
+ '20120102090705',
+ '07',
+ 'Minutes'
+ ),
+ array(
+ 's',
+ '20120102090705',
+ '05',
+ 'seconds'
+ ),
+ array(
+ 'U',
+ '20120102090705',
+ '1325495225',
+ 'unix time'
+ ),
+ array(
+ 't',
+ '20120102090705',
+ '31',
+ 'Days in current month'
+ ),
+ array(
+ 'c',
+ '20120102090705',
+ '2012-01-02T09:07:05+00:00',
+ 'ISO 8601 timestamp'
+ ),
+ array(
+ 'r',
+ '20120102090705',
+ 'Mon, 02 Jan 2012 09:07:05 +0000',
+ 'RFC 5322'
+ ),
+ array(
+ 'xmj xmF xmn xmY',
+ '20120102090705',
+ '7 Safar 2 1433',
+ 'Islamic'
+ ),
+ array(
+ 'xij xiF xin xiY',
+ '20120102090705',
+ '12 Dey 10 1390',
+ 'Iranian'
+ ),
+ array(
+ 'xjj xjF xjn xjY',
+ '20120102090705',
+ '7 Tevet 4 5772',
+ 'Hebrew'
+ ),
+ array(
+ 'xjt',
+ '20120102090705',
+ '29',
+ 'Hebrew number of days in month'
+ ),
+ array(
+ 'xjx',
+ '20120102090705',
+ 'Tevet',
+ 'Hebrew genitive month name (No difference in EN)'
+ ),
+ array(
+ 'xkY',
+ '20120102090705',
+ '2555',
+ 'Thai year'
+ ),
+ array(
+ 'xoY',
+ '20120102090705',
+ '101',
+ 'Minguo'
+ ),
+ array(
+ 'xtY',
+ '20120102090705',
+ '平成24',
+ 'nengo'
+ ),
+ array(
+ 'xrxkYY',
+ '20120102090705',
+ 'MMDLV2012',
+ 'Roman numerals'
+ ),
+ array(
+ 'xhxjYY',
+ '20120102090705',
+ 'ה\'תשע"ב2012',
+ 'Hebrew numberals'
+ ),
+ array(
+ 'xnY',
+ '20120102090705',
+ '2012',
+ 'Raw numerals (doesn\'t mean much in EN)'
+ ),
+ array(
+ '[[Y "(yea"\\r)]] \\"xx\\"',
+ '20120102090705',
+ '[[2012 (year)]] "x"',
+ 'Various escaping'
+ ),
+
+ );
+ }
+
+ /**
+ * @dataProvider provideFormatSizes
+ */
+ function testFormatSize( $size, $expected, $msg ) {
+ $this->assertEquals(
+ $expected,
+ $this->lang->formatSize( $size ),
+ "formatSize('$size'): $msg"
+ );
+ }
+
+ function provideFormatSizes() {
+ return array(
+ array(
+ 0,
+ "0 B",
+ "Zero bytes"
+ ),
+ array(
+ 1024,
+ "1 KB",
+ "1 kilobyte"
+ ),
+ array(
+ 1024 * 1024,
+ "1 MB",
+ "1,024 megabytes"
+ ),
+ array(
+ 1024 * 1024 * 1024,
+ "1 GB",
+ "1 gigabytes"
+ ),
+ array(
+ pow( 1024, 4 ),
+ "1 TB",
+ "1 terabyte"
+ ),
+ array(
+ pow( 1024, 5 ),
+ "1 PB",
+ "1 petabyte"
+ ),
+ array(
+ pow( 1024, 6 ),
+ "1 EB",
+ "1,024 exabyte"
+ ),
+ array(
+ pow( 1024, 7 ),
+ "1 ZB",
+ "1 zetabyte"
+ ),
+ array(
+ pow( 1024, 8 ),
+ "1 YB",
+ "1 yottabyte"
+ ),
+ // How big!? THIS BIG!
+ );
+ }
+
+ /**
+ * @dataProvider provideFormatBitrate
+ */
+ function testFormatBitrate( $bps, $expected, $msg ) {
+ $this->assertEquals(
+ $expected,
+ $this->lang->formatBitrate( $bps ),
+ "formatBitrate('$bps'): $msg"
+ );
+ }
+
+ function provideFormatBitrate() {
+ return array(
+ array(
+ 0,
+ "0bps",
+ "0 bits per second"
+ ),
+ array(
+ 999,
+ "999bps",
+ "999 bits per second"
+ ),
+ array(
+ 1000,
+ "1kbps",
+ "1 kilobit per second"
+ ),
+ array(
+ 1000 * 1000,
+ "1Mbps",
+ "1 megabit per second"
+ ),
+ array(
+ pow( 10, 9 ),
+ "1Gbps",
+ "1 gigabit per second"
+ ),
+ array(
+ pow( 10, 12 ),
+ "1Tbps",
+ "1 terabit per second"
+ ),
+ array(
+ pow( 10, 15 ),
+ "1Pbps",
+ "1 petabit per second"
+ ),
+ array(
+ pow( 10, 18 ),
+ "1Ebps",
+ "1 exabit per second"
+ ),
+ array(
+ pow( 10, 21 ),
+ "1Zbps",
+ "1 zetabit per second"
+ ),
+ array(
+ pow( 10, 24 ),
+ "1Ybps",
+ "1 yottabit per second"
+ ),
+ array(
+ pow( 10, 27 ),
+ "1,000Ybps",
+ "1,000 yottabits per second"
+ ),
+ );
+ }
}
diff --git a/tests/phpunit/languages/LanguageTiTest.php b/tests/phpunit/languages/LanguageTiTest.php
new file mode 100644
index 00000000..4bfaa009
--- /dev/null
+++ b/tests/phpunit/languages/LanguageTiTest.php
@@ -0,0 +1,32 @@
+<?php
+/**
+ * @author Amir E. Aharoni
+ * @copyright Copyright © 2012, Amir E. Aharoni
+ * @file
+ */
+
+/** 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 );
+ }
+
+ /** @dataProvider providerPlural */
+ function testPlural( $result, $value ) {
+ $forms = array( 'one', 'many' );
+ $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) );
+ }
+
+ function providerPlural() {
+ return array (
+ array( 'one', 0 ),
+ array( 'one', 1 ),
+ array( 'many', 2 ),
+ );
+ }
+}
diff --git a/tests/phpunit/languages/LanguageTlTest.php b/tests/phpunit/languages/LanguageTlTest.php
new file mode 100644
index 00000000..a1facd14
--- /dev/null
+++ b/tests/phpunit/languages/LanguageTlTest.php
@@ -0,0 +1,32 @@
+<?php
+/**
+ * @author Amir E. Aharoni
+ * @copyright Copyright © 2012, Amir E. Aharoni
+ * @file
+ */
+
+/** 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 );
+ }
+
+ /** @dataProvider providerPlural */
+ function testPlural( $result, $value ) {
+ $forms = array( 'one', 'many' );
+ $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) );
+ }
+
+ function providerPlural() {
+ return array (
+ array( 'one', 0 ),
+ array( 'one', 1 ),
+ array( 'many', 2 ),
+ );
+ }
+}
diff --git a/tests/phpunit/languages/LanguageTrTest.php b/tests/phpunit/languages/LanguageTrTest.php
index d2a5ff36..bda4c9d9 100644
--- a/tests/phpunit/languages/LanguageTrTest.php
+++ b/tests/phpunit/languages/LanguageTrTest.php
@@ -1,7 +1,7 @@
<?php
/**
- * @author Ashar Voultoiz
- * @copyright Copyright © 2011, Ashar Voultoiz
+ * @author Antoine Musso
+ * @copyright Copyright © 2011, Antoine Musso
* @file
*/
@@ -18,7 +18,10 @@ class LanguageTrTest extends MediaWikiTestCase {
/**
* See @bug 28040
- * Credits to #wikipedia-tr users berm, []LuCkY[] and Emperyan
+ * Credits to irc://irc.freenode.net/wikipedia-tr users:
+ * - berm
+ * - []LuCkY[]
+ * - Emperyan
* @see http://en.wikipedia.org/wiki/Dotted_and_dotless_I
* @dataProvider provideDottedAndDotlessI
*/
diff --git a/tests/phpunit/languages/LanguageUkTest.php b/tests/phpunit/languages/LanguageUkTest.php
new file mode 100644
index 00000000..60fafb0d
--- /dev/null
+++ b/tests/phpunit/languages/LanguageUkTest.php
@@ -0,0 +1,54 @@
+<?php
+/**
+ * @author Amir E. Aharoni
+ * based on LanguageBe_tarask.php
+ * @copyright Copyright © 2012, Amir E. Aharoni
+ * @file
+ */
+
+/** Tests for MediaWiki languages/classes/LanguageUk.php */
+class LanguageUkTest extends MediaWikiTestCase {
+ private $lang;
+
+ function setUp() {
+ $this->lang = Language::factory( 'Uk' );
+ }
+ function tearDown() {
+ unset( $this->lang );
+ }
+
+ /** @dataProvider providePluralFourForms */
+ function testPluralFourForms( $result, $value ) {
+ $forms = array( 'one', 'few', 'many', 'other' );
+ $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) );
+ }
+
+ function providePluralFourForms() {
+ 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( '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 ) );
+ }
+ function providePluralTwoForms() {
+ return array (
+ array( 'one', 1 ),
+ array( 'several', 11 ),
+ array( 'several', 91 ),
+ array( 'several', 121 ),
+ );
+ }
+}
diff --git a/tests/phpunit/languages/LanguageWaTest.php b/tests/phpunit/languages/LanguageWaTest.php
new file mode 100644
index 00000000..172f19b9
--- /dev/null
+++ b/tests/phpunit/languages/LanguageWaTest.php
@@ -0,0 +1,32 @@
+<?php
+/**
+ * @author Amir E. Aharoni
+ * @copyright Copyright © 2012, Amir E. Aharoni
+ * @file
+ */
+
+/** 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 );
+ }
+
+ /** @dataProvider providerPlural */
+ function testPlural( $result, $value ) {
+ $forms = array( 'one', 'many' );
+ $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) );
+ }
+
+ function providerPlural() {
+ return array (
+ array( 'one', 0 ),
+ array( 'one', 1 ),
+ array( 'many', 2 ),
+ );
+ }
+}
diff --git a/tests/phpunit/phpunit.php b/tests/phpunit/phpunit.php
index 39cccf80..92eeffa2 100644
--- a/tests/phpunit/phpunit.php
+++ b/tests/phpunit/phpunit.php
@@ -46,8 +46,8 @@ require( RUN_MAINTENANCE_IF_MAIN );
if( !in_array( '--configuration', $_SERVER['argv'] ) ) {
//Hack to eliminate the need to use the Makefile (which sucks ATM)
- $_SERVER['argv'][] = '--configuration';
- $_SERVER['argv'][] = $IP . '/tests/phpunit/suite.xml';
+ array_splice( $_SERVER['argv'], 1, 0,
+ array( '--configuration', $IP . '/tests/phpunit/suite.xml' ) );
}
require_once( 'PHPUnit/Runner/Version.php' );
diff --git a/tests/phpunit/skins/SideBarTest.php b/tests/phpunit/skins/SideBarTest.php
index bf79e760..912d7602 100644
--- a/tests/phpunit/skins/SideBarTest.php
+++ b/tests/phpunit/skins/SideBarTest.php
@@ -37,6 +37,7 @@ class SideBarTest extends MediaWikiLangTestCase {
parent::setUp();
$this->initMessagesHref();
$this->skin = new SkinTemplate();
+ $this->skin->getContext()->setLanguage( Language::factory( 'en' ) );
}
function tearDown() {
parent::tearDown();
@@ -106,7 +107,7 @@ class SideBarTest extends MediaWikiLangTestCase {
}
/**
- * bug 33321
+ * bug 33321 - Make sure there's a | after transforming.
* @group Database
*/
function testTrickyPipe() {
@@ -168,7 +169,7 @@ class SideBarTest extends MediaWikiLangTestCase {
}
/**
- * Test wgNoFollowLinks in sidebar
+ * Test $wgNoFollowLinks in sidebar
*/
function testRespectWgnofollowlinks() {
global $wgNoFollowLinks;
@@ -177,7 +178,7 @@ class SideBarTest extends MediaWikiLangTestCase {
$attribs = $this->getAttribs();
$this->assertArrayNotHasKey( 'rel', $attribs,
- 'External URL in sidebar do not have rel=nofollow when wgNoFollowLinks = false'
+ 'External URL in sidebar do not have rel=nofollow when $wgNoFollowLinks = false'
);
// Restore global
@@ -185,7 +186,7 @@ class SideBarTest extends MediaWikiLangTestCase {
}
/**
- * Test wgExternaLinkTarget in sidebar
+ * Test $wgExternaLinkTarget in sidebar
*/
function testRespectExternallinktarget() {
global $wgExternalLinkTarget;
diff --git a/tests/phpunit/suite.xml b/tests/phpunit/suite.xml
index e6649beb..1227a17a 100644
--- a/tests/phpunit/suite.xml
+++ b/tests/phpunit/suite.xml
@@ -2,35 +2,42 @@
<!-- colors don't work on Windows! -->
<phpunit bootstrap="./bootstrap.php"
- colors="false"
+ colors="true"
backupGlobals="false"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
stopOnFailure="false"
+ timeoutForSmallTests="2"
+ timeoutForMediumTests="10"
+ timeoutForLargeTests="60"
strict="true"
verbose="true">
<testsuites>
<testsuite name="includes">
- <directory>./includes</directory>
+ <directory>includes</directory>
</testsuite>
<testsuite name="languages">
- <directory>./languages</directory>
+ <directory>languages</directory>
</testsuite>
<testsuite name="skins">
- <directory>./skins</directory>
+ <directory>skins</directory>
+ </testsuite>
+ <testsuite name="structure">
+ <file>StructureTest.php</file>
</testsuite>
<testsuite name="uploadfromurl">
- <file>./suites/UploadFromUrlTestSuite.php</file>
+ <file>suites/UploadFromUrlTestSuite.php</file>
</testsuite>
<testsuite name="extensions">
- <file>./suites/ExtensionsTestSuite.php</file>
+ <file>suites/ExtensionsTestSuite.php</file>
</testsuite>
</testsuites>
<groups>
<exclude>
<group>Utility</group>
<group>Broken</group>
+ <group>ParserFuzz</group>
<group>Stub</group>
</exclude>
</groups>
diff --git a/tests/phpunit/suites/UploadFromUrlTestSuite.php b/tests/phpunit/suites/UploadFromUrlTestSuite.php
index 9b666825..6779ad47 100644
--- a/tests/phpunit/suites/UploadFromUrlTestSuite.php
+++ b/tests/phpunit/suites/UploadFromUrlTestSuite.php
@@ -3,6 +3,8 @@
require_once( dirname( dirname( __FILE__ ) ) . '/includes/upload/UploadFromUrlTest.php' );
class UploadFromUrlTestSuite extends PHPUnit_Framework_TestSuite {
+ public $savedGlobals = array();
+
public static function addTables( &$tables ) {
$tables[] = 'user_properties';
$tables[] = 'filearchive';
@@ -14,34 +16,49 @@ class UploadFromUrlTestSuite extends PHPUnit_Framework_TestSuite {
}
function setUp() {
- global $wgParser, $wgParserConf, $IP, $messageMemc, $wgMemc, $wgDeferredUpdateList,
- $wgUser, $wgLang, $wgOut, $wgRequest, $wgStyleDirectory, $wgEnableParserCache,
- $wgNamespaceAliases, $wgNamespaceProtection, $wgLocalFileRepo,
- $parserMemc, $wgThumbnailScriptPath, $wgScriptPath,
- $wgArticlePath, $wgStyleSheetPath, $wgScript, $wgStylePath;
-
- $wgScript = '/index.php';
- $wgScriptPath = '/';
- $wgArticlePath = '/wiki/$1';
- $wgStyleSheetPath = '/skins';
- $wgStylePath = '/skins';
- $wgThumbnailScriptPath = false;
- $wgLocalFileRepo = array(
- 'class' => 'LocalRepo',
- 'name' => 'local',
- 'directory' => wfTempDir() . '/test-repo',
- 'url' => 'http://example.com/images',
- 'deletedDir' => wfTempDir() . '/test-repo/delete',
- 'hashLevels' => 2,
+ 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,
'transformVia404' => false,
+ '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-deleted' => wfTempDir() . '/test-repo/delete',
+ )
+ ) ),
);
+ foreach ( $tmpGlobals as $var => $val ) {
+ if ( array_key_exists( $var, $GLOBALS ) ) {
+ $this->savedGlobals[$var] = $GLOBALS[$var];
+ }
+ $GLOBALS[$var] = $val;
+ }
+
$wgNamespaceProtection[NS_MEDIAWIKI] = 'editinterface';
$wgNamespaceAliases['Image'] = NS_FILE;
$wgNamespaceAliases['Image_talk'] = NS_FILE_TALK;
$wgEnableParserCache = false;
- $wgDeferredUpdateList = array();
+ DeferredUpdates::clearPendingUpdates();
$wgMemc = wfGetMainCache();
$messageMemc = wfGetMessageCacheStorage();
$parserMemc = wfGetParserCacheStorage();
@@ -49,18 +66,27 @@ class UploadFromUrlTestSuite extends PHPUnit_Framework_TestSuite {
// $wgContLang = new StubContLang;
$wgUser = new User;
$context = new RequestContext;
- $wgLang = $context->getLang();
+ $wgLang = $context->getLanguage();
$wgOut = $context->getOutput();
$wgParser = new StubObject( 'wgParser', $wgParserConf['class'], array( $wgParserConf ) );
- $wgRequest = new WebRequest;
+ $wgRequest = $context->getRequest();
if ( $wgStyleDirectory === false ) {
$wgStyleDirectory = "$IP/skins";
}
+ RepoGroup::destroySingleton();
+ FileBackendGroup::destroySingleton();
}
public function tearDown() {
+ foreach ( $this->savedGlobals as $var => $val ) {
+ $GLOBALS[$var] = $val;
+ }
+ // Restore backends
+ RepoGroup::destroySingleton();
+ FileBackendGroup::destroySingleton();
+
$this->teardownUploadDir( $this->uploadDir );
}
@@ -159,10 +185,10 @@ class UploadFromUrlTestSuite extends PHPUnit_Framework_TestSuite {
return $dir;
}
- wfMkdirParents( $dir . '/3/3a' );
+ wfMkdirParents( $dir . '/3/3a', null, __METHOD__ );
copy( "$IP/skins/monobook/headbg.jpg", "$dir/3/3a/Foobar.jpg" );
- wfMkdirParents( $dir . '/0/09' );
+ wfMkdirParents( $dir . '/0/09', null, __METHOD__ );
copy( "$IP/skins/monobook/headbg.jpg", "$dir/0/09/Bad.jpg" );
return $dir;
diff --git a/tests/qunit/QUnitTestResources.php b/tests/qunit/QUnitTestResources.php
new file mode 100644
index 00000000..670e3d11
--- /dev/null
+++ b/tests/qunit/QUnitTestResources.php
@@ -0,0 +1,52 @@
+<?php
+
+return array(
+
+ /* Test suites for MediaWiki core modules */
+
+ 'mediawiki.tests.qunit.suites' => array(
+ 'scripts' => array(
+ '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',
+ 'tests/qunit/suites/resources/jquery/jquery.client.test.js',
+ '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.highlightText.test.js',
+ 'tests/qunit/suites/resources/jquery/jquery.localize.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/suites/resources/mediawiki/mediawiki.jscompat.test.js',
+ 'tests/qunit/suites/resources/mediawiki/mediawiki.test.js',
+ 'tests/qunit/suites/resources/mediawiki/mediawiki.Title.test.js',
+ 'tests/qunit/suites/resources/mediawiki/mediawiki.user.test.js',
+ 'tests/qunit/suites/resources/mediawiki/mediawiki.util.test.js',
+ 'tests/qunit/suites/resources/mediawiki.special/mediawiki.special.recentchanges.test.js',
+ "tests/qunit/suites/resources/mediawiki/mediawiki.jqueryMsg.test.js",
+ ),
+ 'dependencies' => array(
+ 'jquery.autoEllipsis',
+ 'jquery.byteLength',
+ 'jquery.byteLimit',
+ 'jquery.client',
+ 'jquery.colorUtil',
+ 'jquery.delayedBind',
+ 'jquery.getAttrs',
+ 'jquery.highlightText',
+ 'jquery.localize',
+ 'jquery.mwExtension',
+ 'jquery.tabIndex',
+ 'jquery.tablesorter',
+ 'jquery.textSelection',
+ 'mediawiki',
+ 'mediawiki.Title',
+ 'mediawiki.user',
+ 'mediawiki.util',
+ 'mediawiki.special.recentchanges',
+ 'mediawiki.jqueryMsg',
+ ),
+ )
+);
diff --git a/tests/qunit/data/qunitOkCall.js b/tests/qunit/data/qunitOkCall.js
new file mode 100644
index 00000000..2fb6e01d
--- /dev/null
+++ b/tests/qunit/data/qunitOkCall.js
@@ -0,0 +1,2 @@
+start();
+ok( true, 'Successfully loaded!');
diff --git a/tests/qunit/data/testrunner.js b/tests/qunit/data/testrunner.js
index dbfe9fad..fdd3116b 100644
--- a/tests/qunit/data/testrunner.js
+++ b/tests/qunit/data/testrunner.js
@@ -1,36 +1,61 @@
-( function( $ ) {
+( function ( $, mw, QUnit, undefined ) {
+"use strict";
+
+var mwTestIgnore, mwTester, addons;
/**
* Add bogus to url to prevent IE crazy caching
*
- * @param value {String} a relative path (eg. 'data/defineTestCallback.js' or 'data/test.php?foo=bar')
+ * @param value {String} a relative path (eg. 'data/defineTestCallback.js'
+ * or 'data/test.php?foo=bar').
* @return {String} Such as 'data/defineTestCallback.js?131031765087663960'
*/
-QUnit.fixurl = function(value) {
- return value + (/\?/.test(value) ? "&" : "?") + new Date().getTime() + "" + parseInt(Math.random()*100000);
+QUnit.fixurl = function (value) {
+ return value + (/\?/.test( value ) ? '&' : '?')
+ + String( new Date().getTime() )
+ + String( parseInt( Math.random()*100000, 10 ) );
};
/**
+ * Configuration
+ */
+QUnit.config.testTimeout = 5000;
+
+/**
+ * MediaWiki debug mode
+ */
+QUnit.config.urlConfig.push( 'debug' );
+
+/**
* Load TestSwarm agent
*/
if ( QUnit.urlParams.swarmURL ) {
- document.write("<scr" + "ipt src='" + QUnit.fixurl( 'data/testwarm.inject.js' ) + "'></scr" + "ipt>");
+ document.write( "<scr" + "ipt src='" + QUnit.fixurl( mw.config.get( 'wgScriptPath' )
+ + '/tests/qunit/data/testwarm.inject.js' ) + "'></scr" + "ipt>" );
}
/**
- * Load completenesstest
+ * CompletenessTest
*/
+// Adds toggle checkbox to header
+QUnit.config.urlConfig.push( 'completenesstest' );
+
+// Initiate when enabled
if ( QUnit.urlParams.completenesstest ) {
// Return true to ignore
- var mwTestIgnore = function( val, tester, funcPath ) {
+ 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;
+ tester.methodCallTracker.Map = true;
+ return true;
+ }
+ if ( val instanceof mw.Title ) {
+ tester.methodCallTracker.Title = true;
return true;
}
@@ -42,42 +67,113 @@ if ( QUnit.urlParams.completenesstest ) {
return false;
};
- var mwTester = new CompletenessTest( mw, mwTestIgnore );
+ 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 to a fresh copy of the live config for each test();
+ * @param override {Object} [optional]
+ * @example:
+ * <code>
+ * module( .., newMwEnvironment() );
+ *
+ * test( .., function () {
+ * mw.config.set( 'foo', 'bar' ); // just for this test
+ * } );
+ *
+ * test( .., function () {
+ * mw.config.get( 'foo' ); // doesn't exist
+ * } );
+ *
+ *
+ * module( .., newMwEnvironment({ quux: 'corge' }) );
+ *
+ * test( .., function () {
+ * mw.config.get( 'quux' ); // "corge"
+ * mw.config.set( 'quux', "grault" );
+ * } );
+ *
+ * test( .., function () {
+ * mw.config.get( 'quux' ); // "corge"
+ * } );
+ * </code>
+ */
+QUnit.newMwEnvironment = ( function () {
+ var liveConfig, freshConfigCopy, log;
+
+ liveConfig = mw.config.values;
+
+ freshConfigCopy = function ( 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 );
+ };
+
+ log = QUnit.urlParams.mwlogenv ? mw.log : function () {};
+
+ return function ( override ) {
+ override = override || {};
+
+ return {
+ setup: function () {
+ log( 'MwEnvironment> SETUP for "' + QUnit.config.current.module
+ + ': ' + QUnit.config.current.testName + '"' );
+ // Greetings, mock configuration!
+ mw.config.values = freshConfigCopy( override );
+ },
+
+ teardown: function () {
+ log( 'MwEnvironment> TEARDOWN for "' + QUnit.config.current.module
+ + ': ' + QUnit.config.current.testName + '"' );
+ // Farewell, mock configuration!
+ mw.config.values = liveConfig;
+ }
+ };
+ };
+}() );
+
+/**
* Add-on assertion helpers
*/
// Define the add-ons
-var addons = {
+addons = {
// Expect boolean true
- assertTrue: function( actual, message ) {
+ assertTrue: function ( actual, message ) {
strictEqual( actual, true, message );
},
// Expect boolean false
- assertFalse: function( actual, message ) {
+ assertFalse: function ( actual, message ) {
strictEqual( actual, false, message );
},
// Expect numerical value less than X
- lt: function( actual, expected, message ) {
+ 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 ) {
+ 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 ) {
+ 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 ) {
+ gtOrEq: function ( actual, expected, message ) {
QUnit.push( actual >= expected, actual, 'greater than or equal to ' + expected, message );
},
@@ -89,4 +185,4 @@ var addons = {
$.extend( QUnit, addons );
$.extend( window, addons );
-})( jQuery );
+})( jQuery, mediaWiki, QUnit );
diff --git a/tests/qunit/index.html b/tests/qunit/index.html
index f748b87f..ef7ff8de 100644
--- a/tests/qunit/index.html
+++ b/tests/qunit/index.html
@@ -9,6 +9,43 @@
<script>
function startUp(){
mw.config = new mw.Map( false );
+
+ /**
+ * Simulate an average mw.config context
+ */
+ /* StartUp module */
+ mw.config.set({"wgLoadScript": "/mw/trunk/phase3/load.php", "debug": true, "skin": "vector", "stylepath": "/mw/trunk/phase3/skins", "wgUrlProtocols": "http\\:\\/\\/|https\\:\\/\\/|ftp\\:\\/\\/|irc\\:\\/\\/|ircs\\:\\/\\/|gopher\\:\\/\\/|telnet\\:\\/\\/|nntp\\:\\/\\/|worldwind\\:\\/\\/|mailto\\:|news\\:|svn\\:\\/\\/|git\\:\\/\\/|mms\\:\\/\\/|\\/\\/", "wgArticlePath": "/mw/trunk/phase3/index.php/$1", "wgScriptPath": "/mw/trunk/phase3", "wgScriptExtension": ".php", "wgScript": "/mw/trunk/phase3/index.php", "wgVariantArticlePath": false, "wgActionPaths": [], "wgServer": "http://localhost", "wgUserLanguage": "en", "wgContentLanguage": "en", "wgVersion": "1.19alpha", "wgEnableAPI": true, "wgEnableWriteAPI": true, "wgDefaultDateFormat": "dmy", "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"], "wgMainPageTitle": "Main Page", "wgFormattedNamespaces": {"-2": "Media", "-1": "Special", "0": "", "1": "Talk", "2": "User", "3": "User talk", "4": "Testopedia", "5": "Testopedia 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"}, "wgNamespaceIds": {"media": -2, "special": -1, "": 0, "talk": 1, "user": 2, "user_talk": 3, "testopedia": 4, "testopedia_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}, "wgSiteName": "Testopedia", "wgFileExtensions": ["png", "gif", "jpg", "jpeg"], "wgDBname": "mediawiki", "wgFileCanRotate": true, "wgAvailableSkins": {"chick": "Chick", "cologneblue": "CologneBlue", "modern": "Modern", "monobook": "MonoBook", "myskin": "MySkin", "nostalgia": "Nostalgia", "simple": "Simple", "standard": "Standard", "vector": "Vector"}, "wgExtensionAssetsPath": "/mw/trunk/phase3/extensions", "wgCookiePrefix": "mediawiki", "wgResourceLoaderMaxQueryLength": -1, "wgCaseSensitiveNamespaces": []});
+
+ /* WikiPage specific */
+ mw.config.set({"wgCanonicalNamespace": "", "wgCanonicalSpecialPageName": false, "wgNamespaceNumber": 0, "wgPageName": "Sandbox", "wgTitle": "Sandbox", "wgCurRevisionId": 486, "wgArticleId": 84, "wgIsArticle": true, "wgAction": "view", "wgUserName": null, "wgUserGroups": ["*"], "wgCategories": [], "wgBreakFrames": false, "wgPageContentLanguage": "en", "wgSeparatorTransformTable": ["", ""], "wgDigitTransformTable": ["", ""], "wgRestrictionEdit": [], "wgRestrictionMove": [], "wgRedirectedFrom": "Sandbox"});
+
+ /**
+ * Fix wgScriptPath and the like to the real thing,
+ * instead of fake ones (for access to /tests/qunit/data/)
+ */
+
+ // Regular expression to extract the path for the QUnit tests
+ // Takes into account that tests could be run from a file:// URL
+ // by excluding the 'index.html' part from the URL
+ var rePath = /(?:[^#\?](?!index.html))*\/?/;
+
+ // Extract path to /tests/qunit/
+ var qunitTestsPath = rePath.exec( location.pathname )[0];
+
+ // Traverse up to script path
+ var pathParts = qunitTestsPath.split( '/' );
+ pathParts.pop(); pathParts.pop(); pathParts.pop();
+ var scriptPath = pathParts.join( '/' );
+
+ mw.config.set({
+ "wgServer": location.protocol + '//' + location.host,
+ "wgScriptPath": scriptPath,
+ "wgLoadScript": scriptPath + '/load.php',
+ "stylepath": scriptPath + '/skins',
+ "wgArticlePath": scriptPath + '/index.php/$1',
+ "wgScript": scriptPath + '/index.php',
+ "wgExtensionAssetsPath": scriptPath + '/extensions'
+ });
}
</script>
@@ -18,15 +55,15 @@
<!-- MW: mediawiki.page.startup -->
<script src="../../resources/jquery/jquery.client.js"></script>
+ <script src="../../resources/mediawiki/mediawiki.util.js"></script>
<script src="../../resources/mediawiki.page/mediawiki.page.startup.js"></script>
- <!-- MW: mediawiki.user|mediawiki.util|mediawiki.page.ready -->
+ <!-- MW: mediawiki.user|mediawiki.page.ready -->
<script src="../../resources/jquery/jquery.cookie.js"></script>
<script src="../../resources/mediawiki/mediawiki.user.js"></script>
<script src="../../resources/jquery/jquery.messageBox.js"></script>
- <script src="../../resources/jquery/jquery.mwPrototypes.js"></script>
- <script src="../../resources/mediawiki/mediawiki.util.js"></script>
+ <script src="../../resources/jquery/jquery.mwExtension.js"></script>
<script src="../../resources/jquery/jquery.checkboxShiftClick.js"></script>
<script src="../../resources/jquery/jquery.makeCollapsible.js"></script>
@@ -45,9 +82,14 @@
<script src="../../resources/jquery/jquery.colorUtil.js"></script>
<script src="../../resources/jquery/jquery.delayedBind.js"></script>
<script src="../../resources/jquery/jquery.getAttrs.js"></script>
+ <script src="../../resources/jquery/jquery.highlightText.js"></script>
<script src="../../resources/jquery/jquery.localize.js"></script>
<script src="../../resources/jquery/jquery.tabIndex.js"></script>
<script src="../../resources/jquery/jquery.tablesorter.js"></script>
+ <script src="../../resources/jquery/jquery.textSelection.js"></script>
+ <script src="../../resources/mediawiki/mediawiki.Title.js"></script>
+ <script src="../../resources/mediawiki.language/mediawiki.language.js"></script>
+ <script src="../../resources/mediawiki/mediawiki.jqueryMsg.js"></script>
<script src="../../resources/mediawiki.special/mediawiki.special.js"></script>
<script src="../../resources/mediawiki.special/mediawiki.special.recentchanges.js"></script>
@@ -59,23 +101,27 @@
<!-- QUnit: Load test suites (maintain the same order as above please) -->
<script src="suites/resources/mediawiki/mediawiki.jscompat.test.js"></script>
- <script src="suites/resources/mediawiki/mediawiki.js"></script>
- <script src="suites/resources/mediawiki/mediawiki.user.js"></script>
+ <script src="suites/resources/mediawiki/mediawiki.test.js"></script>
+ <script src="suites/resources/mediawiki/mediawiki.user.test.js"></script>
- <script src="suites/resources/jquery/jquery.client.js"></script>
- <script src="suites/resources/jquery/jquery.mwPrototypes.js"></script>
- <script src="suites/resources/mediawiki/mediawiki.util.js"></script>
+ <script src="suites/resources/jquery/jquery.client.test.js"></script>
+ <script src="suites/resources/jquery/jquery.mwExtension.test.js"></script>
+ <script src="suites/resources/mediawiki/mediawiki.util.test.js"></script>
- <script src="suites/resources/jquery/jquery.autoEllipsis.js"></script>
- <script src="suites/resources/jquery/jquery.byteLength.js"></script>
- <script src="suites/resources/jquery/jquery.byteLimit.js"></script>
- <script src="suites/resources/jquery/jquery.colorUtil.js"></script>
+ <script src="suites/resources/jquery/jquery.autoEllipsis.test.js"></script>
+ <script src="suites/resources/jquery/jquery.byteLength.test.js"></script>
+ <script src="suites/resources/jquery/jquery.byteLimit.test.js"></script>
+ <script src="suites/resources/jquery/jquery.colorUtil.test.js"></script>
<script src="suites/resources/jquery/jquery.delayedBind.test.js"></script>
- <script src="suites/resources/jquery/jquery.getAttrs.js"></script>
- <script src="suites/resources/jquery/jquery.localize.js"></script>
- <script src="suites/resources/jquery/jquery.tabIndex.js"></script>
+ <script src="suites/resources/jquery/jquery.getAttrs.test.js"></script>
+ <script src="suites/resources/jquery/jquery.highlightText.test.js"></script>
+ <script src="suites/resources/jquery/jquery.localize.test.js"></script>
+ <script src="suites/resources/jquery/jquery.tabIndex.test.js"></script>
<script src="suites/resources/jquery/jquery.tablesorter.test.js" charset="UTF-8"></script>
- <script src="suites/resources/mediawiki.special/mediawiki.special.recentchanges.js"></script>
+ <script src="suites/resources/jquery/jquery.textSelection.test.js" charset="UTF-8"></script>
+ <script src="suites/resources/mediawiki/mediawiki.Title.test.js"></script>
+ <script src="suites/resources/mediawiki/mediawiki.jqueryMsg.test.js"></script>
+ <script src="suites/resources/mediawiki.special/mediawiki.special.recentchanges.test.js"></script>
</head>
<body>
<h1 id="qunit-header">MediaWiki JavaScript Test Suite</h1>
@@ -85,6 +131,7 @@
</div>
<h2 id="qunit-userAgent"></h2>
<ol id="qunit-tests"></ol>
+ <div id="qunit-fixture"></div>
<!-- Scripts inserting stuff here shall remove it themselfs! -->
<div id="content"></div>
diff --git a/tests/qunit/suites/resources/jquery/jquery.autoEllipsis.js b/tests/qunit/suites/resources/jquery/jquery.autoEllipsis.test.js
index caf5a6f1..6e371384 100644
--- a/tests/qunit/suites/resources/jquery/jquery.autoEllipsis.js
+++ b/tests/qunit/suites/resources/jquery/jquery.autoEllipsis.test.js
@@ -1,4 +1,4 @@
-module( 'jquery.autoEllipsis.js' );
+module( 'jquery.autoEllipsis', QUnit.newMwEnvironment() );
test( '-- Initial check', function() {
expect(1);
@@ -6,8 +6,8 @@ test( '-- Initial check', function() {
});
function createWrappedDiv( text, width ) {
- var $wrapper = $( '<div />' ).css( 'width', width );
- var $div = $( '<div />' ).text( text );
+ var $wrapper = $( '<div>' ).css( 'width', width );
+ var $div = $( '<div>' ).text( text );
$wrapper.append( $div );
return $wrapper;
}
@@ -26,7 +26,7 @@ test( 'Position right', function() {
// 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' );
- $( 'body' ).append( $wrapper );
+ $( '#qunit-fixture' ).append( $wrapper );
$wrapper.autoEllipsis( { position: 'right' } );
// Verify that, and only one, span element was created
@@ -47,12 +47,9 @@ test( 'Position right', function() {
// 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 ) {
+ if ( $.browser.msie && Number( $.browser.version ) === 6 ) {
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 {
gt( $span.width(), $span.parent().width(), 'Fit is maximal (adding two characters makes it not fit any more)' );
}
-
- // Clean up
- $wrapper.remove();
});
diff --git a/tests/qunit/suites/resources/jquery/jquery.byteLength.js b/tests/qunit/suites/resources/jquery/jquery.byteLength.test.js
index f82fda27..15fac691 100644
--- a/tests/qunit/suites/resources/jquery/jquery.byteLength.js
+++ b/tests/qunit/suites/resources/jquery/jquery.byteLength.test.js
@@ -1,4 +1,4 @@
-module( 'jquery.byteLength.js' );
+module( 'jquery.byteLength', QUnit.newMwEnvironment() );
test( '-- Initial check', function() {
expect(1);
@@ -25,7 +25,7 @@ test( 'Simple text', function() {
test( 'Special text', window.foo = function() {
expect(5);
- // http://en.wikipedia.org/wiki/UTF-8
+ // http://en.wikipedia.org/wiki/UTF-8
var U_0024 = '\u0024',
U_00A2 = '\u00A2',
U_20AC = '\u20AC',
diff --git a/tests/qunit/suites/resources/jquery/jquery.byteLimit.js b/tests/qunit/suites/resources/jquery/jquery.byteLimit.test.js
index 461ea49b..3346c2d5 100644
--- a/tests/qunit/suites/resources/jquery/jquery.byteLimit.js
+++ b/tests/qunit/suites/resources/jquery/jquery.byteLimit.test.js
@@ -1,4 +1,6 @@
-module( 'jquery.byteLimit.js' );
+( function () {
+
+module( 'jquery.byteLimit', QUnit.newMwEnvironment() );
test( '-- Initial check', function() {
expect(1);
@@ -23,46 +25,47 @@ $.addChars = function( $input, charstr ) {
}
}
};
-var blti = 0;
+
/**
* Test factory for $.fn.byteLimit
*
* @param $input {jQuery} jQuery object in an input element
- * @param useLimit {Boolean} Wether a limit should apply at all
+ * @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)
+ * The limit should be less than 20 (the sample data's length)
*/
var byteLimitTest = function( options ) {
var opt = $.extend({
description: '',
$input: null,
sample: '',
- useLimit: false,
- expected: 0,
+ hasLimit: false,
+ expected: '',
limit: null
}, options);
- var i = blti++;
test( opt.description, function() {
- opt.$input.appendTo( 'body' );
-
+ opt.$input.appendTo( '#qunit-fixture' );
+
// Simulate pressing keys for each of the sample characters
$.addChars( opt.$input, opt.sample );
- var newVal = opt.$input.val();
-
- if ( opt.useLimit ) {
- expect(2);
-
+ var rawVal = opt.$input.val(),
+ fn = opt.$input.data( 'byteLimit-callback' ),
+ newVal = $.isFunction( fn ) ? fn( rawVal ) : rawVal;
+
+ if ( opt.hasLimit ) {
+ expect(3);
+
ltOrEq( $.byteLength( newVal ), opt.limit, 'Prevent keypresses after byteLimit was reached, length never exceeded the limit' );
- equal( $.byteLength( newVal ), opt.expected, 'Not preventing keypresses too early, length has reached the expected length' );
-
+ equal( $.byteLength( rawVal ), $.byteLength( opt.expected ), 'Not preventing keypresses too early, length has reached the expected length' );
+ equal( rawVal, opt.expected, 'New value matches the expected string' );
+
} else {
- expect(1);
- equal( $.byteLength( newVal ), opt.expected, 'Unlimited scenarios are not affected, expected length reached' );
+ expect(2);
+ equal( newVal, opt.expected, 'New value matches the expected string' );
+ equal( $.byteLength( newVal ), $.byteLength( opt.expected ), 'Unlimited scenarios are not affected, expected length reached' );
}
-
- opt.$input.remove();
} );
};
@@ -79,77 +82,106 @@ var
byteLimitTest({
description: 'Plain text input',
$input: $( '<input>' )
- .attr( {
- 'type': 'text'
- }),
+ .attr( 'type', 'text' ),
sample: simpleSample,
- useLimit: false,
- expected: $.byteLength( simpleSample )
+ hasLimit: false,
+ expected: simpleSample
});
byteLimitTest({
description: 'Limit using the maxlength attribute',
$input: $( '<input>' )
- .attr( {
- 'type': 'text',
- 'maxlength': '10'
- })
+ .attr( 'type', 'text' )
+ .prop( 'maxLength', '10' )
.byteLimit(),
sample: simpleSample,
- useLimit: true,
+ hasLimit: true,
limit: 10,
- expected: 10
+ expected: '1234567890'
});
byteLimitTest({
description: 'Limit using a custom value',
$input: $( '<input>' )
- .attr( {
- 'type': 'text'
- })
+ .attr( 'type', 'text' )
.byteLimit( 10 ),
sample: simpleSample,
- useLimit: true,
+ hasLimit: true,
limit: 10,
- expected: 10
+ expected: '1234567890'
});
byteLimitTest({
description: 'Limit using a custom value, overriding maxlength attribute',
$input: $( '<input>' )
- .attr( {
- 'type': 'text',
- 'maxLength': '10'
- })
+ .attr( 'type', 'text' )
+ .prop( 'maxLength', '10' )
.byteLimit( 15 ),
sample: simpleSample,
- useLimit: true,
+ hasLimit: true,
limit: 15,
- expected: 15
+ expected: '123456789012345'
});
byteLimitTest({
description: 'Limit using a custom value (multibyte)',
$input: $( '<input>' )
- .attr( {
- 'type': 'text'
- })
+ .attr( 'type', 'text' )
.byteLimit( 14 ),
sample: mbSample,
- useLimit: true,
+ hasLimit: true,
limit: 14,
- expected: 14 // (10 x 1-byte char) + (1 x 3-byte char) + (1 x 1-byte char)
+ expected: '1234567890' + U_20AC + '1'
});
byteLimitTest({
description: 'Limit using a custom value (multibyte) overlapping a byte',
$input: $( '<input>' )
- .attr( {
- 'type': 'text'
- })
+ .attr( 'type', 'text' )
.byteLimit( 12 ),
sample: mbSample,
- useLimit: true,
+ hasLimit: true,
limit: 12,
- expected: 12 // 10 x 1-byte char. The next 3-byte char exceeds limit of 12, but 2 more 1-byte chars come in after.
+ expected: '1234567890' + '12'
});
+
+byteLimitTest({
+ description: 'Pass the limit and a callback as input filter',
+ $input: $( '<input>' )
+ .attr( 'type', 'text' )
+ .byteLimit( 6, function( val ) {
+ // Invalid title
+ if ( val == '' ) {
+ return '';
+ }
+
+ // Return without namespace prefix
+ return new mw.Title( '' + val ).getMain();
+ } ),
+ sample: 'User:Sample',
+ hasLimit: true,
+ limit: 6, // 'Sample' length
+ expected: 'User:Sample'
+});
+
+byteLimitTest({
+ description: 'Limit using the maxlength attribute and pass a callback as input filter',
+ $input: $( '<input>' )
+ .attr( 'type', 'text' )
+ .prop( 'maxLength', '6' )
+ .byteLimit( function( val ) {
+ // Invalid title
+ if ( val === '' ) {
+ return '';
+ }
+
+ // Return without namespace prefix
+ return new mw.Title( '' + val ).getMain();
+ } ),
+ sample: 'User:Sample',
+ hasLimit: true,
+ limit: 6, // 'Sample' length
+ expected: 'User:Sample'
+});
+
+}() ); \ No newline at end of file
diff --git a/tests/qunit/suites/resources/jquery/jquery.client.js b/tests/qunit/suites/resources/jquery/jquery.client.test.js
index 50df2928..7be41971 100644
--- a/tests/qunit/suites/resources/jquery/jquery.client.js
+++ b/tests/qunit/suites/resources/jquery/jquery.client.test.js
@@ -1,12 +1,14 @@
-module( 'jquery.client.js' );
+module( 'jquery.client', QUnit.newMwEnvironment() );
test( '-- Initial check', function() {
expect(1);
ok( jQuery.client, 'jQuery.client defined' );
});
-test( 'profile userAgent support', function() {
- expect(8);
+/** Number of user-agent defined */
+var uacount = 0;
+
+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/
@@ -24,11 +26,32 @@ test( 'profile userAgent support', function() {
"version": "7.0",
"versionBase": "7",
"versionNumber": 7
+ },
+ 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
+ },
+ 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': {
@@ -42,6 +65,10 @@ test( 'profile userAgent support', function() {
"version": "3.5.19",
"versionBase": "3",
"versionNumber": 3.5
+ },
+ wikiEditor: {
+ ltr: true,
+ rtl: true
}
},
// Firefox 3.6
@@ -56,6 +83,10 @@ test( 'profile userAgent support', function() {
"version": "3.6.17",
"versionBase": "3",
"versionNumber": 3.6
+ },
+ wikiEditor: {
+ ltr: true,
+ rtl: true
}
},
// Firefox 4
@@ -70,7 +101,29 @@ test( 'profile userAgent support', function() {
"version": "4.0.1",
"versionBase": "4",
"versionNumber": 4
- }
+ },
+ 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
+ },
+ wikiEditor: {
+ ltr: true,
+ rtl: true
+ }
},
// Firefox 5
// Safari 3
@@ -86,7 +139,11 @@ test( 'profile userAgent support', function() {
"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',
@@ -99,6 +156,10 @@ test( 'profile userAgent support', function() {
"version": "4.0.5",
"versionBase": "4",
"versionNumber": 4
+ },
+ wikiEditor: {
+ ltr: true,
+ rtl: true
}
},
// Safari 5
@@ -122,6 +183,10 @@ test( 'profile userAgent support', function() {
"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': {
@@ -135,9 +200,19 @@ test( 'profile userAgent support', function() {
"version": "12.0.742.68",
"versionBase": "12",
"versionNumber": 12
+ },
+ wikiEditor: {
+ ltr: true,
+ rtl: true
}
}
};
+ $.each( uas, function() { uacount++ });
+ return uas;
+})();
+
+test( 'profile userAgent support', function() {
+ expect(uacount);
// Generate a client profile object and compare recursively
var uaTest = function( rawUserAgent, data ) {
@@ -168,34 +243,37 @@ test( 'profile return validation for current user agent', function() {
equal( typeof p.versionNumber, 'number', 'p.versionNumber is a number' );
});
+// 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
+ }
+};
+
test( 'test', function() {
expect(1);
- // Example from WikiEditor
- var testMap = {
- 'ltr': {
- 'msie': [['>=', 7]],
- '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
- }
- };
// .test() uses eval, make sure no exceptions are thrown
// then do a basic return value type check
var testMatch = $.client.test( testMap );
@@ -203,3 +281,29 @@ test( 'test', function() {
equal( typeof testMatch, 'boolean', 'test returns a boolean value' );
});
+
+test( 'User-agent matches against WikiEditor\'s compatibility map', function() {
+ expect( uacount * 2 ); // double since we test both LTR and RTL
+
+ var $body = $( 'body' ),
+ bodyClasses = $body.attr( 'class' );
+
+ // 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 );
+
+ equal( testMatch, data.wikiEditor[dir], 'testing comparison based on ' + dir + ', ' + agent );
+ });
+ });
+
+ // Restore body classes
+ $body.attr( 'class', bodyClasses );
+});
+
diff --git a/tests/qunit/suites/resources/jquery/jquery.colorUtil.js b/tests/qunit/suites/resources/jquery/jquery.colorUtil.test.js
index 93f12b82..655ee564 100644
--- a/tests/qunit/suites/resources/jquery/jquery.colorUtil.js
+++ b/tests/qunit/suites/resources/jquery/jquery.colorUtil.test.js
@@ -1,4 +1,4 @@
-module( 'jquery.colorUtil.js' );
+module( 'jquery.colorUtil', QUnit.newMwEnvironment() );
test( '-- Initial check', function() {
expect(1);
diff --git a/tests/qunit/suites/resources/jquery/jquery.delayedBind.test.js b/tests/qunit/suites/resources/jquery/jquery.delayedBind.test.js
index 8688f12e..6489a1f1 100644
--- a/tests/qunit/suites/resources/jquery/jquery.delayedBind.test.js
+++ b/tests/qunit/suites/resources/jquery/jquery.delayedBind.test.js
@@ -1,5 +1,5 @@
test('jquery.delayedBind with data option', function() {
- var $fixture = $('<div>').appendTo('body'),
+ var $fixture = $('<div>').appendTo('#qunit-fixture'),
data = { magic: "beeswax" },
delay = 50;
@@ -20,7 +20,7 @@ test('jquery.delayedBind with data option', function() {
});
test('jquery.delayedBind without data option', function() {
- var $fixture = $('<div>').appendTo('body'),
+ var $fixture = $('<div>').appendTo('#qunit-fixture'),
data = { magic: "beeswax" },
delay = 50;
diff --git a/tests/qunit/suites/resources/jquery/jquery.getAttrs.js b/tests/qunit/suites/resources/jquery/jquery.getAttrs.test.js
index 3d3d01e1..9377a2f6 100644
--- a/tests/qunit/suites/resources/jquery/jquery.getAttrs.js
+++ b/tests/qunit/suites/resources/jquery/jquery.getAttrs.test.js
@@ -1,4 +1,4 @@
-module( 'jquery.getAttrs.js' );
+module( 'jquery.getAttrs', QUnit.newMwEnvironment() );
test( '-- Initial check', function() {
expect(1);
diff --git a/tests/qunit/suites/resources/jquery/jquery.highlightText.test.js b/tests/qunit/suites/resources/jquery/jquery.highlightText.test.js
new file mode 100644
index 00000000..4750d2b8
--- /dev/null
+++ b/tests/qunit/suites/resources/jquery/jquery.highlightText.test.js
@@ -0,0 +1,239 @@
+module( 'jquery.highlightText', QUnit.newMwEnvironment() );
+
+test( '-- Initial check', function() {
+ expect(1);
+ ok( $.fn.highlightText, 'jQuery.fn.highlightText defined' );
+} );
+
+test( 'Check', function() {
+ var 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>ل إيردوس'
+ }
+ ];
+ expect(cases.length);
+ var $fixture;
+
+ $.each(cases, function( i, item ) {
+ $fixture = $( '<p></p>' ).text( item.text );
+ $fixture.highlightText( item.highlight );
+ equals(
+ $fixture.html(),
+ $('<p>' + item.expected + '</p>').html(), // re-parse to normalize!
+ item.desc || undefined
+ );
+ } );
+} );
diff --git a/tests/qunit/suites/resources/jquery/jquery.localize.js b/tests/qunit/suites/resources/jquery/jquery.localize.test.js
index 40b58687..cd828634 100644
--- a/tests/qunit/suites/resources/jquery/jquery.localize.js
+++ b/tests/qunit/suites/resources/jquery/jquery.localize.test.js
@@ -1,4 +1,4 @@
-module( 'jquery.localize.js' );
+module( 'jquery.localize', QUnit.newMwEnvironment() );
test( '-- Initial check', function() {
expect(1);
diff --git a/tests/qunit/suites/resources/jquery/jquery.mwPrototypes.js b/tests/qunit/suites/resources/jquery/jquery.mwExtension.test.js
index bb6d2a1b..3a2d0d83 100644
--- a/tests/qunit/suites/resources/jquery/jquery.mwPrototypes.js
+++ b/tests/qunit/suites/resources/jquery/jquery.mwExtension.test.js
@@ -1,10 +1,10 @@
-module( 'jquery.mwPrototypes.js' );
+module( 'jquery.mwExtension', QUnit.newMwEnvironment() );
test( 'String functions', function() {
equal( $.trimLeft( ' foo bar ' ), 'foo bar ', 'trimLeft' );
equal( $.trimRight( ' foo bar ' ), ' foo bar', 'trimRight' );
- equal( $.ucFirst( 'foo'), 'Foo', 'ucFirst' );
+ equal( $.ucFirst( 'foo' ), 'Foo', 'ucFirst' );
equal( $.escapeRE( '<!-- ([{+mW+}]) $^|?>' ),
'<!\\-\\- \\(\\[\\{\\+mW\\+\\}\\]\\) \\$\\^\\|\\?>', 'escapeRE - Escape specials' );
@@ -36,6 +36,8 @@ test( 'Is functions', function() {
strictEqual( $.isEmpty( 'string' ), false, 'isEmptry: "string"' );
strictEqual( $.isEmpty( '0' ), true, 'isEmptry: "0"' );
+ strictEqual( $.isEmpty( '' ), true, 'isEmptry: ""' );
+ strictEqual( $.isEmpty( 1 ), false, 'isEmptry: 1' );
strictEqual( $.isEmpty( [] ), true, 'isEmptry: []' );
strictEqual( $.isEmpty( {} ), true, 'isEmptry: {}' );
diff --git a/tests/qunit/suites/resources/jquery/jquery.tabIndex.js b/tests/qunit/suites/resources/jquery/jquery.tabIndex.test.js
index 1ff81e58..98ff5508 100644
--- a/tests/qunit/suites/resources/jquery/jquery.tabIndex.js
+++ b/tests/qunit/suites/resources/jquery/jquery.tabIndex.test.js
@@ -1,4 +1,4 @@
-module( 'jquery.tabIndex.js' );
+module( 'jquery.tabIndex', QUnit.newMwEnvironment() );
test( '-- Initial check', function() {
expect(2);
@@ -18,14 +18,11 @@ test( 'firstTabIndex', function() {
'<textarea tabindex="5">Foobar</textarea>' +
'</form>';
- var $testA = $( '<div>' ).html( testEnvironment ).appendTo( 'body' );
+ var $testA = $( '<div>' ).html( testEnvironment ).appendTo( '#qunit-fixture' );
strictEqual( $testA.firstTabIndex(), 2, 'First tabindex should be 2 within this context.' );
var $testB = $( '<div>' );
strictEqual( $testB.firstTabIndex(), null, 'Return null if none available.' );
-
- // Clean up
- $testA.add( $testB ).remove();
});
test( 'lastTabIndex', function() {
@@ -39,12 +36,9 @@ test( 'lastTabIndex', function() {
'<textarea tabindex="5">Foobar</textarea>' +
'</form>';
- var $testA = $( '<div>' ).html( testEnvironment ).appendTo( 'body' );
+ var $testA = $( '<div>' ).html( testEnvironment ).appendTo( '#qunit-fixture' );
strictEqual( $testA.lastTabIndex(), 9, 'Last tabindex should be 9 within this context.' );
var $testB = $( '<div>' );
strictEqual( $testB.lastTabIndex(), null, 'Return null if none available.' );
-
- // Clean up
- $testA.add( $testB ).remove();
});
diff --git a/tests/qunit/suites/resources/jquery/jquery.tablesorter.test.js b/tests/qunit/suites/resources/jquery/jquery.tablesorter.test.js
index f47b7f40..7ecdc4b1 100644
--- a/tests/qunit/suites/resources/jquery/jquery.tablesorter.test.js
+++ b/tests/qunit/suites/resources/jquery/jquery.tablesorter.test.js
@@ -1,11 +1,13 @@
-(function() {
+( function () {
-module( 'jquery.tablesorter.test.js' );
+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'
+};
-// setup hack
-mw.config.set('wgMonthNames', window.wgMonthNames = ['', 'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December']);
-mw.config.set('wgMonthNamesShort', window.wgMonthNamesShort = ['', 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']);
-mw.config.set('wgDefaultDateFormat', window.wgDefaultDateFormat = 'dmy');
+module( 'jquery.tablesorter', QUnit.newMwEnvironment( config ) );
test( '-- Initial check', function() {
expect(1);
@@ -21,23 +23,24 @@ test( '-- Initial check', function() {
* @return jQuery
*/
var tableCreate = function( header, data ) {
- var $table = $('<table class="sortable"><thead></thead><tbody></tbody></table>'),
- $thead = $table.find('thead'),
- $tbody = $table.find('tbody');
- var $tr = $('<tr>');
- $.each(header, function(i, str) {
- var $th = $('<th>');
- $th.text(str).appendTo($tr);
+ var $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);
+ $tr.appendTo( $thead );
for (var i = 0; i < data.length; i++) {
- $tr = $('<tr>');
- $.each(data[i], function(j, str) {
- var $td = $('<td>');
- $td.text(str).appendTo($tr);
+ $tr = $( '<tr>' );
+ $.each( data[i], function( j, str ) {
+ var $td = $( '<td>' );
+ $td.text( str ).appendTo( $tr );
});
- $tr.appendTo($tbody);
+ $tr.appendTo( $tbody );
}
return $table;
};
@@ -50,12 +53,13 @@ var tableCreate = function( header, data ) {
*/
var tableExtract = function( $table ) {
var data = [];
- $table.find('tbody').find('tr').each(function(i, tr) {
+
+ $table.find( 'tbody' ).find( 'tr' ).each( function( i, tr ) {
var row = [];
- $(tr).find('td,th').each(function(i, td) {
- row.push($(td).text());
+ $( tr ).find( 'td,th' ).each( function( i, td ) {
+ row.push( $( td ).text() );
});
- data.push(row);
+ data.push( row );
});
return data;
};
@@ -75,7 +79,6 @@ var tableTest = function( msg, header, data, expected, callback ) {
expect(1);
var $table = tableCreate( header, data );
- //$('body').append($table);
// Give caller a chance to set up sorting and manipulate the table.
callback( $table );
@@ -93,18 +96,18 @@ var reversed = function(arr) {
return arr2;
};
-// Sample data set: some planets!
-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'];
+// 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 planets = [mercury, venus, earth, mars, jupiter, saturn];
+var ascendingName = [earth, jupiter, mars, mercury, saturn, venus];
var ascendingRadius = [mercury, mars, venus, earth, saturn, jupiter];
tableTest(
@@ -114,7 +117,7 @@ tableTest(
ascendingName,
function( $table ) {
$table.tablesorter();
- $table.find('.headerSort:eq(0)').click();
+ $table.find( '.headerSort:eq(0)' ).click();
}
);
tableTest(
@@ -124,7 +127,7 @@ tableTest(
ascendingName,
function( $table ) {
$table.tablesorter();
- $table.find('.headerSort:eq(0)').click();
+ $table.find( '.headerSort:eq(0)' ).click();
}
);
tableTest(
@@ -134,7 +137,7 @@ tableTest(
reversed(ascendingName),
function( $table ) {
$table.tablesorter();
- $table.find('.headerSort:eq(0)').click().click();
+ $table.find( '.headerSort:eq(0)' ).click().click();
}
);
tableTest(
@@ -144,7 +147,7 @@ tableTest(
ascendingRadius,
function( $table ) {
$table.tablesorter();
- $table.find('.headerSort:eq(1)').click();
+ $table.find( '.headerSort:eq(1)' ).click();
}
);
tableTest(
@@ -154,25 +157,23 @@ tableTest(
reversed(ascendingRadius),
function( $table ) {
$table.tablesorter();
- $table.find('.headerSort:eq(1)').click().click();
+ $table.find( '.headerSort:eq(1)' ).click().click();
}
);
// Regression tests!
tableTest(
- 'Bug 28775: German-style short numeric dates',
+ 'Bug 28775: German-style (dmy) short numeric dates',
['Date'],
- [
- // German-style dates are day-month-year
+ [ // 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
+ [ // Sorted by ascending date
['03.08.2011'],
['02.10.2011'],
['01.11.2011'],
@@ -180,25 +181,25 @@ tableTest(
['11.11.2011']
],
function( $table ) {
- // @fixme reset it at end or change module to allow us to override it
- mw.config.set('wgDefaultDateFormat', window.wgDefaultDateFormat = 'dmy');
+ mw.config.set( 'wgDefaultDateFormat', 'dmy' );
+ mw.config.set( 'wgContentLanguage', 'de' );
+
$table.tablesorter();
- $table.find('.headerSort:eq(0)').click();
+ $table.find( '.headerSort:eq(0)' ).click();
}
);
+
tableTest(
- 'Bug 28775: American-style short numeric dates',
+ 'Bug 28775: American-style (mdy) short numeric dates',
['Date'],
- [
- // American-style dates are month-day-year
+ [ // 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
+ [ // Sorted by ascending date
['01.11.2011'],
['02.10.2011'],
['03.08.2011'],
@@ -206,10 +207,10 @@ tableTest(
['11.11.2011']
],
function( $table ) {
- // @fixme reset it at end or change module to allow us to override it
- mw.config.set('wgDefaultDateFormat', window.wgDefaultDateFormat = 'mdy');
+ mw.config.set( 'wgDefaultDateFormat', 'mdy' );
+
$table.tablesorter();
- $table.find('.headerSort:eq(0)').click();
+ $table.find( '.headerSort:eq(0)' ).click();
}
);
@@ -235,6 +236,7 @@ var ipv4Sorted = [
['204.204.132.158'],
['247.240.82.209']
];
+
tableTest(
'Bug 17141: IPv4 address sorting',
['IP'],
@@ -242,7 +244,7 @@ tableTest(
ipv4Sorted,
function( $table ) {
$table.tablesorter();
- $table.find('.headerSort:eq(0)').click();
+ $table.find( '.headerSort:eq(0)' ).click();
}
);
tableTest(
@@ -252,7 +254,7 @@ tableTest(
reversed(ipv4Sorted),
function( $table ) {
$table.tablesorter();
- $table.find('.headerSort:eq(0)').click().click();
+ $table.find( '.headerSort:eq(0)' ).click().click();
}
);
@@ -286,27 +288,36 @@ tableTest(
umlautWords,
umlautWordsSorted,
function( $table ) {
- mw.config.set('tableSorterCollation', {'ä':'ae', 'ö' : 'oe', 'ß': 'ss', 'ü':'ue'});
+ mw.config.set( 'tableSorterCollation', {
+ 'ä': 'ae',
+ 'ö': 'oe',
+ 'ß': 'ss',
+ 'ü':'ue'
+ } );
+
$table.tablesorter();
- $table.find('.headerSort:eq(0)').click();
- mw.config.set('tableSorterCollation', {});
+ $table.find( '.headerSort:eq(0)' ).click();
}
);
-var planetsRowspan =[["Earth","6051.8"], jupiter, ["Mars","6051.8"], mercury, saturn, venus];
-var planetsRowspanII =[jupiter, mercury, saturn, ['Venus', '6371.0'], venus, ['Venus', '3390.0']];
+var planetsRowspan = [["Earth","6051.8"], jupiter, ["Mars","6051.8"], mercury, saturn, venus];
+var planetsRowspanII = [jupiter, mercury, saturn, ['Venus', '6371.0'], venus, ['Venus', '3390.0']];
tableTest(
- 'Basic planet table: Same value for multiple rows via rowspan',
+ 'Basic planet table: same value for multiple rows via rowspan',
header,
planets,
planetsRowspan,
function( $table ) {
- //Quick&Dirty mod
- $table.find('tr:eq(3) td:eq(1), tr:eq(4) td:eq(1)').remove();
- $table.find('tr:eq(2) td:eq(1)').attr('rowspan', '3');
+ // Modify the table to have a multiuple-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.tablesorter();
- $table.find('.headerSort:eq(0)').click();
+ $table.find( '.headerSort:eq(0)' ).click();
}
);
tableTest(
@@ -315,11 +326,15 @@ tableTest(
planets,
planetsRowspanII,
function( $table ) {
- //Quick&Dirty mod
- $table.find('tr:eq(3) td:eq(0), tr:eq(4) td:eq(0)').remove();
- $table.find('tr:eq(2) td:eq(0)').attr('rowspan', '3');
+ // 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();
+ $table.find( '.headerSort:eq(0)' ).click();
}
);
@@ -346,9 +361,10 @@ tableTest(
complexMDYDates,
complexMDYSorted,
function( $table ) {
- mw.config.set('wgDefaultDateFormat', window.wgDefaultDateFormat = 'mdy');
+ mw.config.set( 'wgDefaultDateFormat', 'mdy' );
+
$table.tablesorter();
- $table.find('.headerSort:eq(0)').click();
+ $table.find( '.headerSort:eq(0)' ).click();
}
);
@@ -362,9 +378,9 @@ tableTest(
planets,
ascendingNameLegacy,
function( $table ) {
- $table.find('tr:last').addClass('sortbottom');
+ $table.find( 'tr:last' ).addClass( 'sortbottom' );
$table.tablesorter();
- $table.find('.headerSort:eq(0)').click();
+ $table.find( '.headerSort:eq(0)' ).click();
}
);
@@ -472,4 +488,65 @@ test( 'data-sort-value attribute, when available, should override sorting positi
});
+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();
+ }
+);
+
+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
+
+test( 'bug 32888 - Tables inside a tableheader cell', function() {
+ expect(2);
+
+ var $table;
+ $table = $(
+ '<table class="sortable" id="32888">' +
+ '<tr><th>header<table id="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();
+
+ equals(
+ $table.find('> thead:eq(0) > tr > th.headerSort').length,
+ 1,
+ 'Child tables inside a headercell should not interfere with sortable headers (bug 32888)'
+ );
+ equals(
+ $('#32888-2').find('th.headerSort').length,
+ 0,
+ 'The headers of child tables inside a headercell should not be sortable themselves (bug 32888)'
+ );
+});
+
})();
diff --git a/tests/qunit/suites/resources/jquery/jquery.textSelection.test.js b/tests/qunit/suites/resources/jquery/jquery.textSelection.test.js
new file mode 100644
index 00000000..1b2f3024
--- /dev/null
+++ b/tests/qunit/suites/resources/jquery/jquery.textSelection.test.js
@@ -0,0 +1,279 @@
+module( 'jquery.textSelection', QUnit.newMwEnvironment() );
+
+test( '-- Initial check', function() {
+ expect(1);
+ ok( $.fn.textSelection, 'jQuery.fn.textSelection defined' );
+} );
+
+/**
+ * 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' )
+ */
+var encapsulateTest = function( 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);
+
+ test( opt.description, function() {
+ var tests = 1;
+ if ( opt.after.selected !== null ) {
+ tests++;
+ }
+ 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" );
+
+ equal( text, opt.after.text, 'Checking full text after encapsulation' );
+
+ if (opt.after.selected !== null) {
+ var selected = $textarea.textSelection( 'getSelection' );
+ equal( selected, opt.after.selected, 'Checking selected text after encapsulation.' );
+ }
+
+ } );
+};
+
+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
+});
+
+
+var caretTest = function(options) {
+ test(options.description, function() {
+ expect(2);
+
+ var $textarea = $( '<textarea>' ).text(options.text);
+
+ $( '#qunit-fixture' ).append( $textarea );
+
+ if (options.mode == 'set') {
+ $textarea.textSelection('setSelection', {
+ start: options.start,
+ end: options.end
+ });
+ }
+
+ var among = function(actual, expected, message) {
+ if ($.isArray(expected)) {
+ ok($.inArray(actual, expected) !== -1 , message + ' (got ' + actual + '; expected one of ' + expected.join(', ') + ')');
+ } else {
+ 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?";
+
+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'
+});
+
diff --git a/tests/qunit/suites/resources/mediawiki.special/mediawiki.special.recentchanges.js b/tests/qunit/suites/resources/mediawiki.special/mediawiki.special.recentchanges.test.js
index bcc9b96b..d73fe5a6 100644
--- a/tests/qunit/suites/resources/mediawiki.special/mediawiki.special.recentchanges.js
+++ b/tests/qunit/suites/resources/mediawiki.special/mediawiki.special.recentchanges.test.js
@@ -1,13 +1,9 @@
-module( 'mediawiki.special.recentchanges.js' );
+module( 'mediawiki.special.recentchanges', QUnit.newMwEnvironment() );
test( '-- Initial check', function() {
expect( 2 );
- ok( mw.special.recentchanges.init,
- 'mw.special.recentchanges.init defined'
- );
- ok( mw.special.recentchanges.updateCheckboxes,
- 'mw.special.recentchanges.updateCheckboxes defined'
- );
+ ok( mw.special.recentchanges.init, 'mw.special.recentchanges.init defined' );
+ ok( mw.special.recentchanges.updateCheckboxes, 'mw.special.recentchanges.updateCheckboxes defined' );
// TODO: verify checkboxes == [ 'nsassociated', 'nsinvert' ]
});
@@ -37,34 +33,34 @@ test( '"all" namespace disable checkboxes', function() {
// TODO abstract the double strictEquals
// At first checkboxes are enabled
- strictEqual( $( '#nsinvert' ).attr( 'disabled' ), undefined );
- strictEqual( $( '#nsassociated' ).attr( 'disabled' ), undefined );
+ strictEqual( $( '#nsinvert' ).prop( 'disabled' ), false );
+ strictEqual( $( '#nsassociated' ).prop( 'disabled' ), false );
// Initiate the recentchanges module
mw.special.recentchanges.init();
// By default
- strictEqual( $( '#nsinvert' ).attr( 'disabled' ), 'disabled' );
- strictEqual( $( '#nsassociated' ).attr( 'disabled' ), 'disabled' );
+ strictEqual( $( '#nsinvert' ).prop( 'disabled' ), true );
+ strictEqual( $( '#nsassociated' ).prop( 'disabled' ), true );
// select second option...
var $options = $( '#namespace' ).find( 'option' );
- $options.eq(0).removeAttr( 'selected' );
- $options.eq(1).attr( 'selected', 'selected' );
+ $options.eq(0).removeProp( 'selected' );
+ $options.eq(1).prop( 'selected', true );
$( '#namespace' ).change();
// ... and checkboxes should be enabled again
- strictEqual( $( '#nsinvert' ).attr( 'disabled' ), undefined );
- strictEqual( $( '#nsassociated' ).attr( 'disabled' ), undefined );
+ strictEqual( $( '#nsinvert' ).prop( 'disabled' ), false );
+ strictEqual( $( '#nsassociated' ).prop( 'disabled' ), false );
// select first option ( 'all' namespace)...
- $options.eq(1).removeAttr( 'selected' );
- $options.eq(0).attr( 'selected', 'selected' );;
+ $options.eq(1).removeProp( 'selected' );
+ $options.eq(0).prop( 'selected', true );
$( '#namespace' ).change();
-
+
// ... and checkboxes should now be disabled
- strictEqual( $( '#nsinvert' ).attr( 'disabled' ), 'disabled' );
- strictEqual( $( '#nsassociated' ).attr( 'disabled' ), 'disabled' );
+ strictEqual( $( '#nsinvert' ).prop( 'disabled' ), true );
+ strictEqual( $( '#nsassociated' ).prop( 'disabled' ), true );
// DOM cleanup
$env.remove();
diff --git a/tests/qunit/suites/resources/mediawiki/mediawiki.Title.test.js b/tests/qunit/suites/resources/mediawiki/mediawiki.Title.test.js
new file mode 100644
index 00000000..e04111f1
--- /dev/null
+++ b/tests/qunit/suites/resources/mediawiki/mediawiki.Title.test.js
@@ -0,0 +1,201 @@
+( 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"
+ },
+ "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
+ },
+ "wgCaseSensitiveNamespaces": []
+};
+
+module( 'mediawiki.Title', QUnit.newMwEnvironment( config ) );
+
+test( '-- Initial check', function () {
+ expect(1);
+ ok( mw.Title, 'mw.Title defined' );
+});
+
+test( 'Transformation', function () {
+ expect(8);
+
+ var title;
+
+ title = new mw.Title( 'File:quux pif.jpg' );
+ equal( title.getName(), 'Quux_pif' );
+
+ title = new mw.Title( 'File:Glarg_foo_glang.jpg' );
+ equal( title.getNameText(), 'Glarg foo glang' );
+
+ title = new mw.Title( 'User:ABC.DEF' );
+ equal( title.toText(), 'User:ABC.DEF' );
+ equal( title.getNamespaceId(), 2 );
+ equal( title.getNamespacePrefix(), 'User:' );
+
+ title = new mw.Title( 'uSEr:hAshAr' );
+ equal( title.toText(), 'User:HAshAr' );
+ 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
+ equal( title.getName(), 'Foo_bar_.js', "Merge multiple spaces to a single space." );
+});
+
+test( 'Main text for filename', function () {
+ expect(8);
+
+ var title = new mw.Title( 'File:foo_bar.JPG' );
+
+ equal( title.getNamespaceId(), 6 );
+ equal( title.getNamespacePrefix(), 'File:' );
+ equal( title.getName(), 'Foo_bar' );
+ equal( title.getNameText(), 'Foo bar' );
+ equal( title.getMain(), 'Foo_bar.JPG' );
+ equal( title.getMainText(), 'Foo bar.JPG' );
+ equal( title.getExtension(), 'JPG' );
+ equal( title.getDotExtension(), '.JPG' );
+});
+
+test( 'Namespace detection and conversion', function () {
+ expect(6);
+
+ var title;
+
+ title = new mw.Title( 'something.PDF', 6 );
+ equal( title.toString(), 'File:Something.PDF' );
+
+ title = new mw.Title( 'NeilK', 3 );
+ equal( title.toString(), 'User_talk:NeilK' );
+ equal( title.toText(), 'User talk:NeilK' );
+
+ title = new mw.Title( 'Frobisher', 100 );
+ equal( title.toString(), 'Penguins:Frobisher' );
+
+ title = new mw.Title( 'antarctic_waterfowl:flightless_yet_cute.jpg' );
+ equal( title.toString(), 'Penguins:Flightless_yet_cute.jpg' );
+
+ title = new mw.Title( 'Penguins:flightless_yet_cute.jpg' );
+ equal( title.toString(), 'Penguins:Flightless_yet_cute.jpg' );
+});
+
+test( 'Throw error on invalid title', function () {
+ expect(1);
+
+ raises(function () {
+ var title = new mw.Title( '' );
+ }, 'Throw error on empty string' );
+});
+
+test( 'Case-sensivity', function () {
+ expect(3);
+
+ var title;
+
+ // Default config
+ mw.config.set( 'wgCaseSensitiveNamespaces', [] );
+
+ title = new mw.Title( 'article' );
+ 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' );
+ equal( title.toString(), 'article', '$wgCapitalLinks=false: Article namespace is sensitive, first-letter case stays lowercase' );
+
+ title = new mw.Title( 'john', 2 );
+ equal( title.toString(), 'User:John', '$wgCapitalLinks=false: User namespace is insensitive, first-letter becomes uppercase' );
+});
+
+test( 'toString / toText', function () {
+ expect(2);
+
+ var title = new mw.Title( 'Some random page' );
+
+ equal( title.toString(), title.getPrefixedDb() );
+ equal( title.toText(), title.getPrefixedText() );
+});
+
+test( 'Exists', function () {
+ expect(3);
+
+ var title;
+
+ // Empty registry, checks default to null
+
+ title = new mw.Title( 'Some random page', 4 );
+ 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' );
+ assertTrue( title.exists(), 'Return true for page titles marked as existing' );
+ title = new mw.Title( 'Foobar' );
+ assertFalse( title.exists(), 'Return false for page titles marked as nonexistent' );
+
+});
+
+test( 'Url', function () {
+ expect(2);
+
+ var title;
+
+ // Config
+ mw.config.set( 'wgArticlePath', '/wiki/$1' );
+
+ title = new mw.Title( 'Foobar' );
+ equal( title.getUrl(), '/wiki/Foobar', 'Basic functionally, toString passing to wikiGetlink' );
+
+ title = new mw.Title( 'John Doe', 3 );
+ equal( title.getUrl(), '/wiki/User_talk:John_Doe', 'Escaping in title and namespace for urls' );
+});
+
+}() ); \ No newline at end of file
diff --git a/tests/qunit/suites/resources/mediawiki/mediawiki.jqueryMsg.test.js b/tests/qunit/suites/resources/mediawiki/mediawiki.jqueryMsg.test.js
new file mode 100644
index 00000000..265ec2ae
--- /dev/null
+++ b/tests/qunit/suites/resources/mediawiki/mediawiki.jqueryMsg.test.js
@@ -0,0 +1,43 @@
+module( 'mediawiki.jqueryMsg' );
+
+test( '-- Initial check', function() {
+ expect( 1 );
+ ok( mw.jqueryMsg, 'mw.jqueryMsg defined' );
+} );
+
+test( 'mw.jqueryMsg Plural', function() {
+ expect( 5 );
+ var parser = mw.jqueryMsg.getMessageFunction();
+ ok( parser, 'Parser Function initialized' );
+ ok( mw.messages.set( 'plural-msg', 'Found $1 {{PLURAL:$1|item|items}}' ), 'mw.messages.set: Register' );
+ equal( parser( 'plural-msg', 0 ) , 'Found 0 items', 'Plural test for english with zero as count' );
+ equal( parser( 'plural-msg', 1 ) , 'Found 1 item', 'Singular test for english' );
+ equal( parser( 'plural-msg', 2 ) , 'Found 2 items', 'Plural test for english' );
+} );
+
+
+test( 'mw.jqueryMsg Gender', function() {
+ expect( 16 );
+ //TODO: These tests should be for mw.msg once mw.msg integrated with mw.jqueryMsg
+ var user = mw.user;
+ user.options.set( 'gender', 'male' );
+ var parser = mw.jqueryMsg.getMessageFunction();
+ ok( parser, 'Parser Function initialized' );
+ //TODO: English may not be the best language for these tests. Use a language like Arabic or Russian
+ ok( mw.messages.set( 'gender-msg', '$1 reverted {{GENDER:$2|his|her|their}} last edit' ), 'mw.messages.set: Register' );
+ equal( parser( 'gender-msg', 'Bob', 'male' ) , 'Bob reverted his last edit', 'Gender masculine' );
+ equal( parser( 'gender-msg', 'Bob', user ) , 'Bob reverted his last edit', 'Gender masculine' );
+ user.options.set( 'gender', 'unknown' );
+ equal( parser( 'gender-msg', 'They', user ) , 'They reverted their last edit', 'Gender neutral or unknown' );
+ equal( parser( 'gender-msg', 'Alice', 'female' ) , 'Alice reverted her last edit', 'Gender feminine' );
+ equal( parser( 'gender-msg', 'User' ) , 'User reverted their last edit', 'Gender neutral' );
+ equal( parser( 'gender-msg', 'User', 'unknown' ) , 'User reverted their last edit', 'Gender neutral' );
+ ok( mw.messages.set( 'gender-msg-one-form', '{{GENDER:$1|User}} reverted last $2 {{PLURAL:$2|edit|edits}}' ), 'mw.messages.set: Register' );
+ equal( parser( 'gender-msg-one-form', 'male', 10 ) , 'User reverted last 10 edits', 'Gender neutral and plural form' );
+ equal( parser( 'gender-msg-one-form', 'female', 1 ) , 'User reverted last 1 edit', 'Gender neutral and singular form' );
+ ok( mw.messages.set( 'gender-msg-lowercase', '{{gender:$1|he|she}} is awesome' ), 'mw.messages.set: Register' );
+ equal( parser( 'gender-msg-lowercase', 'male' ) , 'he is awesome', 'Gender masculine' );
+ equal( parser( 'gender-msg-lowercase', 'female' ) , 'she is awesome', 'Gender feminine' );
+ ok( mw.messages.set( 'gender-msg-wrong', '{{gender}} is awesome' ), 'mw.messages.set: Register' );
+ equal( parser( 'gender-msg-wrong', 'female' ) , ' is awesome', 'Wrong syntax used, but ignore the {{gender}}' );
+} );
diff --git a/tests/qunit/suites/resources/mediawiki/mediawiki.jscompat.test.js b/tests/qunit/suites/resources/mediawiki/mediawiki.jscompat.test.js
index 52cd32c8..24005b64 100644
--- a/tests/qunit/suites/resources/mediawiki/mediawiki.jscompat.test.js
+++ b/tests/qunit/suites/resources/mediawiki/mediawiki.jscompat.test.js
@@ -1,6 +1,6 @@
/* Some misc JavaScript compatibility tests, just to make sure the environments we run in are consistent */
-module( 'mediawiki.jscompat' );
+module( 'mediawiki.jscompat', QUnit.newMwEnvironment() );
test( 'Variable with Unicode letter in name', function() {
expect(3);
@@ -33,3 +33,30 @@ test( 'Keyword workaround: "if" as member variable name using Unicode escapes',
deepEqual( foo.\u0069\u0066, orig, 'foo.\\u0069\\u0066' );
});
*/
+
+test( 'Stripping of single initial newline from textarea\'s literal contents (bug 12130)', function() {
+ var maxn = 4;
+ expect(maxn * 2);
+
+ var repeat = function(str, n) {
+ if (n <= 0) {
+ return '';
+ } else {
+ var out = Array(n);
+ for (var i = 0; i < n; i++) {
+ out[i] = str;
+ }
+ return out.join('');
+ }
+ };
+
+ for (var n = 0; n < maxn; n++) {
+ var expected = repeat('\n', n) + 'some text';
+
+ var $textarea = $('<textarea>\n' + expected + '</textarea>');
+ equal($textarea.val(), expected, 'Expecting ' + n + ' newlines (HTML contained ' + (n + 1) + ')');
+
+ var $textarea2 = $('<textarea>').val(expected);
+ equal($textarea2.val(), expected, 'Expecting ' + n + ' newlines (from DOM set with ' + n + ')');
+ }
+});
diff --git a/tests/qunit/suites/resources/mediawiki/mediawiki.js b/tests/qunit/suites/resources/mediawiki/mediawiki.test.js
index 4beed881..e6934eda 100644
--- a/tests/qunit/suites/resources/mediawiki/mediawiki.js
+++ b/tests/qunit/suites/resources/mediawiki/mediawiki.test.js
@@ -1,4 +1,4 @@
-module( 'mediawiki.js' );
+module( 'mediawiki', QUnit.newMwEnvironment() );
test( '-- Initial check', function() {
expect(8);
@@ -80,7 +80,7 @@ test( 'mw.config', function() {
});
test( 'mw.message & mw.messages', function() {
- expect(17);
+ expect(20);
ok( mw.messages, 'messages defined' );
ok( mw.messages instanceof mw.Map, 'mw.messages instance of mw.Map' );
@@ -88,7 +88,7 @@ test( 'mw.message & mw.messages', function() {
var hello = mw.message( 'hello' );
- equal( hello.format, 'parse', 'Message property "format" defaults to "parse"' );
+ equal( hello.format, 'plain', 'Message property "format" defaults to "plain"' );
strictEqual( hello.map, mw.messages, 'Message property "map" defaults to the global instance in mw.messages' );
equal( hello.key, 'hello', 'Message property "key" (currect key)' );
deepEqual( hello.parameters, [], 'Message property "parameters" defaults to an empty array' );
@@ -111,59 +111,45 @@ test( 'mw.message & mw.messages', function() {
strictEqual( hello.exists(), true, 'Message.exists returns true for existing messages' );
var goodbye = mw.message( 'goodbye' );
- strictEqual( goodbye.exists(), false, 'Message.exists returns false for inexisting messages' );
+ strictEqual( goodbye.exists(), false, 'Message.exists returns false for nonexistent messages' );
equal( goodbye.plain(), '<goodbye>', 'Message.toString returns plain <key> if format is "plain" and key does not exist' );
// bug 30684
equal( goodbye.escaped(), '&lt;goodbye&gt;', 'Message.toString returns properly escaped &lt;key&gt; if format is "escaped" and key does not exist' );
+
+ ok( mw.messages.set( 'pluraltestmsg', 'There {{PLURAL:$1|is|are}} $1 {{PLURAL:$1|result|results}}' ), 'mw.messages.set: Register' );
+ var pluralMessage = mw.message( 'pluraltestmsg' , 6 );
+ equal( pluralMessage.plain(), 'There are 6 results', 'plural get resolved when format is plain' );
+ equal( pluralMessage.parse(), 'There are 6 results', 'plural get resolved when format is parse' );
+
});
test( 'mw.msg', function() {
- expect(3);
+ expect(11);
ok( mw.messages.set( 'hello', 'Hello <b>awesome</b> world' ), 'mw.messages.set: Register' );
-
equal( mw.msg( 'hello' ), 'Hello <b>awesome</b> world', 'Gets message with default options (existing message)' );
- equal( mw.msg( 'goodbye' ), '<goodbye>', 'Gets message with default options (inexisting message)' );
-});
+ equal( mw.msg( 'goodbye' ), '<goodbye>', 'Gets message with default options (nonexistent message)' );
-test( 'mw.loader', function() {
- expect(5);
+ ok( mw.messages.set( 'plural-item' , 'Found $1 {{PLURAL:$1|item|items}}' ) );
+ equal( mw.msg( 'plural-item', 5 ), 'Found 5 items', 'Apply plural for count 5' );
+ equal( mw.msg( 'plural-item', 0 ), 'Found 0 items', 'Apply plural for count 0' );
+ equal( mw.msg( 'plural-item', 1 ), 'Found 1 item', 'Apply plural for count 1' );
- // Regular expression to extract the path for the QUnit tests
- // Takes into account that tests could be run from a file:// URL
- // by excluding the 'index.html' part from the URL
- var rePath = /(?:[^#\?](?!index.html))*\/?/;
+ ok( mw.messages.set('gender-plural-msg' , '{{GENDER:$1|he|she|they}} {{PLURAL:$2|is|are}} awesome' ) );
+ equal( mw.msg( 'gender-plural-msg', 'male', 1 ), 'he is awesome', 'Gender test for male, plural count 1' );
+ equal( mw.msg( 'gender-plural-msg', 'female', '1' ), 'she is awesome', 'Gender test for female, plural count 1' );
+ equal( mw.msg( 'gender-plural-msg', 'unknown', 10 ), 'they are awesome', 'Gender test for neutral, plural count 10' );
- // Four assertions to test the above regular expression:
- equal(
- rePath.exec( 'http://path/to/tests/?foobar' )[0],
- "http://path/to/tests/",
- "Extracting path from http URL with query"
- );
- equal(
- rePath.exec( 'http://path/to/tests/#frag' )[0],
- "http://path/to/tests/",
- "Extracting path from http URL with fragment"
- );
- equal(
- rePath.exec( 'file://path/to/tests/index.html?foobar' )[0],
- "file://path/to/tests/",
- "Extracting path from local URL (file://) with query"
- );
- equal(
- rePath.exec( 'file://path/to/tests/index.html#frag' )[0],
- "file://path/to/tests/",
- "Extracting path from local URL (file://) with fragment"
- );
+});
- // Asynchronous ahead
- stop(5000);
+test( 'mw.loader', function() {
+ expect(1);
- // Extract path
- var tests_path = rePath.exec( location.href );
+ // Asynchronous ahead
+ stop();
- mw.loader.implement( 'is.awesome', [QUnit.fixurl( tests_path + 'data/defineTestCallback.js')], {}, {} );
+ mw.loader.implement( 'is.awesome', [QUnit.fixurl( mw.config.get( 'wgScriptPath' ) + '/tests/qunit/data/defineTestCallback.js' )], {}, {} );
mw.loader.using( 'is.awesome', function() {
@@ -185,9 +171,9 @@ test( 'mw.loader.bug29107' , function() {
// Message doesn't exist already
ok( !mw.messages.exists( 'bug29107' ) );
- // Async! Include a timeout, as failure in this test leads to neither the
- // success nor failure callbacks getting called.
- stop(5000);
+ // Async! Failure in this test may lead to neither the success nor error callbacks getting called.
+ // Due to QUnit's timeout feauture we won't hang here forever if this happends.
+ stop();
mw.loader.implement( 'bug29107.messages-only', [], {}, {'bug29107': 'loaded'} );
mw.loader.using( 'bug29107.messages-only', function() {
@@ -199,8 +185,31 @@ test( 'mw.loader.bug29107' , function() {
});
});
+test( 'mw.loader.bug30825', function() {
+ // This bug was actually already fixed in 1.18 and later when discovered in 1.17.
+ // Test is for regressions!
+
+ expect(2);
+
+ // 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?:/, '' );
+
+ equal( target.substr( 0, 2 ), '//',
+ 'URL must be relative to test relative URLs!'
+ );
+
+ // Async!
+ stop();
+ mw.loader.load( target );
+});
+
test( 'mw.html', function() {
- expect(7);
+ expect(11);
raises( function(){
mw.html.escape();
@@ -214,11 +223,40 @@ test( 'mw.html', function() {
equal( mw.html.element( 'div' ), '<div/>', 'html.element DIV (simple)' );
- equal( mw.html.element( 'div',
- { id: 'foobar' } ),
+ equal(
+ mw.html.element(
+ 'div', {
+ id: 'foobar'
+ }
+ ),
'<div id="foobar"/>',
'html.element DIV (attribs)' );
+ equal( mw.html.element( 'p', null, 12 ), '<p>12</p>', 'Numbers are valid content and should be casted to a string' );
+
+ equal( mw.html.element( 'p', { title: 12 }, '' ), '<p title="12"></p>', 'Numbers are valid attribute values' );
+
+ 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.'
+ );
+
+ 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.'
+ );
+
equal( mw.html.element( 'div',
null, 'a' ),
'<div>a</div>',
diff --git a/tests/qunit/suites/resources/mediawiki/mediawiki.user.js b/tests/qunit/suites/resources/mediawiki/mediawiki.user.test.js
index d5c6baad..15265db5 100644
--- a/tests/qunit/suites/resources/mediawiki/mediawiki.user.js
+++ b/tests/qunit/suites/resources/mediawiki/mediawiki.user.test.js
@@ -1,4 +1,4 @@
-module( 'mediawiki.user.js' );
+module( 'mediawiki.user', QUnit.newMwEnvironment() );
test( '-- Initial check', function() {
expect(1);
@@ -16,6 +16,16 @@ test( 'options', function() {
test( 'User login status', function() {
expect(5);
+ /**
+ * 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);
+
strictEqual( mw.user.name(), null, 'user.name should return null when anonymous' );
ok( mw.user.anonymous(), 'user.anonymous should reutrn true when anonymous' );
diff --git a/tests/qunit/suites/resources/mediawiki/mediawiki.util.js b/tests/qunit/suites/resources/mediawiki/mediawiki.util.test.js
index 9c05d9b2..ea28935e 100644
--- a/tests/qunit/suites/resources/mediawiki/mediawiki.util.js
+++ b/tests/qunit/suites/resources/mediawiki/mediawiki.util.test.js
@@ -1,4 +1,4 @@
-module( 'mediawiki.util.js' );
+module( 'mediawiki.util', QUnit.newMwEnvironment() );
test( '-- Initial check', function() {
expect(1);
@@ -47,13 +47,12 @@ test( 'wikiScript', function() {
equal( mw.util.wikiScript(), mw.config.get( 'wgScript' ), 'Defaults to index.php and is equal to wgScript' );
equal( mw.util.wikiScript( 'api' ), '/w/api.php', 'API path' );
-
});
test( 'addCSS', function() {
expect(3);
- var $testEl = $( '<div>' ).attr( 'id', 'mw-addcsstest' ).appendTo( 'body' );
+ var $testEl = $( '<div>' ).attr( 'id', 'mw-addcsstest' ).appendTo( '#qunit-fixture' );
var style = mw.util.addCSS( '#mw-addcsstest { visibility: hidden; }' );
equal( typeof style, 'object', 'addCSS returned an object' );
@@ -62,9 +61,7 @@ test( 'addCSS', function() {
equal( $testEl.css( 'visibility' ), 'hidden', 'Added style properties are in effect' );
// Clean up
- $( style.ownerNode )
- .add( $testEl )
- .remove();
+ $( style.ownerNode ).remove();
});
test( 'toggleToc', function() {
@@ -80,7 +77,7 @@ test( 'toggleToc', function() {
'</div>' +
'<ul><li></li></ul>' +
'</td></tr></table>',
- $toc = $(tocHtml).appendTo( 'body' ),
+ $toc = $(tocHtml).appendTo( '#qunit-fixture' ),
$toggleLink = $( '#togglelink' );
strictEqual( $toggleLink.length, 1, 'Toggle link is appended to the page.' );
@@ -91,9 +88,6 @@ test( 'toggleToc', function() {
var actionC = function() {
start();
-
- // Clean up
- $toc.remove();
};
var actionB = function() {
start(); stop();
@@ -109,18 +103,18 @@ test( 'toggleToc', function() {
test( 'getParamValue', function() {
expect(5);
- var url1 = 'http://mediawiki.org/?foo=wrong&foo=right#&foo=bad';
+ var url1 = 'http://example.org/?foo=wrong&foo=right#&foo=bad';
equal( mw.util.getParamValue( 'foo', url1 ), 'right', 'Use latest one, ignore hash' );
strictEqual( mw.util.getParamValue( 'bar', url1 ), null, 'Return null when not found' );
- var url2 = 'http://mediawiki.org/#&foo=bad';
+ var url2 = 'http://example.org/#&foo=bad';
strictEqual( mw.util.getParamValue( 'foo', url2 ), null, 'Ignore hash if param is not in querystring but in hash (bug 27427)' );
- var url3 = 'example.com?' + $.param({ 'TEST': 'a b+c' });
+ var url3 = 'example.org?' + $.param({ 'TEST': 'a b+c' });
strictEqual( mw.util.getParamValue( 'TEST', url3 ), 'a b+c', 'Bug 30441: getParamValue must understand "+" encoding of space' );
- var url4 = 'example.com?' + $.param({ 'TEST': 'a b+c d' }); // check for sloppy code from r95332 :)
+ var url4 = 'example.org?' + $.param({ 'TEST': 'a b+c d' }); // check for sloppy code from r95332 :)
strictEqual( mw.util.getParamValue( 'TEST', url4 ), 'a b+c d', 'Bug 30441: getParamValue must understand "+" encoding of space (multiple spaces)' );
});
@@ -139,51 +133,55 @@ test( '$content', function() {
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.
+ */
test( 'addPortletLink', function() {
expect(7);
var mwPanel = '<div id="mw-panel" class="noprint">\
<h5>Toolbox</h5>\
- <div class="portlet" id="p-tb">\
+ <div class="portlet" id="p-test-tb">\
<ul class="body"></ul>\
</div>\
</div>',
- vectorTabs = '<div id="p-views" class="vectorTabs">\
+ vectorTabs = '<div id="p-test-views" class="vectorTabs">\
<h5>Views</h5>\
<ul></ul>\
</div>',
- $mwPanel = $(mwPanel).appendTo( 'body' ),
- $vectorTabs = $(vectorTabs).appendTo( 'body' );
+ $mwPanel = $(mwPanel).appendTo( '#qunit-fixture' ),
+ $vectorTabs = $(vectorTabs).appendTo( '#qunit-fixture' );
- var tbRL = mw.util.addPortletLink( 'p-tb', 'http://mediawiki.org/wiki/ResourceLoader',
+ var tbRL = mw.util.addPortletLink( 'p-test-tb', '//mediawiki.org/wiki/ResourceLoader',
'ResourceLoader', 't-rl', 'More info about ResourceLoader on MediaWiki.org ', 'l' );
ok( $.isDomElement( tbRL ), 'addPortletLink returns a valid DOM Element according to $.isDomElement' );
- var tbMW = mw.util.addPortletLink( 'p-tb', 'http://mediawiki.org/',
+ var tbMW = mw.util.addPortletLink( 'p-test-tb', '//mediawiki.org/',
'MediaWiki.org', 't-mworg', 'Go to MediaWiki.org ', 'm', tbRL ),
$tbMW = $( tbMW );
-
+
equal( $tbMW.attr( 'id' ), 't-mworg', 'Link has correct ID set' );
- equal( $tbMW.closest( '.portlet' ).attr( 'id' ), 'p-tb', 'Link was inserted within correct portlet' );
+ equal( $tbMW.closest( '.portlet' ).attr( 'id' ), 'p-test-tb', 'Link was inserted within correct portlet' );
equal( $tbMW.next().attr( 'id' ), 't-rl', 'Link is in the correct position (by passing nextnode)' );
- var tbRLDM = mw.util.addPortletLink( 'p-tb', 'http://mediawiki.org/wiki/RL/DM',
+ var tbRLDM = mw.util.addPortletLink( 'p-test-tb', '//mediawiki.org/wiki/RL/DM',
'Default modules', 't-rldm', 'List of all default modules ', 'd', '#t-rl' );
equal( $( tbRLDM ).next().attr( 'id' ), 't-rl', 'Link is in the correct position (by passing CSS selector)' );
- var caFoo = mw.util.addPortletLink( 'p-views', '#', 'Foo' );
+ var caFoo = mw.util.addPortletLink( 'p-test-views', '#', 'Foo' );
strictEqual( $tbMW.find( 'span').length, 0, 'No <span> element should be added for porlets without vectorTabs class.' );
strictEqual( $( caFoo ).find( 'span').length, 1, 'A <span> element should be added for porlets with vectorTabs class.' );
-
+
// Clean up
- $( [tbRL, tbMW, tbRLDM, caFoo] )
- .add( $mwPanel )
- .add( $vectorTabs )
- .remove();
+ $( [tbRL, tbMW, tbRLDM, caFoo] ).remove();
});
test( 'jsMessage', function() {
diff --git a/tests/selenium/SeleniumConfig.php b/tests/selenium/SeleniumConfig.php
index b8cdf1c5..b1487154 100644
--- a/tests/selenium/SeleniumConfig.php
+++ b/tests/selenium/SeleniumConfig.php
@@ -5,7 +5,7 @@ if ( !defined( 'SELENIUMTEST' ) ) {
class SeleniumConfig {
- /*
+ /**
* Retreives the Selenium configuration values from an ini file.
* See sample config file in selenium_settings.ini.sample
*
@@ -72,14 +72,14 @@ class SeleniumConfig {
return false;
}
$header = '';
-
+
$configArray = array();
-
+
while ( ( $line = fgets( $file ) ) !== false ) {
$line = strtok( $line, "\r\n" );
-
+
if ( !$line || $line[0] == ';' ) continue;
-
+
if ( $line[0] == '[' && substr( $line, -1 ) == ']' ) {
$header = substr( $line, 1, -1 );
$configArray[$header] = array();
@@ -95,19 +95,19 @@ class SeleniumConfig {
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/data/SimpleSeleniumTestDB.sql b/tests/selenium/data/SimpleSeleniumTestDB.sql
index 7944c45f..1a3196c3 100644
--- a/tests/selenium/data/SimpleSeleniumTestDB.sql
+++ b/tests/selenium/data/SimpleSeleniumTestDB.sql
@@ -1295,7 +1295,6 @@ CREATE TABLE `mw_user` (
`user_newpassword` tinyblob NOT NULL,
`user_newpass_time` binary(14) DEFAULT NULL,
`user_email` tinytext NOT NULL,
- `user_options` blob 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,
diff --git a/tests/selenium/data/mediawiki118_fresh_installation.sql b/tests/selenium/data/mediawiki118_fresh_installation.sql
index 89bc3191..2724bad5 100644
--- a/tests/selenium/data/mediawiki118_fresh_installation.sql
+++ b/tests/selenium/data/mediawiki118_fresh_installation.sql
@@ -767,13 +767,13 @@ CREATE 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','K2.2R\ns\r\nSδ2\0','2010-12-31 13:16:31');
-INSERT INTO `mw_objectcache` VALUES ('test_wiki-mw_:pcache:idhash:1-0!*!*!!en!*','V[oFg~BPm)$TKU\08$9a\rIlX>ܹqSa,ʕ x?[~ʃ|.Z$8Y\'IYK-\04UJ\'&uB):IEmsk``kQva\rZt0+P%GEـJX;\n-sP@Bb8~첒$eaυf+0[,Fxd\'z0BJ=Jc\\:&BT\'CFdÿ׆FqGd%8G0AI; Ԙ`75LI\r({cg+8Qr&ͦA)VЕPT\\UƧtnZeSfJZ(VP}0ON =j\\H y\\U[h]T: bdu+j%\'6kf:E;@Yך4Ȁq Zº6<b3TU(d,\nY|e\'5TfU8}\"m/}Uk9o;|*R?n 3dg1y\\f8gkw=:/Y7ۋ^<Ōv#iC#6\Z.0Ua$4=\Z;4Y=5:kpΐqŦ4XCqYߵ-LjDf :(3t14CJ#WXTΔy:^6v7IUEe(p0ga6MjSc,ѫ@ޅ+RAxХ\'6utǷbۛ`j8ؚGIC<KS5|krJ\ry\\b3xPua@$SS`tQ.gwW\r@\'w xZ(>5{ dw>=J)\r6t XMB\nŖTb>qg 7 zn7vwr-%u-QiiX1NeA#vӧ3?','2010-12-31 13:16:31');
-INSERT INTO `mw_objectcache` VALUES ('test_wiki-mw_:pcache:idoptions:1','E@ D` \"vƣWh,b!⭙7L+|}t I$<F\rpSl4OJN`\r\Z ծ)PY$Kգ9Vjp72EcWp2cVxu7 p#r=.[>y)Zp','2010-12-31 13:16:31');
-INSERT INTO `mw_objectcache` VALUES ('test_wiki-mw_:resourceloader:filter:minify-css:3832ee25d9c44988461f5f339b9b6a48','+26RrMMLTHɩV\0Z((3(Rd\r\0','2038-01-19 03:14:07');
-INSERT INTO `mw_objectcache` VALUES ('test_wiki-mw_:resourceloader:filter:minify-css:aa0df16258ad99a1d249e796b5067ed9','+2RrMMLTHɩNK-Q.,LNJ,R\0s򋬔\rV\Z\0','2038-01-19 03:14:07');
-INSERT INTO `mw_objectcache` VALUES ('test_wiki-mw_:resourceloader:filter:minify-js:22814eeadc9cf0a9ebcd844e14198e66','mr0 y r&Qޡמ!\nqQXq;}$ވ c!]]].o5S\n)Fq L^?sF!OM\\\0NɁլ:-jF{ۅG\"i \Z6K!Y]=F[~竍䶃`9NǴ@K|z1A@J#_ԁ7\'l1) J͵).3 zfTAHњ[#)BzRA7֌\"T*~SW/PBŎ;\Zay6+U?.$6-uTv@hs&NإbfJ~]6p/q)>E1͔A\neLg\ZE`cW`fJEa>b\nӑd.udo[\ntb+l\Z?X*Y(օ;LJqťɝd$\"WzG-@b~+#kǞَƂ~P)B qҖ2rRl`z 4ÝXm;X݁t;r.sARy)kA\nRJTJU*W_ߟ4@vtf>x','2038-01-19 03:14:07');
-INSERT INTO `mw_objectcache` VALUES ('test_wiki-mw_:resourceloader:filter:minify-js:dd9440c19c575629ac5ec90e489cf62e','+21RԔLĔ\"ĒTj̒T%+ĔJZMk.%k\0','2038-01-19 03:14:07');
+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�͵�).�3 z�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;
@@ -1384,7 +1384,6 @@ CREATE TABLE `mw_user` (
`user_newpassword` tinyblob NOT NULL,
`user_newpass_time` binary(14) DEFAULT NULL,
`user_email` tinytext NOT NULL,
- `user_options` blob 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,
diff --git a/tests/selenium/installer/MediaWikiButtonsAvailabilityTestCase.php b/tests/selenium/installer/MediaWikiButtonsAvailabilityTestCase.php
index 3557f516..bf5b379d 100644
--- a/tests/selenium/installer/MediaWikiButtonsAvailabilityTestCase.php
+++ b/tests/selenium/installer/MediaWikiButtonsAvailabilityTestCase.php
@@ -30,7 +30,7 @@
require_once (dirname(__FILE__).'/'.'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
diff --git a/tests/selenium/installer/MediaWikiDifferentDatabaseAccountTestCase.php b/tests/selenium/installer/MediaWikiDifferentDatabaseAccountTestCase.php
index 4afcdc0e..f1b79459 100644
--- a/tests/selenium/installer/MediaWikiDifferentDatabaseAccountTestCase.php
+++ b/tests/selenium/installer/MediaWikiDifferentDatabaseAccountTestCase.php
@@ -30,7 +30,7 @@
require_once ( dirname( __FILE__ ) . '/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
diff --git a/tests/selenium/installer/MediaWikiDifferntDatabasePrefixTestCase.php b/tests/selenium/installer/MediaWikiDifferntDatabasePrefixTestCase.php
index b6a0fc09..2d623afc 100644
--- a/tests/selenium/installer/MediaWikiDifferntDatabasePrefixTestCase.php
+++ b/tests/selenium/installer/MediaWikiDifferntDatabasePrefixTestCase.php
@@ -29,7 +29,7 @@
require_once ( dirname( __FILE__ ) . '/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).
diff --git a/tests/selenium/installer/MediaWikiErrorsConnectToDatabasePageTestCase.php b/tests/selenium/installer/MediaWikiErrorsConnectToDatabasePageTestCase.php
index 3642a8ef..b112bc0e 100644
--- a/tests/selenium/installer/MediaWikiErrorsConnectToDatabasePageTestCase.php
+++ b/tests/selenium/installer/MediaWikiErrorsConnectToDatabasePageTestCase.php
@@ -30,7 +30,7 @@
require_once ( dirname( __FILE__ ) . '/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
diff --git a/tests/selenium/installer/MediaWikiErrorsNamepageTestCase.php b/tests/selenium/installer/MediaWikiErrorsNamepageTestCase.php
index d70dcc42..024fe5d6 100644
--- a/tests/selenium/installer/MediaWikiErrorsNamepageTestCase.php
+++ b/tests/selenium/installer/MediaWikiErrorsNamepageTestCase.php
@@ -27,7 +27,7 @@
*
*/
-/*
+/**
* 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
diff --git a/tests/selenium/installer/MediaWikiHelpFieldHintTestCase.php b/tests/selenium/installer/MediaWikiHelpFieldHintTestCase.php
index 355a2857..806fcfde 100644
--- a/tests/selenium/installer/MediaWikiHelpFieldHintTestCase.php
+++ b/tests/selenium/installer/MediaWikiHelpFieldHintTestCase.php
@@ -27,7 +27,7 @@
*
*/
-/*
+/**
* 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
diff --git a/tests/selenium/installer/MediaWikiInstallationConfig.php b/tests/selenium/installer/MediaWikiInstallationConfig.php
index d3067d69..d86bcb85 100644
--- a/tests/selenium/installer/MediaWikiInstallationConfig.php
+++ b/tests/selenium/installer/MediaWikiInstallationConfig.php
@@ -28,7 +28,7 @@
*/
-/*
+/**
* 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
@@ -39,7 +39,7 @@ 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
diff --git a/tests/selenium/installer/MediaWikiMySQLDataBaseTestCase.php b/tests/selenium/installer/MediaWikiMySQLDataBaseTestCase.php
index abf9ddf2..399ed4e5 100644
--- a/tests/selenium/installer/MediaWikiMySQLDataBaseTestCase.php
+++ b/tests/selenium/installer/MediaWikiMySQLDataBaseTestCase.php
@@ -30,7 +30,7 @@
require_once (dirname(__FILE__).'/'.'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
diff --git a/tests/selenium/installer/MediaWikiMySQLiteDataBaseTestCase.php b/tests/selenium/installer/MediaWikiMySQLiteDataBaseTestCase.php
index fe704a42..f57c1a55 100644
--- a/tests/selenium/installer/MediaWikiMySQLiteDataBaseTestCase.php
+++ b/tests/selenium/installer/MediaWikiMySQLiteDataBaseTestCase.php
@@ -30,7 +30,7 @@
require_once (dirname(__FILE__).'/'.'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
diff --git a/tests/selenium/installer/MediaWikiOnAlreadyInstalledTestCase.php b/tests/selenium/installer/MediaWikiOnAlreadyInstalledTestCase.php
index e8b8f9b0..4c052666 100644
--- a/tests/selenium/installer/MediaWikiOnAlreadyInstalledTestCase.php
+++ b/tests/selenium/installer/MediaWikiOnAlreadyInstalledTestCase.php
@@ -31,7 +31,7 @@
require_once (dirname(__FILE__).'/'.'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
diff --git a/tests/selenium/installer/MediaWikiRestartInstallationTestCase.php b/tests/selenium/installer/MediaWikiRestartInstallationTestCase.php
index b0a38000..b9ca8305 100644
--- a/tests/selenium/installer/MediaWikiRestartInstallationTestCase.php
+++ b/tests/selenium/installer/MediaWikiRestartInstallationTestCase.php
@@ -31,7 +31,7 @@
require_once (dirname(__FILE__).'/'.'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
diff --git a/tests/selenium/installer/MediaWikiRightFrameworkLinksTestCase.php b/tests/selenium/installer/MediaWikiRightFrameworkLinksTestCase.php
index 346f24f8..700172c2 100644
--- a/tests/selenium/installer/MediaWikiRightFrameworkLinksTestCase.php
+++ b/tests/selenium/installer/MediaWikiRightFrameworkLinksTestCase.php
@@ -30,7 +30,7 @@
require_once (dirname(__FILE__).'/'.'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.
diff --git a/tests/selenium/installer/MediaWikiUpgradeExistingDatabaseTestCase.php b/tests/selenium/installer/MediaWikiUpgradeExistingDatabaseTestCase.php
index 0ab5e659..eb82071e 100644
--- a/tests/selenium/installer/MediaWikiUpgradeExistingDatabaseTestCase.php
+++ b/tests/selenium/installer/MediaWikiUpgradeExistingDatabaseTestCase.php
@@ -30,7 +30,7 @@
require_once (dirname(__FILE__).'/'.'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
diff --git a/tests/selenium/installer/MediaWikiUserInterfaceTestCase.php b/tests/selenium/installer/MediaWikiUserInterfaceTestCase.php
index 7be39c04..0994892f 100644
--- a/tests/selenium/installer/MediaWikiUserInterfaceTestCase.php
+++ b/tests/selenium/installer/MediaWikiUserInterfaceTestCase.php
@@ -29,7 +29,7 @@
require_once (dirname(__FILE__).'/'.'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
diff --git a/tests/selenium/suites/MediawikiCoreSmokeTestCase.php b/tests/selenium/suites/MediawikiCoreSmokeTestCase.php
index 7b9525af..5fc1a5a6 100644
--- a/tests/selenium/suites/MediawikiCoreSmokeTestCase.php
+++ b/tests/selenium/suites/MediawikiCoreSmokeTestCase.php
@@ -1,44 +1,44 @@
<?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() .
@@ -48,10 +48,10 @@ class MediawikiCoreSmokeTestCase extends SeleniumTestCase {
$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' );
@@ -64,6 +64,6 @@ class MediawikiCoreSmokeTestCase extends SeleniumTestCase {
ucfirst( $this->filename ) . '.*has been deleted.' );
*/
}
-
+
}
diff --git a/tests/selenium/suites/MediawikiCoreSmokeTestSuite.php b/tests/selenium/suites/MediawikiCoreSmokeTestSuite.php
index 5d5ef518..a9a9b4d6 100644
--- a/tests/selenium/suites/MediawikiCoreSmokeTestSuite.php
+++ b/tests/selenium/suites/MediawikiCoreSmokeTestSuite.php
@@ -1,5 +1,5 @@
<?php
-/*
+/**
* Stubs for now. We're going to start populating this test.
*/
class MediawikiCoreSmokeTestSuite extends SeleniumTestSuite
diff --git a/tests/selenium/suites/SimpleSeleniumTestCase.php b/tests/selenium/suites/SimpleSeleniumTestCase.php
index 99a75c12..b87172e6 100644
--- a/tests/selenium/suites/SimpleSeleniumTestCase.php
+++ b/tests/selenium/suites/SimpleSeleniumTestCase.php
@@ -1,11 +1,11 @@
<?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() .
+ $this->open( $this->getUrl() .
'/index.php?title=Selenium&action=edit' );
$this->type( "wpTextbox1", "This is a basic test" );
$this->click( "wpPreview" );
@@ -16,8 +16,8 @@ class SimpleSeleniumTestCase extends SeleniumTestCase {
$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
*/
@@ -26,9 +26,9 @@ class SimpleSeleniumTestCase extends SeleniumTestCase {
$bodyClass = $this->getAttribute( "//body/@class" );
$this-> assertContains('skin-chick', $bodyClass, 'Chick skin not set');
}
-
- /*
- * Just verify that the test db was loaded correctly
+
+ /**
+ * Just verify that the test db was loaded correctly
*/
public function testDatabaseResourceLoadedCorrectly() {
$this->open( $this->getUrl() . '/index.php/TestResources?action=purge' );
diff --git a/tests/selenium/suites/SimpleSeleniumTestSuite.php b/tests/selenium/suites/SimpleSeleniumTestSuite.php
index 3f5e3645..2e0c4ee2 100644
--- a/tests/selenium/suites/SimpleSeleniumTestSuite.php
+++ b/tests/selenium/suites/SimpleSeleniumTestSuite.php
@@ -1,5 +1,5 @@
<?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
diff --git a/tests/testHelpers.inc b/tests/testHelpers.inc
index 5d56e625..7fc60a5c 100644
--- a/tests/testHelpers.inc
+++ b/tests/testHelpers.inc
@@ -1,53 +1,5 @@
<?php
-/**
- * @ingroup Testing
- *
- * Set of classes to help with test output and such. Right now pretty specific
- * to the parser tests but could be more useful one day :)
- *
- * @todo Fixme: Make this more generic
- */
-
-class AnsiTermColorer {
- function __construct() {
- }
-
- /**
- * Return ANSI terminal escape code for changing text attribs/color
- *
- * @param $color String: semicolon-separated list of attribute/color codes
- * @return String
- */
- public function color( $color ) {
- global $wgCommandLineDarkBg;
-
- $light = $wgCommandLineDarkBg ? "1;" : "0;";
-
- return "\x1b[{$light}{$color}m";
- }
-
- /**
- * Return ANSI terminal escape code for restoring default text attributes
- *
- * @return String
- */
- public function reset() {
- return $this->color( 0 );
- }
-}
-
-/* A colour-less terminal */
-class DummyTermColorer {
- public function color( $color ) {
- return '';
- }
-
- public function reset() {
- return '';
- }
-}
-
class TestRecorder {
var $parent;
var $term;
@@ -121,8 +73,8 @@ class DbTestPreviewer extends TestRecorder {
function start() {
parent::start();
- if ( ! $this->db->tableExists( 'testrun' )
- or ! $this->db->tableExists( 'testitem' ) )
+ if ( ! $this->db->tableExists( 'testrun', __METHOD__ )
+ || ! $this->db->tableExists( 'testitem', __METHOD__ ) )
{
print "WARNING> `testrun` table not found in database.\n";
$this->prevRun = false;
@@ -305,7 +257,7 @@ class DbTestRecorder extends DbTestPreviewer {
$this->db->begin();
if ( ! $this->db->tableExists( 'testrun' )
- or ! $this->db->tableExists( 'testitem' ) )
+ || ! $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' ) );
@@ -355,12 +307,12 @@ 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 $sectionData = array();
private $lineNum;
private $eof;
function __construct( $file, $parserTest ) {
- global $IP;
-
$this->file = $file;
$this->fh = fopen( $this->file, "rt" );
@@ -369,7 +321,6 @@ class TestFileIterator implements Iterator {
}
$this->parserTest = $parserTest;
- $this->parserTest->showRunFile( wfRelativePath( $this->file, $IP ) );
$this->lineNum = $this->index = 0;
}
@@ -409,128 +360,223 @@ class TestFileIterator implements Iterator {
}
function readNextTest() {
- $data = array();
- $section = null;
+ $this->clearSection();
+
+ # Create a fake parser tests which never run anything unless
+ # asked to do so. This will avoid running hooks for a disabled test
+ $delayedParserTest = new DelayedParserTest();
while ( false !== ( $line = fgets( $this->fh ) ) ) {
$this->lineNum++;
$matches = array();
if ( preg_match( '/^!!\s*(\w+)/', $line, $matches ) ) {
- $section = strtolower( $matches[1] );
+ $this->section = strtolower( $matches[1] );
- if ( $section == 'endarticle' ) {
- if ( !isset( $data['text'] ) ) {
- throw new MWException( "'endarticle' without 'text' at line {$this->lineNum} of $this->file\n" );
- }
-
- if ( !isset( $data['article'] ) ) {
- throw new MWException( "'endarticle' without 'article' at line {$this->lineNum} of $this->file\n" );
- }
+ if ( $this->section == 'endarticle' ) {
+ $this->checkSection( 'text' );
+ $this->checkSection( 'article' );
- $this->parserTest->addArticle( ParserTest::chomp( $data['article'] ), $data['text'], $this->lineNum );
+ $this->parserTest->addArticle( ParserTest::chomp( $this->sectionData['article'] ), $this->sectionData['text'], $this->lineNum );
- $data = array();
- $section = null;
+ $this->clearSection();
continue;
}
- if ( $section == 'endhooks' ) {
- if ( !isset( $data['hooks'] ) ) {
- throw new MWException( "'endhooks' without 'hooks' at line {$this->lineNum} of $this->file\n" );
- }
+ if ( $this->section == 'endhooks' ) {
+ $this->checkSection( 'hooks' );
- foreach ( explode( "\n", $data['hooks'] ) as $line ) {
+ foreach ( explode( "\n", $this->sectionData['hooks'] ) as $line ) {
$line = trim( $line );
if ( $line ) {
- if ( !$this->parserTest->requireHook( $line ) ) {
- return false;
- }
+ $delayedParserTest->requireHook( $line );
}
}
- $data = array();
- $section = null;
+ $this->clearSection();
continue;
}
- if ( $section == 'endfunctionhooks' ) {
- if ( !isset( $data['functionhooks'] ) ) {
- throw new MWException( "'endfunctionhooks' without 'functionhooks' at line {$this->lineNum} of $this->file\n" );
- }
+ if ( $this->section == 'endfunctionhooks' ) {
+ $this->checkSection( 'functionhooks' );
- foreach ( explode( "\n", $data['functionhooks'] ) as $line ) {
+ foreach ( explode( "\n", $this->sectionData['functionhooks'] ) as $line ) {
$line = trim( $line );
if ( $line ) {
- if ( !$this->parserTest->requireFunctionHook( $line ) ) {
- return false;
- }
+ $delayedParserTest->requireFunctionHook( $line );
}
}
- $data = array();
- $section = null;
+ $this->clearSection();
continue;
}
- if ( $section == 'end' ) {
- if ( !isset( $data['test'] ) ) {
- throw new MWException( "'end' without 'test' at line {$this->lineNum} of $this->file\n" );
- }
-
- if ( !isset( $data['input'] ) ) {
- throw new MWException( "'end' without 'input' at line {$this->lineNum} of $this->file\n" );
- }
+ if ( $this->section == 'end' ) {
+ $this->checkSection( 'test' );
+ $this->checkSection( 'input' );
+ $this->checkSection( 'result' );
- if ( !isset( $data['result'] ) ) {
- throw new MWException( "'end' without 'result' at line {$this->lineNum} of $this->file\n" );
+ if ( !isset( $this->sectionData['options'] ) ) {
+ $this->sectionData['options'] = '';
}
- if ( !isset( $data['options'] ) ) {
- $data['options'] = '';
+ if ( !isset( $this->sectionData['config'] ) ) {
+ $this->sectionData['config'] = '';
}
- if ( !isset( $data['config'] ) )
- $data['config'] = '';
-
- if ( ( ( preg_match( '/\\bdisabled\\b/i', $data['options'] ) && !$this->parserTest->runDisabled )
- || !preg_match( "/" . $this->parserTest->regex . "/i", $data['test'] ) ) ) {
+ if ( ( ( preg_match( '/\\bdisabled\\b/i', $this->sectionData['options'] ) && !$this->parserTest->runDisabled )
+ || !preg_match( "/" . $this->parserTest->regex . "/i", $this->sectionData['test'] ) ) ) {
# disabled test
- $data = array();
- $section = null;
+ $this->clearSection();
+
+ # Forget any pending hooks call since test is disabled
+ $delayedParserTest->reset();
continue;
}
+ # 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 ) {
+ # Some hook reported an issue. Abort.
+ return false;
+ }
+
$this->test = array(
- 'test' => ParserTest::chomp( $data['test'] ),
- 'input' => ParserTest::chomp( $data['input'] ),
- 'result' => ParserTest::chomp( $data['result'] ),
- 'options' => ParserTest::chomp( $data['options'] ),
- 'config' => ParserTest::chomp( $data['config'] ) );
+ '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'] ),
+ );
return true;
}
- if ( isset ( $data[$section] ) ) {
+ if ( isset ( $this->sectionData[$this->section] ) ) {
throw new MWException( "duplicate section '$section' at line {$this->lineNum} of $this->file\n" );
}
- $data[$section] = '';
+ $this->sectionData[$this->section] = '';
continue;
}
- if ( $section ) {
- $data[$section] .= $line;
+ if ( $this->section ) {
+ $this->sectionData[$this->section] .= $line;
}
}
return false;
}
+
+
+ /**
+ * Clear section name and its data
+ */
+ private function clearSection() {
+ $this->sectionData = array();
+ $this->section = null;
+
+ }
+
+ /**
+ * Verify the current section data has some value for the given token
+ * name (first parameter).
+ * 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
+ */
+ private function checkSection( $token ) {
+ if( is_null( $this->section ) ) {
+ throw new MWException( __METHOD__ . " can not verify a null section!\n" );
+ }
+
+ 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;
+ }
}
+
+/**
+ * A class to delay execution of a parser test hooks.
+ */
+class DelayedParserTest {
+
+ /** Initialized on construction */
+ private $hooks;
+ private $fnHooks;
+
+ public function __construct() {
+ $this->reset();
+ }
+
+ /**
+ * Init/reset or forgot about the current delayed test.
+ * Call to this will erase any hooks function that were pending.
+ */
+ public function reset() {
+ $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
+ */
+ public function unleash( &$parserTest ) {
+ 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 ) {
+ $ret = $parserTest->requireHook( $hook );
+ if( !$ret ) {
+ return false;
+ }
+ }
+
+ # Trigger delayed function hooks. Any failure will make us abort
+ foreach( $this->fnHooks as $fnHook ) {
+ $ret = $parserTest->requireFunctionHook( $fnHook );
+ if( !$ret ) {
+ return false;
+ }
+ }
+
+ # Delayed execution was successful.
+ return true;
+ }
+
+ /**
+ * Similar to ParserTest object but does not run anything
+ * Use unleash() to really execute the hook
+ */
+ public function requireHook( $hook ) {
+ $this->hooks[] = $hook;
+ }
+ /**
+ * Similar to ParserTest object but does not run anything
+ * Use unleash() to really execute the hook function
+ */
+ public function requireFunctionHook( $fnHook ) {
+ $this->fnHooks[] = $fnHook;
+ }
+
+} \ No newline at end of file