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
*
*
* 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 );
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 );
-
- if ( localEnv.setup ) {
- localEnv.setup.call( this );
- }
- },
- teardown: function () {
- if ( localEnv.teardown ) {
- localEnv.teardown.call( this );
- }
-
- this.sandbox.verifyAndRestore();
+ orgBeforeEach = localEnv.beforeEach;
+ orgAfterEach = localEnv.afterEach;
+ localEnv.beforeEach = function () {
+ var config = sinon.getConfig( sinon.config );
+ config.injectInto = this;
+ sinon.sandbox.create( config );
+
+ if ( orgBeforeEach ) {
+ return orgBeforeEach.apply( this, arguments );
+ }
+ };
+ localEnv.afterEach = function () {
+ var ret;
+ if ( orgAfterEach ) {
+ ret = orgAfterEach.apply( this, arguments );
}
- }, executeNow );
+
+ 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 );
-
- if ( localEnv.setup ) {
- localEnv.setup.call( this );
- }
- },
- teardown: function () {
- if ( localEnv.teardown ) {
- localEnv.teardown.call( this );
- }
-
- fixture.parentNode.removeChild( 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 ( 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 );
};
}() );
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 ) );
// 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 );
throw new Error( 'Pending AJAX requests: ' + pending.length + ' (active: ' + $activeLen + ')' );
}
+
+ return ret;
}
};
};
// 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
+ } );
},
/**
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
+ } );
},
/**
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
+ } );
}
};
* 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 = {
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>',
'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.' );
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 ) );