jenkins-bot has submitted this change and it was merged. (
https://gerrit.wikimedia.org/r/365688 )
Change subject: qunit: Prepare testrunner for QUnit 2
......................................................................
qunit: Prepare testrunner for QUnit 2
* Nested modules:
- Support for Sinon extension was fixed by Ib17bbbef45b2bd.
- Support for Fixture extension was still broken, masked by the use
of a local variable that made the handler not fail when setup ran twice
in a row. Fixed using the same moduleStack.length check.
- Add regression test.
* beforeEach/afterEach:
- Added in 1.16, with compat for setup/teardown.
Our wrapper adds its own setup/teardown, and preserves any original one.
However, it didn't account for beforeEach/afterEach, so it ends up
sending both but only one is used.
- Fix to support both on the incoming localEnv object, and also switch
our wrapper to use beforeEach/afterEach in prep for QUnit 2.0.
- Fix our wrappers to preserve return value since QUnit 2 allows beforeEach
and afterEach hooks to be asynchronous by returning a Promise, similar
to how one can do from QUnit.test().
- Add regression test.
* Centralise makeSafeEnv logic
- We always create our own env object to pass to orgModule().
Document why this is (to avoid recursion).
- Add regression test.
* Custom assertion methods:
- Use this.pushResult instead of the deprecated QUnit.push() method.
This also improves the in-browser reporting of errors by properly
supporting 'negative' results for notHtmlEqual reporter.
Bug: T170515
Change-Id: If4141df10eae55cbe8a5ca7a26707be1cd7b9217
---
M tests/qunit/data/testrunner.js
1 file changed, 185 insertions(+), 85 deletions(-)
Approvals:
jenkins-bot: Verified
Jforrester: Looks good to me, approved
diff --git a/tests/qunit/data/testrunner.js b/tests/qunit/data/testrunner.js
index f023ddd..929fa1f 100644
--- a/tests/qunit/data/testrunner.js
+++ b/tests/qunit/data/testrunner.js
@@ -5,6 +5,22 @@
var addons;
/**
+ * Make a safe copy of localEnv:
+ * - Creates a copy so that when the same object reference to module
hooks is
+ * used by multipe test hooks, our QUnit.module extension will not
wrap the
+ * callbacks multiple times. Instead, they wrap using a new object.
+ * - Normalise setup/teardown to avoid having to repeat this in each
extension
+ * (deprecated in QUnit 1.16, removed in QUnit 2).
+ * - Strip any other properties.
+ */
+ function makeSafeEnv( localEnv ) {
+ return {
+ beforeEach: localEnv.setup || localEnv.beforeEach,
+ afterEach: localEnv.teardown || localEnv.afterEach
+ };
+ }
+
+ /**
* Add bogus to url to prevent IE crazy caching
*
* @param {string} value a relative path (eg. 'data/foo.js'
@@ -42,9 +58,6 @@
*
* Glue code for nicer integration with QUnit setup/teardown
* Inspired by http://sinonjs.org/releases/sinon-qunit-1.0.0.js
- * Fixes:
- * - Work properly with asynchronous QUnit by using module
setup/teardown
- * instead of synchronously wrapping QUnit.test.
*/
sinon.assert.fail = function ( msg ) {
QUnit.assert.ok( false, msg );
@@ -60,74 +73,94 @@
useFakeTimers: false,
useFakeServer: false
};
+ // Extend QUnit.module to provide a Sinon sandbox.
( function () {
var orgModule = QUnit.module;
-
QUnit.module = function ( name, localEnv, executeNow ) {
+ var orgBeforeEach, orgAfterEach;
if ( QUnit.config.moduleStack.length ) {
- // When inside a nested module, don't add our
Sinon
- // setup/teardown a second time.
+ // In a nested module, don't re-run our
handlers.
return orgModule.apply( this, arguments );
}
-
if ( arguments.length === 2 && typeof localEnv ===
'function' ) {
executeNow = localEnv;
localEnv = undefined;
}
localEnv = localEnv || {};
- orgModule( name, {
- setup: function () {
- var config = sinon.getConfig(
sinon.config );
- config.injectInto = this;
- sinon.sandbox.create( config );
+ orgBeforeEach = localEnv.beforeEach;
+ orgAfterEach = localEnv.afterEach;
+ localEnv.beforeEach = function () {
+ var config = sinon.getConfig( sinon.config );
+ config.injectInto = this;
+ sinon.sandbox.create( config );
- if ( localEnv.setup ) {
- localEnv.setup.call( this );
- }
- },
- teardown: function () {
- if ( localEnv.teardown ) {
- localEnv.teardown.call( this );
- }
-
- this.sandbox.verifyAndRestore();
+ if ( orgBeforeEach ) {
+ return orgBeforeEach.apply( this,
arguments );
}
- }, executeNow );
+ };
+ localEnv.afterEach = function () {
+ var ret;
+ if ( orgAfterEach ) {
+ ret = orgAfterEach.apply( this,
arguments );
+ }
+
+ this.sandbox.verifyAndRestore();
+ return ret;
+ };
+ return orgModule( name, localEnv, executeNow );
};
}() );
// Extend QUnit.module to provide a fixture element.
( function () {
var orgModule = QUnit.module;
-
QUnit.module = function ( name, localEnv, executeNow ) {
- var fixture;
-
+ var orgBeforeEach, orgAfterEach;
+ if ( QUnit.config.moduleStack.length ) {
+ // In a nested module, don't re-run our
handlers.
+ return orgModule.apply( this, arguments );
+ }
if ( arguments.length === 2 && typeof localEnv ===
'function' ) {
executeNow = localEnv;
localEnv = undefined;
}
localEnv = localEnv || {};
- orgModule( name, {
- setup: function () {
- fixture = document.createElement( 'div'
);
- fixture.id = 'qunit-fixture';
- document.body.appendChild( fixture );
+ orgBeforeEach = localEnv.beforeEach;
+ orgAfterEach = localEnv.afterEach;
+ localEnv.beforeEach = function () {
+ this.fixture = document.createElement( 'div' );
+ this.fixture.id = 'qunit-fixture';
+ document.body.appendChild( this.fixture );
- if ( localEnv.setup ) {
- localEnv.setup.call( this );
- }
- },
- teardown: function () {
- if ( localEnv.teardown ) {
- localEnv.teardown.call( this );
- }
-
- fixture.parentNode.removeChild( fixture
);
+ if ( orgBeforeEach ) {
+ return orgBeforeEach.apply( this,
arguments );
}
- }, executeNow );
+ };
+ localEnv.afterEach = function () {
+ var ret;
+ if ( orgAfterEach ) {
+ ret = orgAfterEach.apply( this,
arguments );
+ }
+
+ this.fixture.parentNode.removeChild(
this.fixture );
+ return ret;
+ };
+ return orgModule( name, localEnv, executeNow );
+ };
+ }() );
+
+ // Extend QUnit.module to normalise localEnv.
+ // NOTE: This MUST be the last QUnit.module extension so that the above
extensions
+ // may safely modify the object and assume beforeEach/afterEach.
+ ( function () {
+ var orgModule = QUnit.module;
+ QUnit.module = function ( name, localEnv, executeNow ) {
+ if ( typeof localEnv === 'object' ) {
+ localEnv = makeSafeEnv( localEnv );
+ }
+ return orgModule( name, localEnv, executeNow );
};
}() );
@@ -194,18 +227,14 @@
ajaxRequests.push( { xhr: jqXHR, options: ajaxOptions }
);
}
- return function ( localEnv ) {
- localEnv = $.extend( {
- // QUnit
- setup: $.noop,
- teardown: $.noop,
- // MediaWiki
- config: {},
- messages: {}
- }, localEnv );
+ return function ( orgEnv ) {
+ var localEnv = orgEnv ? makeSafeEnv( orgEnv ) : {};
+ // MediaWiki env testing
+ localEnv.config = orgEnv && orgEnv.config || {};
+ localEnv.messages = orgEnv && orgEnv.messages || {};
return {
- setup: function () {
+ beforeEach: function () {
// Greetings, mock environment!
mw.config = new MwMap();
mw.config.set( freshConfigCopy(
localEnv.config ) );
@@ -222,13 +251,17 @@
// Start tracking ajax requests
$( document ).on( 'ajaxSend', trackAjax
);
- localEnv.setup.call( this );
+ if ( localEnv.beforeEach ) {
+ return
localEnv.beforeEach.apply( this, arguments );
+ }
},
- teardown: function () {
- var timers, pending, $activeLen;
+ afterEach: function () {
+ var timers, pending, $activeLen, ret;
- localEnv.teardown.call( this );
+ if ( localEnv.afterEach ) {
+ ret = localEnv.afterEach.apply(
this, arguments );
+ }
// Stop tracking ajax requests
$( document ).off( 'ajaxSend',
trackAjax );
@@ -283,6 +316,8 @@
throw new Error( 'Pending AJAX
requests: ' + pending.length + ' (active: ' + $activeLen + ')' );
}
+
+ return ret;
}
};
};
@@ -356,32 +391,62 @@
// Expect boolean true
assertTrue: function ( actual, message ) {
- QUnit.push( actual === true, actual, true, message );
+ this.pushResult( {
+ result: actual === true,
+ actual: actual,
+ expected: true,
+ message: message
+ } );
},
// Expect boolean false
assertFalse: function ( actual, message ) {
- QUnit.push( actual === false, actual, false, message );
+ this.pushResult( {
+ result: actual === false,
+ actual: actual,
+ expected: false,
+ message: message
+ } );
},
// Expect numerical value less than X
lt: function ( actual, expected, message ) {
- QUnit.push( actual < expected, actual, 'less than ' +
expected, message );
+ this.pushResult( {
+ result: actual < expected,
+ actual: actual,
+ expected: 'less than ' + expected,
+ message: message
+ } );
},
// Expect numerical value less than or equal to X
ltOrEq: function ( actual, expected, message ) {
- QUnit.push( actual <= expected, actual, 'less than or
equal to ' + expected, message );
+ this.pushResult( {
+ result: actual <= expected,
+ actual: actual,
+ expected: 'less than or equal to ' + expected,
+ message: message
+ } );
},
// Expect numerical value greater than X
gt: function ( actual, expected, message ) {
- QUnit.push( actual > expected, actual, 'greater than '
+ expected, message );
+ this.pushResult( {
+ result: actual > expected,
+ actual: actual,
+ expected: 'greater than ' + expected,
+ message: message
+ } );
},
// Expect numerical value greater than or equal to X
gtOrEq: function ( actual, expected, message ) {
- QUnit.push( actual >= expected, actual, 'greater than
or equal to ' + expected, message );
+ this.pushResult( {
+ result: actual >= true,
+ actual: actual,
+ expected: 'greater than or equal to ' +
expected,
+ message: message
+ } );
},
/**
@@ -394,16 +459,12 @@
htmlEqual: function ( actualHtml, expectedHtml, message ) {
var actual = getHtmlStructure( actualHtml ),
expected = getHtmlStructure( expectedHtml );
-
- QUnit.push(
- QUnit.equiv(
- actual,
- expected
- ),
- actual,
- expected,
- message
- );
+ this.pushResult( {
+ result: QUnit.equiv( actual, expected ),
+ actual: actual,
+ expected: expected,
+ message: message
+ } );
},
/**
@@ -417,15 +478,13 @@
var actual = getHtmlStructure( actualHtml ),
expected = getHtmlStructure( expectedHtml );
- QUnit.push(
- !QUnit.equiv(
- actual,
- expected
- ),
- actual,
- expected,
- message
- );
+ this.pushResult( {
+ result: !QUnit.equiv( actual, expected ),
+ actual: actual,
+ expected: expected,
+ message: message,
+ negative: true
+ } );
}
};
@@ -435,7 +494,7 @@
* Small test suite to confirm proper functionality of the utilities and
* initializations defined above in this file.
*/
- QUnit.module( 'test.mediawiki.qunit.testrunner',
QUnit.newMwEnvironment( {
+ QUnit.module( 'testrunner', QUnit.newMwEnvironment( {
setup: function () {
this.mwHtmlLive = mw.html;
mw.html = {
@@ -488,7 +547,7 @@
assert.deepEqual( missing, [], 'Modules in missing state' );
} );
- QUnit.test( 'htmlEqual', function ( assert ) {
+ QUnit.test( 'assert.htmlEqual', function ( assert ) {
assert.htmlEqual(
'<div><p class="some classes" data-length="10">Child
paragraph with <a href="http://example.com">A link</a></p>Regular text<span>A
span</span></div>',
'<div><p data-length=\'10\' class=\'some
classes\'>Child paragraph with <a href=\'http://example.com\' >A
link</a></p>Regular text<span>A span</span></div>',
@@ -535,10 +594,9 @@
'foo<a href="http://example.com">example</a>quux',
'Outer text nodes are compared (last text node
different)'
);
-
} );
- QUnit.module( 'test.mediawiki.qunit.testrunner-after',
QUnit.newMwEnvironment() );
+ QUnit.module( 'testrunner-after', QUnit.newMwEnvironment() );
QUnit.test( 'Teardown', function ( assert ) {
assert.equal( mw.html.escape( '<' ), '<', 'teardown()
callback was ran.' );
@@ -546,4 +604,46 @@
assert.equal( mw.messages.get( 'testMsg' ), null, 'messages
object restored to live in next module()' );
} );
+ QUnit.module( 'testrunner-each', {
+ beforeEach: function () {
+ this.mwHtmlLive = mw.html;
+ },
+ afterEach: function () {
+ mw.html = this.mwHtmlLive;
+ }
+ } );
+ QUnit.test( 'beforeEach', function ( assert ) {
+ assert.ok( this.mwHtmlLive, 'setup() ran' );
+ mw.html = null;
+ } );
+ QUnit.test( 'afterEach', function ( assert ) {
+ assert.equal( mw.html.escape( '<' ), '<', 'afterEach() ran'
);
+ } );
+
+ QUnit.module( 'testrunner-each-compat', {
+ setup: function () {
+ this.mwHtmlLive = mw.html;
+ },
+ teardown: function () {
+ mw.html = this.mwHtmlLive;
+ }
+ } );
+ QUnit.test( 'setup', function ( assert ) {
+ assert.ok( this.mwHtmlLive, 'setup() ran' );
+ mw.html = null;
+ } );
+ QUnit.test( 'teardown', function ( assert ) {
+ assert.equal( mw.html.escape( '<' ), '<', 'teardown() ran' );
+ } );
+
+ // Regression test for 'this.sandbox undefined' error, fixed by
+ // ensuring Sinon setup/teardown is not re-run on inner module.
+ QUnit.module( 'testrunner-nested', function () {
+ QUnit.module( 'testrunner-nested-inner', function () {
+ QUnit.test( 'Dummy', function ( assert ) {
+ assert.ok( true, 'Nested modules supported' );
+ } );
+ } );
+ } );
+
}( jQuery, mediaWiki, QUnit ) );
--
To view, visit https://gerrit.wikimedia.org/r/365688
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings
Gerrit-MessageType: merged
Gerrit-Change-Id: If4141df10eae55cbe8a5ca7a26707be1cd7b9217
Gerrit-PatchSet: 16
Gerrit-Project: mediawiki/core
Gerrit-Branch: master
Gerrit-Owner: Krinkle <[email protected]>
Gerrit-Reviewer: Catrope <[email protected]>
Gerrit-Reviewer: Jforrester <[email protected]>
Gerrit-Reviewer: Krinkle <[email protected]>
Gerrit-Reviewer: Legoktm <[email protected]>
Gerrit-Reviewer: Thiemo Mättig (WMDE) <[email protected]>
Gerrit-Reviewer: jenkins-bot <>
_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits