Updating jquery.qunit from upstream
authorKrinkle <krinkle@users.mediawiki.org>
Fri, 12 Aug 2011 14:03:05 +0000 (14:03 +0000)
committerKrinkle <krinkle@users.mediawiki.org>
Fri, 12 Aug 2011 14:03:05 +0000 (14:03 +0000)
* Source: https://github.com/jquery/qunit/commit/75dae0b9b8eb2e8151da502beb7a9c1c399f8dbe

resources/jquery/jquery.qunit.css
resources/jquery/jquery.qunit.js

index fa9156f..c85f36a 100644 (file)
@@ -1,9 +1,9 @@
 /**
  * QUnit - A JavaScript Unit Testing Framework
- * 
+ *
  * http://docs.jquery.com/QUnit
  *
- * Copyright (c) 2011 John Resig, JÃ\83¶rn Zaefferer
+ * Copyright (c) 2011 John Resig, Jörn Zaefferer
  * Dual licensed under the MIT (MIT-LICENSE.txt)
  * or GPL (GPL-LICENSE.txt) licenses.
  */
@@ -11,7 +11,7 @@
 /** Font Family and Sizes */
 
 #qunit-tests, #qunit-header, #qunit-banner, #qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult {
-       font-family: "Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial;
+       font-family: "Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial, sans-serif;
 }
 
 #qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult, #qunit-tests li { font-size: small; }
@@ -37,7 +37,7 @@
        font-size: 1.5em;
        line-height: 1em;
        font-weight: normal;
-       
+
        border-radius: 15px 15px 0 0;
        -moz-border-radius: 15px 15px 0 0;
        -webkit-border-top-right-radius: 15px;
 #qunit-tests ol {
        margin-top: 0.5em;
        padding: 0.5em;
-       
+
        background-color: #fff;
-       
+
        border-radius: 15px;
        -moz-border-radius: 15px;
        -webkit-border-radius: 15px;
-       
+
        box-shadow: inset 0px 2px 13px #999;
        -moz-box-shadow: inset 0px 2px 13px #999;
        -webkit-box-shadow: inset 0px 2px 13px #999;
 
 #qunit-tests .pass                          { color: #528CE0; background-color: #D2E0E6; }
 #qunit-tests .pass .test-name               { color: #366097; }
+
 #qunit-tests .pass .test-actual,
 #qunit-tests .pass .test-expected           { color: #999999; }
 
        color: #710909;
        background-color: #fff;
        border-left: 26px solid #EE5757;
+       white-space: pre;
 }
 
 #qunit-tests > li:last-child {
        position: absolute;
        top: -10000px;
        left: -10000px;
-}
\ No newline at end of file
+}
index 1405e79..5951e76 100644 (file)
@@ -1,6 +1,6 @@
 /**
  * QUnit - A JavaScript Unit Testing Framework
- * 
+ *
  * http://docs.jquery.com/QUnit
  *
  * Copyright (c) 2011 John Resig, Jörn Zaefferer
@@ -15,10 +15,10 @@ var defined = {
        sessionStorage: (function() {
                try {
                        return !!sessionStorage.getItem;
-               } catch(e){
+               } catch(e) {
                        return false;
                }
-  })()
+       })()
 };
 
 var testId = 0;
@@ -78,7 +78,7 @@ Test.prototype = {
                // allow utility functions to access the current test environment
                // TODO why??
                QUnit.current_testEnvironment = this.testEnvironment;
-               
+
                try {
                        if ( !config.pollution ) {
                                saveGlobal();
@@ -114,8 +114,8 @@ Test.prototype = {
        },
        teardown: function() {
                try {
-                       checkPollution();
                        this.testEnvironment.teardown.call(this.testEnvironment);
+                       checkPollution();
                } catch(e) {
                        QUnit.ok( false, "Teardown failed on " + this.testName + ": " + e.message );
                }
@@ -124,7 +124,7 @@ Test.prototype = {
                if ( this.expected && this.expected != this.assertions.length ) {
                        QUnit.ok( false, "Expected " + this.expected + " assertions, but " + this.assertions.length + " were run" );
                }
-               
+
                var good = 0, bad = 0,
                        tests = id("qunit-tests");
 
@@ -132,7 +132,7 @@ Test.prototype = {
                config.moduleStats.all += this.assertions.length;
 
                if ( tests ) {
-                       var ol  = document.createElement("ol");
+                       var ol = document.createElement("ol");
 
                        for ( var i = 0; i < this.assertions.length; i++ ) {
                                var assertion = this.assertions[i];
@@ -166,17 +166,17 @@ Test.prototype = {
 
                        var b = document.createElement("strong");
                        b.innerHTML = this.name + " <b class='counts'>(<b class='failed'>" + bad + "</b>, <b class='passed'>" + good + "</b>, " + this.assertions.length + ")</b>";
-                       
+
                        var a = document.createElement("a");
                        a.innerHTML = "Rerun";
                        a.href = QUnit.url({ filter: getText([b]).replace(/\([^)]+\)$/, "").replace(/(^\s*|\s*$)/g, "") });
-                       
+
                        addEvent(b, "click", function() {
                                var next = b.nextSibling.nextSibling,
                                        display = next.style.display;
                                next.style.display = display === "none" ? "block" : "none";
                        });
-                       
+
                        addEvent(b, "dblclick", function(e) {
                                var target = e && e.target ? e.target : window.event.srcElement;
                                if ( target.nodeName.toLowerCase() == "span" || target.nodeName.toLowerCase() == "b" ) {
@@ -217,7 +217,7 @@ Test.prototype = {
                        total: this.assertions.length
                } );
        },
-       
+
        queue: function() {
                var test = this;
                synchronize(function() {
@@ -246,7 +246,7 @@ Test.prototype = {
                        synchronize(run);
                };
        }
-       
+
 };
 
 var QUnit = {
@@ -265,7 +265,7 @@ var QUnit = {
 
                QUnit.test(testName, expected, callback, true);
        },
-       
+
        test: function(testName, expected, callback, async) {
                var name = '<span class="test-name">' + testName + '</span>', testEnvironmentArg;
 
@@ -275,7 +275,7 @@ var QUnit = {
                }
                // is 2nd argument a testEnvironment?
                if ( expected && typeof expected === 'object') {
-                       testEnvironmentArg =  expected;
+                       testEnvironmentArg = expected;
                        expected = null;
                }
 
@@ -286,13 +286,13 @@ var QUnit = {
                if ( !validTest(config.currentModule + ": " + testName) ) {
                        return;
                }
-               
+
                var test = new Test(name, testName, expected, testEnvironmentArg, async, callback);
                test.module = config.currentModule;
                test.moduleTestEnvironment = config.currentModuleTestEnviroment;
                test.queue();
        },
-       
+
        /**
         * Specify the number of expected assertions to gurantee that failed test (no assertions are run at all) don't slip through.
         */
@@ -337,7 +337,7 @@ var QUnit = {
        notEqual: function(actual, expected, message) {
                QUnit.push(expected != actual, actual, expected, message);
        },
-       
+
        deepEqual: function(actual, expected, message) {
                QUnit.push(QUnit.equiv(actual, expected), actual, expected, message);
        },
@@ -356,34 +356,34 @@ var QUnit = {
 
        raises: function(block, expected, message) {
                var actual, ok = false;
-       
+
                if (typeof expected === 'string') {
                        message = expected;
                        expected = null;
                }
-       
+
                try {
                        block();
                } catch (e) {
                        actual = e;
                }
-       
+
                if (actual) {
                        // we don't want to validate thrown error
                        if (!expected) {
                                ok = true;
-                       // expected is a regexp 
+                       // expected is a regexp
                        } else if (QUnit.objectType(expected) === "regexp") {
                                ok = expected.test(actual);
-                       // expected is a constructor    
+                       // expected is a constructor
                        } else if (actual instanceof expected) {
                                ok = true;
-                       // expected is a validation function which returns true is validation passed    
+                       // expected is a validation function which returns true is validation passed
                        } else if (expected.call({}, actual) === true) {
                                ok = true;
                        }
                }
-                       
+
                QUnit.ok(ok, message);
        },
 
@@ -400,6 +400,9 @@ var QUnit = {
                // A slight delay, to avoid any current callbacks
                if ( defined.setTimeout ) {
                        window.setTimeout(function() {
+                               if (config.semaphore > 0) {
+                                       return;
+                               }
                                if ( config.timeout ) {
                                        clearTimeout(config.timeout);
                                }
@@ -412,7 +415,7 @@ var QUnit = {
                        process();
                }
        },
-       
+
        stop: function(timeout) {
                config.semaphore++;
                config.blocking = true;
@@ -438,11 +441,18 @@ var config = {
 
        // block until document ready
        blocking: true,
-       
+
+       // when enabled, show only failing tests
+       // gets persisted through sessionStorage and can be changed in UI via checkbox
+       hidepassed: false,
+
        // by default, run previously failed tests first
        // very useful in combination with "Hide passed tests" checked
        reorder: true,
 
+       // by default, modify document.title when suite is done
+       altertitle: true,
+
        noglobals: false,
        notrycatch: false
 };
@@ -519,7 +529,7 @@ extend(QUnit, {
                if ( result ) {
                        result.parentNode.removeChild( result );
                }
-               
+
                if ( tests ) {
                        result = document.createElement( "p" );
                        result.id = "qunit-testresult";
@@ -528,10 +538,10 @@ extend(QUnit, {
                        result.innerHTML = 'Running...<br/>&nbsp;';
                }
        },
-       
+
        /**
         * Resets the test setup. Useful for tests that modify the DOM.
-        * 
+        *
         * If jQuery is available, uses jQuery's html(), otherwise just innerHTML.
         */
        reset: function() {
@@ -544,7 +554,7 @@ extend(QUnit, {
                        }
                }
        },
-       
+
        /**
         * Trigger an event on an element.
         *
@@ -564,12 +574,12 @@ extend(QUnit, {
                        elem.fireEvent("on"+type);
                }
        },
-       
+
        // Safe object type checking
        is: function( type, obj ) {
                return QUnit.objectType( obj ) == type;
        },
-       
+
        objectType: function( obj ) {
                if (typeof obj === "undefined") {
                                return "undefined";
@@ -603,7 +613,7 @@ extend(QUnit, {
                }
                return undefined;
        },
-       
+
        push: function(result, actual, expected, message) {
                var details = {
                        result: result,
@@ -611,7 +621,7 @@ extend(QUnit, {
                        actual: actual,
                        expected: expected
                };
-               
+
                message = escapeHtml(message) || (result ? "okay" : "failed");
                message = '<span class="test-message">' + message + "</span>";
                expected = escapeHtml(QUnit.jsDump.parse(expected));
@@ -625,19 +635,19 @@ extend(QUnit, {
                        var source = sourceFromStacktrace();
                        if (source) {
                                details.source = source;
-                               output += '<tr class="test-source"><th>Source: </th><td><pre>' + source +'</pre></td></tr>';
+                               output += '<tr class="test-source"><th>Source: </th><td><pre>' + escapeHtml(source) + '</pre></td></tr>';
                        }
                }
                output += "</table>";
-               
+
                QUnit.log(details);
-               
+
                config.current.assertions.push({
                        result: !!result,
                        message: output
                });
        },
-       
+
        url: function( params ) {
                params = extend( extend( {}, QUnit.urlParams ), params );
                var querystring = "?",
@@ -648,7 +658,11 @@ extend(QUnit, {
                }
                return window.location.pathname + querystring.slice( 0, -1 );
        },
-       
+
+       extend: extend,
+       id: id,
+       addEvent: addEvent,
+
        // Logging callbacks; all receive a single argument with the listed properties
        // run test/logs.html for any related changes
        begin: function() {},
@@ -670,9 +684,9 @@ if ( typeof document === "undefined" || document.readyState === "complete" ) {
        config.autorun = true;
 }
 
-addEvent(window, "load", function() {
+QUnit.load = function() {
        QUnit.begin({});
-       
+
        // Initialize the config, saving the execution queue
        var oldconfig = extend({}, config);
        QUnit.init();
@@ -695,7 +709,7 @@ addEvent(window, "load", function() {
                        window.location = QUnit.url( params );
                });
        }
-       
+
        var toolbar = id("qunit-testrunner-toolbar");
        if ( toolbar ) {
                var filter = document.createElement("input");
@@ -711,13 +725,13 @@ addEvent(window, "load", function() {
                        }
                        if ( defined.sessionStorage ) {
                                if (filter.checked) {
-                                       sessionStorage.setItem("qunit-filter-passed-tests",  "true");
+                                       sessionStorage.setItem("qunit-filter-passed-tests", "true");
                                } else {
                                        sessionStorage.removeItem("qunit-filter-passed-tests");
                                }
                        }
                });
-               if ( defined.sessionStorage && sessionStorage.getItem("qunit-filter-passed-tests") ) {
+               if ( config.hidepassed || defined.sessionStorage && sessionStorage.getItem("qunit-filter-passed-tests") ) {
                        filter.checked = true;
                        var ol = document.getElementById("qunit-tests");
                        ol.className = ol.className + " hidepass";
@@ -738,7 +752,9 @@ addEvent(window, "load", function() {
        if (config.autostart) {
                QUnit.start();
        }
-});
+};
+
+addEvent(window, "load", QUnit.load);
 
 function done() {
        config.autorun = true;
@@ -774,13 +790,22 @@ function done() {
                banner.className = (config.stats.bad ? "qunit-fail" : "qunit-pass");
        }
 
-       if ( tests ) {  
+       if ( tests ) {
                id( "qunit-testresult" ).innerHTML = html;
        }
 
+       if ( config.altertitle && typeof document !== "undefined" && document.title ) {
+               // show ✖ for good, ✔ for bad suite result in title
+               // use escape sequences in case file gets loaded with non-utf-8-charset
+               document.title = [
+                       (config.stats.bad ? "\u2716" : "\u2714"),
+                       document.title.replace(/^[\u2714\u2716] /i, "")
+               ].join(" ");
+       }
+
        QUnit.done( {
                failed: config.stats.bad,
-               passed: passed, 
+               passed: passed,
                total: config.stats.all,
                runtime: runtime
        } );
@@ -794,7 +819,7 @@ function validTest( name ) {
                return true;
        }
 
-       not = filter.charAt( 0 ) === "!";
+       var not = filter.charAt( 0 ) === "!";
        if ( not ) {
                filter = filter.slice( 1 );
        }
@@ -862,14 +887,14 @@ function process() {
                        break;
                }
        }
-  if (!config.blocking && !config.queue.length) {
-    done();
-  }
+       if (!config.blocking && !config.queue.length) {
+               done();
+       }
 }
 
 function saveGlobal() {
        config.pollution = [];
-       
+
        if ( config.noglobals ) {
                for ( var key in window ) {
                        config.pollution.push( key );
@@ -880,7 +905,7 @@ function saveGlobal() {
 function checkPollution( name ) {
        var old = config.pollution;
        saveGlobal();
-       
+
        var newGlobals = diff( config.pollution, old );
        if ( newGlobals.length > 0 ) {
                ok( false, "Introduced global variable(s): " + newGlobals.join(", ") );
@@ -951,170 +976,182 @@ function id(name) {
 // Author: Philippe Rathé <prathe@gmail.com>
 QUnit.equiv = function () {
 
-    var innerEquiv; // the real equiv function
-    var callers = []; // stack to decide between skip/abort functions
-    var parents = []; // stack to avoiding loops from circular referencing
-
-    // Call the o related callback with the given arguments.
-    function bindCallbacks(o, callbacks, args) {
-        var prop = QUnit.objectType(o);
-        if (prop) {
-            if (QUnit.objectType(callbacks[prop]) === "function") {
-                return callbacks[prop].apply(callbacks, args);
-            } else {
-                return callbacks[prop]; // or undefined
-            }
-        }
-    }
-    
-    var callbacks = function () {
-
-        // for string, boolean, number and null
-        function useStrictEquality(b, a) {
-            if (b instanceof a.constructor || a instanceof b.constructor) {
-                // to catch short annotaion VS 'new' annotation of a declaration
-                // e.g. var i = 1;
-                //      var j = new Number(1);
-                return a == b;
-            } else {
-                return a === b;
-            }
-        }
-
-        return {
-            "string": useStrictEquality,
-            "boolean": useStrictEquality,
-            "number": useStrictEquality,
-            "null": useStrictEquality,
-            "undefined": useStrictEquality,
-
-            "nan": function (b) {
-                return isNaN(b);
-            },
-
-            "date": function (b, a) {
-                return QUnit.objectType(b) === "date" && a.valueOf() === b.valueOf();
-            },
-
-            "regexp": function (b, a) {
-                return QUnit.objectType(b) === "regexp" &&
-                    a.source === b.source && // the regex itself
-                    a.global === b.global && // and its modifers (gmi) ...
-                    a.ignoreCase === b.ignoreCase &&
-                    a.multiline === b.multiline;
-            },
-
-            // - skip when the property is a method of an instance (OOP)
-            // - abort otherwise,
-            //   initial === would have catch identical references anyway
-            "function": function () {
-                var caller = callers[callers.length - 1];
-                return caller !== Object &&
-                        typeof caller !== "undefined";
-            },
-
-            "array": function (b, a) {
-                var i, j, loop;
-                var len;
-
-                // b could be an object literal here
-                if ( ! (QUnit.objectType(b) === "array")) {
-                    return false;
-                }   
-                
-                len = a.length;
-                if (len !== b.length) { // safe and faster
-                    return false;
-                }
-                
-                //track reference to avoid circular references
-                parents.push(a);
-                for (i = 0; i < len; i++) {
-                    loop = false;
-                    for(j=0;j<parents.length;j++){
-                        if(parents[j] === a[i]){
-                            loop = true;//dont rewalk array
-                        }
-                    }
-                    if (!loop && ! innerEquiv(a[i], b[i])) {
-                        parents.pop();
-                        return false;
-                    }
-                }
-                parents.pop();
-                return true;
-            },
-
-            "object": function (b, a) {
-                var i, j, loop;
-                var eq = true; // unless we can proove it
-                var aProperties = [], bProperties = []; // collection of strings
-
-                // comparing constructors is more strict than using instanceof
-                if ( a.constructor !== b.constructor) {
-                    return false;
-                }
+       var innerEquiv; // the real equiv function
+       var callers = []; // stack to decide between skip/abort functions
+       var parents = []; // stack to avoiding loops from circular referencing
 
-                // stack constructor before traversing properties
-                callers.push(a.constructor);
-                //track reference to avoid circular references
-                parents.push(a);
-                
-                for (i in a) { // be strict: don't ensures hasOwnProperty and go deep
-                    loop = false;
-                    for(j=0;j<parents.length;j++){
-                        if(parents[j] === a[i])
-                            loop = true; //don't go down the same path twice
-                    }
-                    aProperties.push(i); // collect a's properties
-
-                    if (!loop && ! innerEquiv(a[i], b[i])) {
-                        eq = false;
-                        break;
-                    }
-                }
+       // Call the o related callback with the given arguments.
+       function bindCallbacks(o, callbacks, args) {
+               var prop = QUnit.objectType(o);
+               if (prop) {
+                       if (QUnit.objectType(callbacks[prop]) === "function") {
+                               return callbacks[prop].apply(callbacks, args);
+                       } else {
+                               return callbacks[prop]; // or undefined
+                       }
+               }
+       }
 
-                callers.pop(); // unstack, we are done
-                parents.pop();
+       var callbacks = function () {
 
-                for (i in b) {
-                    bProperties.push(i); // collect b's properties
-                }
+               // for string, boolean, number and null
+               function useStrictEquality(b, a) {
+                       if (b instanceof a.constructor || a instanceof b.constructor) {
+                               // to catch short annotaion VS 'new' annotation of a
+                               // declaration
+                               // e.g. var i = 1;
+                               // var j = new Number(1);
+                               return a == b;
+                       } else {
+                               return a === b;
+                       }
+               }
+
+               return {
+                       "string" : useStrictEquality,
+                       "boolean" : useStrictEquality,
+                       "number" : useStrictEquality,
+                       "null" : useStrictEquality,
+                       "undefined" : useStrictEquality,
+
+                       "nan" : function(b) {
+                               return isNaN(b);
+                       },
+
+                       "date" : function(b, a) {
+                               return QUnit.objectType(b) === "date"
+                                               && a.valueOf() === b.valueOf();
+                       },
+
+                       "regexp" : function(b, a) {
+                               return QUnit.objectType(b) === "regexp"
+                                               && a.source === b.source && // the regex itself
+                                               a.global === b.global && // and its modifers
+                                                                                                       // (gmi) ...
+                                               a.ignoreCase === b.ignoreCase
+                                               && a.multiline === b.multiline;
+                       },
+
+                       // - skip when the property is a method of an instance (OOP)
+                       // - abort otherwise,
+                       // initial === would have catch identical references anyway
+                       "function" : function() {
+                               var caller = callers[callers.length - 1];
+                               return caller !== Object && typeof caller !== "undefined";
+                       },
 
-                // Ensures identical properties name
-                return eq && innerEquiv(aProperties.sort(), bProperties.sort());
-            }
-        };
-    }();
-
-    innerEquiv = function () { // can take multiple arguments
-        var args = Array.prototype.slice.apply(arguments);
-        if (args.length < 2) {
-            return true; // end transition
-        }
-
-        return (function (a, b) {
-            if (a === b) {
-                return true; // catch the most you can
-            } else if (a === null || b === null || typeof a === "undefined" || typeof b === "undefined" || QUnit.objectType(a) !== QUnit.objectType(b)) {
-                return false; // don't lose time with error prone cases
-            } else {
-                return bindCallbacks(a, callbacks, [b, a]);
-            }
-
-        // apply transition with (1..n) arguments
-        })(args[0], args[1]) && arguments.callee.apply(this, args.splice(1, args.length -1));
-    };
-
-    return innerEquiv;
+                       "array" : function(b, a) {
+                               var i, j, loop;
+                               var len;
+
+                               // b could be an object literal here
+                               if (!(QUnit.objectType(b) === "array")) {
+                                       return false;
+                               }
+
+                               len = a.length;
+                               if (len !== b.length) { // safe and faster
+                                       return false;
+                               }
+
+                               // track reference to avoid circular references
+                               parents.push(a);
+                               for (i = 0; i < len; i++) {
+                                       loop = false;
+                                       for (j = 0; j < parents.length; j++) {
+                                               if (parents[j] === a[i]) {
+                                                       loop = true;// dont rewalk array
+                                               }
+                                       }
+                                       if (!loop && !innerEquiv(a[i], b[i])) {
+                                               parents.pop();
+                                               return false;
+                                       }
+                               }
+                               parents.pop();
+                               return true;
+                       },
+
+                       "object" : function(b, a) {
+                               var i, j, loop;
+                               var eq = true; // unless we can proove it
+                               var aProperties = [], bProperties = []; // collection of
+                                                                                                               // strings
+
+                               // comparing constructors is more strict than using
+                               // instanceof
+                               if (a.constructor !== b.constructor) {
+                                       return false;
+                               }
+
+                               // stack constructor before traversing properties
+                               callers.push(a.constructor);
+                               // track reference to avoid circular references
+                               parents.push(a);
+
+                               for (i in a) { // be strict: don't ensures hasOwnProperty
+                                                               // and go deep
+                                       loop = false;
+                                       for (j = 0; j < parents.length; j++) {
+                                               if (parents[j] === a[i])
+                                                       loop = true; // don't go down the same path
+                                                                                       // twice
+                                       }
+                                       aProperties.push(i); // collect a's properties
+
+                                       if (!loop && !innerEquiv(a[i], b[i])) {
+                                               eq = false;
+                                               break;
+                                       }
+                               }
+
+                               callers.pop(); // unstack, we are done
+                               parents.pop();
+
+                               for (i in b) {
+                                       bProperties.push(i); // collect b's properties
+                               }
+
+                               // Ensures identical properties name
+                               return eq
+                                               && innerEquiv(aProperties.sort(), bProperties
+                                                               .sort());
+                       }
+               };
+       }();
+
+       innerEquiv = function() { // can take multiple arguments
+               var args = Array.prototype.slice.apply(arguments);
+               if (args.length < 2) {
+                       return true; // end transition
+               }
+
+               return (function(a, b) {
+                       if (a === b) {
+                               return true; // catch the most you can
+                       } else if (a === null || b === null || typeof a === "undefined"
+                                       || typeof b === "undefined"
+                                       || QUnit.objectType(a) !== QUnit.objectType(b)) {
+                               return false; // don't lose time with error prone cases
+                       } else {
+                               return bindCallbacks(a, callbacks, [ b, a ]);
+                       }
+
+                       // apply transition with (1..n) arguments
+               })(args[0], args[1])
+                               && arguments.callee.apply(this, args.splice(1,
+                                               args.length - 1));
+       };
+
+       return innerEquiv;
 
 }();
 
 /**
- * jsDump
- * Copyright (c) 2008 Ariel Flesler - aflesler(at)gmail(dot)com | http://flesler.blogspot.com
- * Licensed under BSD (http://www.opensource.org/licenses/bsd-license.php)
- * Date: 5/15/2008
+ * jsDump Copyright (c) 2008 Ariel Flesler - aflesler(at)gmail(dot)com |
+ * http://flesler.blogspot.com Licensed under BSD
+ * (http://www.opensource.org/licenses/bsd-license.php) Date: 5/15/2008
+ *
  * @projectDescription Advanced and extensible data dumping for Javascript.
  * @version 1.0.0
  * @author Ariel Flesler
@@ -1125,7 +1162,7 @@ QUnit.jsDump = (function() {
                return '"' + str.toString().replace(/"/g, '\\"') + '"';
        };
        function literal( o ) {
-               return o + '';  
+               return o + '';
        };
        function join( pre, arr, post ) {
                var s = jsDump.separator(),
@@ -1137,25 +1174,35 @@ QUnit.jsDump = (function() {
                        return pre + post;
                return [ pre, inner + arr, base + post ].join(s);
        };
-       function array( arr ) {
-               var i = arr.length,     ret = Array(i);                                 
+       function array( arr, stack ) {
+               var i = arr.length, ret = Array(i);
                this.up();
                while ( i-- )
-                       ret[i] = this.parse( arr[i] );                          
+                       ret[i] = this.parse( arr[i] , undefined , stack);
                this.down();
                return join( '[', ret, ']' );
        };
-       
+
        var reName = /^function (\w+)/;
-       
+
        var jsDump = {
-               parse:function( obj, type ) { //type is used mostly internally, you can fix a (custom)type in advance
-                       var     parser = this.parsers[ type || this.typeOf(obj) ];
-                       type = typeof parser;                   
-                       
-                       return type == 'function' ? parser.call( this, obj ) :
-                                  type == 'string' ? parser :
-                                  this.parsers.error;
+               parse:function( obj, type, stack ) { //type is used mostly internally, you can fix a (custom)type in advance
+                       stack = stack || [ ];
+                       var parser = this.parsers[ type || this.typeOf(obj) ];
+                       type = typeof parser;
+                       var inStack = inArray(obj, stack);
+                       if (inStack != -1) {
+                               return 'recursion('+(inStack - stack.length)+')';
+                       }
+                       //else
+                       if (type == 'function')  {
+                                       stack.push(obj);
+                                       var res = parser.call( this, obj, stack );
+                                       stack.pop();                    
+                                       return res;
+                       } 
+                       // else 
+                       return (type == 'string') ? parser : this.parsers.error;
                },
                typeOf:function( obj ) {
                        var type;
@@ -1203,7 +1250,7 @@ QUnit.jsDump = (function() {
                        this.parsers[name] = parser;
                },
                // The next 3 are exposed so you can use them
-               quote:quote, 
+               quote:quote,
                literal:literal,
                join:join,
                //
@@ -1222,28 +1269,30 @@ QUnit.jsDump = (function() {
                                if ( name )
                                        ret += ' ' + name;
                                ret += '(';
-                               
+
                                ret = [ ret, QUnit.jsDump.parse( fn, 'functionArgs' ), '){'].join('');
                                return join( ret, QUnit.jsDump.parse(fn,'functionCode'), '}' );
                        },
                        array: array,
                        nodelist: array,
                        arguments: array,
-                       object:function( map ) {
+                       object:function( map, stack ) {
                                var ret = [ ];
                                QUnit.jsDump.up();
-                               for ( var key in map )
-                                       ret.push( QUnit.jsDump.parse(key,'key') + ': ' + QUnit.jsDump.parse(map[key]) );
+                               for ( var key in map ) {
+                                   var val = map[key];
+                                       ret.push( QUnit.jsDump.parse(key,'key') + ': ' + QUnit.jsDump.parse(val, undefined, stack));
+                }
                                QUnit.jsDump.down();
                                return join( '{', ret, '}' );
                        },
                        node:function( node ) {
                                var open = QUnit.jsDump.HTML ? '&lt;' : '<',
                                        close = QUnit.jsDump.HTML ? '&gt;' : '>';
-                                       
+
                                var tag = node.nodeName.toLowerCase(),
                                        ret = open + tag;
-                                       
+
                                for ( var a in QUnit.jsDump.DOMAttrs ) {
                                        var val = node[QUnit.jsDump.DOMAttrs[a]];
                                        if ( val )
@@ -1253,8 +1302,8 @@ QUnit.jsDump = (function() {
                        },
                        functionArgs:function( fn ) {//function calls it internally, it's the arguments part of the function
                                var l = fn.length;
-                               if ( !l ) return '';                            
-                               
+                               if ( !l ) return '';
+
                                var args = Array(l);
                                while ( l-- )
                                        args[l] = String.fromCharCode(97+l);//97 is 'a'
@@ -1302,6 +1351,21 @@ function getText( elems ) {
        return ret;
 };
 
+//from jquery.js
+function inArray( elem, array ) {
+       if ( array.indexOf ) {
+               return array.indexOf( elem );
+       }
+
+       for ( var i = 0, length = array.length; i < length; i++ ) {
+               if ( array[ i ] === elem ) {
+                       return i;
+               }
+       }
+
+       return -1;
+}
+
 /*
  * Javascript Diff Algorithm
  *  By John Resig (http://ejohn.org/)
@@ -1311,34 +1375,34 @@ function getText( elems ) {
  *
  * More Info:
  *  http://ejohn.org/projects/javascript-diff-algorithm/
- *  
+ *
  * Usage: QUnit.diff(expected, actual)
- * 
+ *
  * QUnit.diff("the quick brown fox jumped over", "the quick fox jumps over") == "the  quick <del>brown </del> fox <del>jumped </del><ins>jumps </ins> over"
  */
 QUnit.diff = (function() {
-       function diff(o, n){
+       function diff(o, n) {
                var ns = new Object();
                var os = new Object();
-               
+
                for (var i = 0; i < n.length; i++) {
-                       if (ns[n[i]] == null) 
+                       if (ns[n[i]] == null)
                                ns[n[i]] = {
                                        rows: new Array(),
                                        o: null
                                };
                        ns[n[i]].rows.push(i);
                }
-               
+
                for (var i = 0; i < o.length; i++) {
-                       if (os[o[i]] == null) 
+                       if (os[o[i]] == null)
                                os[o[i]] = {
                                        rows: new Array(),
                                        n: null
                                };
                        os[o[i]].rows.push(i);
                }
-               
+
                for (var i in ns) {
                        if (ns[i].rows.length == 1 && typeof(os[i]) != "undefined" && os[i].rows.length == 1) {
                                n[ns[i].rows[0]] = {
@@ -1351,7 +1415,7 @@ QUnit.diff = (function() {
                                };
                        }
                }
-               
+
                for (var i = 0; i < n.length - 1; i++) {
                        if (n[i].text != null && n[i + 1].text == null && n[i].row + 1 < o.length && o[n[i].row + 1].text == null &&
                        n[i + 1] == o[n[i].row + 1]) {
@@ -1365,7 +1429,7 @@ QUnit.diff = (function() {
                                };
                        }
                }
-               
+
                for (var i = n.length - 1; i > 0; i--) {
                        if (n[i].text != null && n[i - 1].text == null && n[i].row > 0 && o[n[i].row - 1].text == null &&
                        n[i - 1] == o[n[i].row - 1]) {
@@ -1379,20 +1443,20 @@ QUnit.diff = (function() {
                                };
                        }
                }
-               
+
                return {
                        o: o,
                        n: n
                };
        }
-       
-       return function(o, n){
+
+       return function(o, n) {
                o = o.replace(/\s+$/, '');
                n = n.replace(/\s+$/, '');
                var out = diff(o == "" ? [] : o.split(/\s+/), n == "" ? [] : n.split(/\s+/));
 
                var str = "";
-               
+
                var oSpace = o.match(/\s+/g);
                if (oSpace == null) {
                        oSpace = [" "];
@@ -1407,7 +1471,7 @@ QUnit.diff = (function() {
                else {
                        nSpace.push(" ");
                }
-               
+
                if (out.n.length == 0) {
                        for (var i = 0; i < out.o.length; i++) {
                                str += '<del>' + out.o[i] + oSpace[i] + "</del>";
@@ -1419,14 +1483,14 @@ QUnit.diff = (function() {
                                        str += '<del>' + out.o[n] + oSpace[n] + "</del>";
                                }
                        }
-                       
+
                        for (var i = 0; i < out.n.length; i++) {
                                if (out.n[i].text == null) {
                                        str += '<ins>' + out.n[i] + nSpace[i] + "</ins>";
                                }
                                else {
                                        var pre = "";
-                                       
+
                                        for (n = out.n[i].row + 1; n < out.o.length && out.o[n].text == null; n++) {
                                                pre += '<del>' + out.o[n] + oSpace[n] + "</del>";
                                        }
@@ -1434,9 +1498,9 @@ QUnit.diff = (function() {
                                }
                        }
                }
-               
+
                return str;
        };
 })();
 
-})(this);
\ No newline at end of file
+})(this);