QUnit reorganization
authorKrinkle <krinkle@users.mediawiki.org>
Thu, 19 May 2011 21:35:32 +0000 (21:35 +0000)
committerKrinkle <krinkle@users.mediawiki.org>
Thu, 19 May 2011 21:35:32 +0000 (21:35 +0000)
* Moved QUnit directory from /resources/test/ to /tests/qunit/.
* Includes an .htaccess file in /tests/qunit/ to allow reading this from the browser (since /tests/.htaccess disallows this by default, as it should)
* Deleted "/unit/main.css" which wasn't used for anything (was an idea I had but redundant now)
* Renamed /qunit/unit/ to /qunit/suites/
* Re-organized structure within /suites/ to match that of MediaWiki's. (ie. /resources/jquery, /resources/mediawiki.util etc.)
This will make it easier to write a "check if all JS have a test suite" thingy.
* Added a few "Clean up" sections in the test suites to remove added elements (namely added portlet links)

20 files changed:
resources/test/index.html [deleted file]
resources/test/sample/awesome.js [deleted file]
resources/test/testswarm.inject.js [deleted file]
resources/test/unit/jquery/jquery.autoEllipsis.js [deleted file]
resources/test/unit/jquery/jquery.colorUtil.js [deleted file]
resources/test/unit/jquery/jquery.mwPrototypes.js [deleted file]
resources/test/unit/main.css [deleted file]
resources/test/unit/mediawiki.util/mediawiki.util.js [deleted file]
resources/test/unit/mediawiki/mediawiki.js [deleted file]
resources/test/unit/mediawiki/mediawiki.user.js [deleted file]
tests/qunit/.htaccess [new file with mode: 0644]
tests/qunit/index.html [new file with mode: 0644]
tests/qunit/sample/awesome.js [new file with mode: 0644]
tests/qunit/suites/resources/jquery/jquery.autoEllipsis.js [new file with mode: 0644]
tests/qunit/suites/resources/jquery/jquery.colorUtil.js [new file with mode: 0644]
tests/qunit/suites/resources/jquery/jquery.mwPrototypes.js [new file with mode: 0644]
tests/qunit/suites/resources/mediawiki.util/mediawiki.util.js [new file with mode: 0644]
tests/qunit/suites/resources/mediawiki/mediawiki.js [new file with mode: 0644]
tests/qunit/suites/resources/mediawiki/mediawiki.user.js [new file with mode: 0644]
tests/qunit/testswarm.inject.js [new file with mode: 0644]

