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
This commit is contained in:
parent
f05716b1f6
commit
43dc5c1539
1 changed files with 185 additions and 85 deletions
|
|
@ -4,6 +4,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
|
||||
*
|
||||
|
|
@ -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 ) );
|
||||
|
|
|
|||
Loading…
Reference in a new issue