summaryrefslogtreecommitdiff
path: root/tests/qunit
diff options
context:
space:
mode:
authorPierre Schmitz <pierre@archlinux.de>2011-12-03 13:29:22 +0100
committerPierre Schmitz <pierre@archlinux.de>2011-12-03 13:29:22 +0100
commitca32f08966f1b51fcb19460f0996bb0c4048e6fe (patch)
treeec04cc15b867bc21eedca904cea9af0254531a11 /tests/qunit
parenta22fbfc60f36f5f7ee10d5ae6fe347340c2ee67c (diff)
Update to MediaWiki 1.18.0
* also update ArchLinux skin to chagnes in MonoBook * Use only css to hide our menu bar when printing
Diffstat (limited to 'tests/qunit')
-rw-r--r--tests/qunit/.htaccess1
-rw-r--r--tests/qunit/data/defineTestCallback.js4
-rw-r--r--tests/qunit/data/testrunner.js92
-rw-r--r--tests/qunit/data/testwarm.inject.js349
-rw-r--r--tests/qunit/index.html90
-rw-r--r--tests/qunit/suites/resources/jquery/jquery.autoEllipsis.js58
-rw-r--r--tests/qunit/suites/resources/jquery/jquery.byteLength.js42
-rw-r--r--tests/qunit/suites/resources/jquery/jquery.byteLimit.js155
-rw-r--r--tests/qunit/suites/resources/jquery/jquery.client.js205
-rw-r--r--tests/qunit/suites/resources/jquery/jquery.colorUtil.js71
-rw-r--r--tests/qunit/suites/resources/jquery/jquery.getAttrs.js17
-rw-r--r--tests/qunit/suites/resources/jquery/jquery.localize.js119
-rw-r--r--tests/qunit/suites/resources/jquery/jquery.mwPrototypes.js56
-rw-r--r--tests/qunit/suites/resources/jquery/jquery.tabIndex.js50
-rw-r--r--tests/qunit/suites/resources/jquery/jquery.tablesorter.test.js475
-rw-r--r--tests/qunit/suites/resources/mediawiki.special/mediawiki.special.recentchanges.js71
-rw-r--r--tests/qunit/suites/resources/mediawiki/mediawiki.js232
-rw-r--r--tests/qunit/suites/resources/mediawiki/mediawiki.jscompat.test.js35
-rw-r--r--tests/qunit/suites/resources/mediawiki/mediawiki.user.js29
-rw-r--r--tests/qunit/suites/resources/mediawiki/mediawiki.util.js307
20 files changed, 2458 insertions, 0 deletions
diff --git a/tests/qunit/.htaccess b/tests/qunit/.htaccess
new file mode 100644
index 00000000..605d2f4c
--- /dev/null
+++ b/tests/qunit/.htaccess
@@ -0,0 +1 @@
+Allow from all
diff --git a/tests/qunit/data/defineTestCallback.js b/tests/qunit/data/defineTestCallback.js
new file mode 100644
index 00000000..6fcd4595
--- /dev/null
+++ b/tests/qunit/data/defineTestCallback.js
@@ -0,0 +1,4 @@
+window.mw.loader.testCallback = function() {
+ start();
+ ok( true, 'Implementing a module, is the callback timed properly ?');
+};
diff --git a/tests/qunit/data/testrunner.js b/tests/qunit/data/testrunner.js
new file mode 100644
index 00000000..dbfe9fad
--- /dev/null
+++ b/tests/qunit/data/testrunner.js
@@ -0,0 +1,92 @@
+( function( $ ) {
+
+/**
+ * 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')
+ * @return {String} Such as 'data/defineTestCallback.js?131031765087663960'
+ */
+QUnit.fixurl = function(value) {
+ return value + (/\?/.test(value) ? "&" : "?") + new Date().getTime() + "" + parseInt(Math.random()*100000);
+};
+
+/**
+ * Load TestSwarm agent
+ */
+if ( QUnit.urlParams.swarmURL ) {
+ document.write("<scr" + "ipt src='" + QUnit.fixurl( 'data/testwarm.inject.js' ) + "'></scr" + "ipt>");
+}
+
+/**
+ * Load completenesstest
+ */
+if ( QUnit.urlParams.completenesstest ) {
+
+ // Return true to ignore
+ var 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;
+ return true;
+ }
+
+ // Don't record methods of the properties of a jQuery object
+ if ( val instanceof $ ) {
+ return true;
+ }
+
+ return false;
+ };
+
+ var mwTester = new CompletenessTest( mw, mwTestIgnore );
+}
+
+/**
+ * Add-on assertion helpers
+ */
+// Define the add-ons
+var addons = {
+
+ // Expect boolean true
+ assertTrue: function( actual, message ) {
+ strictEqual( actual, true, message );
+ },
+
+ // Expect boolean false
+ assertFalse: function( actual, message ) {
+ strictEqual( actual, false, message );
+ },
+
+ // Expect numerical value less than X
+ 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 ) {
+ QUnit.push( actual <= expected, actual, 'less than or equal to ' + expected, message );
+ },
+
+ // Expect numerical value greater than X
+ 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 ) {
+ 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 );
+
+})( jQuery );
diff --git a/tests/qunit/data/testwarm.inject.js b/tests/qunit/data/testwarm.inject.js
new file mode 100644
index 00000000..14ee8f93
--- /dev/null
+++ b/tests/qunit/data/testwarm.inject.js
@@ -0,0 +1,349 @@
+/*
+ Copyright (c) 2009 John Resig
+
+ Permission is hereby granted, free of charge, to any person
+ obtaining a copy of this software and associated documentation
+ files (the "Software"), to deal in the Software without
+ restriction, including without limitation the rights to use,
+ copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the
+ Software is furnished to do so, subject to the following
+ conditions:
+
+ The above copyright notice and this permission notice shall be
+ included in all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ OTHER DEALINGS IN THE SOFTWARE.
+
+*/
+(function(){
+
+ var DEBUG = false;
+
+ var doPost = false;
+
+ try {
+ doPost = !!window.top.postMessage;
+ } catch(e){}
+
+ var search = window.location.search,
+ url, index;
+ if( ( index = search.indexOf( "swarmURL=" ) ) != -1 )
+ url = decodeURIComponent( search.slice( index + 9 ) );
+
+ if ( !DEBUG && (!url || url.indexOf("http") !== 0) ) {
+ return;
+ }
+
+ var submitTimeout = 5;
+
+ var curHeartbeat;
+ var beatRate = 20;
+
+ // Expose the TestSwarm API
+ window.TestSwarm = {
+ submit: submit,
+ heartbeat: function(){
+ if ( curHeartbeat ) {
+ clearTimeout( curHeartbeat );
+ }
+
+ curHeartbeat = setTimeout(function(){
+ submit({ fail: -1, total: -1 });
+ }, beatRate * 1000);
+ },
+ serialize: function(){
+ return trimSerialize();
+ }
+ };
+
+ // Prevent careless things from executing
+ window.print = window.confirm = window.alert = window.open = function(){};
+
+ window.onerror = function(e){
+ document.body.appendChild( document.createTextNode( "ERROR: " + e ));
+ submit({ fail: 0, error: 1, total: 1 });
+ return false;
+ };
+
+ // QUnit (jQuery)
+ // http://docs.jquery.com/QUnit
+ if ( typeof QUnit !== "undefined" ) {
+ QUnit.done = function(results){
+ submit({
+ fail: results.failed,
+ error: 0,
+ total: results.total
+ });
+ };
+
+ QUnit.log = window.TestSwarm.heartbeat;
+ window.TestSwarm.heartbeat();
+
+ window.TestSwarm.serialize = function(){
+ // Clean up the HTML (remove any un-needed test markup)
+ remove("nothiddendiv");
+ remove("loadediframe");
+ remove("dl");
+ remove("main");
+
+ // Show any collapsed results
+ var ol = document.getElementsByTagName("ol");
+ for ( var i = 0; i < ol.length; i++ ) {
+ ol[i].style.display = "block";
+ }
+
+ return trimSerialize();
+ };
+
+ // UnitTestJS (Prototype, Scriptaculous)
+ // http://github.com/tobie/unittest_js/tree/master
+ } else if ( typeof Test !== "undefined" && Test && Test.Unit && Test.Unit.runners ) {
+ var total_runners = Test.Unit.runners.length, cur_runners = 0;
+ var total = 0, fail = 0, error = 0;
+
+ for (var i = 0; i < Test.Unit.runners.length; i++) (function(i){
+ var finish = Test.Unit.runners[i].finish;
+ Test.Unit.runners[i].finish = function(){
+ finish.call( this );
+
+ var results = this.getResult();
+ total += results.assertions;
+ fail += results.failures;
+ error += results.errors;
+
+ if ( ++cur_runners === total_runners ) {
+ submit({
+ fail: fail,
+ error: error,
+ total: total
+ });
+ }
+ };
+ })(i);
+
+ // JSSpec (MooTools)
+ // http://jania.pe.kr/aw/moin.cgi/JSSpec
+ } else if ( typeof JSSpec !== "undefined" && JSSpec && JSSpec.Logger ) {
+ var onRunnerEnd = JSSpec.Logger.prototype.onRunnerEnd;
+ JSSpec.Logger.prototype.onRunnerEnd = function(){
+ onRunnerEnd.call(this);
+
+ // Show any collapsed results
+ var ul = document.getElementsByTagName("ul");
+ for ( var i = 0; i < ul.length; i++ ) {
+ ul[i].style.display = "block";
+ }
+
+ submit({
+ fail: JSSpec.runner.getTotalFailures(),
+ error: JSSpec.runner.getTotalErrors(),
+ total: JSSpec.runner.totalExamples
+ });
+ };
+
+ window.TestSwarm.serialize = function(){
+ // Show any collapsed results
+ var ul = document.getElementsByTagName("ul");
+ for ( var i = 0; i < ul.length; i++ ) {
+ ul[i].style.display = "block";
+ }
+
+ return trimSerialize();
+ };
+
+ // JSUnit
+ // http://www.jsunit.net/
+ // Note: Injection file must be included before the frames
+ // are document.write()d into the page.
+ } else if ( typeof JsUnitTestManager !== "undefined" ) {
+ var _done = JsUnitTestManager.prototype._done;
+ JsUnitTestManager.prototype._done = function(){
+ _done.call(this);
+
+ submit({
+ fail: this.failureCount,
+ error: this.errorCount,
+ total: this.totalCount
+ });
+ };
+
+ window.TestSwarm.serialize = function(){
+ return "<pre>" + this.log.join("\n") + "</pre>";
+ };
+
+ // Selenium Core
+ // http://seleniumhq.org/projects/core/
+ } else if ( typeof SeleniumTestResult !== "undefined" && typeof LOG !== "undefined" ) {
+ // Completely overwrite the postback
+ SeleniumTestResult.prototype.post = function(){
+ submit({
+ fail: this.metrics.numCommandFailures,
+ error: this.metrics.numCommandErrors,
+ total: this.metrics.numCommandPasses + this.metrics.numCommandFailures + this.metrics.numCommandErrors
+ });
+ };
+
+ window.TestSwarm.serialize = function(){
+ var results = [];
+ while ( LOG.pendingMessages.length ) {
+ var msg = LOG.pendingMessages.shift();
+ results.push( msg.type + ": " + msg.msg );
+ }
+
+ return "<pre>" + results.join("\n") + "</pre>";
+ };
+
+ // Dojo Objective Harness
+ // http://docs.dojocampus.org/quickstart/doh
+ } else if ( typeof doh !== "undefined" && doh._report ) {
+ var _report = doh._report;
+ doh._report = function(){
+ _report.apply(this, arguments);
+
+ submit({
+ fail: doh._failureCount,
+ error: doh._errorCount,
+ total: doh._testCount
+ });
+ };
+
+ window.TestSwarm.serialize = function(){
+ return "<pre>" + document.getElementById("logBody").innerHTML + "</pre>";
+ };
+ // Screw.Unit
+ // git://github.com/nathansobo/screw-unit.git
+ } else if ( typeof Screw !== "undefined" && typeof jQuery !== 'undefined' && Screw && Screw.Unit ) {
+ $(Screw).bind("after", function() {
+ var passed = $('.passed').length;
+ var failed = $('.failed').length;
+ submit({
+ fail: failed,
+ error: 0,
+ total: failed + passed
+ });
+ });
+
+ $(Screw).bind("loaded", function() {
+ $('.it')
+ .bind("passed", window.TestSwarm.heartbeat)
+ .bind("failed", window.TestSwarm.heartbeat);
+ window.TestSwarm.heartbeat();
+ });
+
+ window.TestSwarm.serialize = function(){
+ return trimSerialize();
+ };
+ }
+
+ function trimSerialize(doc) {
+ doc = doc || document;
+
+ var scripts = doc.getElementsByTagName("script");
+ while ( scripts.length ) {
+ remove( scripts[0] );
+ }
+
+ var root = window.location.href.replace(/(https?:\/\/.*?)\/.*/, "$1");
+ var cur = window.location.href.replace(/[^\/]*$/, "");
+
+ var links = doc.getElementsByTagName("link");
+ for ( var i = 0; i < links.length; i++ ) {
+ var href = links[i].href;
+ if ( href.indexOf("/") === 0 ) {
+ href = root + href;
+ } else if ( !/^https?:\/\//.test( href ) ) {
+ href = cur + href;
+ }
+ links[i].href = href;
+ }
+
+ return ("<html>" + doc.documentElement.innerHTML + "</html>")
+ .replace(/\s+/g, " ");
+ }
+
+ function remove(elem){
+ if ( typeof elem === "string" ) {
+ elem = document.getElementById( elem );
+ }
+
+ if ( elem ) {
+ elem.parentNode.removeChild( elem );
+ }
+ }
+
+ function submit(params){
+ if ( curHeartbeat ) {
+ clearTimeout( curHeartbeat );
+ }
+
+ var paramItems = (url.split("?")[1] || "").split("&");
+
+ for ( var i = 0; i < paramItems.length; i++ ) {
+ if ( paramItems[i] ) {
+ var parts = paramItems[i].split("=");
+ if ( !params[ parts[0] ] ) {
+ params[ parts[0] ] = parts[1];
+ }
+ }
+ }
+
+ if ( !params.state ) {
+ params.state = "saverun";
+ }
+
+ if ( !params.results ) {
+ params.results = window.TestSwarm.serialize();
+ }
+
+ if ( doPost ) {
+ // Build Query String
+ var query = "";
+
+ for ( var i in params ) {
+ query += ( query ? "&" : "" ) + i + "=" +
+ encodeURIComponent(params[i]);
+ }
+
+ if ( DEBUG ) {
+ alert( query );
+ } else {
+ window.top.postMessage( query, "*" );
+ }
+
+ } else {
+ var form = document.createElement("form");
+ form.action = url;
+ form.method = "POST";
+
+ for ( var i in params ) {
+ var input = document.createElement("input");
+ input.type = "hidden";
+ input.name = i;
+ input.value = params[i];
+ form.appendChild( input );
+ }
+
+ if ( DEBUG ) {
+ alert( form.innerHTML );
+ } else {
+
+ // Watch for the result submission timing out
+ setTimeout(function(){
+ submit( params );
+ }, submitTimeout * 1000);
+
+ document.body.appendChild( form );
+ form.submit();
+ }
+ }
+ }
+
+})();
diff --git a/tests/qunit/index.html b/tests/qunit/index.html
new file mode 100644
index 00000000..d4bbe7e2
--- /dev/null
+++ b/tests/qunit/index.html
@@ -0,0 +1,90 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>MediaWiki JavaScript Test Suite</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+ <!-- MediaWiki Modules -->
+
+ <!-- MW: startup -->
+ <script>
+ function startUp(){
+ mw.config = new mw.Map( false );
+ }
+ </script>
+
+ <!-- MW: jquery|mediawiki -->
+ <script src="../../resources/jquery/jquery.js"></script>
+ <script src="../../resources/mediawiki/mediawiki.js"></script>
+
+ <!-- MW: mediawiki.page.startup -->
+ <script src="../../resources/jquery/jquery.client.js"></script>
+ <script src="../../resources/mediawiki.page/mediawiki.page.startup.js"></script>
+
+ <!-- MW: mediawiki.user|mediawiki.util|mediawiki.page.ready -->
+ <script src="../../resources/jquery/jquery.cookie.js"></script>
+ <script src="../../resources/mediawiki/mediawiki.user.js"></script>
+
+ <script src="../../resources/jquery/jquery.messageBox.js"></script>
+ <script src="../../resources/jquery/jquery.mwPrototypes.js"></script>
+ <script src="../../resources/mediawiki/mediawiki.util.js"></script>
+
+ <script src="../../resources/jquery/jquery.checkboxShiftClick.js"></script>
+ <script src="../../resources/jquery/jquery.makeCollapsible.js"></script>
+ <script src="../../resources/jquery/jquery.placeholder.js"></script>
+ <script src="../../resources/mediawiki.page/mediawiki.page.ready.js"></script>
+
+ <!-- MW: user.options -->
+ <script>
+ mw.user.options.set({"skin": "vector"});
+ </script>
+
+ <!-- MW: Non-default modules -->
+ <script src="../../resources/jquery/jquery.autoEllipsis.js"></script>
+ <script src="../../resources/jquery/jquery.byteLength.js"></script>
+ <script src="../../resources/jquery/jquery.byteLimit.js"></script>
+ <script src="../../resources/jquery/jquery.colorUtil.js"></script>
+ <script src="../../resources/jquery/jquery.getAttrs.js"></script>
+ <script src="../../resources/jquery/jquery.localize.js"></script>
+ <script src="../../resources/jquery/jquery.tabIndex.js"></script>
+ <script src="../../resources/jquery/jquery.tablesorter.js"></script>
+ <script src="../../resources/mediawiki.special/mediawiki.special.js"></script>
+ <script src="../../resources/mediawiki.special/mediawiki.special.recentchanges.js"></script>
+
+ <!-- QUnit: Load framework -->
+ <link rel="stylesheet" href="../../resources/jquery/jquery.qunit.css" />
+ <script src="../../resources/jquery/jquery.qunit.js"></script>
+ <script src="../../resources/jquery/jquery.qunit.completenessTest.js"></script>
+ <script src="data/testrunner.js"></script>
+
+ <!-- QUnit: Load test suites (maintain the same order as above please) -->
+ <script src="suites/resources/mediawiki/mediawiki.jscompat.test.js"></script>
+ <script src="suites/resources/mediawiki/mediawiki.js"></script>
+ <script src="suites/resources/mediawiki/mediawiki.user.js"></script>
+
+ <script src="suites/resources/jquery/jquery.client.js"></script>
+ <script src="suites/resources/jquery/jquery.mwPrototypes.js"></script>
+ <script src="suites/resources/mediawiki/mediawiki.util.js"></script>
+
+ <script src="suites/resources/jquery/jquery.autoEllipsis.js"></script>
+ <script src="suites/resources/jquery/jquery.byteLength.js"></script>
+ <script src="suites/resources/jquery/jquery.byteLimit.js"></script>
+ <script src="suites/resources/jquery/jquery.colorUtil.js"></script>
+ <script src="suites/resources/jquery/jquery.getAttrs.js"></script>
+ <script src="suites/resources/jquery/jquery.localize.js"></script>
+ <script src="suites/resources/jquery/jquery.tabIndex.js"></script>
+ <script src="suites/resources/jquery/jquery.tablesorter.test.js" charset="UTF-8"></script>
+ <script src="suites/resources/mediawiki.special/mediawiki.special.recentchanges.js"></script>
+</head>
+<body>
+ <h1 id="qunit-header">MediaWiki JavaScript Test Suite</h1>
+ <h2 id="qunit-banner"></h2>
+ <div id="qunit-testrunner-toolbar">
+ <p><a href="http://www.mediawiki.org/wiki/Manual:JavaScript_unit_testing">See testing documentation on mediawiki.org</a></p>
+ </div>
+ <h2 id="qunit-userAgent"></h2>
+ <ol id="qunit-tests"></ol>
+
+<!-- Scripts inserting stuff here shall remove it themselfs! -->
+<div id="content"></div>
+</body>
+</html>
diff --git a/tests/qunit/suites/resources/jquery/jquery.autoEllipsis.js b/tests/qunit/suites/resources/jquery/jquery.autoEllipsis.js
new file mode 100644
index 00000000..caf5a6f1
--- /dev/null
+++ b/tests/qunit/suites/resources/jquery/jquery.autoEllipsis.js
@@ -0,0 +1,58 @@
+module( 'jquery.autoEllipsis.js' );
+
+test( '-- Initial check', function() {
+ expect(1);
+ ok( $.fn.autoEllipsis, 'jQuery.fn.autoEllipsis defined' );
+});
+
+function createWrappedDiv( text, width ) {
+ var $wrapper = $( '<div />' ).css( 'width', width );
+ var $div = $( '<div />' ).text( text );
+ $wrapper.append( $div );
+ return $wrapper;
+}
+
+function findDivergenceIndex( a, b ) {
+ var i = 0;
+ while ( i < a.length && i < b.length && a[i] == b[i] ) {
+ i++;
+ }
+ return i;
+}
+
+test( 'Position right', function() {
+ expect(4);
+
+ // We need this thing to be visible, so append it to the DOM
+ var origText = 'This is a really long random string and there is no way it fits in 100 pixels.';
+ var $wrapper = createWrappedDiv( origText, '100px' );
+ $( 'body' ).append( $wrapper );
+ $wrapper.autoEllipsis( { position: 'right' } );
+
+ // Verify that, and only one, span element was created
+ var $span = $wrapper.find( '> span' );
+ strictEqual( $span.length, 1, 'autoEllipsis wrapped the contents in a span element' );
+
+ // Check that the text fits by turning on word wrapping
+ $span.css( 'whiteSpace', 'nowrap' );
+ ltOrEq( $span.width(), $span.parent().width(), "Text fits (making the span 'white-space:nowrap' does not make it wider than its parent)" );
+
+ // Add two characters using scary black magic
+ var spanText = $span.text();
+ var d = findDivergenceIndex( origText, spanText );
+ var spanTextNew = spanText.substr( 0, d ) + origText[d] + origText[d] + '...';
+
+ gt( spanTextNew.length, spanText.length, 'Verify that the new span-length is indeed greater' );
+
+ // Put this text in the span and verify it doesn't fit
+ $span.text( spanTextNew );
+ // In IE6 width works like min-width, allow IE6's width to be "equal to"
+ if ( $.browser.msie && Number( $.browser.version ) == 6 ) {
+ gtOrEq( $span.width(), $span.parent().width(), 'Fit is maximal (adding two characters makes it not fit any more) - IE6: Maybe equal to as well due to width behaving like min-width in IE6' );
+ } else {
+ gt( $span.width(), $span.parent().width(), 'Fit is maximal (adding two characters makes it not fit any more)' );
+ }
+
+ // Clean up
+ $wrapper.remove();
+});
diff --git a/tests/qunit/suites/resources/jquery/jquery.byteLength.js b/tests/qunit/suites/resources/jquery/jquery.byteLength.js
new file mode 100644
index 00000000..f82fda27
--- /dev/null
+++ b/tests/qunit/suites/resources/jquery/jquery.byteLength.js
@@ -0,0 +1,42 @@
+module( 'jquery.byteLength.js' );
+
+test( '-- Initial check', function() {
+ expect(1);
+ ok( $.byteLength, 'jQuery.byteLength defined' );
+} );
+
+test( 'Simple text', function() {
+ expect(5);
+
+ var azLc = 'abcdefghijklmnopqrstuvwxyz',
+ azUc = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
+ num = '0123456789',
+ x = '*',
+ space = ' ';
+
+ equal( $.byteLength( azLc ), 26, 'Lowercase a-z' );
+ equal( $.byteLength( azUc ), 26, 'Uppercase A-Z' );
+ equal( $.byteLength( num ), 10, 'Numbers 0-9' );
+ equal( $.byteLength( x ), 1, 'An asterisk' );
+ equal( $.byteLength( space ), 3, '3 spaces' );
+
+} );
+
+test( 'Special text', window.foo = function() {
+ expect(5);
+
+ // http://en.wikipedia.org/wiki/UTF-8
+ var U_0024 = '\u0024',
+ U_00A2 = '\u00A2',
+ U_20AC = '\u20AC',
+ U_024B62 = '\u024B62',
+ // The normal one doesn't display properly, try the below which is the same
+ // according to http://www.fileformat.info/info/unicode/char/24B62/index.htm
+ U_024B62_alt = '\uD852\uDF62';
+
+ strictEqual( $.byteLength( U_0024 ), 1, 'U+0024: 1 byte. \u0024 (dollar sign)' );
+ strictEqual( $.byteLength( U_00A2 ), 2, 'U+00A2: 2 bytes. \u00A2 (cent sign)' );
+ strictEqual( $.byteLength( U_20AC ), 3, 'U+20AC: 3 bytes. \u20AC (euro sign)' );
+ strictEqual( $.byteLength( U_024B62 ), 4, 'U+024B62: 4 bytes. \uD852\uDF62 (a Han character)' );
+ strictEqual( $.byteLength( U_024B62_alt ), 4, 'U+024B62: 4 bytes. \uD852\uDF62 (a Han character) - alternative method' );
+} );
diff --git a/tests/qunit/suites/resources/jquery/jquery.byteLimit.js b/tests/qunit/suites/resources/jquery/jquery.byteLimit.js
new file mode 100644
index 00000000..461ea49b
--- /dev/null
+++ b/tests/qunit/suites/resources/jquery/jquery.byteLimit.js
@@ -0,0 +1,155 @@
+module( 'jquery.byteLimit.js' );
+
+test( '-- Initial check', function() {
+ expect(1);
+ ok( $.fn.byteLimit, 'jQuery.fn.byteLimit defined' );
+} );
+
+// Basic sendkey-implementation
+$.addChars = function( $input, charstr ) {
+ var len = charstr.length;
+ for ( var i = 0; i < len; i++ ) {
+ // Keep track of the previous value
+ var prevVal = $input.val();
+
+ // Get the key code
+ var code = charstr.charCodeAt(i);
+
+ // Trigger event and undo if prevented
+ var event = new jQuery.Event( 'keypress', { keyCode: code, which: code, charCode: code } );
+ $input.trigger( event );
+ if ( !event.isDefaultPrevented() ) {
+ $input.val( prevVal + charstr.charAt(i) );
+ }
+ }
+};
+var blti = 0;
+/**
+ * Test factory for $.fn.byteLimit
+ *
+ * @param $input {jQuery} jQuery object in an input element
+ * @param useLimit {Boolean} Wether a limit should apply at all
+ * @param limit {Number} Limit (if used) otherwise undefined
+ * The limit should be less than 20 (the sample data's length)
+ */
+var byteLimitTest = function( options ) {
+ var opt = $.extend({
+ description: '',
+ $input: null,
+ sample: '',
+ useLimit: false,
+ expected: 0,
+ limit: null
+ }, options);
+ var i = blti++;
+
+ test( opt.description, function() {
+
+ opt.$input.appendTo( 'body' );
+
+ // Simulate pressing keys for each of the sample characters
+ $.addChars( opt.$input, opt.sample );
+ var newVal = opt.$input.val();
+
+ if ( opt.useLimit ) {
+ expect(2);
+
+ ltOrEq( $.byteLength( newVal ), opt.limit, 'Prevent keypresses after byteLimit was reached, length never exceeded the limit' );
+ equal( $.byteLength( newVal ), opt.expected, 'Not preventing keypresses too early, length has reached the expected length' );
+
+ } else {
+ expect(1);
+ equal( $.byteLength( newVal ), opt.expected, 'Unlimited scenarios are not affected, expected length reached' );
+ }
+
+ opt.$input.remove();
+ } );
+};
+
+var
+ // Simple sample (20 chars, 20 bytes)
+ simpleSample = '12345678901234567890',
+
+ // 3 bytes (euro-symbol)
+ U_20AC = '\u20AC',
+
+ // Multi-byte sample (22 chars, 26 bytes)
+ mbSample = '1234567890' + U_20AC + '1234567890' + U_20AC;
+
+byteLimitTest({
+ description: 'Plain text input',
+ $input: $( '<input>' )
+ .attr( {
+ 'type': 'text'
+ }),
+ sample: simpleSample,
+ useLimit: false,
+ expected: $.byteLength( simpleSample )
+});
+
+byteLimitTest({
+ description: 'Limit using the maxlength attribute',
+ $input: $( '<input>' )
+ .attr( {
+ 'type': 'text',
+ 'maxlength': '10'
+ })
+ .byteLimit(),
+ sample: simpleSample,
+ useLimit: true,
+ limit: 10,
+ expected: 10
+});
+
+byteLimitTest({
+ description: 'Limit using a custom value',
+ $input: $( '<input>' )
+ .attr( {
+ 'type': 'text'
+ })
+ .byteLimit( 10 ),
+ sample: simpleSample,
+ useLimit: true,
+ limit: 10,
+ expected: 10
+});
+
+byteLimitTest({
+ description: 'Limit using a custom value, overriding maxlength attribute',
+ $input: $( '<input>' )
+ .attr( {
+ 'type': 'text',
+ 'maxLength': '10'
+ })
+ .byteLimit( 15 ),
+ sample: simpleSample,
+ useLimit: true,
+ limit: 15,
+ expected: 15
+});
+
+byteLimitTest({
+ description: 'Limit using a custom value (multibyte)',
+ $input: $( '<input>' )
+ .attr( {
+ 'type': 'text'
+ })
+ .byteLimit( 14 ),
+ sample: mbSample,
+ useLimit: true,
+ limit: 14,
+ expected: 14 // (10 x 1-byte char) + (1 x 3-byte char) + (1 x 1-byte char)
+});
+
+byteLimitTest({
+ description: 'Limit using a custom value (multibyte) overlapping a byte',
+ $input: $( '<input>' )
+ .attr( {
+ 'type': 'text'
+ })
+ .byteLimit( 12 ),
+ sample: mbSample,
+ useLimit: true,
+ limit: 12,
+ expected: 12 // 10 x 1-byte char. The next 3-byte char exceeds limit of 12, but 2 more 1-byte chars come in after.
+});
diff --git a/tests/qunit/suites/resources/jquery/jquery.client.js b/tests/qunit/suites/resources/jquery/jquery.client.js
new file mode 100644
index 00000000..50df2928
--- /dev/null
+++ b/tests/qunit/suites/resources/jquery/jquery.client.js
@@ -0,0 +1,205 @@
+module( 'jquery.client.js' );
+
+test( '-- Initial check', function() {
+ expect(1);
+ ok( jQuery.client, 'jQuery.client defined' );
+});
+
+test( 'profile userAgent support', function() {
+ expect(8);
+
+ // Object keyed by userAgent. Value is an array (human-readable name, client-profile object, navigator.platform value)
+ // Info based on results from http://toolserver.org/~krinkle/testswarm/job/174/
+ var uas = {
+ // Internet Explorer 6
+ // Internet Explorer 7
+ 'Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1)': {
+ title: 'Internet Explorer 7',
+ platform: 'Win32',
+ profile: {
+ "name": "msie",
+ "layout": "trident",
+ "layoutVersion": "unknown",
+ "platform": "win",
+ "version": "7.0",
+ "versionBase": "7",
+ "versionNumber": 7
+ }
+ },
+ // Internet Explorer 8
+ // Internet Explorer 9
+ // Internet Explorer 10
+ // Firefox 2
+ // Firefox 3.5
+ 'Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.1.19) Gecko/20110420 Firefox/3.5.19': {
+ title: 'Firefox 3.5',
+ platform: 'MacIntel',
+ profile: {
+ "name": "firefox",
+ "layout": "gecko",
+ "layoutVersion": 20110420,
+ "platform": "mac",
+ "version": "3.5.19",
+ "versionBase": "3",
+ "versionNumber": 3.5
+ }
+ },
+ // Firefox 3.6
+ 'Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.2.17) Gecko/20110422 Ubuntu/10.10 (maverick) Firefox/3.6.17': {
+ title: 'Firefox 3.6',
+ platform: 'Linux i686',
+ profile: {
+ "name": "firefox",
+ "layout": "gecko",
+ "layoutVersion": 20110422,
+ "platform": "linux",
+ "version": "3.6.17",
+ "versionBase": "3",
+ "versionNumber": 3.6
+ }
+ },
+ // Firefox 4
+ 'Mozilla/5.0 (Windows NT 6.0; rv:2.0.1) Gecko/20100101 Firefox/4.0.1': {
+ title: 'Firefox 4',
+ platform: 'Win32',
+ profile: {
+ "name": "firefox",
+ "layout": "gecko",
+ "layoutVersion": 20100101,
+ "platform": "win",
+ "version": "4.0.1",
+ "versionBase": "4",
+ "versionNumber": 4
+ }
+ },
+ // Firefox 5
+ // Safari 3
+ // Safari 4
+ 'Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_7; nl-nl) AppleWebKit/531.22.7 (KHTML, like Gecko) Version/4.0.5 Safari/531.22.7': {
+ title: 'Safari 4',
+ platform: 'MacIntel',
+ profile: {
+ "name": "safari",
+ "layout": "webkit",
+ "layoutVersion": 531,
+ "platform": "mac",
+ "version": "4.0.5",
+ "versionBase": "4",
+ "versionNumber": 4
+ }
+ },
+ 'Mozilla/5.0 (Windows; U; Windows NT 6.0; cs-CZ) AppleWebKit/533.21.1 (KHTML, like Gecko) Version/4.0.5 Safari/531.22.7': {
+ title: 'Safari 4',
+ platform: 'Win32',
+ profile: {
+ "name": "safari",
+ "layout": "webkit",
+ "layoutVersion": 533,
+ "platform": "win",
+ "version": "4.0.5",
+ "versionBase": "4",
+ "versionNumber": 4
+ }
+ },
+ // Safari 5
+ // Opera 10
+ // Chrome 5
+ // Chrome 6
+ // Chrome 7
+ // Chrome 8
+ // Chrome 9
+ // Chrome 10
+ // Chrome 11
+ // Chrome 12
+ 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_5_8) AppleWebKit/534.30 (KHTML, like Gecko) Chrome/12.0.742.112 Safari/534.30': {
+ title: 'Chrome 12',
+ platform: 'MacIntel',
+ profile: {
+ "name": "chrome",
+ "layout": "webkit",
+ "layoutVersion": 534,
+ "platform": "mac",
+ "version": "12.0.742.112",
+ "versionBase": "12",
+ "versionNumber": 12
+ }
+ },
+ 'Mozilla/5.0 (X11; Linux i686) AppleWebKit/534.30 (KHTML, like Gecko) Chrome/12.0.742.68 Safari/534.30': {
+ title: 'Chrome 12',
+ platform: 'Linux i686',
+ profile: {
+ "name": "chrome",
+ "layout": "webkit",
+ "layoutVersion": 534,
+ "platform": "linux",
+ "version": "12.0.742.68",
+ "versionBase": "12",
+ "versionNumber": 12
+ }
+ }
+ };
+
+ // Generate a client profile object and compare recursively
+ var uaTest = function( rawUserAgent, data ) {
+ var ret = $.client.profile( {
+ userAgent: rawUserAgent,
+ platform: data.platform
+ } );
+ deepEqual( ret, data.profile, 'Client profile support check for ' + data.title + ' (' + data.platform + '): ' + rawUserAgent );
+ };
+
+ // Loop through and run tests
+ $.each( uas, uaTest );
+} );
+
+test( 'profile return validation for current user agent', function() {
+ expect(7);
+ var p = $.client.profile();
+ var unknownOrType = function( val, type, summary ) {
+ return ok( typeof val === type || val === 'unknown', summary );
+ };
+
+ equal( typeof p, 'object', 'profile returns an object' );
+ unknownOrType( p.layout, 'string', 'p.layout is a string (or "unknown")' );
+ unknownOrType( p.layoutVersion, 'number', 'p.layoutVersion is a number (or "unknown")' );
+ unknownOrType( p.platform, 'string', 'p.platform is a string (or "unknown")' );
+ unknownOrType( p.version, 'string', 'p.version is a string (or "unknown")' );
+ unknownOrType( p.versionBase, 'string', 'p.versionBase is a string (or "unknown")' );
+ equal( typeof p.versionNumber, 'number', 'p.versionNumber is a number' );
+});
+
+test( 'test', function() {
+ expect(1);
+
+ // Example from WikiEditor
+ var testMap = {
+ 'ltr': {
+ 'msie': [['>=', 7]],
+ 'firefox': [['>=', 2]],
+ 'opera': [['>=', 9.6]],
+ 'safari': [['>=', 3]],
+ 'chrome': [['>=', 3]],
+ 'netscape': [['>=', 9]],
+ 'blackberry': false,
+ 'ipod': false,
+ 'iphone': false
+ },
+ 'rtl': {
+ 'msie': [['>=', 8]],
+ 'firefox': [['>=', 2]],
+ 'opera': [['>=', 9.6]],
+ 'safari': [['>=', 3]],
+ 'chrome': [['>=', 3]],
+ 'netscape': [['>=', 9]],
+ 'blackberry': false,
+ 'ipod': false,
+ 'iphone': false
+ }
+ };
+ // .test() uses eval, make sure no exceptions are thrown
+ // then do a basic return value type check
+ var testMatch = $.client.test( testMap );
+
+ equal( typeof testMatch, 'boolean', 'test returns a boolean value' );
+
+});
diff --git a/tests/qunit/suites/resources/jquery/jquery.colorUtil.js b/tests/qunit/suites/resources/jquery/jquery.colorUtil.js
new file mode 100644
index 00000000..93f12b82
--- /dev/null
+++ b/tests/qunit/suites/resources/jquery/jquery.colorUtil.js
@@ -0,0 +1,71 @@
+module( 'jquery.colorUtil.js' );
+
+test( '-- Initial check', function() {
+ expect(1);
+ ok( $.colorUtil, '$.colorUtil defined' );
+});
+
+test( 'getRGB', function() {
+ expect(18);
+
+ strictEqual( $.colorUtil.getRGB(), undefined, 'No arguments' );
+ strictEqual( $.colorUtil.getRGB( '' ), undefined, 'Empty string' );
+ deepEqual( $.colorUtil.getRGB( [0, 100, 255] ), [0, 100, 255], 'Parse array of rgb values' );
+ deepEqual( $.colorUtil.getRGB( 'rgb(0,100,255)' ), [0, 100, 255], 'Parse simple rgb string' );
+ deepEqual( $.colorUtil.getRGB( 'rgb(0, 100, 255)' ), [0, 100, 255], 'Parse simple rgb string with spaces' );
+ deepEqual( $.colorUtil.getRGB( 'rgb(0%,20%,40%)' ), [0, 51, 102], 'Parse rgb string with percentages' );
+ deepEqual( $.colorUtil.getRGB( 'rgb(0%, 20%, 40%)' ), [0, 51, 102], 'Parse rgb string with percentages and spaces' );
+ deepEqual( $.colorUtil.getRGB( '#f2ddee' ), [242, 221, 238], 'Hex string: 6 char lowercase' );
+ deepEqual( $.colorUtil.getRGB( '#f2DDEE' ), [242, 221, 238], 'Hex string: 6 char uppercase' );
+ deepEqual( $.colorUtil.getRGB( '#f2DdEe' ), [242, 221, 238], 'Hex string: 6 char mixed' );
+ deepEqual( $.colorUtil.getRGB( '#eee' ), [238, 238, 238], 'Hex string: 3 char lowercase' );
+ deepEqual( $.colorUtil.getRGB( '#EEE' ), [238, 238, 238], 'Hex string: 3 char uppercase' );
+ deepEqual( $.colorUtil.getRGB( '#eEe' ), [238, 238, 238], 'Hex string: 3 char mixed' );
+ deepEqual( $.colorUtil.getRGB( 'rgba(0, 0, 0, 0)' ), [255, 255, 255], 'Zero rgba for Safari 3; Transparent (whitespace)' );
+
+ // Perhaps this is a bug in colorUtil, but it is the current behaviour so, let's keep
+ // track of it, so we will know in case it would ever change.
+ strictEqual( $.colorUtil.getRGB( 'rgba(0,0,0,0)' ), undefined, 'Zero rgba without whitespace' );
+
+ deepEqual( $.colorUtil.getRGB( 'lightGreen' ), [144, 238, 144], 'Color names (lightGreen)' );
+ deepEqual( $.colorUtil.getRGB( 'transparent' ), [255, 255, 255], 'Color names (transparent)' );
+ strictEqual( $.colorUtil.getRGB( 'mediaWiki' ), undefined, 'Inexisting color name' );
+});
+
+test( 'rgbToHsl', function() {
+ expect(1);
+
+ var hsl = $.colorUtil.rgbToHsl( 144, 238, 144 );
+
+ // Cross-browser differences in decimals...
+ // Round to two decimals so they can be more reliably checked.
+ var dualDecimals = function(a,b){
+ return Math.round(a*100)/100;
+ };
+ // Re-create the rgbToHsl return array items, limited to two decimals.
+ var ret = [dualDecimals(hsl[0]), dualDecimals(hsl[1]), dualDecimals(hsl[2])];
+
+ deepEqual( ret, [0.33, 0.73, 0.75], 'rgb(144, 238, 144): hsl(0.33, 0.73, 0.75)' );
+});
+
+test( 'hslToRgb', function() {
+ expect(1);
+
+ var rgb = $.colorUtil.hslToRgb( 0.3, 0.7, 0.8 );
+
+ // Cross-browser differences in decimals...
+ // Re-create the hslToRgb return array items, rounded to whole numbers.
+ var ret = [Math.round(rgb[0]), Math.round(rgb[1]), Math.round(rgb[2])];
+
+ deepEqual( ret ,[183, 240, 168], 'hsl(0.3, 0.7, 0.8): rgb(183, 240, 168)' );
+});
+
+test( 'getColorBrightness', function() {
+ expect(2);
+
+ var a = $.colorUtil.getColorBrightness( 'red', +0.1 );
+ equal( a, 'rgb(255,50,50)', 'Start with named color "red", brighten 10%' );
+
+ var b = $.colorUtil.getColorBrightness( 'rgb(200,50,50)', -0.2 );
+ equal( b, 'rgb(118,29,29)', 'Start with rgb string "rgb(200,50,50)", darken 20%' );
+});
diff --git a/tests/qunit/suites/resources/jquery/jquery.getAttrs.js b/tests/qunit/suites/resources/jquery/jquery.getAttrs.js
new file mode 100644
index 00000000..3d3d01e1
--- /dev/null
+++ b/tests/qunit/suites/resources/jquery/jquery.getAttrs.js
@@ -0,0 +1,17 @@
+module( 'jquery.getAttrs.js' );
+
+test( '-- Initial check', function() {
+ expect(1);
+ ok( $.fn.getAttrs, 'jQuery.fn.getAttrs defined' );
+} );
+
+test( 'Check', function() {
+ expect(1);
+ var attrs = {
+ foo: 'bar',
+ 'class': 'lorem'
+ },
+ $el = $( '<div>', attrs );
+
+ deepEqual( $el.getAttrs(), attrs, 'getAttrs() return object should match the attributes set, no more, no less' );
+} );
diff --git a/tests/qunit/suites/resources/jquery/jquery.localize.js b/tests/qunit/suites/resources/jquery/jquery.localize.js
new file mode 100644
index 00000000..40b58687
--- /dev/null
+++ b/tests/qunit/suites/resources/jquery/jquery.localize.js
@@ -0,0 +1,119 @@
+module( 'jquery.localize.js' );
+
+test( '-- Initial check', function() {
+ expect(1);
+ ok( $.fn.localize, 'jQuery.fn.localize defined' );
+} );
+
+test( 'Handle basic replacements', function() {
+ expect(3);
+
+ var html, $lc;
+ mw.messages.set( 'basic', 'Basic stuff' );
+
+ // Tag: html:msg
+ html = '<div><span><html:msg key="basic" /></span></div>';
+ $lc = $( html ).localize().find( 'span' );
+
+ strictEqual( $lc.text(), 'Basic stuff', 'Tag: html:msg' );
+
+ // Attribute: title-msg
+ html = '<div><span title-msg="basic" /></span></div>';
+ $lc = $( html ).localize().find( 'span' );
+
+ strictEqual( $lc.attr( 'title' ), 'Basic stuff', 'Attribute: title-msg' );
+
+ // Attribute: alt-msg
+ html = '<div><span alt-msg="basic" /></span></div>';
+ $lc = $( html ).localize().find( 'span' );
+
+ strictEqual( $lc.attr( 'alt' ), 'Basic stuff', 'Attribute: alt-msg' );
+} );
+
+test( 'Proper escaping', function() {
+ expect(2);
+
+ var html, $lc;
+ mw.messages.set( 'properfoo', '<proper esc="test">' );
+
+ // This is handled by jQuery inside $.fn.localize, just a simple sanity checked
+ // making sure it is actually using text() and attr() (or something with the same effect)
+
+ // Text escaping
+ html = '<div><span><html:msg key="properfoo" /></span></div>';
+ $lc = $( html ).localize().find( 'span' );
+
+ strictEqual( $lc.text(), mw.msg( 'properfoo' ), 'Content is inserted as text, not as html.' );
+
+ // Attribute escaping
+ html = '<div><span title-msg="properfoo" /></span></div>';
+ $lc = $( html ).localize().find( 'span' );
+
+ strictEqual( $lc.attr( 'title' ), mw.msg( 'properfoo' ), 'Attributes are not inserted raw.' );
+} );
+
+test( 'Options', function() {
+ expect(7);
+
+ mw.messages.set( {
+ 'foo-lorem': 'Lorem',
+ 'foo-ipsum': 'Ipsum',
+ 'foo-bar-title': 'Read more about bars',
+ 'foo-bar-label': 'The Bars',
+ 'foo-bazz-title': 'Read more about bazz at $1 (last modified: $2)',
+ 'foo-bazz-label': 'The Bazz ($1)',
+ 'foo-welcome': 'Welcome to $1! (last visit: $2)'
+ } );
+ var html, $lc, attrs, x, sitename = 'Wikipedia';
+
+ // Message key prefix
+ html = '<div><span title-msg="lorem"><html:msg key="ipsum" /></span></div>';
+ $lc = $( html ).localize( {
+ prefix: 'foo-'
+ } ).find( 'span' );
+
+ strictEqual( $lc.attr( 'title' ), 'Lorem', 'Message key prefix - attr' );
+ strictEqual( $lc.text(), 'Ipsum', 'Message key prefix - text' );
+
+ // Variable keys mapping
+ x = 'bar';
+ html = '<div><span title-msg="title"><html:msg key="label" /></span></div>';
+ $lc = $( html ).localize( {
+ keys: {
+ 'title': 'foo-' + x + '-title',
+ 'label': 'foo-' + x + '-label'
+ }
+ } ).find( 'span' );
+
+ strictEqual( $lc.attr( 'title' ), 'Read more about bars', 'Variable keys mapping - attr' );
+ strictEqual( $lc.text(), 'The Bars', 'Variable keys mapping - text' );
+
+ // Passing parameteters to mw.msg
+ html = '<div><span><html:msg key="foo-welcome" /></span></div>';
+ $lc = $( html ).localize( {
+ params: {
+ 'foo-welcome': [sitename, 'yesterday']
+ }
+ } ).find( 'span' );
+
+ strictEqual( $lc.text(), 'Welcome to Wikipedia! (last visit: yesterday)', 'Passing parameteters to mw.msg' );
+
+ // Combination of options prefix, params and keys
+ x = 'bazz';
+ html = '<div><span title-msg="title"><html:msg key="label" /></span></div>';
+ $lc = $( html ).localize( {
+ prefix: 'foo-',
+ keys: {
+ 'title': x + '-title',
+ 'label': x + '-label'
+ },
+ params: {
+ 'title': [sitename, '3 minutes ago'],
+ 'label': [sitename, '3 minutes ago']
+
+ }
+ } ).find( 'span' );
+
+ strictEqual( $lc.text(), 'The Bazz (Wikipedia)', 'Combination of options prefix, params and keys - text' );
+ strictEqual( $lc.attr( 'title' ), 'Read more about bazz at Wikipedia (last modified: 3 minutes ago)', 'Combination of options prefix, params and keys - attr' );
+} );
diff --git a/tests/qunit/suites/resources/jquery/jquery.mwPrototypes.js b/tests/qunit/suites/resources/jquery/jquery.mwPrototypes.js
new file mode 100644
index 00000000..bb6d2a1b
--- /dev/null
+++ b/tests/qunit/suites/resources/jquery/jquery.mwPrototypes.js
@@ -0,0 +1,56 @@
+module( 'jquery.mwPrototypes.js' );
+
+test( 'String functions', function() {
+
+ equal( $.trimLeft( ' foo bar ' ), 'foo bar ', 'trimLeft' );
+ equal( $.trimRight( ' foo bar ' ), ' foo bar', 'trimRight' );
+ equal( $.ucFirst( 'foo'), 'Foo', 'ucFirst' );
+
+ equal( $.escapeRE( '<!-- ([{+mW+}]) $^|?>' ),
+ '<!\\-\\- \\(\\[\\{\\+mW\\+\\}\\]\\) \\$\\^\\|\\?>', 'escapeRE - Escape specials' );
+ equal( $.escapeRE( 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' ),
+ 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'escapeRE - Leave uppercase alone' );
+ equal( $.escapeRE( 'abcdefghijklmnopqrstuvwxyz' ),
+ 'abcdefghijklmnopqrstuvwxyz', 'escapeRE - Leave lowercase alone' );
+ equal( $.escapeRE( '0123456789' ), '0123456789', 'escapeRE - Leave numbers alone' );
+});
+
+test( 'Is functions', function() {
+
+ strictEqual( $.isDomElement( document.getElementById( 'qunit-header' ) ), true,
+ 'isDomElement: #qunit-header Node' );
+ strictEqual( $.isDomElement( document.getElementById( 'random-name' ) ), false,
+ 'isDomElement: #random-name (null)' );
+ strictEqual( $.isDomElement( document.getElementsByTagName( 'div' ) ), false,
+ 'isDomElement: getElementsByTagName Array' );
+ strictEqual( $.isDomElement( document.getElementsByTagName( 'div' )[0] ), true,
+ 'isDomElement: getElementsByTagName(..)[0] Node' );
+ strictEqual( $.isDomElement( $( 'div' ) ), false,
+ 'isDomElement: jQuery object' );
+ strictEqual( $.isDomElement( $( 'div' ).get(0) ), true,
+ 'isDomElement: jQuery object > Get node' );
+ strictEqual( $.isDomElement( document.createElement( 'div' ) ), true,
+ 'isDomElement: createElement' );
+ strictEqual( $.isDomElement( { foo: 1 } ), false,
+ 'isDomElement: Object' );
+
+ strictEqual( $.isEmpty( 'string' ), false, 'isEmptry: "string"' );
+ strictEqual( $.isEmpty( '0' ), true, 'isEmptry: "0"' );
+ strictEqual( $.isEmpty( [] ), true, 'isEmptry: []' );
+ strictEqual( $.isEmpty( {} ), true, 'isEmptry: {}' );
+
+ // Documented behaviour
+ strictEqual( $.isEmpty( { length: 0 } ), true, 'isEmptry: { length: 0 }' );
+});
+
+test( 'Comparison functions', function() {
+
+ ok( $.compareArray( [0, 'a', [], [2, 'b'] ], [0, "a", [], [2, "b"] ] ),
+ 'compareArray: Two deep arrays that are excactly the same' );
+ ok( !$.compareArray( [1], [2] ), 'compareArray: Two different arrays (false)' );
+
+ ok( $.compareObject( {}, {} ), 'compareObject: Two empty objects' );
+ ok( $.compareObject( { foo: 1 }, { foo: 1 } ), 'compareObject: Two the same objects' );
+ ok( !$.compareObject( { bar: true }, { baz: false } ),
+ 'compareObject: Two different objects (false)' );
+});
diff --git a/tests/qunit/suites/resources/jquery/jquery.tabIndex.js b/tests/qunit/suites/resources/jquery/jquery.tabIndex.js
new file mode 100644
index 00000000..1ff81e58
--- /dev/null
+++ b/tests/qunit/suites/resources/jquery/jquery.tabIndex.js
@@ -0,0 +1,50 @@
+module( 'jquery.tabIndex.js' );
+
+test( '-- Initial check', function() {
+ expect(2);
+
+ ok( $.fn.firstTabIndex, '$.fn.firstTabIndex defined' );
+ ok( $.fn.lastTabIndex, '$.fn.lastTabIndex defined' );
+});
+
+test( 'firstTabIndex', function() {
+ expect(2);
+
+ var testEnvironment =
+'<form>' +
+ '<input tabindex="7" />' +
+ '<input tabindex="9" />' +
+ '<textarea tabindex="2">Foobar</textarea>' +
+ '<textarea tabindex="5">Foobar</textarea>' +
+'</form>';
+
+ var $testA = $( '<div>' ).html( testEnvironment ).appendTo( 'body' );
+ strictEqual( $testA.firstTabIndex(), 2, 'First tabindex should be 2 within this context.' );
+
+ var $testB = $( '<div>' );
+ strictEqual( $testB.firstTabIndex(), null, 'Return null if none available.' );
+
+ // Clean up
+ $testA.add( $testB ).remove();
+});
+
+test( 'lastTabIndex', function() {
+ expect(2);
+
+ var testEnvironment =
+'<form>' +
+ '<input tabindex="7" />' +
+ '<input tabindex="9" />' +
+ '<textarea tabindex="2">Foobar</textarea>' +
+ '<textarea tabindex="5">Foobar</textarea>' +
+'</form>';
+
+ var $testA = $( '<div>' ).html( testEnvironment ).appendTo( 'body' );
+ strictEqual( $testA.lastTabIndex(), 9, 'Last tabindex should be 9 within this context.' );
+
+ var $testB = $( '<div>' );
+ strictEqual( $testB.lastTabIndex(), null, 'Return null if none available.' );
+
+ // Clean up
+ $testA.add( $testB ).remove();
+});
diff --git a/tests/qunit/suites/resources/jquery/jquery.tablesorter.test.js b/tests/qunit/suites/resources/jquery/jquery.tablesorter.test.js
new file mode 100644
index 00000000..f47b7f40
--- /dev/null
+++ b/tests/qunit/suites/resources/jquery/jquery.tablesorter.test.js
@@ -0,0 +1,475 @@
+(function() {
+
+module( 'jquery.tablesorter.test.js' );
+
+// setup hack
+mw.config.set('wgMonthNames', window.wgMonthNames = ['', 'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December']);
+mw.config.set('wgMonthNamesShort', window.wgMonthNamesShort = ['', 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']);
+mw.config.set('wgDefaultDateFormat', window.wgDefaultDateFormat = 'dmy');
+
+test( '-- Initial check', function() {
+ expect(1);
+ ok( $.tablesorter, '$.tablesorter defined' );
+});
+
+/**
+ * Create an HTML table from an array of row arrays containing text strings.
+ * First row will be header row. No fancy rowspan/colspan stuff.
+ *
+ * @param {String[]} header
+ * @param {String[][]} data
+ * @return jQuery
+ */
+var tableCreate = function( header, data ) {
+ var $table = $('<table class="sortable"><thead></thead><tbody></tbody></table>'),
+ $thead = $table.find('thead'),
+ $tbody = $table.find('tbody');
+ var $tr = $('<tr>');
+ $.each(header, function(i, str) {
+ var $th = $('<th>');
+ $th.text(str).appendTo($tr);
+ });
+ $tr.appendTo($thead);
+
+ for (var i = 0; i < data.length; i++) {
+ $tr = $('<tr>');
+ $.each(data[i], function(j, str) {
+ var $td = $('<td>');
+ $td.text(str).appendTo($tr);
+ });
+ $tr.appendTo($tbody);
+ }
+ return $table;
+};
+
+/**
+ * Extract text from table.
+ *
+ * @param {jQuery} $table
+ * @return String[][]
+ */
+var tableExtract = function( $table ) {
+ var data = [];
+ $table.find('tbody').find('tr').each(function(i, tr) {
+ var row = [];
+ $(tr).find('td,th').each(function(i, td) {
+ row.push($(td).text());
+ });
+ data.push(row);
+ });
+ return data;
+};
+
+/**
+ * Run a table test by building a table with the given data,
+ * running some callback on it, then checking the results.
+ *
+ * @param {String} msg text to pass on to qunit for the comparison
+ * @param {String[]} header cols to make the table
+ * @param {String[][]} data rows/cols to make the table
+ * @param {String[][]} expected rows/cols to compare against at end
+ * @param {function($table)} callback something to do with the table before we compare
+ */
+var tableTest = function( msg, header, data, expected, callback ) {
+ test( msg, function() {
+ expect(1);
+
+ var $table = tableCreate( header, data );
+ //$('body').append($table);
+
+ // Give caller a chance to set up sorting and manipulate the table.
+ callback( $table );
+
+ // Table sorting is done synchronously; if it ever needs to change back
+ // to asynchronous, we'll need a timeout or a callback here.
+ var extracted = tableExtract( $table );
+ deepEqual( extracted, expected, msg );
+ });
+};
+
+var reversed = function(arr) {
+ var arr2 = arr.slice(0);
+ arr2.reverse();
+ return arr2;
+};
+
+// Sample data set: some planets!
+var header = ['Planet', 'Radius (km)'],
+ mercury = ['Mercury', '2439.7'],
+ venus = ['Venus', '6051.8'],
+ earth = ['Earth', '6371.0'],
+ mars = ['Mars', '3390.0'],
+ jupiter = ['Jupiter', '69911'],
+ saturn = ['Saturn', '58232'];
+
+// Initial data set
+var planets = [mercury, venus, earth, mars, jupiter, saturn];
+var ascendingName = [earth, jupiter, mars, mercury, saturn, venus];
+var ascendingRadius = [mercury, mars, venus, earth, saturn, jupiter];
+
+tableTest(
+ 'Basic planet table: ascending by name',
+ header,
+ planets,
+ ascendingName,
+ function( $table ) {
+ $table.tablesorter();
+ $table.find('.headerSort:eq(0)').click();
+ }
+);
+tableTest(
+ 'Basic planet table: ascending by name a second time',
+ header,
+ planets,
+ ascendingName,
+ function( $table ) {
+ $table.tablesorter();
+ $table.find('.headerSort:eq(0)').click();
+ }
+);
+tableTest(
+ 'Basic planet table: descending by name',
+ header,
+ planets,
+ reversed(ascendingName),
+ function( $table ) {
+ $table.tablesorter();
+ $table.find('.headerSort:eq(0)').click().click();
+ }
+);
+tableTest(
+ 'Basic planet table: ascending radius',
+ header,
+ planets,
+ ascendingRadius,
+ function( $table ) {
+ $table.tablesorter();
+ $table.find('.headerSort:eq(1)').click();
+ }
+);
+tableTest(
+ 'Basic planet table: descending radius',
+ header,
+ planets,
+ reversed(ascendingRadius),
+ function( $table ) {
+ $table.tablesorter();
+ $table.find('.headerSort:eq(1)').click().click();
+ }
+);
+
+
+// Regression tests!
+tableTest(
+ 'Bug 28775: German-style short numeric dates',
+ ['Date'],
+ [
+ // German-style dates are day-month-year
+ ['11.11.2011'],
+ ['01.11.2011'],
+ ['02.10.2011'],
+ ['03.08.2011'],
+ ['09.11.2011']
+ ],
+ [
+ // Sorted by ascending date
+ ['03.08.2011'],
+ ['02.10.2011'],
+ ['01.11.2011'],
+ ['09.11.2011'],
+ ['11.11.2011']
+ ],
+ function( $table ) {
+ // @fixme reset it at end or change module to allow us to override it
+ mw.config.set('wgDefaultDateFormat', window.wgDefaultDateFormat = 'dmy');
+ $table.tablesorter();
+ $table.find('.headerSort:eq(0)').click();
+ }
+);
+tableTest(
+ 'Bug 28775: American-style short numeric dates',
+ ['Date'],
+ [
+ // American-style dates are month-day-year
+ ['11.11.2011'],
+ ['01.11.2011'],
+ ['02.10.2011'],
+ ['03.08.2011'],
+ ['09.11.2011']
+ ],
+ [
+ // Sorted by ascending date
+ ['01.11.2011'],
+ ['02.10.2011'],
+ ['03.08.2011'],
+ ['09.11.2011'],
+ ['11.11.2011']
+ ],
+ function( $table ) {
+ // @fixme reset it at end or change module to allow us to override it
+ mw.config.set('wgDefaultDateFormat', window.wgDefaultDateFormat = 'mdy');
+ $table.tablesorter();
+ $table.find('.headerSort:eq(0)').click();
+ }
+);
+
+var ipv4 = [
+ // Some randomly generated fake IPs
+ ['45.238.27.109'],
+ ['44.172.9.22'],
+ ['247.240.82.209'],
+ ['204.204.132.158'],
+ ['170.38.91.162'],
+ ['197.219.164.9'],
+ ['45.68.154.72'],
+ ['182.195.149.80']
+];
+var ipv4Sorted = [
+ // Sort order should go octet by octet
+ ['44.172.9.22'],
+ ['45.68.154.72'],
+ ['45.238.27.109'],
+ ['170.38.91.162'],
+ ['182.195.149.80'],
+ ['197.219.164.9'],
+ ['204.204.132.158'],
+ ['247.240.82.209']
+];
+tableTest(
+ 'Bug 17141: IPv4 address sorting',
+ ['IP'],
+ ipv4,
+ ipv4Sorted,
+ function( $table ) {
+ $table.tablesorter();
+ $table.find('.headerSort:eq(0)').click();
+ }
+);
+tableTest(
+ 'Bug 17141: IPv4 address sorting (reverse)',
+ ['IP'],
+ ipv4,
+ reversed(ipv4Sorted),
+ function( $table ) {
+ $table.tablesorter();
+ $table.find('.headerSort:eq(0)').click().click();
+ }
+);
+
+var umlautWords = [
+ // Some words with Umlauts
+ ['Günther'],
+ ['Peter'],
+ ['Björn'],
+ ['Bjorn'],
+ ['Apfel'],
+ ['Äpfel'],
+ ['Strasse'],
+ ['Sträßschen']
+];
+
+var umlautWordsSorted = [
+ // Some words with Umlauts
+ ['Äpfel'],
+ ['Apfel'],
+ ['Björn'],
+ ['Bjorn'],
+ ['Günther'],
+ ['Peter'],
+ ['Sträßschen'],
+ ['Strasse']
+];
+
+tableTest(
+ 'Accented Characters with custom collation',
+ ['Name'],
+ umlautWords,
+ umlautWordsSorted,
+ function( $table ) {
+ mw.config.set('tableSorterCollation', {'ä':'ae', 'ö' : 'oe', 'ß': 'ss', 'ü':'ue'});
+ $table.tablesorter();
+ $table.find('.headerSort:eq(0)').click();
+ mw.config.set('tableSorterCollation', {});
+ }
+);
+
+var planetsRowspan =[["Earth","6051.8"], jupiter, ["Mars","6051.8"], mercury, saturn, venus];
+var planetsRowspanII =[jupiter, mercury, saturn, ['Venus', '6371.0'], venus, ['Venus', '3390.0']];
+
+tableTest(
+ 'Basic planet table: Same value for multiple rows via rowspan',
+ header,
+ planets,
+ planetsRowspan,
+ function( $table ) {
+ //Quick&Dirty mod
+ $table.find('tr:eq(3) td:eq(1), tr:eq(4) td:eq(1)').remove();
+ $table.find('tr:eq(2) td:eq(1)').attr('rowspan', '3');
+ $table.tablesorter();
+ $table.find('.headerSort:eq(0)').click();
+ }
+);
+tableTest(
+ 'Basic planet table: Same value for multiple rows via rowspan II',
+ header,
+ planets,
+ planetsRowspanII,
+ function( $table ) {
+ //Quick&Dirty mod
+ $table.find('tr:eq(3) td:eq(0), tr:eq(4) td:eq(0)').remove();
+ $table.find('tr:eq(2) td:eq(0)').attr('rowspan', '3');
+ $table.tablesorter();
+ $table.find('.headerSort:eq(0)').click();
+ }
+);
+
+var complexMDYDates = [
+ // Some words with Umlauts
+ ['January, 19 2010'],
+ ['April 21 1991'],
+ ['04 22 1991'],
+ ['5.12.1990'],
+ ['December 12 \'10']
+];
+
+var complexMDYSorted = [
+ ["5.12.1990"],
+ ["April 21 1991"],
+ ["04 22 1991"],
+ ["January, 19 2010"],
+ ["December 12 '10"]
+];
+
+tableTest(
+ 'Complex date parsing I',
+ ['date'],
+ complexMDYDates,
+ complexMDYSorted,
+ function( $table ) {
+ mw.config.set('wgDefaultDateFormat', window.wgDefaultDateFormat = 'mdy');
+ $table.tablesorter();
+ $table.find('.headerSort:eq(0)').click();
+ }
+);
+
+var ascendingNameLegacy = ascendingName.slice(0);
+ascendingNameLegacy[4] = ascendingNameLegacy[5];
+ascendingNameLegacy.pop();
+
+tableTest(
+ 'Legacy compat with .sortbottom',
+ header,
+ planets,
+ ascendingNameLegacy,
+ function( $table ) {
+ $table.find('tr:last').addClass('sortbottom');
+ $table.tablesorter();
+ $table.find('.headerSort:eq(0)').click();
+ }
+);
+
+/** FIXME: the diff output is not very readeable. */
+test( 'bug 32047 - caption must be before thead', function() {
+ var $table;
+ $table = $(
+ '<table class="sortable">' +
+ '<caption>CAPTION</caption>' +
+ '<tr><th>THEAD</th></tr>' +
+ '<tr><td>A</td></tr>' +
+ '<tr><td>B</td></tr>' +
+ '<tr class="sortbottom"><td>TFOOT</td></tr>' +
+ '</table>'
+ );
+ $table.tablesorter();
+
+ equals(
+ $table.children( ).get( 0 ).nodeName,
+ 'CAPTION',
+ 'First element after <thead> must be <caption> (bug 32047)'
+ );
+});
+
+test( 'data-sort-value attribute, when available, should override sorting position', function() {
+ var $table, data;
+
+ // Simple example, one without data-sort-value which should be sorted at it's text.
+ $table = $(
+ '<table class="sortable"><thead><tr><th>Data</th></tr></thead>' +
+ '<tbody>' +
+ '<tr><td>Cheetah</td></tr>' +
+ '<tr><td data-sort-value="Apple">Bird</td></tr>' +
+ '<tr><td data-sort-value="Bananna">Ferret</td></tr>' +
+ '<tr><td data-sort-value="Drupe">Elephant</td></tr>' +
+ '<tr><td data-sort-value="Cherry">Dolphin</td></tr>' +
+ '</tbody></table>'
+ );
+ $table.tablesorter().find( '.headerSort:eq(0)' ).click();
+
+ data = [];
+ $table.find( 'tbody > tr' ).each( function( i, tr ) {
+ $( tr ).find( 'td' ).each( function( i, td ) {
+ data.push( { data: $( td ).data( 'sort-value' ), text: $( td ).text() } );
+ });
+ });
+
+ deepEqual( data, [
+ {
+ "data": "Apple",
+ "text": "Bird"
+ }, {
+ "data": "Bananna",
+ "text": "Ferret"
+ }, {
+ "data": undefined,
+ "text": "Cheetah"
+ }, {
+ "data": "Cherry",
+ "text": "Dolphin"
+ }, {
+ "data": "Drupe",
+ "text": "Elephant"
+ }
+ ] );
+
+ // Another example
+ $table = $(
+ '<table class="sortable"><thead><tr><th>Data</th></tr></thead>' +
+ '<tbody>' +
+ '<tr><td>D</td></tr>' +
+ '<tr><td data-sort-value="E">A</td></tr>' +
+ '<tr><td>B</td></tr>' +
+ '<tr><td>G</td></tr>' +
+ '<tr><td data-sort-value="F">C</td></tr>' +
+ '</tbody></table>'
+ );
+ $table.tablesorter().find( '.headerSort:eq(0)' ).click();
+
+ data = [];
+ $table.find( 'tbody > tr' ).each( function( i, tr ) {
+ $( tr ).find( 'td' ).each( function( i, td ) {
+ data.push( { data: $( td ).data( 'sort-value' ), text: $( td ).text() } );
+ });
+ });
+
+ deepEqual( data, [
+ {
+ "data": undefined,
+ "text": "B"
+ }, {
+ "data": undefined,
+ "text": "D"
+ }, {
+ "data": "E",
+ "text": "A"
+ }, {
+ "data": "F",
+ "text": "C"
+ }, {
+ "data": undefined,
+ "text": "G"
+ }
+ ] );
+
+});
+
+})();
diff --git a/tests/qunit/suites/resources/mediawiki.special/mediawiki.special.recentchanges.js b/tests/qunit/suites/resources/mediawiki.special/mediawiki.special.recentchanges.js
new file mode 100644
index 00000000..bcc9b96b
--- /dev/null
+++ b/tests/qunit/suites/resources/mediawiki.special/mediawiki.special.recentchanges.js
@@ -0,0 +1,71 @@
+module( 'mediawiki.special.recentchanges.js' );
+
+test( '-- Initial check', function() {
+ expect( 2 );
+ ok( mw.special.recentchanges.init,
+ 'mw.special.recentchanges.init defined'
+ );
+ ok( mw.special.recentchanges.updateCheckboxes,
+ 'mw.special.recentchanges.updateCheckboxes defined'
+ );
+ // TODO: verify checkboxes == [ 'nsassociated', 'nsinvert' ]
+});
+
+test( '"all" namespace disable checkboxes', function() {
+
+ // from Special:Recentchanges
+ var select =
+ '<select id="namespace" name="namespace" class="namespaceselector">'
+ + '<option value="" selected="selected">all</option>'
+ + '<option value="0">(Main)</option>'
+ + '<option value="1">Talk</option>'
+ + '<option value="2">User</option>'
+ + '<option value="3">User talk</option>'
+ + '<option value="4">ProjectName</option>'
+ + '<option value="5">ProjectName talk</option>'
+ + '</select>'
+ + '<input name="invert" type="checkbox" value="1" id="nsinvert" title="no title" />'
+ + '<label for="nsinvert" title="no title">Invert selection</label>'
+ + '<input name="associated" type="checkbox" value="1" id="nsassociated" title="no title" />'
+ + '<label for="nsassociated" title="no title">Associated namespace</label>'
+ + '<input type="submit" value="Go" />'
+ + '<input type="hidden" value="Special:RecentChanges" name="title" />'
+ ;
+
+ var $env = $( '<div>' ).html( select ).appendTo( 'body' );
+
+ // TODO abstract the double strictEquals
+
+ // At first checkboxes are enabled
+ strictEqual( $( '#nsinvert' ).attr( 'disabled' ), undefined );
+ strictEqual( $( '#nsassociated' ).attr( 'disabled' ), undefined );
+
+ // Initiate the recentchanges module
+ mw.special.recentchanges.init();
+
+ // By default
+ strictEqual( $( '#nsinvert' ).attr( 'disabled' ), 'disabled' );
+ strictEqual( $( '#nsassociated' ).attr( 'disabled' ), 'disabled' );
+
+ // select second option...
+ var $options = $( '#namespace' ).find( 'option' );
+ $options.eq(0).removeAttr( 'selected' );
+ $options.eq(1).attr( 'selected', 'selected' );
+ $( '#namespace' ).change();
+
+ // ... and checkboxes should be enabled again
+ strictEqual( $( '#nsinvert' ).attr( 'disabled' ), undefined );
+ strictEqual( $( '#nsassociated' ).attr( 'disabled' ), undefined );
+
+ // select first option ( 'all' namespace)...
+ $options.eq(1).removeAttr( 'selected' );
+ $options.eq(0).attr( 'selected', 'selected' );;
+ $( '#namespace' ).change();
+
+ // ... and checkboxes should now be disabled
+ strictEqual( $( '#nsinvert' ).attr( 'disabled' ), 'disabled' );
+ strictEqual( $( '#nsassociated' ).attr( 'disabled' ), 'disabled' );
+
+ // DOM cleanup
+ $env.remove();
+});
diff --git a/tests/qunit/suites/resources/mediawiki/mediawiki.js b/tests/qunit/suites/resources/mediawiki/mediawiki.js
new file mode 100644
index 00000000..4beed881
--- /dev/null
+++ b/tests/qunit/suites/resources/mediawiki/mediawiki.js
@@ -0,0 +1,232 @@
+module( 'mediawiki.js' );
+
+test( '-- Initial check', function() {
+ expect(8);
+
+ ok( window.jQuery, 'jQuery defined' );
+ ok( window.$, '$j defined' );
+ ok( window.$j, '$j defined' );
+ strictEqual( window.$, window.jQuery, '$ alias to jQuery' );
+ strictEqual( window.$j, window.jQuery, '$j alias to jQuery' );
+
+ ok( window.mediaWiki, 'mediaWiki defined' );
+ ok( window.mw, 'mw defined' );
+ strictEqual( window.mw, window.mediaWiki, 'mw alias to mediaWiki' );
+});
+
+test( 'mw.Map', function() {
+ expect(17);
+
+ ok( mw.Map, 'mw.Map defined' );
+
+ var conf = new mw.Map(),
+ // Dummy variables
+ funky = function() {},
+ arry = [],
+ nummy = 7;
+
+ // Tests for input validation
+ strictEqual( conf.get( 'inexistantKey' ), null, 'Map.get returns null if selection was a string and the key was not found' );
+ strictEqual( conf.set( 'myKey', 'myValue' ), true, 'Map.set returns boolean true if a value was set for a valid key string' );
+ strictEqual( conf.set( funky, 'Funky' ), false, 'Map.set returns boolean false if key was invalid (Function)' );
+ strictEqual( conf.set( arry, 'Arry' ), false, 'Map.set returns boolean false if key was invalid (Array)' );
+ strictEqual( conf.set( nummy, 'Nummy' ), false, 'Map.set returns boolean false if key was invalid (Number)' );
+ equal( conf.get( 'myKey' ), 'myValue', 'Map.get returns a single value value correctly' );
+ strictEqual( conf.get( nummy ), null, 'Map.get ruturns null if selection was invalid (Number)' );
+ strictEqual( conf.get( funky ), null, 'Map.get ruturns null if selection was invalid (Function)' );
+
+ // Multiple values at once
+ var someValues = {
+ 'foo': 'bar',
+ 'lorem': 'ipsum',
+ 'MediaWiki': true
+ };
+ strictEqual( conf.set( someValues ), true, 'Map.set returns boolean true if multiple values were set by passing an object' );
+ deepEqual( conf.get( ['foo', 'lorem'] ), {
+ 'foo': 'bar',
+ 'lorem': 'ipsum'
+ }, 'Map.get returns multiple values correctly as an object' );
+
+ deepEqual( conf.get( ['foo', 'notExist'] ), {
+ 'foo': 'bar',
+ 'notExist': null
+ }, 'Map.get return includes keys that were not found as null values' );
+
+ strictEqual( conf.exists( 'foo' ), true, 'Map.exists returns boolean true if a key exists' );
+ strictEqual( conf.exists( 'notExist' ), false, 'Map.exists returns boolean false if a key does not exists' );
+
+ // Interacting with globals and accessing the values object
+ strictEqual( conf.get(), conf.values, 'Map.get returns the entire values object by reference (if called without arguments)' );
+
+ conf.set( 'globalMapChecker', 'Hi' );
+
+ ok( false === 'globalMapChecker' in window, 'new mw.Map did not store its values in the global window object by default' );
+
+ var globalConf = new mw.Map( true );
+ globalConf.set( 'anotherGlobalMapChecker', 'Hello' );
+
+ ok( 'anotherGlobalMapChecker' in window, 'new mw.Map( true ) did store its values in the global window object' );
+
+ // Whitelist this global variable for QUnit's 'noglobal' mode
+ if ( QUnit.config.noglobals ) {
+ QUnit.config.pollution.push( 'anotherGlobalMapChecker' );
+ }
+});
+
+test( 'mw.config', function() {
+ expect(1);
+
+ ok( mw.config instanceof mw.Map, 'mw.config instance of mw.Map' );
+});
+
+test( 'mw.message & mw.messages', function() {
+ expect(17);
+
+ ok( mw.messages, 'messages defined' );
+ ok( mw.messages instanceof mw.Map, 'mw.messages instance of mw.Map' );
+ ok( mw.messages.set( 'hello', 'Hello <b>awesome</b> world' ), 'mw.messages.set: Register' );
+
+ var hello = mw.message( 'hello' );
+
+ equal( hello.format, 'parse', 'Message property "format" defaults to "parse"' );
+ strictEqual( hello.map, mw.messages, 'Message property "map" defaults to the global instance in mw.messages' );
+ equal( hello.key, 'hello', 'Message property "key" (currect key)' );
+ deepEqual( hello.parameters, [], 'Message property "parameters" defaults to an empty array' );
+
+ // Todo
+ ok( hello.params, 'Message prototype "params"' );
+
+ hello.format = 'plain';
+ equal( hello.toString(), 'Hello <b>awesome</b> world', 'Message.toString returns the message as a string with the current "format"' );
+
+ equal( hello.escaped(), 'Hello &lt;b&gt;awesome&lt;/b&gt; world', 'Message.escaped returns the escaped message' );
+ equal( hello.format, 'escaped', 'Message.escaped correctly updated the "format" property' );
+
+ hello.parse();
+ equal( hello.format, 'parse', 'Message.parse correctly updated the "format" property' );
+
+ hello.plain();
+ equal( hello.format, 'plain', 'Message.plain correctly updated the "format" property' );
+
+ strictEqual( hello.exists(), true, 'Message.exists returns true for existing messages' );
+
+ var goodbye = mw.message( 'goodbye' );
+ strictEqual( goodbye.exists(), false, 'Message.exists returns false for inexisting messages' );
+
+ equal( goodbye.plain(), '<goodbye>', 'Message.toString returns plain <key> if format is "plain" and key does not exist' );
+ // bug 30684
+ equal( goodbye.escaped(), '&lt;goodbye&gt;', 'Message.toString returns properly escaped &lt;key&gt; if format is "escaped" and key does not exist' );
+});
+
+test( 'mw.msg', function() {
+ expect(3);
+
+ ok( mw.messages.set( 'hello', 'Hello <b>awesome</b> world' ), 'mw.messages.set: Register' );
+
+ equal( mw.msg( 'hello' ), 'Hello <b>awesome</b> world', 'Gets message with default options (existing message)' );
+ equal( mw.msg( 'goodbye' ), '<goodbye>', 'Gets message with default options (inexisting message)' );
+});
+
+test( 'mw.loader', function() {
+ expect(5);
+
+ // Regular expression to extract the path for the QUnit tests
+ // Takes into account that tests could be run from a file:// URL
+ // by excluding the 'index.html' part from the URL
+ var rePath = /(?:[^#\?](?!index.html))*\/?/;
+
+ // Four assertions to test the above regular expression:
+ equal(
+ rePath.exec( 'http://path/to/tests/?foobar' )[0],
+ "http://path/to/tests/",
+ "Extracting path from http URL with query"
+ );
+ equal(
+ rePath.exec( 'http://path/to/tests/#frag' )[0],
+ "http://path/to/tests/",
+ "Extracting path from http URL with fragment"
+ );
+ equal(
+ rePath.exec( 'file://path/to/tests/index.html?foobar' )[0],
+ "file://path/to/tests/",
+ "Extracting path from local URL (file://) with query"
+ );
+ equal(
+ rePath.exec( 'file://path/to/tests/index.html#frag' )[0],
+ "file://path/to/tests/",
+ "Extracting path from local URL (file://) with fragment"
+ );
+
+ // Asynchronous ahead
+ stop(5000);
+
+ // Extract path
+ var tests_path = rePath.exec( location.href );
+
+ mw.loader.implement( 'is.awesome', [QUnit.fixurl( tests_path + 'data/defineTestCallback.js')], {}, {} );
+
+ mw.loader.using( 'is.awesome', function() {
+
+ // /sample/awesome.js declares the "mw.loader.testCallback" function
+ // which contains a call to start() and ok()
+ mw.loader.testCallback();
+ mw.loader.testCallback = undefined;
+
+ }, function() {
+ start();
+ ok( false, 'Error callback fired while implementing "is.awesome" module' );
+ });
+
+});
+
+test( 'mw.loader.bug29107' , function() {
+ expect(2);
+
+ // Message doesn't exist already
+ ok( !mw.messages.exists( 'bug29107' ) );
+
+ // Async! Include a timeout, as failure in this test leads to neither the
+ // success nor failure callbacks getting called.
+ stop(5000);
+
+ mw.loader.implement( 'bug29107.messages-only', [], {}, {'bug29107': 'loaded'} );
+ mw.loader.using( 'bug29107.messages-only', function() {
+ start();
+ ok( mw.messages.exists( 'bug29107' ), 'Bug 29107: messages-only module should implement ok' );
+ }, function() {
+ start();
+ ok( false, 'Error callback fired while implementing "bug29107.messages-only" module' );
+ });
+});
+
+test( 'mw.html', function() {
+ expect(7);
+
+ raises( function(){
+ mw.html.escape();
+ }, TypeError, 'html.escape throws a TypeError if argument given is not a string' );
+
+ equal( mw.html.escape( '<mw awesome="awesome" value=\'test\' />' ),
+ '&lt;mw awesome=&quot;awesome&quot; value=&#039;test&#039; /&gt;', 'html.escape escapes html snippet' );
+
+ equal( mw.html.element(),
+ '<undefined/>', 'html.element Always return a valid html string (even without arguments)' );
+
+ equal( mw.html.element( 'div' ), '<div/>', 'html.element DIV (simple)' );
+
+ equal( mw.html.element( 'div',
+ { id: 'foobar' } ),
+ '<div id="foobar"/>',
+ 'html.element DIV (attribs)' );
+
+ equal( mw.html.element( 'div',
+ null, 'a' ),
+ '<div>a</div>',
+ 'html.element DIV (content)' );
+
+ equal( mw.html.element( 'a',
+ { href: 'http://mediawiki.org/w/index.php?title=RL&action=history' }, 'a' ),
+ '<a href="http://mediawiki.org/w/index.php?title=RL&amp;action=history">a</a>',
+ 'html.element DIV (attribs + content)' );
+
+});
diff --git a/tests/qunit/suites/resources/mediawiki/mediawiki.jscompat.test.js b/tests/qunit/suites/resources/mediawiki/mediawiki.jscompat.test.js
new file mode 100644
index 00000000..52cd32c8
--- /dev/null
+++ b/tests/qunit/suites/resources/mediawiki/mediawiki.jscompat.test.js
@@ -0,0 +1,35 @@
+/* Some misc JavaScript compatibility tests, just to make sure the environments we run in are consistent */
+
+module( 'mediawiki.jscompat' );
+
+test( 'Variable with Unicode letter in name', function() {
+ expect(3);
+ var orig = "some token";
+ var ŝablono = orig;
+ deepEqual( ŝablono, orig, 'ŝablono' );
+ deepEqual( \u015dablono, orig, '\\u015dablono' );
+ deepEqual( \u015Dablono, orig, '\\u015Dablono' );
+});
+
+/*
+// Not that we need this. ;)
+// This fails on IE 6-8
+// Works on IE 9, Firefox 6, Chrome 14
+test( 'Keyword workaround: "if" as variable name using Unicode escapes', function() {
+ var orig = "another token";
+ \u0069\u0066 = orig;
+ deepEqual( \u0069\u0066, orig, '\\u0069\\u0066' );
+});
+*/
+
+/*
+// Not that we need this. ;)
+// This fails on IE 6-9
+// Works on Firefox 6, Chrome 14
+test( 'Keyword workaround: "if" as member variable name using Unicode escapes', function() {
+ var orig = "another token";
+ var foo = {};
+ foo.\u0069\u0066 = orig;
+ deepEqual( foo.\u0069\u0066, orig, 'foo.\\u0069\\u0066' );
+});
+*/
diff --git a/tests/qunit/suites/resources/mediawiki/mediawiki.user.js b/tests/qunit/suites/resources/mediawiki/mediawiki.user.js
new file mode 100644
index 00000000..d5c6baad
--- /dev/null
+++ b/tests/qunit/suites/resources/mediawiki/mediawiki.user.js
@@ -0,0 +1,29 @@
+module( 'mediawiki.user.js' );
+
+test( '-- Initial check', function() {
+ expect(1);
+
+ ok( mw.user, 'mw.user defined' );
+});
+
+
+test( 'options', function() {
+ expect(1);
+
+ ok( mw.user.options instanceof mw.Map, 'options instance of mw.Map' );
+});
+
+test( 'User login status', function() {
+ expect(5);
+
+ strictEqual( mw.user.name(), null, 'user.name should return null when anonymous' );
+ ok( mw.user.anonymous(), 'user.anonymous should reutrn true when anonymous' );
+
+ // Not part of startUp module
+ mw.config.set( 'wgUserName', 'John' );
+
+ equal( mw.user.name(), 'John', 'user.name returns username when logged-in' );
+ ok( !mw.user.anonymous(), 'user.anonymous returns false when logged-in' );
+
+ equal( mw.user.id(), 'John', 'user.id Returns username when logged-in' );
+});
diff --git a/tests/qunit/suites/resources/mediawiki/mediawiki.util.js b/tests/qunit/suites/resources/mediawiki/mediawiki.util.js
new file mode 100644
index 00000000..9c05d9b2
--- /dev/null
+++ b/tests/qunit/suites/resources/mediawiki/mediawiki.util.js
@@ -0,0 +1,307 @@
+module( 'mediawiki.util.js' );
+
+test( '-- Initial check', function() {
+ expect(1);
+
+ ok( mw.util, 'mw.util defined' );
+});
+
+test( 'rawurlencode', function() {
+ expect(1);
+
+ equal( mw.util.rawurlencode( 'Test:A & B/Here' ), 'Test%3AA%20%26%20B%2FHere' );
+});
+
+test( 'wikiUrlencode', function() {
+ expect(1);
+
+ equal( mw.util.wikiUrlencode( 'Test:A & B/Here' ), 'Test:A_%26_B/Here' );
+});
+
+test( 'wikiGetlink', function() {
+ expect(3);
+
+ // Not part of startUp module
+ mw.config.set( 'wgArticlePath', '/wiki/$1' );
+ mw.config.set( 'wgPageName', 'Foobar' );
+
+ var hrefA = mw.util.wikiGetlink( 'Sandbox' );
+ equal( hrefA, '/wiki/Sandbox', 'Simple title; Get link for "Sandbox"' );
+
+ var hrefB = mw.util.wikiGetlink( 'Foo:Sandbox ? 5+5=10 ! (test)/subpage' );
+ equal( hrefB, '/wiki/Foo:Sandbox_%3F_5%2B5%3D10_%21_%28test%29/subpage',
+ 'Advanced title; Get link for "Foo:Sandbox ? 5+5=10 ! (test)/subpage"' );
+
+ var hrefC = mw.util.wikiGetlink();
+ equal( hrefC, '/wiki/Foobar', 'Default title; Get link for current page ("Foobar")' );
+});
+
+test( 'wikiScript', function() {
+ expect(2);
+
+ mw.config.set({
+ 'wgScript': '/w/index.php',
+ 'wgScriptPath': '/w',
+ 'wgScriptExtension': '.php'
+ });
+
+ 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' );
+
+});
+
+test( 'addCSS', function() {
+ expect(3);
+
+ var $testEl = $( '<div>' ).attr( 'id', 'mw-addcsstest' ).appendTo( 'body' );
+
+ var style = mw.util.addCSS( '#mw-addcsstest { visibility: hidden; }' );
+ equal( typeof style, 'object', 'addCSS returned an object' );
+ strictEqual( style.disabled, false, 'property "disabled" is available and set to false' );
+
+ equal( $testEl.css( 'visibility' ), 'hidden', 'Added style properties are in effect' );
+
+ // Clean up
+ $( style.ownerNode )
+ .add( $testEl )
+ .remove();
+});
+
+test( 'toggleToc', function() {
+ expect(4);
+
+ strictEqual( mw.util.toggleToc(), null, 'Return null if there is no table of contents on the page.' );
+
+ var tocHtml =
+ '<table id="toc" class="toc"><tr><td>' +
+ '<div id="toctitle">' +
+ '<h2>Contents</h2>' +
+ '<span class="toctoggle">&nbsp;[<a href="#" class="internal" id="togglelink">Hide</a>&nbsp;]</span>' +
+ '</div>' +
+ '<ul><li></li></ul>' +
+ '</td></tr></table>',
+ $toc = $(tocHtml).appendTo( 'body' ),
+ $toggleLink = $( '#togglelink' );
+
+ strictEqual( $toggleLink.length, 1, 'Toggle link is appended to the page.' );
+
+ // Toggle animation is asynchronous
+ // QUnit should not finish this test() untill they are all done
+ stop();
+
+ var actionC = function() {
+ start();
+
+ // Clean up
+ $toc.remove();
+ };
+ var actionB = function() {
+ start(); stop();
+ strictEqual( mw.util.toggleToc( $toggleLink, actionC ), true, 'Return boolean true if the TOC is now visible.' );
+ };
+ var actionA = function() {
+ strictEqual( mw.util.toggleToc( $toggleLink, actionB ), false, 'Return boolean false if the TOC is now hidden.' );
+ };
+
+ actionA();
+});
+
+test( 'getParamValue', function() {
+ expect(5);
+
+ var url1 = 'http://mediawiki.org/?foo=wrong&foo=right#&foo=bad';
+
+ equal( mw.util.getParamValue( 'foo', url1 ), 'right', 'Use latest one, ignore hash' );
+ strictEqual( mw.util.getParamValue( 'bar', url1 ), null, 'Return null when not found' );
+
+ var url2 = 'http://mediawiki.org/#&foo=bad';
+ strictEqual( mw.util.getParamValue( 'foo', url2 ), null, 'Ignore hash if param is not in querystring but in hash (bug 27427)' );
+
+ var url3 = 'example.com?' + $.param({ 'TEST': 'a b+c' });
+ strictEqual( mw.util.getParamValue( 'TEST', url3 ), 'a b+c', 'Bug 30441: getParamValue must understand "+" encoding of space' );
+
+ var url4 = 'example.com?' + $.param({ 'TEST': 'a b+c d' }); // check for sloppy code from r95332 :)
+ strictEqual( mw.util.getParamValue( 'TEST', url4 ), 'a b+c d', 'Bug 30441: getParamValue must understand "+" encoding of space (multiple spaces)' );
+});
+
+test( 'tooltipAccessKey', function() {
+ expect(3);
+
+ equal( typeof mw.util.tooltipAccessKeyPrefix, 'string', 'mw.util.tooltipAccessKeyPrefix must be a string' );
+ ok( mw.util.tooltipAccessKeyRegexp instanceof RegExp, 'mw.util.tooltipAccessKeyRegexp instance of RegExp' );
+ ok( mw.util.updateTooltipAccessKeys, 'mw.util.updateTooltipAccessKeys' );
+});
+
+test( '$content', function() {
+ expect(2);
+
+ ok( mw.util.$content instanceof jQuery, 'mw.util.$content instance of jQuery' );
+ strictEqual( mw.util.$content.length, 1, 'mw.util.$content must have length of 1' );
+});
+
+test( 'addPortletLink', function() {
+ expect(7);
+
+ var mwPanel = '<div id="mw-panel" class="noprint">\
+ <h5>Toolbox</h5>\
+ <div class="portlet" id="p-tb">\
+ <ul class="body"></ul>\
+ </div>\
+</div>',
+ vectorTabs = '<div id="p-views" class="vectorTabs">\
+ <h5>Views</h5>\
+ <ul></ul>\
+</div>',
+ $mwPanel = $(mwPanel).appendTo( 'body' ),
+ $vectorTabs = $(vectorTabs).appendTo( 'body' );
+
+ var tbRL = mw.util.addPortletLink( 'p-tb', 'http://mediawiki.org/wiki/ResourceLoader',
+ 'ResourceLoader', 't-rl', 'More info about ResourceLoader on MediaWiki.org ', 'l' );
+
+ ok( $.isDomElement( tbRL ), 'addPortletLink returns a valid DOM Element according to $.isDomElement' );
+
+ var tbMW = mw.util.addPortletLink( 'p-tb', 'http://mediawiki.org/',
+ 'MediaWiki.org', 't-mworg', 'Go to MediaWiki.org ', 'm', tbRL ),
+ $tbMW = $( tbMW );
+
+
+ equal( $tbMW.attr( 'id' ), 't-mworg', 'Link has correct ID set' );
+ equal( $tbMW.closest( '.portlet' ).attr( 'id' ), 'p-tb', 'Link was inserted within correct portlet' );
+ equal( $tbMW.next().attr( 'id' ), 't-rl', 'Link is in the correct position (by passing nextnode)' );
+
+ var tbRLDM = mw.util.addPortletLink( 'p-tb', 'http://mediawiki.org/wiki/RL/DM',
+ 'Default modules', 't-rldm', 'List of all default modules ', 'd', '#t-rl' );
+
+ equal( $( tbRLDM ).next().attr( 'id' ), 't-rl', 'Link is in the correct position (by passing CSS selector)' );
+
+ var caFoo = mw.util.addPortletLink( 'p-views', '#', 'Foo' );
+
+ strictEqual( $tbMW.find( 'span').length, 0, 'No <span> element should be added for porlets without vectorTabs class.' );
+ strictEqual( $( caFoo ).find( 'span').length, 1, 'A <span> element should be added for porlets with vectorTabs class.' );
+
+ // Clean up
+ $( [tbRL, tbMW, tbRLDM, caFoo] )
+ .add( $mwPanel )
+ .add( $vectorTabs )
+ .remove();
+});
+
+test( 'jsMessage', function() {
+ expect(1);
+
+ var a = mw.util.jsMessage( "MediaWiki is <b>Awesome</b>." );
+ ok( a, 'Basic checking of return value' );
+
+ // Clean up
+ $( '#mw-js-message' ).remove();
+});
+
+test( 'validateEmail', function() {
+ expect(6);
+
+ strictEqual( mw.util.validateEmail( "" ), null, 'Should return null for empty string ' );
+ strictEqual( mw.util.validateEmail( "user@localhost" ), true, 'Return true for a valid e-mail address' );
+
+ // testEmailWithCommasAreInvalids
+ strictEqual( mw.util.validateEmail( "user,foo@example.org" ), false, 'Emails with commas are invalid' );
+ strictEqual( mw.util.validateEmail( "userfoo@ex,ample.org" ), false, 'Emails with commas are invalid' );
+
+ // testEmailWithHyphens
+ strictEqual( mw.util.validateEmail( "user-foo@example.org" ), true, 'Emails may contain a hyphen' );
+ strictEqual( mw.util.validateEmail( "userfoo@ex-ample.org" ), true, 'Emails may contain a hyphen' );
+});
+
+test( 'isIPv6Address', function() {
+ expect(40);
+
+ // Shortcuts
+ var assertFalseIPv6 = function( addy, summary ) {
+ return strictEqual( mw.util.isIPv6Address( addy ), false, summary );
+ },
+ assertTrueIPv6 = function( addy, summary ) {
+ return strictEqual( mw.util.isIPv6Address( addy ), true, summary );
+ };
+
+ // Based on IPTest.php > testisIPv6
+ assertFalseIPv6( ':fc:100::', 'IPv6 starting with lone ":"' );
+ assertFalseIPv6( 'fc:100:::', 'IPv6 ending with a ":::"' );
+ assertFalseIPv6( 'fc:300', 'IPv6 with only 2 words' );
+ assertFalseIPv6( 'fc:100:300', 'IPv6 with only 3 words' );
+
+ $.each(
+ ['fc:100::',
+ 'fc:100:a::',
+ 'fc:100:a:d::',
+ 'fc:100:a:d:1::',
+ 'fc:100:a:d:1:e::',
+ 'fc:100:a:d:1:e:ac::'], function( i, addy ){
+ assertTrueIPv6( addy, addy + ' is a valid IP' );
+ });
+
+ assertFalseIPv6( 'fc:100:a:d:1:e:ac:0::', 'IPv6 with 8 words ending with "::"' );
+ assertFalseIPv6( 'fc:100:a:d:1:e:ac:0:1::', 'IPv6 with 9 words ending with "::"' );
+
+ assertFalseIPv6( ':::' );
+ assertFalseIPv6( '::0:', 'IPv6 ending in a lone ":"' );
+
+ assertTrueIPv6( '::', 'IPv6 zero address' );
+ $.each(
+ ['::0',
+ '::fc',
+ '::fc:100',
+ '::fc:100:a',
+ '::fc:100:a:d',
+ '::fc:100:a:d:1',
+ '::fc:100:a:d:1:e',
+ '::fc:100:a:d:1:e:ac',
+
+ 'fc:100:a:d:1:e:ac:0'], function( i, addy ){
+ assertTrueIPv6( addy, addy + ' is a valid IP' );
+ });
+
+ assertFalseIPv6( '::fc:100:a:d:1:e:ac:0', 'IPv6 with "::" and 8 words' );
+ assertFalseIPv6( '::fc:100:a:d:1:e:ac:0:1', 'IPv6 with 9 words' );
+
+ assertFalseIPv6( ':fc::100', 'IPv6 starting with lone ":"' );
+ assertFalseIPv6( 'fc::100:', 'IPv6 ending with lone ":"' );
+ assertFalseIPv6( 'fc:::100', 'IPv6 with ":::" in the middle' );
+
+ assertTrueIPv6( 'fc::100', 'IPv6 with "::" and 2 words' );
+ assertTrueIPv6( 'fc::100:a', 'IPv6 with "::" and 3 words' );
+ assertTrueIPv6( 'fc::100:a:d', 'IPv6 with "::" and 4 words' );
+ assertTrueIPv6( 'fc::100:a:d:1', 'IPv6 with "::" and 5 words' );
+ assertTrueIPv6( 'fc::100:a:d:1:e', 'IPv6 with "::" and 6 words' );
+ assertTrueIPv6( 'fc::100:a:d:1:e:ac', 'IPv6 with "::" and 7 words' );
+ assertTrueIPv6( '2001::df', 'IPv6 with "::" and 2 words' );
+ assertTrueIPv6( '2001:5c0:1400:a::df', 'IPv6 with "::" and 5 words' );
+ assertTrueIPv6( '2001:5c0:1400:a::df:2', 'IPv6 with "::" and 6 words' );
+
+ assertFalseIPv6( 'fc::100:a:d:1:e:ac:0', 'IPv6 with "::" and 8 words' );
+ assertFalseIPv6( 'fc::100:a:d:1:e:ac:0:1', 'IPv6 with 9 words' );
+});
+
+test( 'isIPv4Address', function() {
+ expect(11);
+
+ // Shortcuts
+ var assertFalseIPv4 = function( addy, summary ) {
+ return strictEqual( mw.util.isIPv4Address( addy ), false, summary );
+ },
+ assertTrueIPv4 = function( addy, summary ) {
+ return strictEqual( mw.util.isIPv4Address( addy ), true, summary );
+ };
+
+ // Based on IPTest.php > testisIPv4
+ assertFalseIPv4( false, 'Boolean false is not an IP' );
+ assertFalseIPv4( true, 'Boolean true is not an IP' );
+ assertFalseIPv4( '', 'Empty string is not an IP' );
+ assertFalseIPv4( 'abc', '"abc" is not an IP' );
+ assertFalseIPv4( ':', 'Colon is not an IP' );
+ assertFalseIPv4( '124.24.52', 'IPv4 not enough quads' );
+ assertFalseIPv4( '24.324.52.13', 'IPv4 out of range' );
+ assertFalseIPv4( '.24.52.13', 'IPv4 starts with period' );
+
+ assertTrueIPv4( '124.24.52.13', '124.24.52.134 is a valid IP' );
+ assertTrueIPv4( '1.24.52.13', '1.24.52.13 is a valid IP' );
+ assertFalseIPv4( '74.24.52.13/20', 'IPv4 ranges are not recogzized as valid IPs' );
+});