merge JSTesting branch into trunk

Changed written by Timo and reviewed by Hashar. This should be harmless.

To enable the feature:
  $wgEnableJavaScriptTest = true;

Then head to:
  [[Special:JavaScriptTest/qunit]]
This commit is contained in:
Antoine Musso 2012-01-03 18:33:26 +00:00
parent 0bf95761a2
commit c447423593
32 changed files with 607 additions and 135 deletions

View file

@ -112,6 +112,9 @@ production.
* (bug 23427) Introduced {{PAGEID}} variable to expose page.page_id.
* (bug 33447) Link to the broken image tracking category from Special:Wantedfiles.
* (bug 27724) Add timestamp to job queue.
* (bug 30339) Implement SpecialPage for running javascript tests. Disabled by default, due to
tests potentially being harmful, not to be run on a production wiki.
Enable by setting $wgEnableJavaScriptTest to true.
=== Bug fixes in 1.19 ===
* $wgUploadNavigationUrl should be used for file redlinks if.

View file

@ -1579,6 +1579,16 @@ scripts.
loader request or generating HTML output.
&$resourceLoader: ResourceLoader object
'ResourceLoaderTestModules': let you add new javascript testing modules. This is called after the addition of 'qunit' and MediaWiki testing ressources.
&testModules: array of javascript testing modules. 'qunit' is feed using tests/qunit/QUnitTestResources.php.
&RessourceLoader object
To add a new qunit module named 'myext.tests':
testModules['qunit']['myext.tests'] = array(
'script' => 'extension/myext/tests.js',
'dependencies' => <any module dependency you might have>
);
For qunit framework, the mediawiki.tests.qunit.testrunner dependency will be added to any module.
'RevisionInsertComplete': called after a revision is inserted into the DB
&$revision: the Revision
$data: the data stored in old_text. The meaning depends on $flags: if external

View file

@ -812,6 +812,7 @@ $wgAutoloadLocalClasses = array(
'SpecialExport' => 'includes/specials/SpecialExport.php',
'SpecialFilepath' => 'includes/specials/SpecialFilepath.php',
'SpecialImport' => 'includes/specials/SpecialImport.php',
'SpecialJavaScriptTest' => 'includes/specials/SpecialJavaScriptTest.php',
'SpecialListFiles' => 'includes/specials/SpecialListfiles.php',
'SpecialListGroupRights' => 'includes/specials/SpecialListgrouprights.php',
'SpecialListUsers' => 'includes/specials/SpecialListusers.php',

View file

@ -4194,6 +4194,20 @@ $wgParserTestFiles = array(
* );
*/
$wgParserTestRemote = false;
/**
* Allow running of javascript test suites via [[Special:JavaScriptTest]] (such as QUnit).
*/
$wgEnableJavaScriptTest = false;
/**
* Configuration for javascript testing.
*/
$wgJavaScriptTestConfig = array(
'qunit' => array(
'documentation' => '//www.mediawiki.org/wiki/Manual:JavaScript_unit_testing',
),
);
/**
@ -5248,6 +5262,7 @@ $wgSpecialPageGroups = array(
'Specialpages' => 'other',
'Blockme' => 'other',
'Booksources' => 'other',
'JavaScriptTest' => 'other',
);
/** Whether or not to sort special pages in Special:Specialpages */

View file

@ -22,7 +22,7 @@ abstract class Skin extends ContextSource {
/**
* Fetch the set of available skins.
* @return array of strings
* @return associative array of strings
*/
static function getSkinNames() {
global $wgValidSkinNames;
@ -55,6 +55,18 @@ abstract class Skin extends ContextSource {
}
return $wgValidSkinNames;
}
/**
* Fetch the skinname messages for available skins.
* @return array of strings
*/
static function getSkinNameMessages() {
$messages = array();
foreach( self::getSkinNames() as $skinKey => $skinName ) {
$messages[] = "skinname-$skinKey";
}
return $messages;
}
/**
* Fetch the list of usable skins in regards to $wgSkipSkins.

View file

@ -138,6 +138,7 @@ class SpecialPageFactory {
'Blankpage' => 'SpecialBlankpage',
'Blockme' => 'SpecialBlockme',
'Emailuser' => 'SpecialEmailUser',
'JavaScriptTest' => 'SpecialJavaScriptTest',
'Movepage' => 'MovePageForm',
'Mycontributions' => 'SpecialMycontributions',
'Mypage' => 'SpecialMypage',

View file

@ -37,6 +37,10 @@ class ResourceLoader {
/** Associative array mapping module name to info associative array */
protected $moduleInfos = array();
/** Associative array mapping framework ids to a list of names of test suite modules */
/** like array( 'qunit' => array( 'mediawiki.tests.qunit.suites', 'ext.foo.tests', .. ), .. ) */
protected $testModuleNames = array();
/** array( 'source-id' => array( 'loadScript' => 'http://.../load.php' ) ) **/
protected $sources = array();
@ -183,7 +187,7 @@ class ResourceLoader {
* Registers core modules and runs registration hooks.
*/
public function __construct() {
global $IP, $wgResourceModules, $wgResourceLoaderSources, $wgLoadScript;
global $IP, $wgResourceModules, $wgResourceLoaderSources, $wgLoadScript, $wgEnableJavaScriptTest;
wfProfileIn( __METHOD__ );
@ -199,6 +203,11 @@ class ResourceLoader {
wfRunHooks( 'ResourceLoaderRegisterModules', array( &$this ) );
$this->register( $wgResourceModules );
if ( $wgEnableJavaScriptTest === true ) {
$this->registerTestModules();
}
wfProfileOut( __METHOD__ );
}
@ -256,6 +265,40 @@ class ResourceLoader {
wfProfileOut( __METHOD__ );
}
/**
*/
public function registerTestModules() {
global $IP, $wgEnableJavaScriptTest;
if ( $wgEnableJavaScriptTest !== true ) {
throw new MWException( 'Attempt to register JavaScript test modules but <tt>$wgEnableJavaScriptTest</tt> is false. Edit your <tt>LocalSettings.php</tt> to enable it.' );
}
wfProfileIn( __METHOD__ );
// Get core test suites
$testModules = array();
$testModules['qunit'] = include( "$IP/tests/qunit/QUnitTestResources.php" );
// Get other test suites (e.g. from extensions)
wfRunHooks( 'ResourceLoaderTestModules', array( &$testModules, &$this ) );
// Add the testrunner (which configures QUnit) to the dependencies.
// Since it must be ready before any of the test suites are executed.
foreach( $testModules['qunit'] as $moduleName => $moduleProps ) {
$testModules['qunit'][$moduleName]['dependencies'][] = 'mediawiki.tests.qunit.testrunner';
}
foreach( $testModules as $id => $names ) {
// Register test modules
$this->register( $testModules[$id] );
// Keep track of their names so that they can be loaded together
$this->testModuleNames[$id] = array_keys( $testModules[$id] );
}
wfProfileOut( __METHOD__ );
}
/**
* Add a foreign source of modules.
*
@ -300,6 +343,25 @@ class ResourceLoader {
public function getModuleNames() {
return array_keys( $this->moduleInfos );
}
/**
* Get a list of test module names for one (or all) frameworks.
* If the given framework id is unknkown, or if the in-object variable is not an array,
* then it will return an empty array.
*
* @param $framework String: Optional. Get only the test module names for one
* particular framework.
* @return Array
*/
public function getTestModuleNames( $framework = 'all' ) {
if ( $framework == 'all' ) {
return $this->testModuleNames;
} elseif ( isset( $this->testModuleNames[$framework] ) && is_array( $this->testModuleNames[$framework] ) ) {
return $this->testModuleNames[$framework];
} else {
return array();
}
}
/**
* Get the ResourceLoaderModule object for a given module name.

View file

@ -0,0 +1,127 @@
<?php
class SpecialJavaScriptTest extends SpecialPage {
/**
* @var $frameworks Array: Mapping of framework ids and their initilizer methods
* in this class. If a framework is requested but not in this array,
* the 'unknownframework' error is served.
*/
static $frameworks = array(
'qunit' => 'initQUnitTesting',
);
public function __construct() {
parent::__construct( 'JavaScriptTest' );
}
public function execute( $par ) {
global $wgEnableJavaScriptTest;
$out = $this->getOutput();
$this->setHeaders();
$out->disallowUserJs();
// Abort early if we're disabled
if ( $wgEnableJavaScriptTest !== true ) {
$out->addWikiMsg( 'javascripttest-disabled' );
return;
}
$out->addModules( 'mediawiki.special.javaScriptTest' );
// Determine framework
$pars = explode( '/', $par );
$framework = strtolower( $pars[0] );
// No framework specified
if ( $par == '' ) {
$out->setPagetitle( wfMsg( 'javascripttest' ) );
$summary = $this->wrapSummaryHtml(
wfMsg( 'javascripttest-pagetext-noframework' ) . $this->getFrameworkListHtml(),
'noframework'
);
$out->addHtml( $summary );
// Matched! Display proper title and initialize the framework
} elseif ( isset( self::$frameworks[$framework] ) ) {
$out->setPagetitle( wfMsg( 'javascripttest-title', wfMsg( "javascripttest-$framework-name" ) ) );
$out->setSubtitle(
wfMessage( 'javascripttest-backlink' )->rawParams( Linker::linkKnown( $this->getTitle() ) )->escaped()
);
$this->{self::$frameworks[$framework]}();
// Framework not found, display error
} else {
$out->setPagetitle( wfMsg( 'javascripttest' ) );
$summary = $this->wrapSummaryHtml( '<p class="error">'
. wfMsg( 'javascripttest-pagetext-unknownframework', $par )
. '</p>'
. $this->getFrameworkListHtml() );
$out->addHtml( $summary, 'unknownframework' );
}
}
/**
* Get a list of frameworks (including introduction paragraph and links to the framework run pages)
* @return String: HTML
*/
private function getFrameworkListHtml() {
$list = '<ul>';
foreach( self::$frameworks as $framework => $initFn ) {
$list .= Html::rawElement(
'li',
array(),
Linker::link( $this->getTitle( $framework ), wfMsg( "javascripttest-$framework-name" ) )
);
}
$list .= '</ul>';
$msg = wfMessage( 'javascripttest-pagetext-frameworks' )->rawParams( $list )->parseAsBlock();
return $msg;
}
/**
* Function to wrap the summary.
* @param $html String: The raw HTML.
* @param $state String: State, one of 'noframework', 'unknownframework' or 'frameworkfound'
*/
private function wrapSummaryHtml( $html = '', $state ) {
return "<div id=\"mw-javascripttest-summary\" class=\"mw-javascripttest-$state\">$html</div>";
}
/**
* Initialize the page for QUnit.
*/
private function initQUnitTesting() {
global $wgJavaScriptTestConfig;
$out = $this->getOutput();
$out->addModules( 'mediawiki.tests.qunit.testrunner' );
$qunitTestModules = $out->getResourceLoader()->getTestModuleNames( 'qunit' );
$out->addModules( $qunitTestModules );
$summary = wfMessage( 'javascripttest-qunit-intro' )
->params( $wgJavaScriptTestConfig['qunit']['documentation'] )
->parseAsBlock();
$header = wfMessage( 'javascripttest-qunit-heading' )->escaped();
$baseHtml = <<<HTML
<div id="qunit-header">$header</div>
<div id="qunit-banner"></div>
<div id="qunit-testrunner-toolbar"></div>
<div id="qunit-userAgent"></div>
<ol id="qunit-tests"></ol>
HTML;
$out->addHtml( $this->wrapSummaryHtml( $summary, 'frameworkfound' ) . $baseHtml );
}
public function isListed(){
global $wgEnableJavaScriptTest;
return $wgEnableJavaScriptTest === true;
}
}

View file

@ -393,6 +393,7 @@ $specialPageAliases = array(
'Filepath' => array( 'FilePath' ),
'Import' => array( 'Import' ),
'Invalidateemail' => array( 'InvalidateEmail' ),
'JavaScriptTest' => array( 'JavaScriptTest' ),
'BlockList' => array( 'BlockList', 'ListBlocks', 'IPBlockList' ),
'LinkSearch' => array( 'LinkSearch' ),
'Listadmins' => array( 'ListAdmins' ),
@ -3405,6 +3406,19 @@ Please try again.',
'import-logentry-upload-detail' => '$1 {{PLURAL:$1|revision|revisions}}',
'import-logentry-interwiki' => 'transwikied $1',
'import-logentry-interwiki-detail' => '$1 {{PLURAL:$1|revision|revisions}} from $2',
# JavaScriptTest
'javascripttest' => 'JavaScript Test',
'javascripttest-backlink' => '< $1',
'javascripttest-disabled' => 'This function is disabled.',
'javascripttest-title' => 'Running $1 tests',
'javascripttest-pagetext-noframework' => 'This page is reserved for running javascript tests.',
'javascripttest-pagetext-unknownframework' => 'Unknown framework "$1".',
'javascripttest-pagetext-frameworks' => 'Please choose one of the following frameworks: $1',
'javascripttest-pagetext-skins' => 'Available skins',
'javascripttest-qunit-name' => 'QUnit', // Ignore, do not translate
'javascripttest-qunit-intro' => 'See [$1 testing documentation] on mediawiki.org.',
'javascripttest-qunit-heading' => 'MediaWiki JavaScript QUnit Test Suite', // Optional, only translate if needed
# Keyboard access keys for power users
'accesskey-pt-userpage' => '.', # do not translate or duplicate this message to other languages

View file

@ -3059,6 +3059,17 @@ See also:
'import-logentry-upload' => 'This is the text of an entry in the Import log (and Recent Changes), after hour (and date, only in the Import log) and sysop name:
* $1 is the name of the imported file',
# JavaScriptTest
'javascripttest' => 'Title of the special page',
'javascripttest-backlink' => '{{optional}}',
'javascripttest-disabled' => '{{Identical|Function disabled}}.',
'javascripttest-title' => 'Title of the special page when running a test suite. $1 is the name of the framework.',
'javascripttest-pagetext-unknownframework' => 'Error message when given framework id is not found. $1 is the if of the framework.',
'javascripttest-pagetext-frameworks' => '$1 is the if of the framework.',
'javascripttest-qunit-name' => '{{Ignore}}',
'javascripttest-qunit-intro' => '$1 is the configured url to the documentation.',
'javascripttest-qunit-heading' => '{{Optional}}',
# Tooltip help for the actions
'tooltip-pt-userpage' => 'Tooltip shown when hovering the mouse over the link to your own User page in the upper-side personal toolbox.',
'tooltip-pt-mytalk' => 'Tooltip shown when hovering over the "my talk" link in your personal toolbox (upper right side).',

View file

@ -166,15 +166,15 @@ return array(
'jquery.placeholder' => array(
'scripts' => 'resources/jquery/jquery.placeholder.js',
),
'jquery.qunit.completenessTest' => array(
'scripts' => 'resources/jquery/jquery.qunit.completenessTest.js',
'dependencies' => 'jquery.qunit',
),
'jquery.qunit' => array(
'scripts' => 'resources/jquery/jquery.qunit.js',
'styles' => 'resources/jquery/jquery.qunit.css',
'position' => 'top',
),
'jquery.qunit.completenessTest' => array(
'scripts' => 'resources/jquery/jquery.qunit.completenessTest.js',
'dependencies' => 'jquery.qunit',
),
'jquery.spinner' => array(
'scripts' => 'resources/jquery/jquery.spinner.js',
'styles' => 'resources/jquery/jquery.spinner.css',
@ -767,6 +767,28 @@ return array(
),
'dependencies' => array( 'mediawiki.libs.jpegmeta', 'mediawiki.util' ),
),
'mediawiki.special.javaScriptTest' => array(
'scripts' => 'resources/mediawiki.special/mediawiki.special.javaScriptTest.js',
'messages' => array_merge( Skin::getSkinNameMessages(), array(
'colon-separator',
'javascripttest-pagetext-skins',
) ),
'dependencies' => array( 'jquery.qunit' ),
'position' => 'top',
),
/* MediaWiki Tests */
'mediawiki.tests.qunit.testrunner' => array(
'scripts' => 'tests/qunit/data/testrunner.js',
'dependencies' => array(
'jquery.qunit',
'jquery.qunit.completenessTest',
'mediawiki.page.startup',
'mediawiki.page.ready',
),
'position' => 'top',
),
/* MediaWiki Legacy */

View file

@ -0,0 +1,33 @@
/*
* JavaScript for Special:JavaScriptTest
*/
jQuery( document ).ready( function( $ ) {
// Create useskin dropdown menu and reload onchange to the selected skin
// (only if a framework was found, not on error pages).
$( '#mw-javascripttest-summary.mw-javascripttest-frameworkfound' ).append( function() {
var $html = $( '<p><label for="useskin">'
+ mw.message( 'javascripttest-pagetext-skins' ).escaped()
+ mw.message( 'colon-separator' ).plain()
+ '</label></p>' ),
select = '<select name="useskin" id="useskin">';
// Build <select> further
$.each( mw.config.get( 'wgAvailableSkins' ), function( id ) {
select += '<option value="' + id + '"'
+ ( mw.config.get( 'skin' ) === id ? ' selected="selected"' : '' )
+ '>' + mw.message( 'skinname-' + id ).escaped() + '</option>';
} );
select += '</select>';
// Bind onchange event handler and append to form
$html.append(
$( select ).change( function() {
window.location = QUnit.url( { useskin: $(this).val() } );
} )
);
return $html;
} );
} );

View file

@ -0,0 +1,55 @@
<?php
return array(
/* Test suites for MediaWiki core modules */
'mediawiki.tests.qunit.suites' => array(
'scripts' => array(
'tests/qunit/suites/resources/jquery/jquery.autoEllipsis.test.js',
'tests/qunit/suites/resources/jquery/jquery.byteLength.test.js',
'tests/qunit/suites/resources/jquery/jquery.byteLimit.test.js',
'tests/qunit/suites/resources/jquery/jquery.client.test.js',
'tests/qunit/suites/resources/jquery/jquery.colorUtil.test.js',
'tests/qunit/suites/resources/jquery/jquery.getAttrs.test.js',
'tests/qunit/suites/resources/jquery/jquery.highlightText.test.js',
'tests/qunit/suites/resources/jquery/jquery.localize.test.js',
'tests/qunit/suites/resources/jquery/jquery.mwExtension.test.js',
'tests/qunit/suites/resources/jquery/jquery.tabIndex.test.js',
'tests/qunit/suites/resources/jquery/jquery.tablesorter.test.js',
'tests/qunit/suites/resources/jquery/jquery.textSelection.test.js',
'tests/qunit/suites/resources/mediawiki/mediawiki.jscompat.test.js',
'tests/qunit/suites/resources/mediawiki/mediawiki.test.js',
'tests/qunit/suites/resources/mediawiki/mediawiki.title.test.js',
'tests/qunit/suites/resources/mediawiki/mediawiki.user.test.js',
'tests/qunit/suites/resources/mediawiki/mediawiki.util.test.js',
'tests/qunit/suites/resources/mediawiki.special/mediawiki.special.recentchanges.test.js',
// *has mw-config def:
// This means the module overwrites/sets mw.config variables, reason being that
// the static /qunit/index.html has an empty mw.config since it's static.
// Until /qunit/index.html is fully replaceable and WMF's TestSwarm is up and running
// with Special:JavaScriptTest - untill then, it is important that tests do not depend
// on anything being in mw.config (not even wgServer).
),
'dependencies' => array(
'jquery.autoEllipsis',
'jquery.byteLength',
'jquery.byteLimit',
'jquery.client',
'jquery.colorUtil',
'jquery.getAttrs',
'jquery.highlightText',
'jquery.localize',
'jquery.mwExtension',
'jquery.tabIndex',
'jquery.tablesorter',
'jquery.textSelection',
'mediawiki',
'mediawiki.Title',
'mediawiki.user',
'mediawiki.util',
'mediawiki.special.recentchanges',
),
)
);

View file

@ -1,13 +1,19 @@
( function( $ ) {
( function ( $, mw, QUnit, undefined ) {
"use strict";
var mwTestIgnore, mwTester, addons;
/**
* Add bogus to url to prevent IE crazy caching
*
* @param value {String} a relative path (eg. 'data/defineTestCallback.js' or 'data/test.php?foo=bar')
* @param value {String} a relative path (eg. 'data/defineTestCallback.js'
* or 'data/test.php?foo=bar').
* @return {String} Such as 'data/defineTestCallback.js?131031765087663960'
*/
QUnit.fixurl = function(value) {
return value + (/\?/.test(value) ? "&" : "?") + new Date().getTime() + "" + parseInt(Math.random()*100000);
QUnit.fixurl = function (value) {
return value + (/\?/.test( value ) ? '&' : '?')
+ String( new Date().getTime() )
+ String( parseInt( Math.random()*100000, 10 ) );
};
/**
@ -15,31 +21,41 @@ QUnit.fixurl = function(value) {
*/
QUnit.config.testTimeout = 5000;
/**
* MediaWiki debug mode
*/
QUnit.config.urlConfig.push( 'debug' );
/**
* Load TestSwarm agent
*/
if ( QUnit.urlParams.swarmURL ) {
document.write("<scr" + "ipt src='" + QUnit.fixurl( mw.config.get( 'wgScriptPath' ) + '/tests/qunit/data/testwarm.inject.js' ) + "'></scr" + "ipt>");
document.write( "<scr" + "ipt src='" + QUnit.fixurl( mw.config.get( 'wgScriptPath' )
+ '/tests/qunit/data/testwarm.inject.js' ) + "'></scr" + "ipt>" );
}
/**
* Load completenesstest
* CompletenessTest
*/
// Adds toggle checkbox to header
QUnit.config.urlConfig.push( 'completenesstest' );
// Initiate when enabled
if ( QUnit.urlParams.completenesstest ) {
// Return true to ignore
var mwTestIgnore = function( val, tester, funcPath ) {
mwTestIgnore = function ( val, tester, funcPath ) {
// 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 ) {
tester.methodCallTracker['Map'] = true;
tester.methodCallTracker.Map = true;
return true;
}
if ( val instanceof mw.Title ) {
tester.methodCallTracker['Title'] = true;
tester.methodCallTracker.Title = true;
return true;
}
@ -51,42 +67,113 @@ if ( QUnit.urlParams.completenesstest ) {
return false;
};
var mwTester = new CompletenessTest( mw, mwTestIgnore );
mwTester = new CompletenessTest( mw, mwTestIgnore );
}
/**
* Test environment recommended for all QUnit test modules
*/
// Whether to log environment changes to the console
QUnit.config.urlConfig.push( 'mwlogenv' );
/**
* Reset mw.config to a fresh copy of the live config for each test();
* @param override {Object} [optional]
* @example:
* <code>
* module( .., newMwEnvironment() );
*
* test( .., function () {
* mw.config.set( 'foo', 'bar' ); // just for this test
* } );
*
* test( .., function () {
* mw.config.get( 'foo' ); // doesn't exist
* } );
*
*
* module( .., newMwEnvironment({ quux: 'corge' }) );
*
* test( .., function () {
* mw.config.get( 'quux' ); // "corge"
* mw.config.set( 'quux', "grault" );
* } );
*
* test( .., function () {
* mw.config.get( 'quux' ); // "corge"
* } );
* </code>
*/
QUnit.newMwEnvironment = ( function () {
var liveConfig, freshConfigCopy, log;
liveConfig = mw.config.values;
freshConfigCopy = function ( custom ) {
// "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.
return $.extend({}, liveConfig, custom, /*deep=*/true );
};
log = QUnit.urlParams.mwlogenv ? mw.log : function () {};
return function ( override ) {
override = override || {};
return {
setup: function () {
log( 'MwEnvironment> SETUP for "' + QUnit.config.current.module
+ ': ' + QUnit.config.current.testName + '"' );
// Greetings, mock configuration!
mw.config.values = freshConfigCopy( override );
},
teardown: function () {
log( 'MwEnvironment> TEARDOWN for "' + QUnit.config.current.module
+ ': ' + QUnit.config.current.testName + '"' );
// Farewell, mock configuration!
mw.config.values = liveConfig;
}
};
};
}() );
/**
* Add-on assertion helpers
*/
// Define the add-ons
var addons = {
addons = {
// Expect boolean true
assertTrue: function( actual, message ) {
assertTrue: function ( actual, message ) {
strictEqual( actual, true, message );
},
// Expect boolean false
assertFalse: function( actual, message ) {
assertFalse: function ( actual, message ) {
strictEqual( actual, false, message );
},
// Expect numerical value less than X
lt: function( actual, expected, message ) {
lt: function ( actual, expected, message ) {
QUnit.push( actual < expected, actual, 'less than ' + expected, message );
},
// Expect numerical value less than or equal to X
ltOrEq: function( actual, expected, message ) {
ltOrEq: function ( actual, expected, message ) {
QUnit.push( actual <= expected, actual, 'less than or equal to ' + expected, message );
},
// Expect numerical value greater than X
gt: function( actual, expected, message ) {
gt: function ( actual, expected, message ) {
QUnit.push( actual > expected, actual, 'greater than ' + expected, message );
},
// Expect numerical value greater than or equal to X
gtOrEq: function( actual, expected, message ) {
gtOrEq: function ( actual, expected, message ) {
QUnit.push( actual >= expected, actual, 'greater than or equal to ' + expected, message );
},
@ -98,4 +185,4 @@ var addons = {
$.extend( QUnit, addons );
$.extend( window, addons );
})( jQuery );
})( jQuery, mediaWiki, QUnit );

View file

@ -1,4 +1,4 @@
module( 'jquery.autoEllipsis' );
module( 'jquery.autoEllipsis', QUnit.newMwEnvironment() );
test( '-- Initial check', function() {
expect(1);

View file

@ -1,4 +1,4 @@
module( 'jquery.byteLength' );
module( 'jquery.byteLength', QUnit.newMwEnvironment() );
test( '-- Initial check', function() {
expect(1);

View file

@ -1,4 +1,6 @@
module( 'jquery.byteLimit' );
( function () {
module( 'jquery.byteLimit', QUnit.newMwEnvironment() );
test( '-- Initial check', function() {
expect(1);
@ -150,8 +152,6 @@ byteLimitTest({
$input: $( '<input>' )
.attr( 'type', 'text' )
.byteLimit( 6, function( val ) {
_titleConfig();
// Invalid title
if ( val == '' ) {
return '';
@ -172,8 +172,6 @@ byteLimitTest({
.attr( 'type', 'text' )
.prop( 'maxLength', '6' )
.byteLimit( function( val ) {
_titleConfig();
// Invalid title
if ( val === '' ) {
return '';
@ -187,3 +185,5 @@ byteLimitTest({
limit: 6, // 'Sample' length
expected: 'User:Sample'
});
}() );

View file

@ -1,4 +1,4 @@
module( 'jquery.client' );
module( 'jquery.client', QUnit.newMwEnvironment() );
test( '-- Initial check', function() {
expect(1);

View file

@ -1,4 +1,4 @@
module( 'jquery.colorUtil' );
module( 'jquery.colorUtil', QUnit.newMwEnvironment() );
test( '-- Initial check', function() {
expect(1);

View file

@ -1,4 +1,4 @@
module( 'jquery.getAttrs' );
module( 'jquery.getAttrs', QUnit.newMwEnvironment() );
test( '-- Initial check', function() {
expect(1);

View file

@ -1,4 +1,4 @@
module( 'jquery.highlightText' );
module( 'jquery.highlightText', QUnit.newMwEnvironment() );
test( '-- Initial check', function() {
expect(1);

View file

@ -1,4 +1,4 @@
module( 'jquery.localize' );
module( 'jquery.localize', QUnit.newMwEnvironment() );
test( '-- Initial check', function() {
expect(1);

View file

@ -1,4 +1,4 @@
module( 'jquery.mwExtension' );
module( 'jquery.mwExtension', QUnit.newMwEnvironment() );
test( 'String functions', function() {

View file

@ -1,4 +1,4 @@
module( 'jquery.tabIndex' );
module( 'jquery.tabIndex', QUnit.newMwEnvironment() );
test( '-- Initial check', function() {
expect(2);

View file

@ -1,12 +1,13 @@
(function() {
( function () {
module( 'jquery.tablesorter' );
// setup hack
mw.config.set( 'wgMonthNames', [ '', 'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December' ] );
mw.config.set( 'wgMonthNamesShort', ['', 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'] );
mw.config.set( 'wgDefaultDateFormat', 'dmy' );
var config = {
wgMonthNames: ['', 'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'],
wgMonthNamesShort: ['', 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
wgDefaultDateFormat: 'dmy',
wgContentLanguage: 'en'
};
module( 'jquery.tablesorter', QUnit.newMwEnvironment( config ) );
test( '-- Initial check', function() {
expect(1);
@ -181,8 +182,9 @@ tableTest(
['11.11.2011']
],
function( $table ) {
// @fixme reset it at end or change module to allow us to override it
mw.config.set( 'wgDefaultDateFormat', 'dmy' );
mw.config.set( 'wgContentLanguage', 'de' );
$table.tablesorter();
$table.find( '.headerSort:eq(0)' ).click();
}
@ -206,8 +208,8 @@ tableTest(
['11.11.2011']
],
function( $table ) {
// @fixme reset it at end or change module to allow us to override it
mw.config.set( 'wgDefaultDateFormat', 'mdy' );
$table.tablesorter();
$table.find( '.headerSort:eq(0)' ).click();
}
@ -293,9 +295,9 @@ tableTest(
'ß': 'ss',
'ü':'ue'
} );
$table.tablesorter();
$table.find( '.headerSort:eq(0)' ).click();
mw.config.set( 'tableSorterCollation', {} );
}
);
@ -361,6 +363,7 @@ tableTest(
complexMDYSorted,
function( $table ) {
mw.config.set( 'wgDefaultDateFormat', 'mdy' );
$table.tablesorter();
$table.find( '.headerSort:eq(0)' ).click();
}

View file

@ -1,4 +1,4 @@
module( 'jquery.textSelection' );
module( 'jquery.textSelection', QUnit.newMwEnvironment() );
test( '-- Initial check', function() {
expect(1);

View file

@ -1,4 +1,4 @@
module( 'mediawiki.special.recentchanges' );
module( 'mediawiki.special.recentchanges', QUnit.newMwEnvironment() );
test( '-- Initial check', function() {
expect( 2 );

View file

@ -1,71 +1,69 @@
module( 'mediawiki.Title' );
( function () {
// mw.Title relies on these three config vars
// Restore them after each test run
var _titleConfig = function() {
mw.config.set({
"wgFormattedNamespaces": {
"-2": "Media",
"-1": "Special",
"0": "",
"1": "Talk",
"2": "User",
"3": "User talk",
"4": "Wikipedia",
"5": "Wikipedia talk",
"6": "File",
"7": "File talk",
"8": "MediaWiki",
"9": "MediaWiki talk",
"10": "Template",
"11": "Template talk",
"12": "Help",
"13": "Help talk",
"14": "Category",
"15": "Category talk",
/* testing custom / localized */
"100": "Penguins"
},
"wgNamespaceIds": {
"media": -2,
"special": -1,
"": 0,
"talk": 1,
"user": 2,
"user_talk": 3,
"wikipedia": 4,
"wikipedia_talk": 5,
"file": 6,
"file_talk": 7,
"mediawiki": 8,
"mediawiki_talk": 9,
"template": 10,
"template_talk": 11,
"help": 12,
"help_talk": 13,
"category": 14,
"category_talk": 15,
"image": 6,
"image_talk": 7,
"project": 4,
"project_talk": 5,
/* testing custom / alias */
"penguins": 100,
"antarctic_waterfowl": 100
},
"wgCaseSensitiveNamespaces": []
});
var config = {
"wgFormattedNamespaces": {
"-2": "Media",
"-1": "Special",
"0": "",
"1": "Talk",
"2": "User",
"3": "User talk",
"4": "Wikipedia",
"5": "Wikipedia talk",
"6": "File",
"7": "File talk",
"8": "MediaWiki",
"9": "MediaWiki talk",
"10": "Template",
"11": "Template talk",
"12": "Help",
"13": "Help talk",
"14": "Category",
"15": "Category talk",
// testing custom / localized namespace
"100": "Penguins"
},
"wgNamespaceIds": {
"media": -2,
"special": -1,
"": 0,
"talk": 1,
"user": 2,
"user_talk": 3,
"wikipedia": 4,
"wikipedia_talk": 5,
"file": 6,
"file_talk": 7,
"mediawiki": 8,
"mediawiki_talk": 9,
"template": 10,
"template_talk": 11,
"help": 12,
"help_talk": 13,
"category": 14,
"category_talk": 15,
"image": 6,
"image_talk": 7,
"project": 4,
"project_talk": 5,
/* testing custom / alias */
"penguins": 100,
"antarctic_waterfowl": 100
},
"wgCaseSensitiveNamespaces": []
};
test( '-- Initial check', function() {
module( 'mediawiki.Title', QUnit.newMwEnvironment( config ) );
test( '-- Initial check', function () {
expect(1);
ok( mw.Title, 'mw.Title defined' );
});
test( 'Transformation', function() {
test( 'Transformation', function () {
expect(8);
_titleConfig();
var title;
@ -89,9 +87,8 @@ test( 'Transformation', function() {
equal( title.getName(), 'Foo_bar_.js', "Merge multiple spaces to a single space." );
});
test( 'Main text for filename', function() {
test( 'Main text for filename', function () {
expect(8);
_titleConfig();
var title = new mw.Title( 'File:foo_bar.JPG' );
@ -105,9 +102,8 @@ test( 'Main text for filename', function() {
equal( title.getDotExtension(), '.JPG' );
});
test( 'Namespace detection and conversion', function() {
test( 'Namespace detection and conversion', function () {
expect(6);
_titleConfig();
var title;
@ -128,18 +124,16 @@ test( 'Namespace detection and conversion', function() {
equal( title.toString(), 'Penguins:Flightless_yet_cute.jpg' );
});
test( 'Throw error on invalid title', function() {
test( 'Throw error on invalid title', function () {
expect(1);
_titleConfig();
raises(function() {
raises(function () {
var title = new mw.Title( '' );
}, 'Throw error on empty string' );
});
test( 'Case-sensivity', function() {
test( 'Case-sensivity', function () {
expect(3);
_titleConfig();
var title;
@ -159,9 +153,8 @@ test( 'Case-sensivity', function() {
equal( title.toString(), 'User:John', '$wgCapitalLinks=false: User namespace is insensitive, first-letter becomes uppercase' );
});
test( 'toString / toText', function() {
test( 'toString / toText', function () {
expect(2);
_titleConfig();
var title = new mw.Title( 'Some random page' );
@ -169,9 +162,8 @@ test( 'toString / toText', function() {
equal( title.toText(), title.getPrefixedText() );
});
test( 'Exists', function() {
test( 'Exists', function () {
expect(3);
_titleConfig();
var title;
@ -191,9 +183,8 @@ test( 'Exists', function() {
});
test( 'Url', function() {
test( 'Url', function () {
expect(2);
_titleConfig();
var title;
@ -206,3 +197,5 @@ test( 'Url', function() {
title = new mw.Title( 'John Doe', 3 );
equal( title.getUrl(), '/wiki/User_talk:John_Doe', 'Escaping in title and namespace for urls' );
});
}() );

View file

@ -1,6 +1,6 @@
/* Some misc JavaScript compatibility tests, just to make sure the environments we run in are consistent */
module( 'mediawiki.jscompat' );
module( 'mediawiki.jscompat', QUnit.newMwEnvironment() );
test( 'Variable with Unicode letter in name', function() {
expect(3);

View file

@ -1,4 +1,4 @@
module( 'mediawiki' );
module( 'mediawiki', QUnit.newMwEnvironment() );
test( '-- Initial check', function() {
expect(8);
@ -173,12 +173,29 @@ test( 'mw.loader.bug30825', function() {
// This bug was actually already fixed in 1.18 and later when discovered in 1.17.
// Test is for regressions!
expect(1);
expect(2);
var server = mw.config.get( 'wgServer' ),
basePath = mw.config.get( 'wgScriptPath' );
// From [[Special:JavaScriptTest]] we need to preprend the script path
// with the actual server (http://localhost/).
// Running from file tests/qunit/index.html, wgScriptPath is already
// including the wgServer part
if( server !== null ) {
basePath = server + basePath;
}
// Forge an URL to the test callback script
var target = QUnit.fixurl(
basePath + '/tests/qunit/data/qunitOkCall.js'
);
// Confirm that mw.loader.load() works with protocol-relative URLs
var loc = window.location,
base = ('//' + loc.hostname + loc.pathname).replace(/\/[^\/]*$/, ''),
target = base + '/data/qunitOkCall.js?' + (new Date()).getTime();
target = target.replace( /https?:/, '' );
equal( target.substr( 0, 2 ), '//',
'URL must be relative to test relative URLs!'
);
// Async!
stop();

View file

@ -1,4 +1,4 @@
module( 'mediawiki.user' );
module( 'mediawiki.user', QUnit.newMwEnvironment() );
test( '-- Initial check', function() {
expect(1);
@ -16,6 +16,16 @@ test( 'options', function() {
test( 'User login status', function() {
expect(5);
/**
* Tests can be run under three different conditions:
* 1) From tests/qunit/index.html, user will be anonymous.
* 2) Logged in on [[Special:JavaScriptTest/qunit]]
* 3) Anonymously at the same special page.
*/
// Forge an anonymous user:
mw.config.set( 'wgUserName', null);
strictEqual( mw.user.name(), null, 'user.name should return null when anonymous' );
ok( mw.user.anonymous(), 'user.anonymous should reutrn true when anonymous' );

View file

@ -1,4 +1,4 @@
module( 'mediawiki.util' );
module( 'mediawiki.util', QUnit.newMwEnvironment() );
test( '-- Initial check', function() {
expect(1);
@ -39,7 +39,6 @@ test( 'wikiGetlink', function() {
test( 'wikiScript', function() {
expect(2);
var prevConfig = mw.config.get([ 'wgScript', 'wgScriptPath', 'wgScriptExtension' ]);
mw.config.set({
'wgScript': '/w/index.php',
'wgScriptPath': '/w',
@ -48,9 +47,6 @@ test( 'wikiScript', function() {
equal( mw.util.wikiScript(), mw.config.get( 'wgScript' ), 'Defaults to index.php and is equal to wgScript' );
equal( mw.util.wikiScript( 'api' ), '/w/api.php', 'API path' );
// Restore mw.config
mw.config.set( prevConfig );
});
test( 'addCSS', function() {