2012-01-03 18:33:26 +00:00
|
|
|
( function ( $, mw, QUnit, undefined ) {
|
|
|
|
|
"use strict";
|
|
|
|
|
|
|
|
|
|
var mwTestIgnore, mwTester, addons;
|
2011-07-10 19:10:51 +00:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Add bogus to url to prevent IE crazy caching
|
|
|
|
|
*
|
2012-02-24 00:44:33 +00:00
|
|
|
* @param value {String} a relative path (eg. 'data/foo.js'
|
2012-01-03 18:33:26 +00:00
|
|
|
* or 'data/test.php?foo=bar').
|
2012-02-24 00:44:33 +00:00
|
|
|
* @return {String} Such as 'data/foo.js?131031765087663960'
|
2011-07-10 19:10:51 +00:00
|
|
|
*/
|
2012-04-11 17:26:06 +00:00
|
|
|
QUnit.fixurl = function ( value ) {
|
2012-01-03 18:33:26 +00:00
|
|
|
return value + (/\?/.test( value ) ? '&' : '?')
|
|
|
|
|
+ String( new Date().getTime() )
|
2012-04-11 17:26:06 +00:00
|
|
|
+ String( parseInt( Math.random() * 100000, 10 ) );
|
2011-07-10 19:10:51 +00:00
|
|
|
};
|
|
|
|
|
|
2011-10-20 22:33:05 +00:00
|
|
|
/**
|
|
|
|
|
* Configuration
|
|
|
|
|
*/
|
|
|
|
|
|
2012-04-11 17:26:06 +00:00
|
|
|
// When a test() indicates asynchronicity with stop(),
|
|
|
|
|
// allow 10 seconds to pass before killing the test(),
|
|
|
|
|
// and assuming failure.
|
|
|
|
|
QUnit.config.testTimeout = 10 * 1000;
|
|
|
|
|
|
|
|
|
|
// Add a checkbox to QUnit header to toggle MediaWiki ResourceLoader debug mode.
|
2012-01-03 18:33:26 +00:00
|
|
|
QUnit.config.urlConfig.push( 'debug' );
|
|
|
|
|
|
2011-07-10 19:10:51 +00:00
|
|
|
/**
|
2012-04-11 17:26:06 +00:00
|
|
|
* Load TestSwarm agent
|
2011-07-10 19:10:51 +00:00
|
|
|
*/
|
Fix support for TestSwarm on SpecialJavaScriptTest/qunit
So far we've still been using ./tests/qunit/index.html in TestSwarm, today I've tested locally
to submit a url to SpecialJavaScriptTest instead and made a bunch of browsers join my swarm,
quite a few problems popped up. This commit fixes those issues so that we can actually use
SpecialJavaScriptTest in TestSwarm.
* Add QUnit configuration variable for TestSwarm's inject.js
In order to use TestSwarm, the urls that TestSwarm loads in clients that has
the QUnit test suite running on it need to include a little javascript.
This inject.js registers hooks with QUnit to listen for when the test suite finishes
and contacts the parent window (TestSwarm loads the qunit test suite url in an iframe)
to submit the results. Previously I included a copy of TestSwarm's inject.js in
./tests/qunit/data and in our testrunner.js a relative link to that.
However this is currently breaking because it is an outdated version. Updating brings
no good since someone else might use their own TestSwarm would could still run on an old
version etc. The TestSwarm submitted too always expects that it's own inject.js is used,
not some snapshot copy. I've removed the copy of it in MediaWiki and instead added a
configuration option to point to wherever the you want is located.
Also, since the old static index.html version of the unit test can't retrieve PHP based
content, this means TestSwarm submissions through the old static index.html are no longer
supported. Only through the new Special:JavaScriptTest from now on. I'll probably remove
the whole index.html soon-ish as it's getting quite annoying to maintain all that by hand,
and it's been superseded in everywhere imaginable now anyway. Even not used anymore by
intergration.mediawiki.org because that's been quiet since the Git-switchover.., and when
we update it, we can update it to point to the new SpecialPage instead.
* OutputPage::allowClickjacking() on SpecialJavaScriptTest/qunit.
When initially testing the TestSwarm setup to submit SpecialJavaScriptTest/qunit urls
(instead of the old ./tests/qunit/index.html) it was failing due to an iframe DENY.
This was a bit odd since `$wgBreakFrames = false;` by default, and although
`$wgEditPageFrameOptions = 'DENY';` by default, it wasn't obvious at all that that value
("DENY") is used for all OutputPages by default (as supposed to just action=edit and the
like). This is because OutputPage has mPreventClickjacking=true by default and when it's
true-ish it uses $wgEditPageFrameOptions for the X-Frame-Options.
* 'position' => true; for the mediawiki.tests.qunit.suites module.
QUnit has a hook for "done". Which is called when QUnit.start() is called and all queued tests
have been executed. QUnit.start() is automatically called on window.onload by QUnit.
TestSwarm uses QUnit's hook system to hook into the QUnit "done" event, and at that point
takes the stats, submits them to TestSwarm and go on with the next job.
When testing locally, I got semi-random failures reporting that only 0/0 tests were
successfully ran in IE6. This is because when QUnit.start (and consequently QUnit.done)
are first called, apparently no test suites had finished downloading and/or execution yet
(the bottom queue is asynchronous, and doesn't postpone domready nor window.onload).
When normally viewing Special:JavaScriptTest/qunit this doesn't break anything, because if
QUnit start/done is in the past and another module(), test(), or equal() etc. is called it just
picks up again and adds more results to the page and calls QUnit.done() again.
However in the case of the TestSwarm embed, it submits the results after the first done() and
cleans up the iframe. So I'm making mediawiki.tests.qunit.suites a blocking module instead, so
that there will only be one QUnit.start/done and that's the one that TestSwarm gets and after
which TestSwarm can safely garbage the iframe.
This means that basically all test suite modules and the original modules they are testing will
be loaded from the head. Shouldn't have any side effects, but might cause minor breakage in
future in modules that badly assume they're being put on the bottom.
I'm not considering that a bug in the test, it'll just help catch that bad code sooner :),
it's a test suite after all.
(Yay, my first Git commit to MediaWiki core)
Change-Id: I83f83377f2183b6deb4e901af602ac9a5628558b
2012-03-25 01:09:58 +00:00
|
|
|
// Only if the current url indicates that there is a TestSwarm instance watching us
|
|
|
|
|
// (TestSwarm appends swarmURL to the test suites url it loads in iframes).
|
|
|
|
|
// Otherwise this is just a simple view of Special:JavaScriptTest/qunit directly,
|
|
|
|
|
// no point in loading inject.js in that case. Also, make sure that this instance
|
|
|
|
|
// of MediaWiki has actually been configured with the required url to that inject.js
|
|
|
|
|
// script. By default it is false.
|
|
|
|
|
if ( QUnit.urlParams.swarmURL && mw.config.get( 'QUnitTestSwarmInjectJSPath' ) ) {
|
|
|
|
|
document.write( "<scr" + "ipt src='" + QUnit.fixurl( mw.config.get( 'QUnitTestSwarmInjectJSPath' ) ) + "'></scr" + "ipt>" );
|
2011-07-10 19:10:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2012-01-03 18:33:26 +00:00
|
|
|
* CompletenessTest
|
2011-07-10 19:10:51 +00:00
|
|
|
*/
|
2012-01-03 18:33:26 +00:00
|
|
|
// Adds toggle checkbox to header
|
|
|
|
|
QUnit.config.urlConfig.push( 'completenesstest' );
|
|
|
|
|
|
|
|
|
|
// Initiate when enabled
|
2011-07-10 19:10:51 +00:00
|
|
|
if ( QUnit.urlParams.completenesstest ) {
|
|
|
|
|
|
|
|
|
|
// Return true to ignore
|
2012-01-03 18:33:26 +00:00
|
|
|
mwTestIgnore = function ( val, tester, funcPath ) {
|
2011-07-10 19:10:51 +00:00
|
|
|
|
|
|
|
|
// Don't record methods of the properties of constructors,
|
|
|
|
|
// to avoid getting into a loop (prototype.constructor.prototype..).
|
|
|
|
|
// Since we're therefor skipping any injection for
|
|
|
|
|
// "new mw.Foo()", manually set it to true here.
|
|
|
|
|
if ( val instanceof mw.Map ) {
|
2012-01-03 18:33:26 +00:00
|
|
|
tester.methodCallTracker.Map = true;
|
2011-07-10 19:10:51 +00:00
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
if ( val instanceof mw.Title ) {
|
2012-01-03 18:33:26 +00:00
|
|
|
tester.methodCallTracker.Title = true;
|
2011-07-10 19:10:51 +00:00
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Don't record methods of the properties of a jQuery object
|
|
|
|
|
if ( val instanceof $ ) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
};
|
|
|
|
|
|
2012-01-03 18:33:26 +00:00
|
|
|
mwTester = new CompletenessTest( mw, mwTestIgnore );
|
2011-07-10 19:10:51 +00:00
|
|
|
}
|
|
|
|
|
|
2012-01-03 18:33:26 +00:00
|
|
|
/**
|
|
|
|
|
* Test environment recommended for all QUnit test modules
|
|
|
|
|
*/
|
|
|
|
|
// Whether to log environment changes to the console
|
|
|
|
|
QUnit.config.urlConfig.push( 'mwlogenv' );
|
|
|
|
|
|
|
|
|
|
/**
|
2012-05-12 22:37:30 +00:00
|
|
|
* Reset mw.config and others to a fresh copy of the live config for each test(),
|
|
|
|
|
* and restore it back to the live one afterwards.
|
|
|
|
|
* @param localEnv {Object} [optional]
|
|
|
|
|
* @example (see test suite at the bottom of this file)
|
2012-01-03 18:33:26 +00:00
|
|
|
* </code>
|
|
|
|
|
*/
|
|
|
|
|
QUnit.newMwEnvironment = ( function () {
|
2012-05-12 22:37:30 +00:00
|
|
|
var log, liveConfig, liveMessages;
|
2012-01-03 18:33:26 +00:00
|
|
|
|
|
|
|
|
liveConfig = mw.config.values;
|
2012-05-12 22:37:30 +00:00
|
|
|
liveMessages = mw.messages.values;
|
2012-01-03 18:33:26 +00:00
|
|
|
|
2012-02-24 01:00:54 +00:00
|
|
|
function freshConfigCopy( custom ) {
|
2012-01-03 18:33:26 +00:00
|
|
|
// "deep=true" is important here.
|
|
|
|
|
// Otherwise we just create a new object with values referring to live config.
|
|
|
|
|
// e.g. mw.config.set( 'wgFileExtensions', [] ) would not effect liveConfig,
|
|
|
|
|
// but mw.config.get( 'wgFileExtensions' ).push( 'png' ) would as the array
|
|
|
|
|
// was passed by reference in $.extend's loop.
|
2012-05-12 22:37:30 +00:00
|
|
|
return $.extend( {}, liveConfig, custom, /*deep=*/true );
|
2012-02-24 01:00:54 +00:00
|
|
|
}
|
|
|
|
|
|
2012-05-12 22:37:30 +00:00
|
|
|
function freshMessagesCopy( custom ) {
|
|
|
|
|
return $.extend( {}, liveMessages, custom, /*deep=*/true );
|
2012-02-24 01:00:54 +00:00
|
|
|
}
|
2012-01-03 18:33:26 +00:00
|
|
|
|
|
|
|
|
log = QUnit.urlParams.mwlogenv ? mw.log : function () {};
|
|
|
|
|
|
2012-05-12 22:37:30 +00:00
|
|
|
return function ( localEnv ) {
|
|
|
|
|
localEnv = $.extend( {
|
|
|
|
|
// QUnit
|
|
|
|
|
setup: $.noop,
|
|
|
|
|
teardown: $.noop,
|
|
|
|
|
// MediaWiki
|
|
|
|
|
config: {},
|
|
|
|
|
messages: {}
|
|
|
|
|
}, localEnv );
|
2012-01-03 18:33:26 +00:00
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
setup: function () {
|
|
|
|
|
log( 'MwEnvironment> SETUP for "' + QUnit.config.current.module
|
|
|
|
|
+ ': ' + QUnit.config.current.testName + '"' );
|
2012-05-12 22:37:30 +00:00
|
|
|
|
2012-02-24 01:00:54 +00:00
|
|
|
// Greetings, mock environment!
|
2012-05-12 22:37:30 +00:00
|
|
|
mw.config.values = freshConfigCopy( localEnv.config );
|
|
|
|
|
mw.messages.values = freshMessagesCopy( localEnv.messages );
|
|
|
|
|
|
|
|
|
|
localEnv.setup()
|
2012-01-03 18:33:26 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
|
|
teardown: function () {
|
|
|
|
|
log( 'MwEnvironment> TEARDOWN for "' + QUnit.config.current.module
|
|
|
|
|
+ ': ' + QUnit.config.current.testName + '"' );
|
2012-05-12 22:37:30 +00:00
|
|
|
|
|
|
|
|
localEnv.teardown();
|
|
|
|
|
|
2012-02-24 01:00:54 +00:00
|
|
|
// Farewell, mock environment!
|
2012-01-03 18:33:26 +00:00
|
|
|
mw.config.values = liveConfig;
|
2012-05-12 22:37:30 +00:00
|
|
|
mw.messages.values = liveMessages;
|
2012-01-03 18:33:26 +00:00
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
}() );
|
|
|
|
|
|
2012-06-07 20:45:53 +00:00
|
|
|
// $.when stops as soon as one fails, which makes sense in most
|
|
|
|
|
// practical scenarios, but not in a unit test where we really do
|
|
|
|
|
// need to wait until all of them are finished.
|
|
|
|
|
QUnit.whenPromisesComplete = function () {
|
|
|
|
|
var altPromises = [];
|
|
|
|
|
|
|
|
|
|
$.each( arguments, function ( i, arg ) {
|
|
|
|
|
var alt = $.Deferred();
|
|
|
|
|
altPromises.push( alt );
|
|
|
|
|
|
|
|
|
|
// Whether this one fails or not, forwards it to
|
|
|
|
|
// the 'done' (resolve) callback of the alternative promise.
|
|
|
|
|
arg.always( alt.resolve );
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return $.when.apply( $, altPromises );
|
|
|
|
|
};
|
|
|
|
|
|
2011-07-10 19:10:51 +00:00
|
|
|
/**
|
|
|
|
|
* Add-on assertion helpers
|
|
|
|
|
*/
|
|
|
|
|
// Define the add-ons
|
2012-01-03 18:33:26 +00:00
|
|
|
addons = {
|
2011-07-10 19:10:51 +00:00
|
|
|
|
|
|
|
|
// Expect boolean true
|
2012-01-03 18:33:26 +00:00
|
|
|
assertTrue: function ( actual, message ) {
|
2011-07-10 19:10:51 +00:00
|
|
|
strictEqual( actual, true, message );
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// Expect boolean false
|
2012-01-03 18:33:26 +00:00
|
|
|
assertFalse: function ( actual, message ) {
|
2011-07-10 19:10:51 +00:00
|
|
|
strictEqual( actual, false, message );
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// Expect numerical value less than X
|
2012-01-03 18:33:26 +00:00
|
|
|
lt: function ( actual, expected, message ) {
|
2011-07-10 19:10:51 +00:00
|
|
|
QUnit.push( actual < expected, actual, 'less than ' + expected, message );
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// Expect numerical value less than or equal to X
|
2012-01-03 18:33:26 +00:00
|
|
|
ltOrEq: function ( actual, expected, message ) {
|
2011-07-10 19:10:51 +00:00
|
|
|
QUnit.push( actual <= expected, actual, 'less than or equal to ' + expected, message );
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// Expect numerical value greater than X
|
2012-01-03 18:33:26 +00:00
|
|
|
gt: function ( actual, expected, message ) {
|
2011-07-10 19:10:51 +00:00
|
|
|
QUnit.push( actual > expected, actual, 'greater than ' + expected, message );
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// Expect numerical value greater than or equal to X
|
2012-01-03 18:33:26 +00:00
|
|
|
gtOrEq: function ( actual, expected, message ) {
|
2011-07-10 19:10:51 +00:00
|
|
|
QUnit.push( actual >= expected, actual, 'greater than or equal to ' + expected, message );
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// Backwards compatible with new verions of QUnit
|
|
|
|
|
equals: window.equal,
|
|
|
|
|
same: window.deepEqual
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
$.extend( QUnit, addons );
|
|
|
|
|
$.extend( window, addons );
|
|
|
|
|
|
2012-05-12 22:37:30 +00:00
|
|
|
/**
|
|
|
|
|
* Small test suite to confirm proper functionality of the utilities and
|
|
|
|
|
* initializations in this file.
|
|
|
|
|
*/
|
|
|
|
|
var envExecCount = 0;
|
|
|
|
|
module( 'mediawiki.tests.qunit.testrunner', QUnit.newMwEnvironment({
|
|
|
|
|
setup: function () {
|
|
|
|
|
envExecCount += 1;
|
|
|
|
|
this.mwHtmlLive = mw.html;
|
|
|
|
|
mw.html = {
|
|
|
|
|
escape: function () {
|
|
|
|
|
return 'mocked-' + envExecCount;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
},
|
|
|
|
|
teardown: function () {
|
|
|
|
|
mw.html = this.mwHtmlLive;
|
|
|
|
|
},
|
|
|
|
|
config: {
|
|
|
|
|
testVar: 'foo'
|
|
|
|
|
},
|
|
|
|
|
messages: {
|
|
|
|
|
testMsg: 'Foo.'
|
|
|
|
|
}
|
|
|
|
|
}) );
|
|
|
|
|
|
|
|
|
|
test( 'Setup', function () {
|
|
|
|
|
expect( 3 );
|
|
|
|
|
|
|
|
|
|
equal( mw.html.escape( 'foo' ), 'mocked-1', 'extra setup() callback was ran.' );
|
|
|
|
|
equal( mw.config.get( 'testVar' ), 'foo', 'config object applied' );
|
|
|
|
|
equal( mw.messages.get( 'testMsg' ), 'Foo.', 'messages object applied' );
|
|
|
|
|
|
|
|
|
|
mw.config.set( 'testVar', 'bar' );
|
|
|
|
|
mw.messages.set( 'testMsg', 'Bar.' );
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test( 'Teardown', function () {
|
|
|
|
|
expect( 3 );
|
|
|
|
|
|
|
|
|
|
equal( mw.html.escape( 'foo' ), 'mocked-2', 'extra setup() callback was re-ran.' );
|
|
|
|
|
equal( mw.config.get( 'testVar' ), 'foo', 'config object restored and re-applied after test()' );
|
|
|
|
|
equal( mw.messages.get( 'testMsg' ), 'Foo.', 'messages object restored and re-applied after test()' );
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
module( 'mediawiki.tests.qunit.testrunner-after', QUnit.newMwEnvironment() );
|
|
|
|
|
|
|
|
|
|
test( 'Teardown', function () {
|
|
|
|
|
expect( 3 );
|
|
|
|
|
|
|
|
|
|
equal( mw.html.escape( '<' ), '<', 'extra teardown() callback was ran.' );
|
|
|
|
|
equal( mw.config.get( 'testVar' ), null, 'config object restored to live in next module()' );
|
|
|
|
|
equal( mw.messages.get( 'testMsg' ), null, 'messages object restored to live in next module()' );
|
|
|
|
|
});
|
|
|
|
|
|
2012-01-03 18:33:26 +00:00
|
|
|
})( jQuery, mediaWiki, QUnit );
|