jenkins-bot has submitted this change and it was merged.
Change subject: build: Add Karma task for automated QUnit testing in browsers
......................................................................
build: Add Karma task for automated QUnit testing in browsers
To use, first run 'npm install'. Then run 'grunt qunit' to start
the test suite in Chrome.
Squashed cherry-picks from master:
* ba50b32556: SpecialJavaScriptTest: Add export feat
* 7605f112e4: jquery.mwExtension.test: Fix qunit-fixture conflict
* 365b6f3af9: mediawiki.jqueryMsg.test: Fix crazy concurrency
* 945c1efe37: build: Add Karma task
* 8d92aaf83e: build: Clean up Gruntfile
* 2258f25053: build: Add assert-mw-env task
* dcbbc0489c: build: Increase browserNoActivityTimeout to 60s
* fa4ba8dbd7: build: Declare grunt-cli dependency
Change-Id: I4e96da137340a28789b38940e75d4b6b8bc5d76a
---
M Gruntfile.js
M includes/OutputPage.php
M includes/specials/SpecialJavaScriptTest.php
M languages/i18n/en.json
M languages/i18n/qqq.json
M package.json
M resources/Resources.php
M resources/src/mediawiki.special/mediawiki.special.javaScriptTest.js
M tests/qunit/QUnitTestResources.php
M tests/qunit/data/testrunner.js
M tests/qunit/suites/resources/jquery/jquery.mwExtension.test.js
M tests/qunit/suites/resources/mediawiki/mediawiki.jqueryMsg.test.js
12 files changed, 376 insertions(+), 169 deletions(-)
Approvals:
Krinkle: Looks good to me, approved
jenkins-bot: Verified
diff --git a/Gruntfile.js b/Gruntfile.js
index 9badf03..e4d96a0 100644
--- a/Gruntfile.js
+++ b/Gruntfile.js
@@ -4,6 +4,13 @@
grunt.loadNpmTasks( 'grunt-contrib-jshint' );
grunt.loadNpmTasks( 'grunt-contrib-watch' );
grunt.loadNpmTasks( 'grunt-jscs-checker' );
+ grunt.loadNpmTasks( 'grunt-karma' );
+
+ var wgServer = process.env.MW_SERVER,
+ wgScriptPath = process.env.MW_SCRIPT_PATH,
+ karmaProxy = {};
+
+ karmaProxy[wgScriptPath] = wgServer + wgScriptPath;
grunt.initConfig( {
pkg: grunt.file.readJSON( 'package.json' ),
@@ -38,6 +45,29 @@
],
tasks: 'test'
},
+ karma: {
+ options: {
+ proxies: karmaProxy,
+ files: [ {
+ pattern: wgServer + wgScriptPath +
'/index.php?title=Special:JavaScriptTest/qunit/export',
+ watched: false,
+ included: true,
+ served: false
+ } ],
+ frameworks: [ 'qunit' ],
+ reporters: [ 'dots' ],
+ singleRun: true,
+ autoWatch: false,
+ // Some tests in extensions don't yield for
more than the default 10s (T89075)
+ browserNoActivityTimeout: 60 * 1000
+ },
+ main: {
+ browsers: [ 'Chrome' ]
+ },
+ more: {
+ browsers: [ 'Chrome', 'Firefox' ]
+ }
+ },
copy: {
jsduck: {
src: 'resources/**/*',
@@ -50,7 +80,22 @@
}
} );
+ grunt.registerTask( 'assert-mw-env', function () {
+ if ( !process.env.MW_SERVER ) {
+ grunt.log.error( 'Environment variable MW_SERVER must
be set.\n' +
+ 'Set this like $wgServer, e.g.
"http://localhost"'
+ );
+ }
+ if ( !process.env.MW_SCRIPT_PATH ) {
+ grunt.log.error( 'Environment variable MW_SCRIPT_PATH
must be set.\n' +
+ 'Set this like $wgScriptPath, e.g. "/w"');
+ }
+ return !!( process.env.MW_SERVER && process.env.MW_SCRIPT_PATH
);
+ } );
+
grunt.registerTask( 'lint', ['jshint', 'jscs'] );
+ grunt.registerTask( 'qunit', [ 'assert-mw-env', 'karma:main' ] );
+
grunt.registerTask( 'test', ['lint'] );
- grunt.registerTask( 'default', ['test'] );
+ grunt.registerTask( 'default', 'test' );
};
diff --git a/includes/OutputPage.php b/includes/OutputPage.php
index 4a72ba3..4e4ed1c 100644
--- a/includes/OutputPage.php
+++ b/includes/OutputPage.php
@@ -2632,7 +2632,7 @@
* @param bool $loadCall If true, output an (asynchronous)
mw.loader.load() call rather than a "<script src='...'>" tag
* @return string The html "<script>", "<link>" and "<style>" tags
*/
- protected function makeResourceLoaderLink( $modules, $only, $useESI =
false, array $extraQuery = array(), $loadCall = false ) {
+ public function makeResourceLoaderLink( $modules, $only, $useESI =
false, array $extraQuery = array(), $loadCall = false ) {
global $wgResourceLoaderUseESI;
$modules = (array)$modules;
diff --git a/includes/specials/SpecialJavaScriptTest.php
b/includes/specials/SpecialJavaScriptTest.php
index 7982d5c..c852a06 100644
--- a/includes/specials/SpecialJavaScriptTest.php
+++ b/includes/specials/SpecialJavaScriptTest.php
@@ -27,12 +27,10 @@
class SpecialJavaScriptTest extends SpecialPage {
/**
- * @var $frameworks Array: Mapping of framework ids and their
initilizer methods
- * in this class. If a framework is requested but not in this array,
- * the 'unknownframework' error is served.
+ * @var array Supported frameworks.
*/
- static $frameworks = array(
- 'qunit' => 'initQUnitTesting',
+ private static $frameworks = array(
+ 'qunit',
);
public function __construct() {
@@ -45,43 +43,70 @@
$this->setHeaders();
$out->disallowUserJs();
+ if ( $par === null ) {
+ // No framework specified
+ $out->setStatusCode( 404 );
+ $out->setPageTitle( $this->msg( 'javascripttest' ) );
+ $out->addHTML(
+ $this->msg(
'javascripttest-pagetext-noframework' )->parseAsBlock()
+ . $this->getFrameworkListHtml()
+ );
+ return;
+ }
+
+ // Determine framework and mode
+ $pars = explode( '/', $par, 2 );
+
+ $framework = $pars[0];
+ if ( !in_array( $framework, self::$frameworks ) ) {
+ // Framework not found
+ $out->setStatusCode( 404 );
+ $out->addHTML(
+ '<div class="error">'
+ . $this->msg(
'javascripttest-pagetext-unknownframework' )
+ ->plaintextParams( $par
)->parseAsBlock()
+ . '</div>'
+ . $this->getFrameworkListHtml()
+ );
+ return;
+ }
+
+ // This special page is disabled by default
($wgEnableJavaScriptTest), and contains
+ // no sensitive data. In order to allow TestSwarm to embed it
into a test client window,
+ // we need to allow iframing of this page.
+ $out->allowClickjacking();
+ $out->setSubtitle(
+ $this->msg( 'javascripttest-backlink' )
+ ->rawParams( Linker::linkKnown(
$this->getPageTitle() ) )
+ );
+
+ // Custom actions
+ if ( isset( $pars[1] ) ) {
+ $action = $pars[1];
+ if ( !in_array( $action, array( 'export', 'plain' ) ) )
{
+ $out->setStatusCode( 404 );
+ $out->addHTML(
+ '<div class="error">'
+ . $this->msg(
'javascripttest-pagetext-unknownaction' )
+ ->plaintextParams( $action
)->parseAsBlock()
+ . '</div>'
+ );
+ return;
+ }
+ $method = $action . ucfirst( $framework );
+ $this->$method();
+ return;
+ }
+
$out->addModules( 'mediawiki.special.javaScriptTest' );
- // Determine framework
- $pars = explode( '/', $par );
- $framework = strtolower( $pars[0] );
-
- // No framework specified
- if ( $par == '' ) {
- $out->setPageTitle( $this->msg( 'javascripttest' ) );
- $summary = $this->wrapSummaryHtml(
- $this->msg(
'javascripttest-pagetext-noframework' )->escaped() .
- $this->getFrameworkListHtml(),
- 'noframework'
- );
- $out->addHtml( $summary );
- } elseif ( isset( self::$frameworks[$framework] ) ) {
- // Matched! Display proper title and initialize the
framework
- $out->setPageTitle( $this->msg(
- 'javascripttest-title',
- // Messages: javascripttest-qunit-name
- $this->msg( "javascripttest-$framework-name"
)->plain()
- ) );
- $out->setSubtitle( $this->msg(
'javascripttest-backlink' )
- ->rawParams( Linker::linkKnown(
$this->getPageTitle() ) ) );
- $this->{self::$frameworks[$framework]}();
- } else {
- // Framework not found, display error
- $out->setPageTitle( $this->msg( 'javascripttest' ) );
- $summary = $this->wrapSummaryHtml(
- '<p class="error">' .
- $this->msg(
'javascripttest-pagetext-unknownframework', $par )->escaped() .
- '</p>' .
- $this->getFrameworkListHtml(),
- 'unknownframework'
- );
- $out->addHtml( $summary );
- }
+ $method = 'view' . ucfirst( $framework );
+ $this->$method();
+ $out->setPageTitle( $this->msg(
+ 'javascripttest-title',
+ // Messages: javascripttest-qunit-name
+ $this->msg( "javascripttest-$framework-name" )->plain()
+ ) );
}
/**
@@ -92,7 +117,7 @@
*/
private function getFrameworkListHtml() {
$list = '<ul>';
- foreach ( self::$frameworks as $framework => $initFn ) {
+ foreach ( self::$frameworks as $framework ) {
$list .= Html::rawElement(
'li',
array(),
@@ -110,68 +135,140 @@
}
/**
- * Function to wrap the summary.
- * It must be given a valid state as a second parameter or an exception
will
- * be thrown.
- * @param string $html The raw HTML.
- * @param string $state State, one of 'noframework', 'unknownframework'
or 'frameworkfound'
- * @throws MWException
- * @return string
+ * Wrap HTML contents in a summary container.
+ *
+ * @param string $html HTML contents to be wrapped
+ * @return string HTML
*/
- private function wrapSummaryHtml( $html, $state ) {
- $validStates = array( 'noframework', 'unknownframework',
'frameworkfound' );
-
- if ( !in_array( $state, $validStates ) ) {
- throw new MWException( __METHOD__
- . ' given an invalid state. Must be one of "'
- . join( '", "', $validStates ) . '".'
- );
- }
-
- return "<div id=\"mw-javascripttest-summary\"
class=\"mw-javascripttest-$state\">$html</div>";
+ private function wrapSummaryHtml( $html ) {
+ return "<div id=\"mw-javascripttest-summary\">$html</div>";
}
/**
- * Initialize the page for QUnit.
+ * Run the test suite on the Special page.
+ *
+ * Rendered by OutputPage and Skin.
*/
- private function initQUnitTesting() {
+ private function viewQUnit() {
global $wgJavaScriptTestConfig;
$out = $this->getOutput();
+ $testConfig = $wgJavaScriptTestConfig;
- $out->addModules( 'test.mediawiki.qunit.testrunner' );
- $qunitTestModules =
$out->getResourceLoader()->getTestModuleNames( 'qunit' );
- $out->addModules( $qunitTestModules );
+ $modules = $out->getResourceLoader()->getTestModuleNames(
'qunit' );
$summary = $this->msg( 'javascripttest-qunit-intro' )
->params(
$wgJavaScriptTestConfig['qunit']['documentation'] )
->parseAsBlock();
- $header = $this->msg( 'javascripttest-qunit-heading'
)->escaped();
- $userDir = $this->getLanguage()->getDir();
$baseHtml = <<<HTML
<div class="mw-content-ltr">
-<div id="qunit-header"><span dir="$userDir">$header</span></div>
-<div id="qunit-banner"></div>
-<div id="qunit-testrunner-toolbar"></div>
-<div id="qunit-userAgent"></div>
-<ol id="qunit-tests"></ol>
-<div id="qunit-fixture">test markup, will be hidden</div>
+<div id="qunit"></div>
</div>
HTML;
- $out->addHtml( $this->wrapSummaryHtml( $summary,
'frameworkfound' ) . $baseHtml );
-
- // This special page is disabled by default
($wgEnableJavaScriptTest), and contains
- // no sensitive data. In order to allow TestSwarm to embed it
into a test client window,
- // we need to allow iframing of this page.
- $out->allowClickjacking();
// Used in ./tests/qunit/data/testrunner.js, see also
documentation of
// $wgJavaScriptTestConfig in DefaultSettings.php
$out->addJsConfigVars(
'QUnitTestSwarmInjectJSPath',
- $wgJavaScriptTestConfig['qunit']['testswarm-injectjs']
+ $testConfig['qunit']['testswarm-injectjs']
);
+
+ $out->addHtml( $this->wrapSummaryHtml( $summary ) . $baseHtml );
+
+ // The testrunner configures QUnit and essentially depends on
it. However, test suites
+ // are reusable in environments that preload QUnit (or a
compatibility interface to
+ // another framework). Therefore we have to load it ourselves.
+ $out->addHtml( Html::inlineScript(
+ ResourceLoader::makeLoaderConditionalScript(
+ Xml::encodeJsCall( 'mw.loader.using', array(
+ array( 'jquery.qunit',
'jquery.qunit.completenessTest' ),
+ new XmlJsCode(
+ 'function () {' .
Xml::encodeJsCall( 'mw.loader.load', array( $modules ) ) . '}'
+ )
+ ) )
+ )
+ ) );
+ }
+
+ /**
+ * Generate self-sufficient JavaScript payload to run the tests
elsewhere.
+ *
+ * Includes startup module to request modules from ResourceLoader.
+ *
+ * Note: This modifies the registry to replace 'jquery.qunit' with an
+ * empty module to allow external environment to preload QUnit with any
+ * neccecary framework adapters (e.g. Karma). Loading it again would
+ * re-define QUnit and dereference event handlers from Karma.
+ */
+ private function exportQUnit() {
+ $out = $this->getOutput();
+
+ $out->disable();
+
+ $rl = $out->getResourceLoader();
+
+ $query = array(
+ 'lang' => $this->getLanguage()->getCode(),
+ 'skin' => $this->getSkin()->getSkinName(),
+ 'debug' => ResourceLoader::inDebugMode() ? 'true' :
'false',
+ );
+ $embedContext = new ResourceLoaderContext( $rl, new
FauxRequest( $query ) );
+ $query['only'] = 'scripts';
+ $startupContext = new ResourceLoaderContext( $rl, new
FauxRequest( $query ) );
+
+ $modules = $rl->getTestModuleNames( 'qunit' );
+
+ // The below is essentially a pure-javascript version of
OutputPage::getHeadScripts.
+ $startup = $rl->makeModuleResponse( $startupContext, array(
+ 'startup' => $rl->getModule( 'startup' ),
+ ) );
+ // Embed page-specific mw.config variables.
+ // The current Special page shouldn't be relevant to tests, but
various modules (which
+ // are loaded before the test suites), reference mw.config
while initialising.
+ $code = ResourceLoader::makeConfigSetScript( $out->getJSVars()
);
+ // Embed private modules as they're not allowed to be loaded
dynamically
+ $code .= $rl->makeModuleResponse( $embedContext, array(
+ 'user.options' => $rl->getModule( 'user.options' ),
+ 'user.tokens' => $rl->getModule( 'user.tokens' ),
+ ) );
+ $code .= Xml::encodeJsCall( 'mw.loader.load', array( $modules )
);
+
+ header( 'Content-Type: text/javascript; charset=utf-8' );
+ header( 'Cache-Control: private, no-cache, must-revalidate' );
+ header( 'Pragma: no-cache' );
+ echo $startup;
+ echo "\n";
+ // Note: The following has to be wrapped in a script tag
because the startup module also
+ // writes a script tag (the one loading mediawiki.js). Script
tags are synchronous, block
+ // each other, and run in order. But they don't nest. The code
appended after the startup
+ // module runs before the added script tag is parsed and
executed.
+ echo Xml::encodeJsCall( 'document.write', array(
Html::inlineScript( $code ) ) );
+ }
+
+ private function plainQUnit() {
+ $out = $this->getOutput();
+ $out->disable();
+
+ $url = $this->getPageTitle( 'qunit/export' )->getFullURL( array(
+ 'debug' => ResourceLoader::inDebugMode() ? 'true' :
'false',
+ ) );
+
+ $styles = $out->makeResourceLoaderLink( 'jquery.qunit',
ResourceLoaderModule::TYPE_STYLES, false );
+ // Use 'raw' since this is a plain HTML page without
ResourceLoader
+ $scripts = $out->makeResourceLoaderLink( 'jquery.qunit',
ResourceLoaderModule::TYPE_SCRIPTS, false, array( 'raw' => 'true' ) );
+
+ $head = trim( $styles['html'] . $scripts['html'] );
+ $html = <<<HTML
+<!DOCTYPE html>
+<title>QUnit</title>
+$head
+<div id="qunit"></div>
+HTML;
+ $html .= "\n" . Html::linkedScript( $url );
+
+ header( 'Content-Type: text/html; charset=utf-8' );
+ echo $html;
}
protected function getGroupName() {
diff --git a/languages/i18n/en.json b/languages/i18n/en.json
index cdc8338..8b674fa 100644
--- a/languages/i18n/en.json
+++ b/languages/i18n/en.json
@@ -2329,14 +2329,14 @@
"import-logentry-interwiki-detail": "$1 {{PLURAL:$1|revision|revisions}}
from $2",
"javascripttest": "JavaScript testing",
"javascripttest-backlink": "< $1",
- "javascripttest-title": "Running $1 tests",
+ "javascripttest-title": "$1",
"javascripttest-pagetext-noframework": "This page is reserved for running
JavaScript tests.",
"javascripttest-pagetext-unknownframework": "Unknown testing framework
\"$1\".",
+ "javascripttest-pagetext-unknownaction": "Unknown action \"$1\".",
"javascripttest-pagetext-frameworks": "Please choose one of the following
testing frameworks: $1",
"javascripttest-pagetext-skins": "Choose a skin to run the tests with:",
"javascripttest-qunit-name": "QUnit",
"javascripttest-qunit-intro": "See [$1 testing documentation] on
mediawiki.org.",
- "javascripttest-qunit-heading": "MediaWiki JavaScript QUnit test suite",
"accesskey-pt-userpage": ".",
"accesskey-pt-anonuserpage": ".",
"accesskey-pt-mytalk": "n",
diff --git a/languages/i18n/qqq.json b/languages/i18n/qqq.json
index dab12a7..4d8fb9b 100644
--- a/languages/i18n/qqq.json
+++ b/languages/i18n/qqq.json
@@ -2492,14 +2492,15 @@
"import-logentry-interwiki-detail": "Used as success message.
Parameters:\n* $1 - number of succeeded revisions\n* $2 - interwiki name\nSee
also:\n* {{msg-mw|Import-logentry-upload-detail}}",
"javascripttest": "Title of the special page
[[Special:JavaScriptTest]].\n\nSee also:\n* {{msg-mw|Javascripttest|title}}\n*
{{msg-mw|Javascripttest-pagetext-noframework|summary}}\n*
{{msg-mw|Javascripttest-pagetext-unknownframework|error message}}",
"javascripttest-backlink": "{{optional}}\nUsed as subtitle in
[[Special:JavaScriptTest]]. Parameters:\n* $1 - page title",
- "javascripttest-title": "Title of the special page when running a test
suite. Parameters:\n* $1 is the name of the framework, for example QUnit.",
+ "javascripttest-title": "{{Ignore}}",
"javascripttest-pagetext-noframework": "Used as summary when no framework
specified.\n\nSee also:\n* {{msg-mw|Javascripttest|title}}\n*
{{msg-mw|Javascripttest-pagetext-noframework|summary}}\n*
{{msg-mw|Javascripttest-pagetext-unknownframework|error message}}",
"javascripttest-pagetext-unknownframework": "Error message when given
framework ID is not found. Parameters:\n* $1 - the ID of the framework\nSee
also:\n* {{msg-mw|Javascripttest|title}}\n*
{{msg-mw|Javascripttest-pagetext-noframework|summary}}\n*
{{msg-mw|Javascripttest-pagetext-unknownframework|error message}}",
+ "javascripttest-pagetext-unknownaction": "Error message when url specifies
an unknown action. Parameters:\
+n* $1 - the action specified in the url.",
"javascripttest-pagetext-frameworks": "Parameters:\n* $1 - frameworks list
which contain a link text {{msg-mw|Javascripttest-qunit-name}}",
"javascripttest-pagetext-skins": "Used as label in
[[Special:JavaScriptTest]].",
"javascripttest-qunit-name": "{{Ignore}}",
"javascripttest-qunit-intro": "Used as summary. Parameters:\n* $1 - the
configured URL to the documentation\nSee also:\n*
{{msg-mw|Javascripttest-qunit-heading}}",
- "javascripttest-qunit-heading": "See also:\n*
{{msg-mw|Javascripttest-qunit-intro}}",
"accesskey-pt-userpage": "{{doc-accesskey}}\nSee also:\n<!--*
username-->\n* {{msg-mw|Accesskey-pt-userpage}}\n*
{{msg-mw|Tooltip-pt-userpage}}",
"accesskey-pt-anonuserpage": "{{doc-accesskey}}",
"accesskey-pt-mytalk": "{{doc-accesskey}}\nSee also:\n*
{{msg-mw|Mytalk}}\n* {{msg-mw|Accesskey-pt-mytalk}}\n*
{{msg-mw|Tooltip-pt-mytalk}}",
diff --git a/package.json b/package.json
index a9b9148..9f27d5f 100644
--- a/package.json
+++ b/package.json
@@ -7,10 +7,17 @@
"postdoc": "grunt copy:jsduck"
},
"devDependencies": {
- "grunt": "0.4.2",
+ "grunt": "0.4.5",
+ "grunt-cli": "0.1.13",
"grunt-contrib-jshint": "0.9.2",
"grunt-contrib-copy": "0.8.0",
"grunt-contrib-watch": "0.6.1",
- "grunt-jscs-checker": "0.4.1"
+ "grunt-jscs-checker": "0.4.1",
+ "grunt-karma": "0.10.1",
+ "karma": "0.12.31",
+ "karma-chrome-launcher": "0.1.7",
+ "karma-firefox-launcher": "0.1.4",
+ "karma-qunit": "0.1.4",
+ "qunitjs": "1.16.0"
}
}
diff --git a/resources/Resources.php b/resources/Resources.php
index 05af927..12f889b 100644
--- a/resources/Resources.php
+++ b/resources/Resources.php
@@ -1285,7 +1285,9 @@
'colon-separator',
'javascripttest-pagetext-skins',
) ),
- 'dependencies' => array( 'jquery.qunit' ),
+ 'dependencies' => array(
+ 'mediawiki.Uri',
+ ),
'position' => 'top',
'targets' => array( 'desktop', 'mobile' ),
),
diff --git
a/resources/src/mediawiki.special/mediawiki.special.javaScriptTest.js
b/resources/src/mediawiki.special/mediawiki.special.javaScriptTest.js
index 38f256c..04ea207 100644
--- a/resources/src/mediawiki.special/mediawiki.special.javaScriptTest.js
+++ b/resources/src/mediawiki.special/mediawiki.special.javaScriptTest.js
@@ -6,7 +6,7 @@
// Create useskin dropdown menu and reload onchange to the
selected skin
// (only if a framework was found, not on error pages).
- $(
'#mw-javascripttest-summary.mw-javascripttest-frameworkfound' ).append(
function () {
+ $( '#mw-javascripttest-summary' ).append( function () {
var $html = $( '<p><label for="useskin">'
+ mw.message(
'javascripttest-pagetext-skins' ).escaped()
@@ -25,7 +25,8 @@
// Bind onchange event handler and append to form
$html.append(
$( select ).change( function () {
- window.location = QUnit.url( { useskin:
$( this ).val() } );
+ var url = new mw.Uri();
+ location.href = url.extend( { useskin:
$( this ).val() } );
} )
);
diff --git a/tests/qunit/QUnitTestResources.php
b/tests/qunit/QUnitTestResources.php
index 74ea58e..e599189 100644
--- a/tests/qunit/QUnitTestResources.php
+++ b/tests/qunit/QUnitTestResources.php
@@ -25,9 +25,9 @@
'tests/qunit/data/testrunner.js',
),
'dependencies' => array(
+ // Test runner configures QUnit but can't have it as
dependency,
+ // see SpecialJavaScriptTest::viewQUnit.
'jquery.getAttrs',
- 'jquery.qunit',
- 'jquery.qunit.completenessTest',
'mediawiki.page.ready',
'mediawiki.page.startup',
'test.sinonjs',
diff --git a/tests/qunit/data/testrunner.js b/tests/qunit/data/testrunner.js
index ab9aab1..aca8433e 100644
--- a/tests/qunit/data/testrunner.js
+++ b/tests/qunit/data/testrunner.js
@@ -112,6 +112,34 @@
};
}() );
+ // Extend QUnit.module to provide a fixture element.
+ ( function () {
+ var orgModule = QUnit.module;
+
+ QUnit.module = function ( name, localEnv ) {
+ var fixture;
+ localEnv = localEnv || {};
+ orgModule( name, {
+ setup: function () {
+ fixture = document.createElement( 'div'
);
+ fixture.id = 'qunit-fixture';
+ document.body.appendChild( fixture );
+
+ if ( localEnv.setup ) {
+ localEnv.setup.call( this );
+ }
+ },
+ teardown: function () {
+ if ( localEnv.teardown ) {
+ localEnv.teardown.call( this );
+ }
+
+ fixture.parentNode.removeChild( fixture
);
+ }
+ } );
+ };
+ }() );
+
// Initiate when enabled
if ( QUnit.urlParams.completenesstest ) {
diff --git a/tests/qunit/suites/resources/jquery/jquery.mwExtension.test.js
b/tests/qunit/suites/resources/jquery/jquery.mwExtension.test.js
index 7571b92..795c2bb 100644
--- a/tests/qunit/suites/resources/jquery/jquery.mwExtension.test.js
+++ b/tests/qunit/suites/resources/jquery/jquery.mwExtension.test.js
@@ -15,24 +15,22 @@
assert.equal( $.escapeRE( '0123456789' ), '0123456789',
'escapeRE - Leave numbers alone' );
} );
- QUnit.test( 'Is functions', 15, function ( assert ) {
- assert.strictEqual( $.isDomElement( document.getElementById(
'qunit-header' ) ), true,
- 'isDomElement: #qunit-header Node' );
- assert.strictEqual( $.isDomElement( document.getElementById(
'random-name' ) ), false,
- 'isDomElement: #random-name (null)' );
- assert.strictEqual( $.isDomElement(
document.getElementsByTagName( 'div' ) ), false,
- 'isDomElement: getElementsByTagName Array' );
- assert.strictEqual( $.isDomElement(
document.getElementsByTagName( 'div' )[0] ), true,
- 'isDomElement: getElementsByTagName(..)[0] Node' );
- assert.strictEqual( $.isDomElement( $( 'div' ) ), false,
- 'isDomElement: jQuery object' );
- assert.strictEqual( $.isDomElement( $( 'div' ).get( 0 ) ), true,
- 'isDomElement: jQuery object > Get node' );
+ QUnit.test( 'isDomElement', 6, function ( assert ) {
assert.strictEqual( $.isDomElement( document.createElement(
'div' ) ), true,
- 'isDomElement: createElement' );
+ 'isDomElement: HTMLElement' );
+ assert.strictEqual( $.isDomElement( document.createTextNode( ''
) ), true,
+ 'isDomElement: TextNode' );
+ assert.strictEqual( $.isDomElement( null ), false,
+ 'isDomElement: null' );
+ assert.strictEqual( $.isDomElement(
document.getElementsByTagName( 'div' ) ), false,
+ 'isDomElement: NodeList' );
+ assert.strictEqual( $.isDomElement( $( 'div' ) ), false,
+ 'isDomElement: jQuery' );
assert.strictEqual( $.isDomElement( { foo: 1 } ), false,
- 'isDomElement: Object' );
+ 'isDomElement: Plain Object' );
+ } );
+ QUnit.test( 'isEmpty', 7, function ( assert ) {
assert.strictEqual( $.isEmpty( 'string' ), false, 'isEmpty:
"string"' );
assert.strictEqual( $.isEmpty( '0' ), true, 'isEmpty: "0"' );
assert.strictEqual( $.isEmpty( '' ), true, 'isEmpty: ""' );
diff --git a/tests/qunit/suites/resources/mediawiki/mediawiki.jqueryMsg.test.js
b/tests/qunit/suites/resources/mediawiki/mediawiki.jqueryMsg.test.js
index 995c1ed..2638ed0 100644
--- a/tests/qunit/suites/resources/mediawiki/mediawiki.jqueryMsg.test.js
+++ b/tests/qunit/suites/resources/mediawiki/mediawiki.jqueryMsg.test.js
@@ -1,6 +1,7 @@
( function ( mw, $ ) {
- var mwLanguageCache = {}, formatText, formatParse, formatnumTests,
specialCharactersPageName,
- expectedListUsers, expectedEntrypoints;
+ var formatText, formatParse, formatnumTests, specialCharactersPageName,
expectedListUsers, expectedEntrypoints,
+ mwLanguageCache = {},
+ hasOwn = Object.hasOwnProperty;
// When the expected result is the same in both modes
function assertBothModes( assert, parserArguments, expectedResult,
assertMessage ) {
@@ -59,31 +60,52 @@
}
} ) );
- function getMwLanguage( langCode, cb ) {
- if ( mwLanguageCache[langCode] !== undefined ) {
- mwLanguageCache[langCode].add( cb );
- return;
- }
- mwLanguageCache[langCode] = $.Callbacks( 'once memory' );
- mwLanguageCache[langCode].add( cb );
- $.ajax( {
- url: mw.util.wikiScript( 'load' ),
- data: {
- skin: mw.config.get( 'skin' ),
- lang: langCode,
- debug: mw.config.get( 'debug' ),
- modules: [
- 'mediawiki.language.data',
- 'mediawiki.language'
- ].join( '|' ),
- only: 'scripts'
- },
- dataType: 'script'
- } ).done(function () {
- mwLanguageCache[langCode].fire( mw.language );
- } ).fail( function () {
- mwLanguageCache[langCode].fire( false );
+ /**
+ * Be careful to no run this in parallel as it uses a global identifier
(mw.language)
+ * to transport the module back to the test. It musn't be overwritten
concurrentely.
+ *
+ * This function caches the mw.language data to avoid having to request
the same module
+ * multiple times. There is more than one test case for any given
language.
+ */
+ function getMwLanguage( langCode ) {
+ if ( !hasOwn.call( mwLanguageCache, langCode ) ) {
+ mwLanguageCache[langCode] = $.ajax( {
+ url: mw.util.wikiScript( 'load' ),
+ data: {
+ skin: mw.config.get( 'skin' ),
+ lang: langCode,
+ debug: mw.config.get( 'debug' ),
+ modules: [
+ 'mediawiki.language.data',
+ 'mediawiki.language'
+ ].join( '|' ),
+ only: 'scripts'
+ },
+ dataType: 'script',
+ cache: true
+ } ).then( function () {
+ return mw.language;
} );
+ }
+ return mwLanguageCache[langCode];
+ }
+
+ /**
+ * @param {Function[]} tasks List of functions that perform tasks
+ * that may be asynchronous. Invoke the callback parameter when done.
+ * @param {Function} done When all tasks are done.
+ * @return
+ */
+ function process( tasks, done ) {
+ function run() {
+ var task = tasks.shift();
+ if ( task ) {
+ task( run );
+ } else {
+ done();
+ }
+ }
+ run();
}
QUnit.test( 'Replace', 9, function ( assert ) {
@@ -244,23 +266,27 @@
QUnit.test( 'Match PHP parser', mw.libs.phpParserData.tests.length,
function ( assert ) {
mw.messages.set( mw.libs.phpParserData.messages );
- $.each( mw.libs.phpParserData.tests, function ( i, test ) {
- QUnit.stop();
- getMwLanguage( test.lang, function ( langClass ) {
- QUnit.start();
- if ( !langClass ) {
- assert.ok( false, 'Language "' +
test.lang + '" failed to load' );
- return;
- }
- mw.config.set( 'wgUserLanguage', test.lang );
- var parser = new mw.jqueryMsg.parser( {
language: langClass } );
- assert.equal(
- parser.parse( test.key, test.args
).html(),
- test.result,
- test.name
- );
- } );
+ var tasks = $.map( mw.libs.phpParserData.tests, function ( test
) {
+ return function ( next ) {
+ getMwLanguage( test.lang )
+ .done( function ( langClass ) {
+ mw.config.set(
'wgUserLanguage', test.lang );
+ var parser = new
mw.jqueryMsg.parser( { language: langClass } );
+ assert.equal(
+ parser.parse( test.key,
test.args ).html(),
+ test.result,
+ test.name
+ );
+ } )
+ .fail( function () {
+ assert.ok( false, 'Language "'
+ test.lang + '" failed to load.' );
+ } )
+ .always( next );
+ };
} );
+
+ QUnit.stop();
+ process( tasks, QUnit.start );
} );
QUnit.test( 'Links', 6, function ( assert ) {
@@ -419,8 +445,8 @@
);
} );
-// Tests that getMessageFunction is used for non-plain messages with curly
braces or
-// square brackets, but not otherwise.
+ // Tests that getMessageFunction is used for non-plain messages with
curly braces or
+ // square brackets, but not otherwise.
QUnit.test( 'mw.Message.prototype.parser monkey-patch', 22, function (
assert ) {
var oldGMF, outerCalled, innerCalled;
@@ -571,25 +597,27 @@
QUnit.test( 'formatnum', formatnumTests.length, function ( assert ) {
mw.messages.set( 'formatnum-msg', '{{formatnum:$1}}' );
mw.messages.set( 'formatnum-msg-int', '{{formatnum:$1|R}}' );
- $.each( formatnumTests, function ( i, test ) {
- QUnit.stop();
- getMwLanguage( test.lang, function ( langClass ) {
- QUnit.start();
- if ( !langClass ) {
- assert.ok( false, 'Language "' + test.lang + '"
failed to load' );
- return;
- }
- mw.messages.set(test.message );
- mw.config.set( 'wgUserLanguage', test.lang );
- var parser = new mw.jqueryMsg.parser( { language:
langClass } );
- assert.equal(
- parser.parse( test.integer ?
'formatnum-msg-int' : 'formatnum-msg',
- [ test.number ] ).html(),
- test.result,
- test.description
- );
- } );
+ var queue = $.map( formatnumTests, function ( test ) {
+ return function ( next ) {
+ getMwLanguage( test.lang )
+ .done( function ( langClass ) {
+ mw.config.set( 'wgUserLanguage',
test.lang );
+ var parser = new mw.jqueryMsg.parser( {
language: langClass } );
+ assert.equal(
+ parser.parse( test.integer ?
'formatnum-msg-int' : 'formatnum-msg',
+ [ test.number ]
).html(),
+ test.result,
+ test.description
+ );
+ } )
+ .fail( function () {
+ assert.ok( false, 'Language "' +
test.lang + '" failed to load' );
+ } )
+ .always( next );
+ };
} );
+ QUnit.stop();
+ process( queue, QUnit.start );
} );
// HTML in wikitext
--
To view, visit https://gerrit.wikimedia.org/r/198288
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings
Gerrit-MessageType: merged
Gerrit-Change-Id: I4e96da137340a28789b38940e75d4b6b8bc5d76a
Gerrit-PatchSet: 3
Gerrit-Project: mediawiki/core
Gerrit-Branch: REL1_23
Gerrit-Owner: Krinkle <[email protected]>
Gerrit-Reviewer: Daniel Friesen <[email protected]>
Gerrit-Reviewer: Jack Phoenix <[email protected]>
Gerrit-Reviewer: Krinkle <[email protected]>
Gerrit-Reviewer: Mattflaschen <[email protected]>
Gerrit-Reviewer: Siebrand <[email protected]>
Gerrit-Reviewer: jenkins-bot <>
_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits