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:
* 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
* 0ad7553228: build: Don't run jshint on empty skins dir
* 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/specials/SpecialJavaScriptTest.php
M package.json
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
6 files changed, 188 insertions(+), 84 deletions(-)

Approvals:
  Jforrester: Looks good to me, approved
  jenkins-bot: Verified



diff --git a/Gruntfile.js b/Gruntfile.js
index 375c3a2..f817780 100644
--- a/Gruntfile.js
+++ b/Gruntfile.js
@@ -6,6 +6,13 @@
        grunt.loadNpmTasks( 'grunt-banana-checker' );
        grunt.loadNpmTasks( 'grunt-jscs' );
        grunt.loadNpmTasks( 'grunt-jsonlint' );
+       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' ),
@@ -15,7 +22,7 @@
                        },
                        all: [
                                '*.js',
-                               
'{includes,languages,resources,skins,tests}/**/*.js'
+                               '{includes,languages,resources,tests}/**/*.js'
                        ]
                },
                jscs: {
@@ -56,6 +63,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/**/*',
@@ -68,7 +98,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', 'jsonlint', 'banana'] );
+       grunt.registerTask( 'qunit', [ 'assert-mw-env', 'karma:main' ] );
+
        grunt.registerTask( 'test', ['lint'] );
-       grunt.registerTask( 'default', ['test'] );
+       grunt.registerTask( 'default', 'test' );
 };
diff --git a/includes/specials/SpecialJavaScriptTest.php 
b/includes/specials/SpecialJavaScriptTest.php
index e31258a..7d745a5 100644
--- a/includes/specials/SpecialJavaScriptTest.php
+++ b/includes/specials/SpecialJavaScriptTest.php
@@ -161,7 +161,6 @@
                $baseHtml = <<<HTML
 <div class="mw-content-ltr">
 <div id="qunit"></div>
-<div id="qunit-fixture"></div>
 </div>
 HTML;
 
@@ -262,7 +261,6 @@
 <title>QUnit</title>
 $head
 <div id="qunit"></div>
-<div id="qunit-fixture"></div>
 HTML;
                $html .= "\n" . Html::linkedScript( $url );
 
diff --git a/package.json b/package.json
index a20da53..36d276b 100644
--- a/package.json
+++ b/package.json
@@ -7,12 +7,19 @@
     "postdoc": "grunt copy:jsduck"
   },
   "devDependencies": {
-    "grunt": "0.4.2",
+    "grunt": "0.4.5",
+    "grunt-cli": "0.1.13",
     "grunt-contrib-jshint": "0.10.0",
+    "grunt-banana-checker": "0.2.0",
     "grunt-contrib-copy": "0.8.0",
     "grunt-contrib-watch": "0.6.1",
-    "grunt-banana-checker": "0.2.0",
     "grunt-jscs": "0.6.1",
-    "grunt-jsonlint": "1.0.4"
+    "grunt-jsonlint": "1.0.4",
+    "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/tests/qunit/data/testrunner.js b/tests/qunit/data/testrunner.js
index 2eda8f1..db312b2 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 906fd27..6b3be43 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 ) {
@@ -58,31 +59,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 ) {
@@ -246,23 +268,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 ) {
@@ -429,8 +455,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;
 
@@ -581,25 +607,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/198274
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: merged
Gerrit-Change-Id: I4e96da137340a28789b38940e75d4b6b8bc5d76a
Gerrit-PatchSet: 2
Gerrit-Project: mediawiki/core
Gerrit-Branch: REL1_24
Gerrit-Owner: Krinkle <[email protected]>
Gerrit-Reviewer: Jforrester <[email protected]>
Gerrit-Reviewer: Krinkle <[email protected]>
Gerrit-Reviewer: jenkins-bot <>

_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to