diff --git a/resources/test/index.html b/resources/test/index.html
deleted file mode 100644 (file)
index 3138182..0000000
+++ /dev/null
@@ -1,71 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-       <title>MediaWiki JavaScript Test Suite</title>
-
-       <!-- MediaWiki Modules -->
-       <script>
-       function startUp(){
-               mw.config = new mw.Map( false );
-       }
-       </script>
-       <script src="../jquery/jquery.js"></script>
-       <script src="../mediawiki/mediawiki.js"></script>
-       <script src="../mediawiki/mediawiki.user.js"></script>
-
-       <script src="../jquery/jquery.autoEllipsis.js"></script>
-       
-       <script>
-       mw.user.options.set({"skin": "vector"});
-       </script>
-
-       <script src="../jquery/jquery.checkboxShiftClick.js"></script>
-       <script src="../jquery/jquery.client.js"></script>
-       <script src="../jquery/jquery.cookie.js"></script>
-       <script src="../jquery/jquery.messageBox.js"></script>
-       <script src="../jquery/jquery.makeCollapsible.js"></script>
-       <script src="../jquery/jquery.mwPrototypes.js"></script>
-       <script src="../jquery/jquery.placeholder.js"></script>
-       <script src="../mediawiki.util/mediawiki.util.js"></script>
-
-       <script src="../jquery/jquery.colorUtil.js"></script>
-
-       <meta name="ResourceLoaderDynamicStyles" content="" /> 
-
-       <!-- QUnit -->
-       <link rel="stylesheet" href="../jquery/jquery.qunit.css" />
-       <script src="../jquery/jquery.qunit.js"></script>
-
-       <!-- Custom CSS used for tests -->
-       <link rel="stylesheet" href="unit/main.css" />
-
-       <!-- Load test suitss -->
-       <script src="unit/mediawiki/mediawiki.js"></script>
-       <script src="unit/mediawiki/mediawiki.user.js"></script>
-       <script src="unit/jquery/jquery.mwPrototypes.js"></script>
-       <script src="unit/mediawiki.util/mediawiki.util.js"></script>
-       <script src="unit/jquery/jquery.colorUtil.js"></script>
-       <script src="unit/jquery/jquery.autoEllipsis.js"></script>
-
-       <!-- TestSwarm -->
-       <script src="testswarm.inject.js"></script>
-</head>
-<body>
-       <h1 id="qunit-header">MediaWiki JavaScript Test Suite</h1>
-       <h2 id="qunit-banner"></h2>
-       <div id="qunit-testrunner-toolbar"></div>
-       <h2 id="qunit-userAgent"></h2>
-       <ol id="qunit-tests"></ol>
-
-<div id="mw-content">
-       <div id="bodyContent"></div>
-       <div id="mw-panel">
-               <div id="p-tb" class="portal">
-                       <ul class="body">
-                               <li id="t-specialpages"><a href="#">Special pages</a></li>
-                       </ul>
-               </div>
-       </div>
-</div>
-</body>
-</html>
diff --git a/resources/test/sample/awesome.js b/resources/test/sample/awesome.js
deleted file mode 100644 (file)
index 61fbbc7..0000000
+++ /dev/null
@@ -1 +0,0 @@
-window.awesome = true;
diff --git a/resources/test/testswarm.inject.js b/resources/test/testswarm.inject.js
deleted file mode 100644 (file)
index 14ee8f9..0000000
+++ /dev/null
@@ -1,349 +0,0 @@
-/*
-       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/resources/test/unit/jquery/jquery.autoEllipsis.js b/resources/test/unit/jquery/jquery.autoEllipsis.js
deleted file mode 100644 (file)
index e0d5b98..0000000
+++ /dev/null
@@ -1,49 +0,0 @@
-module( 'jquery.autoEllipsis.js' );
-
-test( '-- Initial check', function(){
-
-       ok( jQuery.fn.autoEllipsis, 'jQuery.fn.autoEllipsis defined' );
-});
-
-function createWrappedDiv( text ) {
-       var $wrapper = $( '<div />' ).css( 'width', '100px' );
-       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() {
-       // 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 );
-       $( 'body' ).append( $wrapper );
-       $wrapper.autoEllipsis( { position: 'right' } );
-
-       // Verify that, and only one, span element was created
-       var $span = $wrapper.find( '> span' );
-       deepEqual( $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' );
-       ok( $span.width() <= $span.parent().width(), "Text fits (span's width is no larger than its parent's width)" );
-
-       // Add one character using scary black magic
-       var spanText = $span.text();
-       var d = findDivergenceIndex( origText, spanText );
-       spanText = spanText.substr( 0, d ) + origText[d] + '...';
-
-       // Put this text in the span and verify it doesn't fit
-       $span.text( spanText );
-       ok( $span.width() > $span.parent().width(), 'Fit is maximal (adding one character makes it not fit any more)' );
-
-       // Clean up
-       $wrapper.remove();
-});
diff --git a/resources/test/unit/jquery/jquery.colorUtil.js b/resources/test/unit/jquery/jquery.colorUtil.js
deleted file mode 100644 (file)
index 5c8f067..0000000
+++ /dev/null
@@ -1,67 +0,0 @@
-module( 'jquery.colorUtil.js' );
-
-test( '-- Initial check', function(){
-
-       ok( jQuery.colorUtil, 'jQuery.colorUtil defined' );
-});
-
-test( 'getRGB', function(){
-
-       equal( typeof jQuery.colorUtil.getRGB(), 'undefined', 'No arguments' );
-       equal( typeof jQuery.colorUtil.getRGB( '' ), 'undefined', 'Empty string' );
-       deepEqual( jQuery.colorUtil.getRGB( [0, 100, 255] ), [0, 100, 255], 'Array' );
-       deepEqual( jQuery.colorUtil.getRGB( 'rgb(0,100,255)' ), [0, 100, 255], 'Parse simple string' );
-       deepEqual( jQuery.colorUtil.getRGB( 'rgb(0, 100, 255)' ), [0, 100, 255], 'Parse simple string (whitespace)' );
-       deepEqual( jQuery.colorUtil.getRGB( 'rgb(0%,20%,40%)' ), [0, 51, 102], 'Parse percentages string' );
-       deepEqual( jQuery.colorUtil.getRGB( 'rgb(0%, 20%, 40%)' ), [0, 51, 102], 'Parse percentages string (whitespace)' );
-       deepEqual( jQuery.colorUtil.getRGB( '#f2ddee' ), [242, 221, 238], 'Hex string: 6 char lowercase' );
-       deepEqual( jQuery.colorUtil.getRGB( '#f2DDEE' ), [242, 221, 238], 'Hex string: 6 char uppercase' );
-       deepEqual( jQuery.colorUtil.getRGB( '#f2DdEe' ), [242, 221, 238], 'Hex string: 6 char mixed' );
-       deepEqual( jQuery.colorUtil.getRGB( '#eee' ), [238, 238, 238], 'Hex string: 3 char lowercase' );
-       deepEqual( jQuery.colorUtil.getRGB( '#EEE' ), [238, 238, 238], 'Hex string: 3 char uppercase' );
-       deepEqual( jQuery.colorUtil.getRGB( '#eEe' ), [238, 238, 238], 'Hex string: 3 char mixed' );
-       deepEqual( jQuery.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
-       // would that ever change
-       equal( typeof jQuery.colorUtil.getRGB( 'rgba(0,0,0,0)' ), 'undefined', 'Zero rgba without whitespace' );
-       
-       deepEqual( jQuery.colorUtil.getRGB( 'lightGreen' ), [144, 238, 144], 'Color names (lightGreen)' );
-       deepEqual( jQuery.colorUtil.getRGB( 'lightGreen' ), [144, 238, 144], 'Color names (transparent)' );
-       equal( typeof jQuery.colorUtil.getRGB( 'mediaWiki' ), 'undefined', 'Inexisting color name' );
-
-});
-
-test( 'rgbToHsl', function(){
-       var hsl = jQuery.colorUtil.rgbToHsl( 144, 238, 144 );
-       var dualDecimals = function(a,b){
-               return Math.round(a*100)/100;
-       };
-
-       ok( hsl, 'Basic return evaluation' );
-       deepEqual( dualDecimals(hsl[0]) , 0.33, 'rgb(144, 238, 144): H 0.33' );
-       deepEqual( dualDecimals(hsl[1]) , 0.73, 'rgb(144, 238, 144): S 0.73' );
-       deepEqual( dualDecimals(hsl[2]) , 0.75, 'rgb(144, 238, 144): L 0.75' );
-
-});
-
-test( 'hslToRgb', function(){
-       var rgb = jQuery.colorUtil.hslToRgb( 0.3, 0.7, 0.8 );
-
-       ok( rgb, 'Basic return evaluation' );
-       deepEqual( Math.round(rgb[0]) , 183, 'hsl(0.3, 0.7, 0.8): R 183' );
-       deepEqual( Math.round(rgb[1]) , 240, 'hsl(0.3, 0.7, 0.8): G 240' );
-       deepEqual( Math.round(rgb[2]) , 168, 'hsl(0.3, 0.7, 0.8): B 168' );
-
-});
-
-test( 'getColorBrightness', function(){
-
-       var a = jQuery.colorUtil.getColorBrightness( 'red', +0.1 );
-
-       equal( a, 'rgb(255,50,50)', 'Start with named color, brighten 10%' );
-       
-       var b = jQuery.colorUtil.getColorBrightness( 'rgb(200,50,50)', -0.2 );
-       
-       equal( b, 'rgb(118,29,29)', 'Start with rgb string, darken 10%' );
-
-});
diff --git a/resources/test/unit/jquery/jquery.mwPrototypes.js b/resources/test/unit/jquery/jquery.mwPrototypes.js
deleted file mode 100644 (file)
index b53e062..0000000
+++ /dev/null
@@ -1,58 +0,0 @@
-module( 'jquery.mwPrototypes.js' );
-
-test( 'String functions', function(){
-
-       equal( $j.trimLeft( '  foo bar  ' ), 'foo bar  ', 'trimLeft' );
-       equal( $j.trimRight( '  foo bar  ' ), '  foo bar', 'trimRight' );
-       equal( $j.ucFirst( 'foo'), 'Foo', 'ucFirst' );
-
-       equal( $j.escapeRE( '<!-- ([{+mW+}]) $^|?>' ),
-        '<!\\-\\- \\(\\[\\{\\+mW\\+\\}\\]\\) \\$\\^\\|\\?>', 'escapeRE - Escape specials' );
-       equal( $j.escapeRE( 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' ),
-        'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'escapeRE - Leave uppercase alone' );
-       equal( $j.escapeRE( 'abcdefghijklmnopqrstuvwxyz' ),
-        'abcdefghijklmnopqrstuvwxyz', 'escapeRE - Leave lowercase alone' );
-       equal( $j.escapeRE( '0123456789' ), '0123456789', 'escapeRE - Leave numbers alone' );
-
-});
-
-test( 'Is functions', function(){
-
-       deepEqual( $j.isDomElement( document.getElementById( 'qunit-header' ) ), true,
-        'isDomElement: #qunit-header Node' );
-       deepEqual( $j.isDomElement( document.getElementById( 'random-name' ) ), false,
-        'isDomElement: #random-name (null)' );
-       deepEqual( $j.isDomElement( document.getElementsByTagName( 'div' ) ), false,
-        'isDomElement: getElementsByTagName Array' );
-       deepEqual( $j.isDomElement( document.getElementsByTagName( 'div' )[0] ), true,
-        'isDomElement: getElementsByTagName(..)[0] Node' );
-       deepEqual( $j.isDomElement( $j( 'div' ) ), false,
-        'isDomElement: jQuery object' );
-       deepEqual( $j.isDomElement( $j( 'div' ).get(0) ), true,
-        'isDomElement: jQuery object > Get node' );
-       deepEqual( $j.isDomElement( document.createElement( 'div' ) ), true,
-        'isDomElement: createElement' );
-       deepEqual( $j.isDomElement( { foo: 1 } ), false,
-        'isDomElement: Object' );
-
-       equal( $j.isEmpty( 'string' ), false, 'isEmptry: "string"' );
-       equal( $j.isEmpty( '0' ), true, 'isEmptry: "0"' );
-       equal( $j.isEmpty( [] ), true, 'isEmptry: []' );
-       equal( $j.isEmpty( {} ), true, 'isEmptry: {}' );
-       // Documented behaviour
-       equal( $j.isEmpty( { length: 0 } ), true, 'isEmptry: { length: 0 }' );
-
-});
-
-test( 'Comparison functions', function(){
-
-       ok( $j.compareArray( [0, 'a', [], [2, 'b'] ], [0, "a", [], [2, "b"] ] ),
-        'compareArray: Two deep arrays that are excactly the same' );
-       ok( !$j.compareArray( [1], [2] ), 'compareArray: Two different arrays (false)' );
-
-       ok( $j.compareObject( {}, {} ), 'compareObject: Two empty objects' );
-       ok( $j.compareObject( { foo: 1 }, { foo: 1 } ), 'compareObject: Two the same objects' );
-       ok( !$j.compareObject( { bar: true }, { baz: false } ),
-        'compareObject: Two different objects (false)' );
-       
-});
\ No newline at end of file
diff --git a/resources/test/unit/main.css b/resources/test/unit/main.css
deleted file mode 100644 (file)
index b4b24e1..0000000
+++ /dev/null
@@ -1,5 +0,0 @@
-/* Hide mediawiki elements */
-div#mw-js-message,
-#mw-content {
-       display: none !important;
-}
\ No newline at end of file
diff --git a/resources/test/unit/mediawiki.util/mediawiki.util.js b/resources/test/unit/mediawiki.util/mediawiki.util.js
deleted file mode 100644 (file)
index 2df7d03..0000000
+++ /dev/null
@@ -1,202 +0,0 @@
-module( 'mediawiki.util.js' );
-
-test( '-- Initial check', function(){
-
-       ok( mw.util, 'mw.util defined' );
-
-});
-
-test( 'rawurlencode', function(){
-
-       equal( mw.util.rawurlencode( 'Test:A & B/Here' ), 'Test%3AA%20%26%20B%2FHere' );
-
-});
-
-test( 'wikiUrlencode', function(){
-
-       equal( mw.util.wikiUrlencode( 'Test:A & B/Here' ), 'Test:A_%26_B/Here' );
-
-});
-
-test( 'addCSS', function(){
-
-       var a = mw.util.addCSS( '#bodyContent { visibility: hidden; }' );
-       ok(  a, 'function works' );
-       deepEqual( a.disabled, false, 'property "disabled" is available and set to false' );
-       
-       var $b = $('#bodyContent');
-       equal( $b.css('visibility'), 'hidden', 'Added style properties are in effect.' );
-       
-
-});
-
-test( 'toggleToc', function(){
-
-       ok( mw.util.toggleToc );
-
-});
-
-test( 'wikiGetlink', function(){
-
-       // Not part of startUp module
-       mw.config.set( 'wgArticlePath', '/wiki/$1' );
-
-       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"' );
-
-});
-
-test( 'getParamValue', function(){
-
-       var url = 'http://mediawiki.org/?foo=wrong&foo=right#&foo=bad';
-
-       equal( mw.util.getParamValue( 'foo', url ), 'right', 'Use latest one, ignore hash' );
-       deepEqual( mw.util.getParamValue( 'bar', url ), null, 'Return null when not found' );
-
-});
-
-test( 'getActionFrom', function(){
-
-       // Example urls
-       var     urlA = 'http://mediawiki.org/wiki/Article',
-               urlB = 'http://mediawiki.org/w/index.php?title=Article&action=edit',
-               urlC = 'http://mediawiki.org/edit/Article',
-               urlD = 'http://mediawiki.org/w/index.php/Article';
-
-       // Common settings
-       mw.config.set( {
-               'wgActionPaths': [],
-               'wgArticlePath': '/wiki/$1'
-       });
-
-       equal( mw.util.getActionFrom( urlA ), 'view', 'wgArticlePath (/wiki/$1) support' );
-       equal( mw.util.getActionFrom( urlB ), 'edit', 'action-parameter support' );
-
-       // Custom settings
-       mw.config.set( 'wgActionPaths', {
-               'view': '/view/$1',
-               'edit': '/edit/$1'
-       });
-
-       equal( mw.util.getActionFrom( urlC ), 'edit', 'wgActionPaths support' );
-
-       // Default settings
-       mw.config.set( {
-               'wgActionPaths': [],
-               'wgArticlePath': '/w/index.php/$1' 
-       });
-       equal( mw.util.getActionFrom( urlD ), 'view', 'wgArticlePath (/index.php/$1) support' );
-
-});
-
-test( 'getTitleFrom', function(){
-
-       // Example urls
-       var     urlA = 'http://mediawiki.org/wiki/Article',
-               urlB = 'http://mediawiki.org/w/index.php?title=Article&action=edit',
-               urlC = 'http://mediawiki.org/edit/Article',
-               urlD = 'http://mediawiki.org/w/index.php/Article';
-
-       // Common settings
-       mw.config.set( {
-               'wgActionPaths': [],
-               'wgArticlePath': '/wiki/$1'
-       });
-
-       equal( mw.util.getTitleFrom( urlA ), 'Article', 'wgArticlePath (/wiki/$1) support' );
-       equal( mw.util.getTitleFrom( urlB ), 'Article', 'action-parameter support' );
-
-       // Custom settings
-       mw.config.set( 'wgActionPaths', {
-               'view': '/view/$1',
-               'edit': '/edit/$1'
-       });
-
-       equal( mw.util.getTitleFrom( urlC ), 'Article', 'wgActionPaths support' );
-
-       // Default settings
-       mw.config.set( {
-               'wgActionPaths': [],
-               'wgArticlePath': '/w/index.php/$1' 
-       });
-
-       equal( mw.util.getTitleFrom( urlD ), 'Article', 'wgArticlePath (/index.php/$1) support' );
-
-});
-
-test( 'tooltipAccessKey', function(){
-
-       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(){
-
-       ok( mw.util.$content instanceof jQuery, 'mw.util.$content instance of jQuery' );
-       deepEqual( mw.util.$content.length, 1, 'mw.util.$content must have length of 1' );
-
-});
-
-test( 'addPortletLink', function(){
-
-       var a = mw.util.addPortletLink( 'p-tb', 'http://mediawiki.org/wiki/ResourceLoader', 'ResourceLoader', 't-rl', 'More info about ResourceLoader on MediaWiki.org ', 'l', '#t-specialpages' );
-       
-       ok( $.isDomElement(a), 'addPortletLink returns a DomElement' );
-       
-       var b = mw.util.addPortletLink( "p-tb", "http://mediawiki.org/", "MediaWiki.org", "t-mworg", "Go to MediaWiki.org ", "m", "#t-rl" );
-       
-       equal( $(a).text(), 'ResourceLoader', 'Link contains correct text' );
-       equal( $(b).next().text(), 'ResourceLoader', 'Link was inserted in correct nextnode position' );
-
-});
-
-test( 'jsMessage', function(){
-
-       var a = mw.util.jsMessage( "MediaWiki is <b>Awesome</b>." );
-
-       ok( a, 'Basic return value checking' );
-
-});
-
-test( 'validateEmail', function(){
-
-       deepEqual( mw.util.validateEmail( "" ), null, 'Empty string should return null' );
-       deepEqual( mw.util.validateEmail( "user@localhost" ), true );
-
-       // testEmailWithCommasAreInvalids
-       deepEqual( mw.util.validateEmail( "user,foo@example.org" ), false, 'Comma' );
-       deepEqual( mw.util.validateEmail( "userfoo@ex,ample.org" ), false, 'Comma' );
-
-       // testEmailWithHyphens
-       deepEqual( mw.util.validateEmail( "user-foo@example.org" ), true, 'Hyphen' );
-       deepEqual( mw.util.validateEmail( "userfoo@ex-ample.org" ), true, 'Hyphen' );
-
-});
-
-test( 'isIPv6Address', function(){
-
-       // Based on IPTest.php > IPv6
-       deepEqual( mw.util.isIPv6Address( "" ), false, 'Empty string is not an IP' );
-       deepEqual( mw.util.isIPv6Address( ":fc:100::" ), false, 'IPv6 starting with lone ":"' );
-       deepEqual( mw.util.isIPv6Address( "fc:100::" ), true );
-       deepEqual( mw.util.isIPv6Address( "fc:100:a:d:1:e:ac::" ), true );
-       deepEqual( mw.util.isIPv6Address( ":::" ), false );
-       deepEqual( mw.util.isIPv6Address( "::0:" ), false );
-
-});
-
-test( 'isIPv4Address', function(){
-
-       // Based on IPTest.php > IPv4
-       deepEqual( mw.util.isIPv4Address( "" ), false, 'Empty string is not an IP' );
-       deepEqual( mw.util.isIPv4Address( "...." ), false );
-       deepEqual( mw.util.isIPv4Address( "1.24.52.13" ), true );
-
-});
diff --git a/resources/test/unit/mediawiki/mediawiki.js b/resources/test/unit/mediawiki/mediawiki.js
deleted file mode 100644 (file)
index bcd8a5a..0000000
+++ /dev/null
@@ -1,107 +0,0 @@
-module( 'mediawiki.js' );
-
-test( '-- Initial check', function(){
-
-       ok( window.jQuery, 'jQuery defined' );
-       ok( window.$j, '$j defined' );
-       equal( window.$j, window.jQuery, '$j alias to jQuery' );
-
-       ok( window.mediaWiki, 'mediaWiki defined' );
-       ok( window.mw, 'mw defined' );
-       equal( window.mw, window.mediaWiki, 'mw alias to mediaWiki' );
-
-});
-
-test( 'mw.Map / mw.config', function(){
-
-       ok( mw.config instanceof mw.Map, 'mw.config instance of mw.Map' );
-       ok( mw.config.get, 'get' );
-       ok( mw.config.set, 'set' );
-       ok( mw.config.exists, 'exists' );
-
-       ok( !mw.config.exists( 'lipsum' ), 'exists: lipsum (inexistant)' );
-       ok( mw.config.set( 'lipsum', 'Lorem ipsum' ), 'set: lipsum' );
-       ok( mw.config.exists( 'lipsum' ), 'exists: lipsum (existant)' );
-
-       equal( mw.config.get( 'lipsum' ), 'Lorem ipsum', 'get: lipsum' );
-       equal( mw.config.get( ['lipsum'] ).lipsum, 'Lorem ipsum', 'get: lipsum (multiple)' );
-
-});
-
-test( 'mw.message / mw.msg / mw.messages', function(){
-       ok( mw.message, 'mw.message defined' );
-       ok( mw.msg, 'mw.msg defined' );
-       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' );
-       ok( hello, 'hello: Instance of Message' );
-
-       equal( hello.format, 'parse', 'Message property "format" (default value)' );
-       equal( hello.key, 'hello', 'Message property "key" (currect key)' );
-       deepEqual( hello.parameters, [], 'Message property "parameters" (default value)' );
-
-
-       ok( hello.params, 'Message prototype "params"');
-       ok( hello.toString, 'Message prototype "toString"');
-       ok( hello.parse, 'Message prototype "parse"');
-       ok( hello.plain, 'Message prototype "plain"');
-       ok( hello.escaped, 'Message prototype "escaped"');
-       ok( hello.exists, 'Message prototype "exists"');
-
-       equal( hello.toString(), 'Hello <b>awesome</b> world', 'Message.toString() test');
-       equal( hello.escaped(), 'Hello &lt;b&gt;awesome&lt;/b&gt; world', 'Message.escaped() test');
-       deepEqual( hello.exists(), true, 'Message.exists() test');
-
-       equal( mw.msg( 'random' ), '<random>', 'square brackets around inexistant messages' );
-       equal( mw.msg( 'hello' ), 'Hello <b>awesome</b> world', 'get message with default options' );
-       
-// params, toString, parse, plain, escaped, exists
-});
-
-test( 'mw.loader', function(){
-       expect(2);
-       
-       ok( location.href.match(/[^#\?]*/) && location.href.match(/[^#\?]*/)[0], true, 'Extracting file path from location' );
-
-       stop();
-       
-       mw.loader.implement( 'is.awesome', [location.href.match(/[^#\?]*/)[0] + 'sample/awesome.js'], {}, {} );
-       mw.loader.using( 'is.awesome', function(){
-               start();
-               deepEqual( window.awesome, true, 'Implementing a module, is the callback timed properly ?');
-
-               // Clean up
-               delete window.awesome;
-
-       }, function(){
-               start();
-               deepEqual( 'mw.loader.using error callback fired', true, 'Implementing a module, is the callback timed properly ?');
-       });
-
-});
-
-test( 'mw.html', function(){
-
-       equal( mw.html.escape( '<mw awesome="awesome" value=\'test\' />' ),
-        '&lt;mw awesome=&quot;awesome&quot; value=&#039;test&#039; /&gt;', 'html.escape()' );
-
-       equal( mw.html.element( 'div' ), '<div/>', 'mw.html.element() DIV (simple)' );
-
-       equal( mw.html.element( 'div',
-        { id: 'foobar' } ),
-        '<div id="foobar"/>',
-        'mw.html.element() DIV (attribs)' );
-
-       equal( mw.html.element( 'div',
-        null, 'a' ),
-        '<div>a</div>',
-        'mw.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>',
-         'mw.html.element() DIV (attribs + content)' );
-
-});
diff --git a/resources/test/unit/mediawiki/mediawiki.user.js b/resources/test/unit/mediawiki/mediawiki.user.js
deleted file mode 100644 (file)
index 8ef44e2..0000000
+++ /dev/null
@@ -1,30 +0,0 @@
-module( 'mediawiki.user.js' );
-
-test( '-- Initial check', function(){
-
-       ok( mw.user, 'mw.user defined' );
-
-});
-
-
-test( 'options', function(){
-
-       ok( mw.user.options instanceof mw.Map, 'options instance of mw.Map' );
-
-});
-
-test( 'User login status', function(){
-
-       deepEqual( mw.user.name(), null, 'user.name() When anonymous' );
-       ok( mw.user.anonymous(), 'user.anonymous() When anonymous' );
-
-       // Not part of startUp module
-       mw.config.set( 'wgUserName', 'John' );
-
-       equal( mw.user.name(), 'John', 'user.name() When logged-in as John' );
-       ok( !mw.user.anonymous(), 'user.anonymous() When logged-in' );
-
-       equal( mw.user.id(), 'John', 'user.id() When logged-in as John' );
-
-
-});
\ No newline at end of file
diff --git a/tests/qunit/.htaccess b/tests/qunit/.htaccess
new file mode 100644 (file)
index 0000000..605d2f4
--- /dev/null
@@ -0,0 +1 @@
+Allow from all
diff --git a/tests/qunit/index.html b/tests/qunit/index.html
new file mode 100644 (file)
index 0000000..c610e57
--- /dev/null
@@ -0,0 +1,75 @@
+<!DOCTYPE html>
+<html>
+<head>
+       <title>MediaWiki JavaScript Test Suite</title>
+
+       <!-- 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.user|mediawiki.util -->
+       <script src="../../resources/mediawiki/mediawiki.user.js"></script>
+               <script src="../../resources/jquery/jquery.checkboxShiftClick.js"></script>
+               <script src="../../resources/jquery/jquery.client.js"></script>
+               <script src="../../resources/jquery/jquery.cookie.js"></script>
+               <script src="../../resources/jquery/jquery.messageBox.js"></script>
+               <script src="../../resources/jquery/jquery.makeCollapsible.js"></script>
+               <script src="../../resources/jquery/jquery.mwPrototypes.js"></script>
+               <script src="../../resources/jquery/jquery.placeholder.js"></script>
+       <script src="../../resources/mediawiki.util/mediawiki.util.js"></script>
+
+       <!-- MW: user.options -->
+       <script>
+       mw.user.options.set({"skin": "vector"});
+       </script>
+
+       <!-- MW: Non-default modules -->
+       <script src="../../resources/jquery/jquery.colorUtil.js"></script>
+       <script src="../../resources/jquery/jquery.autoEllipsis.js"></script>
+
+       <!-- QUnit: Load framework -->
+       <link rel="stylesheet" href="../../resources/jquery/jquery.qunit.css" />
+       <script src="../../resources/jquery/jquery.qunit.js"></script>
+
+       <!-- QUnit: Load test suites (maintain the same order as above please) -->
+       <script src="suites/resources/mediawiki/mediawiki.js"></script>
+       <script src="suites/resources/mediawiki/mediawiki.user.js"></script>
+       <script src="suites/resources/jquery/jquery.mwPrototypes.js"></script>
+       <script src="suites/resources/mediawiki.util/mediawiki.util.js"></script>
+       <script src="suites/resources/jquery/jquery.colorUtil.js"></script>
+       <script src="suites/resources/jquery/jquery.autoEllipsis.js"></script>
+
+       <!-- TestSwarm: If a test swarm is running this,
+            the following script will allow it to extract the results.
+            Harmless otherwise. -->
+       <script src="testswarm.inject.js"></script>
+</head>
+<body>
+       <h1 id="qunit-header">MediaWiki JavaScript Test Suite</h1>
+       <h2 id="qunit-banner"></h2>
+       <div id="qunit-testrunner-toolbar"></div>
+       <h2 id="qunit-userAgent"></h2>
+       <ol id="qunit-tests"></ol>
+
+<!-- Rough page structure for scripts needing it (Vector based). -->
+<!-- Scripts inserting stuff here shall remove it themselfs! -->
+<div id="mw-content">
+       <div id="bodyContent"></div>
+       <div id="mw-panel">
+               <div id="p-tb" class="portal">
+                       <ul class="body">
+                       </ul>
+               </div>
+       </div>
+</div>
+</body>
+</html>
diff --git a/tests/qunit/sample/awesome.js b/tests/qunit/sample/awesome.js
new file mode 100644 (file)
index 0000000..61fbbc7
--- /dev/null
@@ -0,0 +1 @@
+window.awesome = true;
diff --git a/tests/qunit/suites/resources/jquery/jquery.autoEllipsis.js b/tests/qunit/suites/resources/jquery/jquery.autoEllipsis.js
new file mode 100644 (file)
index 0000000..e0d5b98
--- /dev/null
@@ -0,0 +1,49 @@
+module( 'jquery.autoEllipsis.js' );
+
+test( '-- Initial check', function(){
+
+       ok( jQuery.fn.autoEllipsis, 'jQuery.fn.autoEllipsis defined' );
+});
+
+function createWrappedDiv( text ) {
+       var $wrapper = $( '<div />' ).css( 'width', '100px' );
+       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() {
+       // 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 );
+       $( 'body' ).append( $wrapper );
+       $wrapper.autoEllipsis( { position: 'right' } );
+
+       // Verify that, and only one, span element was created
+       var $span = $wrapper.find( '> span' );
+       deepEqual( $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' );
+       ok( $span.width() <= $span.parent().width(), "Text fits (span's width is no larger than its parent's width)" );
+
+       // Add one character using scary black magic
+       var spanText = $span.text();
+       var d = findDivergenceIndex( origText, spanText );
+       spanText = spanText.substr( 0, d ) + origText[d] + '...';
+
+       // Put this text in the span and verify it doesn't fit
+       $span.text( spanText );
+       ok( $span.width() > $span.parent().width(), 'Fit is maximal (adding one character makes it not fit any more)' );
+
+       // Clean up
+       $wrapper.remove();
+});
diff --git a/tests/qunit/suites/resources/jquery/jquery.colorUtil.js b/tests/qunit/suites/resources/jquery/jquery.colorUtil.js
new file mode 100644 (file)
index 0000000..5c8f067
--- /dev/null
@@ -0,0 +1,67 @@
+module( 'jquery.colorUtil.js' );
+
+test( '-- Initial check', function(){
+
+       ok( jQuery.colorUtil, 'jQuery.colorUtil defined' );
+});
+
+test( 'getRGB', function(){
+
+       equal( typeof jQuery.colorUtil.getRGB(), 'undefined', 'No arguments' );
+       equal( typeof jQuery.colorUtil.getRGB( '' ), 'undefined', 'Empty string' );
+       deepEqual( jQuery.colorUtil.getRGB( [0, 100, 255] ), [0, 100, 255], 'Array' );
+       deepEqual( jQuery.colorUtil.getRGB( 'rgb(0,100,255)' ), [0, 100, 255], 'Parse simple string' );
+       deepEqual( jQuery.colorUtil.getRGB( 'rgb(0, 100, 255)' ), [0, 100, 255], 'Parse simple string (whitespace)' );
+       deepEqual( jQuery.colorUtil.getRGB( 'rgb(0%,20%,40%)' ), [0, 51, 102], 'Parse percentages string' );
+       deepEqual( jQuery.colorUtil.getRGB( 'rgb(0%, 20%, 40%)' ), [0, 51, 102], 'Parse percentages string (whitespace)' );
+       deepEqual( jQuery.colorUtil.getRGB( '#f2ddee' ), [242, 221, 238], 'Hex string: 6 char lowercase' );
+       deepEqual( jQuery.colorUtil.getRGB( '#f2DDEE' ), [242, 221, 238], 'Hex string: 6 char uppercase' );
+       deepEqual( jQuery.colorUtil.getRGB( '#f2DdEe' ), [242, 221, 238], 'Hex string: 6 char mixed' );
+       deepEqual( jQuery.colorUtil.getRGB( '#eee' ), [238, 238, 238], 'Hex string: 3 char lowercase' );
+       deepEqual( jQuery.colorUtil.getRGB( '#EEE' ), [238, 238, 238], 'Hex string: 3 char uppercase' );
+       deepEqual( jQuery.colorUtil.getRGB( '#eEe' ), [238, 238, 238], 'Hex string: 3 char mixed' );
+       deepEqual( jQuery.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
+       // would that ever change
+       equal( typeof jQuery.colorUtil.getRGB( 'rgba(0,0,0,0)' ), 'undefined', 'Zero rgba without whitespace' );
+       
+       deepEqual( jQuery.colorUtil.getRGB( 'lightGreen' ), [144, 238, 144], 'Color names (lightGreen)' );
+       deepEqual( jQuery.colorUtil.getRGB( 'lightGreen' ), [144, 238, 144], 'Color names (transparent)' );
+       equal( typeof jQuery.colorUtil.getRGB( 'mediaWiki' ), 'undefined', 'Inexisting color name' );
+
+});
+
+test( 'rgbToHsl', function(){
+       var hsl = jQuery.colorUtil.rgbToHsl( 144, 238, 144 );
+       var dualDecimals = function(a,b){
+               return Math.round(a*100)/100;
+       };
+
+       ok( hsl, 'Basic return evaluation' );
+       deepEqual( dualDecimals(hsl[0]) , 0.33, 'rgb(144, 238, 144): H 0.33' );
+       deepEqual( dualDecimals(hsl[1]) , 0.73, 'rgb(144, 238, 144): S 0.73' );
+       deepEqual( dualDecimals(hsl[2]) , 0.75, 'rgb(144, 238, 144): L 0.75' );
+
+});
+
+test( 'hslToRgb', function(){
+       var rgb = jQuery.colorUtil.hslToRgb( 0.3, 0.7, 0.8 );
+
+       ok( rgb, 'Basic return evaluation' );
+       deepEqual( Math.round(rgb[0]) , 183, 'hsl(0.3, 0.7, 0.8): R 183' );
+       deepEqual( Math.round(rgb[1]) , 240, 'hsl(0.3, 0.7, 0.8): G 240' );
+       deepEqual( Math.round(rgb[2]) , 168, 'hsl(0.3, 0.7, 0.8): B 168' );
+
+});
+
+test( 'getColorBrightness', function(){
+
+       var a = jQuery.colorUtil.getColorBrightness( 'red', +0.1 );
+
+       equal( a, 'rgb(255,50,50)', 'Start with named color, brighten 10%' );
+       
+       var b = jQuery.colorUtil.getColorBrightness( 'rgb(200,50,50)', -0.2 );
+       
+       equal( b, 'rgb(118,29,29)', 'Start with rgb string, darken 10%' );
+
+});
diff --git a/tests/qunit/suites/resources/jquery/jquery.mwPrototypes.js b/tests/qunit/suites/resources/jquery/jquery.mwPrototypes.js
new file mode 100644 (file)
index 0000000..b53e062
--- /dev/null
@@ -0,0 +1,58 @@
+module( 'jquery.mwPrototypes.js' );
+
+test( 'String functions', function(){
+
+       equal( $j.trimLeft( '  foo bar  ' ), 'foo bar  ', 'trimLeft' );
+       equal( $j.trimRight( '  foo bar  ' ), '  foo bar', 'trimRight' );
+       equal( $j.ucFirst( 'foo'), 'Foo', 'ucFirst' );
+
+       equal( $j.escapeRE( '<!-- ([{+mW+}]) $^|?>' ),
+        '<!\\-\\- \\(\\[\\{\\+mW\\+\\}\\]\\) \\$\\^\\|\\?>', 'escapeRE - Escape specials' );
+       equal( $j.escapeRE( 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' ),
+        'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'escapeRE - Leave uppercase alone' );
+       equal( $j.escapeRE( 'abcdefghijklmnopqrstuvwxyz' ),
+        'abcdefghijklmnopqrstuvwxyz', 'escapeRE - Leave lowercase alone' );
+       equal( $j.escapeRE( '0123456789' ), '0123456789', 'escapeRE - Leave numbers alone' );
+
+});
+
+test( 'Is functions', function(){
+
+       deepEqual( $j.isDomElement( document.getElementById( 'qunit-header' ) ), true,
+        'isDomElement: #qunit-header Node' );
+       deepEqual( $j.isDomElement( document.getElementById( 'random-name' ) ), false,
+        'isDomElement: #random-name (null)' );
+       deepEqual( $j.isDomElement( document.getElementsByTagName( 'div' ) ), false,
+        'isDomElement: getElementsByTagName Array' );
+       deepEqual( $j.isDomElement( document.getElementsByTagName( 'div' )[0] ), true,
+        'isDomElement: getElementsByTagName(..)[0] Node' );
+       deepEqual( $j.isDomElement( $j( 'div' ) ), false,
+        'isDomElement: jQuery object' );
+       deepEqual( $j.isDomElement( $j( 'div' ).get(0) ), true,
+        'isDomElement: jQuery object > Get node' );
+       deepEqual( $j.isDomElement( document.createElement( 'div' ) ), true,
+        'isDomElement: createElement' );
+       deepEqual( $j.isDomElement( { foo: 1 } ), false,
+        'isDomElement: Object' );
+
+       equal( $j.isEmpty( 'string' ), false, 'isEmptry: "string"' );
+       equal( $j.isEmpty( '0' ), true, 'isEmptry: "0"' );
+       equal( $j.isEmpty( [] ), true, 'isEmptry: []' );
+       equal( $j.isEmpty( {} ), true, 'isEmptry: {}' );
+       // Documented behaviour
+       equal( $j.isEmpty( { length: 0 } ), true, 'isEmptry: { length: 0 }' );
+
+});
+
+test( 'Comparison functions', function(){
+
+       ok( $j.compareArray( [0, 'a', [], [2, 'b'] ], [0, "a", [], [2, "b"] ] ),
+        'compareArray: Two deep arrays that are excactly the same' );
+       ok( !$j.compareArray( [1], [2] ), 'compareArray: Two different arrays (false)' );
+
+       ok( $j.compareObject( {}, {} ), 'compareObject: Two empty objects' );
+       ok( $j.compareObject( { foo: 1 }, { foo: 1 } ), 'compareObject: Two the same objects' );
+       ok( !$j.compareObject( { bar: true }, { baz: false } ),
+        'compareObject: Two different objects (false)' );
+       
+});
\ No newline at end of file
diff --git a/tests/qunit/suites/resources/mediawiki.util/mediawiki.util.js b/tests/qunit/suites/resources/mediawiki.util/mediawiki.util.js
new file mode 100644 (file)
index 0000000..8addd03
--- /dev/null
@@ -0,0 +1,216 @@
+module( 'mediawiki.util.js' );
+
+test( '-- Initial check', function(){
+
+       ok( mw.util, 'mw.util defined' );
+
+});
+
+test( 'rawurlencode', function(){
+
+       equal( mw.util.rawurlencode( 'Test:A & B/Here' ), 'Test%3AA%20%26%20B%2FHere' );
+
+});
+
+test( 'wikiUrlencode', function(){
+
+       equal( mw.util.wikiUrlencode( 'Test:A & B/Here' ), 'Test:A_%26_B/Here' );
+
+});
+
+test( 'addCSS', function(){
+
+       var a = mw.util.addCSS( '#bodyContent { visibility: hidden; }' );
+       ok(  a, 'function works' );
+       deepEqual( a.disabled, false, 'property "disabled" is available and set to false' );
+
+       var $b = $('#bodyContent');
+       equal( $b.css('visibility'), 'hidden', 'Added style properties are in effect.' );
+
+
+});
+
+test( 'toggleToc', function(){
+
+       ok( mw.util.toggleToc );
+
+});
+
+test( 'wikiGetlink', function(){
+
+       // Not part of startUp module
+       mw.config.set( 'wgArticlePath', '/wiki/$1' );
+
+       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"' );
+
+});
+
+test( 'getParamValue', function(){
+
+       var url = 'http://mediawiki.org/?foo=wrong&foo=right#&foo=bad';
+
+       equal( mw.util.getParamValue( 'foo', url ), 'right', 'Use latest one, ignore hash' );
+       deepEqual( mw.util.getParamValue( 'bar', url ), null, 'Return null when not found' );
+
+});
+
+test( 'getActionFrom', function(){
+
+       // Example urls
+       var     urlA = 'http://mediawiki.org/wiki/Article',
+               urlB = 'http://mediawiki.org/w/index.php?title=Article&action=edit',
+               urlC = 'http://mediawiki.org/edit/Article',
+               urlD = 'http://mediawiki.org/w/index.php/Article';
+
+       // Common settings
+       mw.config.set( {
+               'wgActionPaths': [],
+               'wgArticlePath': '/wiki/$1'
+       });
+
+       equal( mw.util.getActionFrom( urlA ), 'view', 'wgArticlePath (/wiki/$1) support' );
+       equal( mw.util.getActionFrom( urlB ), 'edit', 'action-parameter support' );
+
+       // Custom settings
+       mw.config.set( 'wgActionPaths', {
+               'view': '/view/$1',
+               'edit': '/edit/$1'
+       });
+
+       equal( mw.util.getActionFrom( urlC ), 'edit', 'wgActionPaths support' );
+
+       // Default settings
+       mw.config.set( {
+               'wgActionPaths': [],
+               'wgArticlePath': '/w/index.php/$1'
+       });
+       equal( mw.util.getActionFrom( urlD ), 'view', 'wgArticlePath (/index.php/$1) support' );
+
+});
+
+test( 'getTitleFrom', function(){
+
+       // Example urls
+       var     urlA = 'http://mediawiki.org/wiki/Article',
+               urlB = 'http://mediawiki.org/w/index.php?title=Article&action=edit',
+               urlC = 'http://mediawiki.org/edit/Article',
+               urlD = 'http://mediawiki.org/w/index.php/Article';
+
+       // Common settings
+       mw.config.set( {
+               'wgActionPaths': [],
+               'wgArticlePath': '/wiki/$1'
+       });
+
+       equal( mw.util.getTitleFrom( urlA ), 'Article', 'wgArticlePath (/wiki/$1) support' );
+       equal( mw.util.getTitleFrom( urlB ), 'Article', 'action-parameter support' );
+
+       // Custom settings
+       mw.config.set( 'wgActionPaths', {
+               'view': '/view/$1',
+               'edit': '/edit/$1'
+       });
+
+       equal( mw.util.getTitleFrom( urlC ), 'Article', 'wgActionPaths support' );
+
+       // Default settings
+       mw.config.set( {
+               'wgActionPaths': [],
+               'wgArticlePath': '/w/index.php/$1'
+       });
+
+       equal( mw.util.getTitleFrom( urlD ), 'Article', 'wgArticlePath (/index.php/$1) support' );
+
+});
+
+test( 'tooltipAccessKey', function(){
+
+       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(){
+
+       ok( mw.util.$content instanceof jQuery, 'mw.util.$content instance of jQuery' );
+       deepEqual( mw.util.$content.length, 1, 'mw.util.$content must have length of 1' );
+
+});
+
+test( 'addPortletLink', function(){
+
+       var A = mw.util.addPortletLink( 'p-tb', 'http://mediawiki.org/wiki/ResourceLoader',
+               'ResourceLoader', 't-rl', 'More info about ResourceLoader on MediaWiki.org ', 'l', '#t-specialpages' );
+
+       ok( $.isDomElement( A ), 'addPortletLink returns a valid DOM Element according to $.isDomElement' );
+
+       var B = mw.util.addPortletLink( "p-tb", "http://mediawiki.org/",
+               "MediaWiki.org", "t-mworg", "Go to MediaWiki.org ", "m", A );
+
+       equal( $( B ).attr( 'id' ), 't-mworg', 'Link has correct ID set' );
+       equal( $( B ).closest( '.portal' ).attr( 'id' ), 'p-tb', 'Link was inserted within correct portlet' );
+       equal( $( B ).next().attr( 'id' ), 't-rl', 'Link is in the correct position (by passing nextnode)' );
+
+       var C = mw.util.addPortletLink( "p-tb", "http://mediawiki.org/wiki/RL/DM",
+               "Default modules", "t-rldm", "List of all default modules ", "d", "#t-rl" );
+
+       equal( $( C ).next().attr( 'id' ), 't-rl', 'Link is in the correct position (by passing CSS selector)' );
+
+       // Clean up
+       $( [A, B, C] ).remove();
+
+});
+
+test( 'jsMessage', function(){
+
+       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(){
+
+       deepEqual( mw.util.validateEmail( "" ), null, 'Empty string should return null' );
+       deepEqual( mw.util.validateEmail( "user@localhost" ), true );
+
+       // testEmailWithCommasAreInvalids
+       deepEqual( mw.util.validateEmail( "user,foo@example.org" ), false, 'Comma' );
+       deepEqual( mw.util.validateEmail( "userfoo@ex,ample.org" ), false, 'Comma' );
+
+       // testEmailWithHyphens
+       deepEqual( mw.util.validateEmail( "user-foo@example.org" ), true, 'Hyphen' );
+       deepEqual( mw.util.validateEmail( "userfoo@ex-ample.org" ), true, 'Hyphen' );
+
+});
+
+test( 'isIPv6Address', function(){
+
+       // Based on IPTest.php > IPv6
+       deepEqual( mw.util.isIPv6Address( "" ), false, 'Empty string is not an IP' );
+       deepEqual( mw.util.isIPv6Address( ":fc:100::" ), false, 'IPv6 starting with lone ":"' );
+       deepEqual( mw.util.isIPv6Address( "fc:100::" ), true );
+       deepEqual( mw.util.isIPv6Address( "fc:100:a:d:1:e:ac::" ), true );
+       deepEqual( mw.util.isIPv6Address( ":::" ), false );
+       deepEqual( mw.util.isIPv6Address( "::0:" ), false );
+
+});
+
+test( 'isIPv4Address', function(){
+
+       // Based on IPTest.php > IPv4
+       deepEqual( mw.util.isIPv4Address( "" ), false, 'Empty string is not an IP' );
+       deepEqual( mw.util.isIPv4Address( "...." ), false );
+       deepEqual( mw.util.isIPv4Address( "1.24.52.13" ), true );
+
+});
diff --git a/tests/qunit/suites/resources/mediawiki/mediawiki.js b/tests/qunit/suites/resources/mediawiki/mediawiki.js
new file mode 100644 (file)
index 0000000..bcd8a5a
--- /dev/null
@@ -0,0 +1,107 @@
+module( 'mediawiki.js' );
+
+test( '-- Initial check', function(){
+
+       ok( window.jQuery, 'jQuery defined' );
+       ok( window.$j, '$j defined' );
+       equal( window.$j, window.jQuery, '$j alias to jQuery' );
+
+       ok( window.mediaWiki, 'mediaWiki defined' );
+       ok( window.mw, 'mw defined' );
+       equal( window.mw, window.mediaWiki, 'mw alias to mediaWiki' );
+
+});
+
+test( 'mw.Map / mw.config', function(){
+
+       ok( mw.config instanceof mw.Map, 'mw.config instance of mw.Map' );
+       ok( mw.config.get, 'get' );
+       ok( mw.config.set, 'set' );
+       ok( mw.config.exists, 'exists' );
+
+       ok( !mw.config.exists( 'lipsum' ), 'exists: lipsum (inexistant)' );
+       ok( mw.config.set( 'lipsum', 'Lorem ipsum' ), 'set: lipsum' );
+       ok( mw.config.exists( 'lipsum' ), 'exists: lipsum (existant)' );
+
+       equal( mw.config.get( 'lipsum' ), 'Lorem ipsum', 'get: lipsum' );
+       equal( mw.config.get( ['lipsum'] ).lipsum, 'Lorem ipsum', 'get: lipsum (multiple)' );
+
+});
+
+test( 'mw.message / mw.msg / mw.messages', function(){
+       ok( mw.message, 'mw.message defined' );
+       ok( mw.msg, 'mw.msg defined' );
+       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' );
+       ok( hello, 'hello: Instance of Message' );
+
+       equal( hello.format, 'parse', 'Message property "format" (default value)' );
+       equal( hello.key, 'hello', 'Message property "key" (currect key)' );
+       deepEqual( hello.parameters, [], 'Message property "parameters" (default value)' );
+
+
+       ok( hello.params, 'Message prototype "params"');
+       ok( hello.toString, 'Message prototype "toString"');
+       ok( hello.parse, 'Message prototype "parse"');
+       ok( hello.plain, 'Message prototype "plain"');
+       ok( hello.escaped, 'Message prototype "escaped"');
+       ok( hello.exists, 'Message prototype "exists"');
+
+       equal( hello.toString(), 'Hello <b>awesome</b> world', 'Message.toString() test');
+       equal( hello.escaped(), 'Hello &lt;b&gt;awesome&lt;/b&gt; world', 'Message.escaped() test');
+       deepEqual( hello.exists(), true, 'Message.exists() test');
+
+       equal( mw.msg( 'random' ), '<random>', 'square brackets around inexistant messages' );
+       equal( mw.msg( 'hello' ), 'Hello <b>awesome</b> world', 'get message with default options' );
+       
+// params, toString, parse, plain, escaped, exists
+});
+
+test( 'mw.loader', function(){
+       expect(2);
+       
+       ok( location.href.match(/[^#\?]*/) && location.href.match(/[^#\?]*/)[0], true, 'Extracting file path from location' );
+
+       stop();
+       
+       mw.loader.implement( 'is.awesome', [location.href.match(/[^#\?]*/)[0] + 'sample/awesome.js'], {}, {} );
+       mw.loader.using( 'is.awesome', function(){
+               start();
+               deepEqual( window.awesome, true, 'Implementing a module, is the callback timed properly ?');
+
+               // Clean up
+               delete window.awesome;
+
+       }, function(){
+               start();
+               deepEqual( 'mw.loader.using error callback fired', true, 'Implementing a module, is the callback timed properly ?');
+       });
+
+});
+
+test( 'mw.html', function(){
+
+       equal( mw.html.escape( '<mw awesome="awesome" value=\'test\' />' ),
+        '&lt;mw awesome=&quot;awesome&quot; value=&#039;test&#039; /&gt;', 'html.escape()' );
+
+       equal( mw.html.element( 'div' ), '<div/>', 'mw.html.element() DIV (simple)' );
+
+       equal( mw.html.element( 'div',
+        { id: 'foobar' } ),
+        '<div id="foobar"/>',
+        'mw.html.element() DIV (attribs)' );
+
+       equal( mw.html.element( 'div',
+        null, 'a' ),
+        '<div>a</div>',
+        'mw.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>',
+         'mw.html.element() DIV (attribs + content)' );
+
+});
diff --git a/tests/qunit/suites/resources/mediawiki/mediawiki.user.js b/tests/qunit/suites/resources/mediawiki/mediawiki.user.js
new file mode 100644 (file)
index 0000000..8ef44e2
--- /dev/null
@@ -0,0 +1,30 @@
+module( 'mediawiki.user.js' );
+
+test( '-- Initial check', function(){
+
+       ok( mw.user, 'mw.user defined' );
+
+});
+
+
+test( 'options', function(){
+
+       ok( mw.user.options instanceof mw.Map, 'options instance of mw.Map' );
+
+});
+
+test( 'User login status', function(){
+
+       deepEqual( mw.user.name(), null, 'user.name() When anonymous' );
+       ok( mw.user.anonymous(), 'user.anonymous() When anonymous' );
+
+       // Not part of startUp module
+       mw.config.set( 'wgUserName', 'John' );
+
+       equal( mw.user.name(), 'John', 'user.name() When logged-in as John' );
+       ok( !mw.user.anonymous(), 'user.anonymous() When logged-in' );
+
+       equal( mw.user.id(), 'John', 'user.id() When logged-in as John' );
+
+
+});
\ No newline at end of file
diff --git a/tests/qunit/testswarm.inject.js b/tests/qunit/testswarm.inject.js
new file mode 100644 (file)
index 0000000..14ee8f9
--- /dev/null
@@ -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();
+                       }
+               }
+       }
+
+})();