Added getRootStoragePath() convenience function to get the root path.
[mediawiki.git] / resources / jquery / jquery.qunit.js
blob66dd721542af14e7f7a8cc6d9ef2081f69f8a95a
1 /**
2  * QUnit v1.5.0 - A JavaScript Unit Testing Framework
3  *
4  * http://docs.jquery.com/QUnit
5  *
6  * Copyright (c) 2012 John Resig, Jörn Zaefferer
7  * Dual licensed under the MIT (MIT-LICENSE.txt)
8  * or GPL (GPL-LICENSE.txt) licenses.
9  */
11 (function(window) {
13 var defined = {
14         setTimeout: typeof window.setTimeout !== "undefined",
15         sessionStorage: (function() {
16                 var x = "qunit-test-string";
17                 try {
18                         sessionStorage.setItem(x, x);
19                         sessionStorage.removeItem(x);
20                         return true;
21                 } catch(e) {
22                         return false;
23                 }
24         }())
27 var     testId = 0,
28         toString = Object.prototype.toString,
29         hasOwn = Object.prototype.hasOwnProperty;
31 var Test = function(name, testName, expected, async, callback) {
32         this.name = name;
33         this.testName = testName;
34         this.expected = expected;
35         this.async = async;
36         this.callback = callback;
37         this.assertions = [];
39 Test.prototype = {
40         init: function() {
41                 var tests = id("qunit-tests");
42                 if (tests) {
43                         var b = document.createElement("strong");
44                                 b.innerHTML = "Running " + this.name;
45                         var li = document.createElement("li");
46                                 li.appendChild( b );
47                                 li.className = "running";
48                                 li.id = this.id = "test-output" + testId++;
49                         tests.appendChild( li );
50                 }
51         },
52         setup: function() {
53                 if (this.module != config.previousModule) {
54                         if ( config.previousModule ) {
55                                 runLoggingCallbacks('moduleDone', QUnit, {
56                                         name: config.previousModule,
57                                         failed: config.moduleStats.bad,
58                                         passed: config.moduleStats.all - config.moduleStats.bad,
59                                         total: config.moduleStats.all
60                                 } );
61                         }
62                         config.previousModule = this.module;
63                         config.moduleStats = { all: 0, bad: 0 };
64                         runLoggingCallbacks( 'moduleStart', QUnit, {
65                                 name: this.module
66                         } );
67                 } else if (config.autorun) {
68                         runLoggingCallbacks( 'moduleStart', QUnit, {
69                                 name: this.module
70                         } );
71                 }
73                 config.current = this;
74                 this.testEnvironment = extend({
75                         setup: function() {},
76                         teardown: function() {}
77                 }, this.moduleTestEnvironment);
79                 runLoggingCallbacks( 'testStart', QUnit, {
80                         name: this.testName,
81                         module: this.module
82                 });
84                 // allow utility functions to access the current test environment
85                 // TODO why??
86                 QUnit.current_testEnvironment = this.testEnvironment;
88                 if ( !config.pollution ) {
89                         saveGlobal();
90                 }
91                 if ( config.notrycatch ) {
92                         this.testEnvironment.setup.call(this.testEnvironment);
93                         return;
94                 }
95                 try {
96                         this.testEnvironment.setup.call(this.testEnvironment);
97                 } catch(e) {
98                         QUnit.pushFailure( "Setup failed on " + this.testName + ": " + e.message, extractStacktrace( e, 1 ) );
99                 }
100         },
101         run: function() {
102                 config.current = this;
104                 var running = id("qunit-testresult");
106                 if ( running ) {
107                         running.innerHTML = "Running: <br/>" + this.name;
108                 }
110                 if ( this.async ) {
111                         QUnit.stop();
112                 }
114                 if ( config.notrycatch ) {
115                         this.callback.call(this.testEnvironment);
116                         return;
117                 }
118                 try {
119                         this.callback.call(this.testEnvironment);
120                 } catch(e) {
121                         QUnit.pushFailure( "Died on test #" + (this.assertions.length + 1) + ": " + e.message, extractStacktrace( e, 1 ) );
122                         // else next test will carry the responsibility
123                         saveGlobal();
125                         // Restart the tests if they're blocking
126                         if ( config.blocking ) {
127                                 QUnit.start();
128                         }
129                 }
130         },
131         teardown: function() {
132                 config.current = this;
133                 if ( config.notrycatch ) {
134                         this.testEnvironment.teardown.call(this.testEnvironment);
135                         return;
136                 } else {
137                         try {
138                                 this.testEnvironment.teardown.call(this.testEnvironment);
139                         } catch(e) {
140                                 QUnit.pushFailure( "Teardown failed on " + this.testName + ": " + e.message, extractStacktrace( e, 1 ) );
141                         }
142                 }
143                 checkPollution();
144         },
145         finish: function() {
146                 config.current = this;
147                 if ( this.expected != null && this.expected != this.assertions.length ) {
148                         QUnit.pushFailure( "Expected " + this.expected + " assertions, but " + this.assertions.length + " were run" );
149                 } else if ( this.expected == null && !this.assertions.length ) {
150                         QUnit.pushFailure( "Expected at least one assertion, but none were run - call expect(0) to accept zero assertions." );
151                 }
153                 var good = 0, bad = 0,
154                         li, i,
155                         tests = id("qunit-tests");
157                 config.stats.all += this.assertions.length;
158                 config.moduleStats.all += this.assertions.length;
160                 if ( tests ) {
161                         var ol = document.createElement("ol");
163                         for ( i = 0; i < this.assertions.length; i++ ) {
164                                 var assertion = this.assertions[i];
166                                 li = document.createElement("li");
167                                 li.className = assertion.result ? "pass" : "fail";
168                                 li.innerHTML = assertion.message || (assertion.result ? "okay" : "failed");
169                                 ol.appendChild( li );
171                                 if ( assertion.result ) {
172                                         good++;
173                                 } else {
174                                         bad++;
175                                         config.stats.bad++;
176                                         config.moduleStats.bad++;
177                                 }
178                         }
180                         // store result when possible
181                         if ( QUnit.config.reorder && defined.sessionStorage ) {
182                                 if (bad) {
183                                         sessionStorage.setItem("qunit-test-" + this.module + "-" + this.testName, bad);
184                                 } else {
185                                         sessionStorage.removeItem("qunit-test-" + this.module + "-" + this.testName);
186                                 }
187                         }
189                         if (bad === 0) {
190                                 ol.style.display = "none";
191                         }
193                         var b = document.createElement("strong");
194                         b.innerHTML = this.name + " <b class='counts'>(<b class='failed'>" + bad + "</b>, <b class='passed'>" + good + "</b>, " + this.assertions.length + ")</b>";
196                         var a = document.createElement("a");
197                         a.innerHTML = "Rerun";
198                         a.href = QUnit.url({ filter: getText([b]).replace(/\([^)]+\)$/, "").replace(/(^\s*|\s*$)/g, "") });
200                         addEvent(b, "click", function() {
201                                 var next = b.nextSibling.nextSibling,
202                                         display = next.style.display;
203                                 next.style.display = display === "none" ? "block" : "none";
204                         });
206                         addEvent(b, "dblclick", function(e) {
207                                 var target = e && e.target ? e.target : window.event.srcElement;
208                                 if ( target.nodeName.toLowerCase() == "span" || target.nodeName.toLowerCase() == "b" ) {
209                                         target = target.parentNode;
210                                 }
211                                 if ( window.location && target.nodeName.toLowerCase() === "strong" ) {
212                                         window.location = QUnit.url({ filter: getText([target]).replace(/\([^)]+\)$/, "").replace(/(^\s*|\s*$)/g, "") });
213                                 }
214                         });
216                         li = id(this.id);
217                         li.className = bad ? "fail" : "pass";
218                         li.removeChild( li.firstChild );
219                         li.appendChild( b );
220                         li.appendChild( a );
221                         li.appendChild( ol );
223                 } else {
224                         for ( i = 0; i < this.assertions.length; i++ ) {
225                                 if ( !this.assertions[i].result ) {
226                                         bad++;
227                                         config.stats.bad++;
228                                         config.moduleStats.bad++;
229                                 }
230                         }
231                 }
233                 QUnit.reset();
235                 runLoggingCallbacks( 'testDone', QUnit, {
236                         name: this.testName,
237                         module: this.module,
238                         failed: bad,
239                         passed: this.assertions.length - bad,
240                         total: this.assertions.length
241                 } );
242         },
244         queue: function() {
245                 var test = this;
246                 synchronize(function() {
247                         test.init();
248                 });
249                 function run() {
250                         // each of these can by async
251                         synchronize(function() {
252                                 test.setup();
253                         });
254                         synchronize(function() {
255                                 test.run();
256                         });
257                         synchronize(function() {
258                                 test.teardown();
259                         });
260                         synchronize(function() {
261                                 test.finish();
262                         });
263                 }
264                 // defer when previous test run passed, if storage is available
265                 var bad = QUnit.config.reorder && defined.sessionStorage && +sessionStorage.getItem("qunit-test-" + this.module + "-" + this.testName);
266                 if (bad) {
267                         run();
268                 } else {
269                         synchronize(run, true);
270                 }
271         }
275 var QUnit = {
277         // call on start of module test to prepend name to all tests
278         module: function(name, testEnvironment) {
279                 config.currentModule = name;
280                 config.currentModuleTestEnviroment = testEnvironment;
281         },
283         asyncTest: function(testName, expected, callback) {
284                 if ( arguments.length === 2 ) {
285                         callback = expected;
286                         expected = null;
287                 }
289                 QUnit.test(testName, expected, callback, true);
290         },
292         test: function(testName, expected, callback, async) {
293                 var name = '<span class="test-name">' + escapeInnerText(testName) + '</span>';
295                 if ( arguments.length === 2 ) {
296                         callback = expected;
297                         expected = null;
298                 }
300                 if ( config.currentModule ) {
301                         name = '<span class="module-name">' + config.currentModule + "</span>: " + name;
302                 }
304                 if ( !validTest(config.currentModule + ": " + testName) ) {
305                         return;
306                 }
308                 var test = new Test(name, testName, expected, async, callback);
309                 test.module = config.currentModule;
310                 test.moduleTestEnvironment = config.currentModuleTestEnviroment;
311                 test.queue();
312         },
314         // Specify the number of expected assertions to gurantee that failed test (no assertions are run at all) don't slip through.
315         expect: function(asserts) {
316                 config.current.expected = asserts;
317         },
319         // Asserts true.
320         // @example ok( "asdfasdf".length > 5, "There must be at least 5 chars" );
321         ok: function(result, msg) {
322                 if (!config.current) {
323                         throw new Error("ok() assertion outside test context, was " + sourceFromStacktrace(2));
324                 }
325                 result = !!result;
326                 var details = {
327                         result: result,
328                         message: msg
329                 };
330                 msg = escapeInnerText(msg || (result ? "okay" : "failed"));
331                 if ( !result ) {
332                         var source = sourceFromStacktrace(2);
333                         if (source) {
334                                 details.source = source;
335                                 msg += '<table><tr class="test-source"><th>Source: </th><td><pre>' + escapeInnerText(source) + '</pre></td></tr></table>';
336                         }
337                 }
338                 runLoggingCallbacks( 'log', QUnit, details );
339                 config.current.assertions.push({
340                         result: result,
341                         message: msg
342                 });
343         },
345         // Checks that the first two arguments are equal, with an optional message. Prints out both actual and expected values.
346         // @example equal( format("Received {0} bytes.", 2), "Received 2 bytes." );
347         equal: function(actual, expected, message) {
348                 QUnit.push(expected == actual, actual, expected, message);
349         },
351         notEqual: function(actual, expected, message) {
352                 QUnit.push(expected != actual, actual, expected, message);
353         },
355         deepEqual: function(actual, expected, message) {
356                 QUnit.push(QUnit.equiv(actual, expected), actual, expected, message);
357         },
359         notDeepEqual: function(actual, expected, message) {
360                 QUnit.push(!QUnit.equiv(actual, expected), actual, expected, message);
361         },
363         strictEqual: function(actual, expected, message) {
364                 QUnit.push(expected === actual, actual, expected, message);
365         },
367         notStrictEqual: function(actual, expected, message) {
368                 QUnit.push(expected !== actual, actual, expected, message);
369         },
371         raises: function(block, expected, message) {
372                 var actual, ok = false;
374                 if (typeof expected === 'string') {
375                         message = expected;
376                         expected = null;
377                 }
379                 try {
380                         block.call(config.current.testEnvironment);
381                 } catch (e) {
382                         actual = e;
383                 }
385                 if (actual) {
386                         // we don't want to validate thrown error
387                         if (!expected) {
388                                 ok = true;
389                         // expected is a regexp
390                         } else if (QUnit.objectType(expected) === "regexp") {
391                                 ok = expected.test(actual);
392                         // expected is a constructor
393                         } else if (actual instanceof expected) {
394                                 ok = true;
395                         // expected is a validation function which returns true is validation passed
396                         } else if (expected.call({}, actual) === true) {
397                                 ok = true;
398                         }
399                 }
401                 QUnit.ok(ok, message);
402         },
404         start: function(count) {
405                 config.semaphore -= count || 1;
406                 if (config.semaphore > 0) {
407                         // don't start until equal number of stop-calls
408                         return;
409                 }
410                 if (config.semaphore < 0) {
411                         // ignore if start is called more often then stop
412                         config.semaphore = 0;
413                 }
414                 // A slight delay, to avoid any current callbacks
415                 if ( defined.setTimeout ) {
416                         window.setTimeout(function() {
417                                 if (config.semaphore > 0) {
418                                         return;
419                                 }
420                                 if ( config.timeout ) {
421                                         clearTimeout(config.timeout);
422                                 }
424                                 config.blocking = false;
425                                 process(true);
426                         }, 13);
427                 } else {
428                         config.blocking = false;
429                         process(true);
430                 }
431         },
433         stop: function(count) {
434                 config.semaphore += count || 1;
435                 config.blocking = true;
437                 if ( config.testTimeout && defined.setTimeout ) {
438                         clearTimeout(config.timeout);
439                         config.timeout = window.setTimeout(function() {
440                                 QUnit.ok( false, "Test timed out" );
441                                 config.semaphore = 1;
442                                 QUnit.start();
443                         }, config.testTimeout);
444                 }
445         }
448 //We want access to the constructor's prototype
449 (function() {
450         function F(){}
451         F.prototype = QUnit;
452         QUnit = new F();
453         //Make F QUnit's constructor so that we can add to the prototype later
454         QUnit.constructor = F;
455 }());
457 // deprecated; still export them to window to provide clear error messages
458 // next step: remove entirely
459 QUnit.equals = function() {
460         QUnit.push(false, false, false, "QUnit.equals has been deprecated since 2009 (e88049a0), use QUnit.equal instead");
462 QUnit.same = function() {
463         QUnit.push(false, false, false, "QUnit.same has been deprecated since 2009 (e88049a0), use QUnit.deepEqual instead");
466 // Maintain internal state
467 var config = {
468         // The queue of tests to run
469         queue: [],
471         // block until document ready
472         blocking: true,
474         // when enabled, show only failing tests
475         // gets persisted through sessionStorage and can be changed in UI via checkbox
476         hidepassed: false,
478         // by default, run previously failed tests first
479         // very useful in combination with "Hide passed tests" checked
480         reorder: true,
482         // by default, modify document.title when suite is done
483         altertitle: true,
485         urlConfig: ['noglobals', 'notrycatch'],
487         //logging callback queues
488         begin: [],
489         done: [],
490         log: [],
491         testStart: [],
492         testDone: [],
493         moduleStart: [],
494         moduleDone: []
497 // Load paramaters
498 (function() {
499         var location = window.location || { search: "", protocol: "file:" },
500                 params = location.search.slice( 1 ).split( "&" ),
501                 length = params.length,
502                 urlParams = {},
503                 current;
505         if ( params[ 0 ] ) {
506                 for ( var i = 0; i < length; i++ ) {
507                         current = params[ i ].split( "=" );
508                         current[ 0 ] = decodeURIComponent( current[ 0 ] );
509                         // allow just a key to turn on a flag, e.g., test.html?noglobals
510                         current[ 1 ] = current[ 1 ] ? decodeURIComponent( current[ 1 ] ) : true;
511                         urlParams[ current[ 0 ] ] = current[ 1 ];
512                 }
513         }
515         QUnit.urlParams = urlParams;
516         config.filter = urlParams.filter;
518         // Figure out if we're running the tests from a server or not
519         QUnit.isLocal = location.protocol === 'file:';
520 }());
522 // Expose the API as global variables, unless an 'exports'
523 // object exists, in that case we assume we're in CommonJS - export everything at the end
524 if ( typeof exports === "undefined" || typeof require === "undefined" ) {
525         extend(window, QUnit);
526         window.QUnit = QUnit;
529 // define these after exposing globals to keep them in these QUnit namespace only
530 extend(QUnit, {
531         config: config,
533         // Initialize the configuration options
534         init: function() {
535                 extend(config, {
536                         stats: { all: 0, bad: 0 },
537                         moduleStats: { all: 0, bad: 0 },
538                         started: +new Date(),
539                         updateRate: 1000,
540                         blocking: false,
541                         autostart: true,
542                         autorun: false,
543                         filter: "",
544                         queue: [],
545                         semaphore: 0
546                 });
548                 var qunit = id( "qunit" );
549                 if ( qunit ) {
550                         qunit.innerHTML =
551                                 '<h1 id="qunit-header">' + escapeInnerText( document.title ) + '</h1>' +
552                                 '<h2 id="qunit-banner"></h2>' +
553                                 '<div id="qunit-testrunner-toolbar"></div>' +
554                                 '<h2 id="qunit-userAgent"></h2>' +
555                                 '<ol id="qunit-tests"></ol>';
556                 }
558                 var tests = id( "qunit-tests" ),
559                         banner = id( "qunit-banner" ),
560                         result = id( "qunit-testresult" );
562                 if ( tests ) {
563                         tests.innerHTML = "";
564                 }
566                 if ( banner ) {
567                         banner.className = "";
568                 }
570                 if ( result ) {
571                         result.parentNode.removeChild( result );
572                 }
574                 if ( tests ) {
575                         result = document.createElement( "p" );
576                         result.id = "qunit-testresult";
577                         result.className = "result";
578                         tests.parentNode.insertBefore( result, tests );
579                         result.innerHTML = 'Running...<br/>&nbsp;';
580                 }
581         },
583         // Resets the test setup. Useful for tests that modify the DOM.
584         // If jQuery is available, uses jQuery's html(), otherwise just innerHTML.
585         reset: function() {
586                 if ( window.jQuery ) {
587                         jQuery( "#qunit-fixture" ).html( config.fixture );
588                 } else {
589                         var main = id( 'qunit-fixture' );
590                         if ( main ) {
591                                 main.innerHTML = config.fixture;
592                         }
593                 }
594         },
596         // Trigger an event on an element.
597         // @example triggerEvent( document.body, "click" );
598         triggerEvent: function( elem, type, event ) {
599                 if ( document.createEvent ) {
600                         event = document.createEvent("MouseEvents");
601                         event.initMouseEvent(type, true, true, elem.ownerDocument.defaultView,
602                                 0, 0, 0, 0, 0, false, false, false, false, 0, null);
603                         elem.dispatchEvent( event );
605                 } else if ( elem.fireEvent ) {
606                         elem.fireEvent("on"+type);
607                 }
608         },
610         // Safe object type checking
611         is: function( type, obj ) {
612                 return QUnit.objectType( obj ) == type;
613         },
615         objectType: function( obj ) {
616                 if (typeof obj === "undefined") {
617                                 return "undefined";
619                 // consider: typeof null === object
620                 }
621                 if (obj === null) {
622                                 return "null";
623                 }
625                 var type = toString.call( obj ).match(/^\[object\s(.*)\]$/)[1] || '';
627                 switch (type) {
628                         case 'Number':
629                                 if (isNaN(obj)) {
630                                         return "nan";
631                                 }
632                                 return "number";
633                         case 'String':
634                         case 'Boolean':
635                         case 'Array':
636                         case 'Date':
637                         case 'RegExp':
638                         case 'Function':
639                                         return type.toLowerCase();
640                 }
641                 if (typeof obj === "object") {
642                                 return "object";
643                 }
644                 return undefined;
645         },
647         push: function(result, actual, expected, message) {
648                 if (!config.current) {
649                         throw new Error("assertion outside test context, was " + sourceFromStacktrace());
650                 }
651                 var details = {
652                         result: result,
653                         message: message,
654                         actual: actual,
655                         expected: expected
656                 };
658                 message = escapeInnerText(message) || (result ? "okay" : "failed");
659                 message = '<span class="test-message">' + message + "</span>";
660                 var output = message;
661                 if (!result) {
662                         expected = escapeInnerText(QUnit.jsDump.parse(expected));
663                         actual = escapeInnerText(QUnit.jsDump.parse(actual));
664                         output += '<table><tr class="test-expected"><th>Expected: </th><td><pre>' + expected + '</pre></td></tr>';
665                         if (actual != expected) {
666                                 output += '<tr class="test-actual"><th>Result: </th><td><pre>' + actual + '</pre></td></tr>';
667                                 output += '<tr class="test-diff"><th>Diff: </th><td><pre>' + QUnit.diff(expected, actual) +'</pre></td></tr>';
668                         }
669                         var source = sourceFromStacktrace();
670                         if (source) {
671                                 details.source = source;
672                                 output += '<tr class="test-source"><th>Source: </th><td><pre>' + escapeInnerText(source) + '</pre></td></tr>';
673                         }
674                         output += "</table>";
675                 }
677                 runLoggingCallbacks( 'log', QUnit, details );
679                 config.current.assertions.push({
680                         result: !!result,
681                         message: output
682                 });
683         },
685         pushFailure: function(message, source) {
686                 var details = {
687                         result: false,
688                         message: message
689                 };
690                 var output = escapeInnerText(message);
691                 if (source) {
692                         details.source = source;
693                         output += '<table><tr class="test-source"><th>Source: </th><td><pre>' + escapeInnerText(source) + '</pre></td></tr></table>';
694                 }
695                 runLoggingCallbacks( 'log', QUnit, details );
696                 config.current.assertions.push({
697                         result: false,
698                         message: output
699                 });
700         },
702         url: function( params ) {
703                 params = extend( extend( {}, QUnit.urlParams ), params );
704                 var querystring = "?",
705                         key;
706                 for ( key in params ) {
707                         if ( !hasOwn.call( params, key ) ) {
708                                 continue;
709                         }
710                         querystring += encodeURIComponent( key ) + "=" +
711                                 encodeURIComponent( params[ key ] ) + "&";
712                 }
713                 return window.location.pathname + querystring.slice( 0, -1 );
714         },
716         extend: extend,
717         id: id,
718         addEvent: addEvent
721 //QUnit.constructor is set to the empty F() above so that we can add to it's prototype later
722 //Doing this allows us to tell if the following methods have been overwritten on the actual
723 //QUnit object, which is a deprecated way of using the callbacks.
724 extend(QUnit.constructor.prototype, {
725         // Logging callbacks; all receive a single argument with the listed properties
726         // run test/logs.html for any related changes
727         begin: registerLoggingCallback('begin'),
728         // done: { failed, passed, total, runtime }
729         done: registerLoggingCallback('done'),
730         // log: { result, actual, expected, message }
731         log: registerLoggingCallback('log'),
732         // testStart: { name }
733         testStart: registerLoggingCallback('testStart'),
734         // testDone: { name, failed, passed, total }
735         testDone: registerLoggingCallback('testDone'),
736         // moduleStart: { name }
737         moduleStart: registerLoggingCallback('moduleStart'),
738         // moduleDone: { name, failed, passed, total }
739         moduleDone: registerLoggingCallback('moduleDone')
742 if ( typeof document === "undefined" || document.readyState === "complete" ) {
743         config.autorun = true;
746 QUnit.load = function() {
747         runLoggingCallbacks( 'begin', QUnit, {} );
749         // Initialize the config, saving the execution queue
750         var oldconfig = extend({}, config);
751         QUnit.init();
752         extend(config, oldconfig);
754         config.blocking = false;
756         var urlConfigHtml = '', len = config.urlConfig.length;
757         for ( var i = 0, val; i < len; i++ ) {
758                 val = config.urlConfig[i];
759                 config[val] = QUnit.urlParams[val];
760                 urlConfigHtml += '<label><input name="' + val + '" type="checkbox"' + ( config[val] ? ' checked="checked"' : '' ) + '>' + val + '</label>';
761         }
763         var userAgent = id("qunit-userAgent");
764         if ( userAgent ) {
765                 userAgent.innerHTML = navigator.userAgent;
766         }
767         var banner = id("qunit-header");
768         if ( banner ) {
769                 banner.innerHTML = '<a href="' + QUnit.url({ filter: undefined }) + '"> ' + banner.innerHTML + '</a> ' + urlConfigHtml;
770                 addEvent( banner, "change", function( event ) {
771                         var params = {};
772                         params[ event.target.name ] = event.target.checked ? true : undefined;
773                         window.location = QUnit.url( params );
774                 });
775         }
777         var toolbar = id("qunit-testrunner-toolbar");
778         if ( toolbar ) {
779                 var filter = document.createElement("input");
780                 filter.type = "checkbox";
781                 filter.id = "qunit-filter-pass";
782                 addEvent( filter, "click", function() {
783                         var ol = document.getElementById("qunit-tests");
784                         if ( filter.checked ) {
785                                 ol.className = ol.className + " hidepass";
786                         } else {
787                                 var tmp = " " + ol.className.replace( /[\n\t\r]/g, " " ) + " ";
788                                 ol.className = tmp.replace(/ hidepass /, " ");
789                         }
790                         if ( defined.sessionStorage ) {
791                                 if (filter.checked) {
792                                         sessionStorage.setItem("qunit-filter-passed-tests", "true");
793                                 } else {
794                                         sessionStorage.removeItem("qunit-filter-passed-tests");
795                                 }
796                         }
797                 });
798                 if ( config.hidepassed || defined.sessionStorage && sessionStorage.getItem("qunit-filter-passed-tests") ) {
799                         filter.checked = true;
800                         var ol = document.getElementById("qunit-tests");
801                         ol.className = ol.className + " hidepass";
802                 }
803                 toolbar.appendChild( filter );
805                 var label = document.createElement("label");
806                 label.setAttribute("for", "qunit-filter-pass");
807                 label.innerHTML = "Hide passed tests";
808                 toolbar.appendChild( label );
809         }
811         var main = id('qunit-fixture');
812         if ( main ) {
813                 config.fixture = main.innerHTML;
814         }
816         if (config.autostart) {
817                 QUnit.start();
818         }
821 addEvent(window, "load", QUnit.load);
823 // addEvent(window, "error") gives us a useless event object
824 window.onerror = function( message, file, line ) {
825         if ( QUnit.config.current ) {
826                 QUnit.pushFailure( message, file + ":" + line );
827         } else {
828                 QUnit.test( "global failure", function() {
829                         QUnit.pushFailure( message, file + ":" + line );
830                 });
831         }
834 function done() {
835         config.autorun = true;
837         // Log the last module results
838         if ( config.currentModule ) {
839                 runLoggingCallbacks( 'moduleDone', QUnit, {
840                         name: config.currentModule,
841                         failed: config.moduleStats.bad,
842                         passed: config.moduleStats.all - config.moduleStats.bad,
843                         total: config.moduleStats.all
844                 } );
845         }
847         var banner = id("qunit-banner"),
848                 tests = id("qunit-tests"),
849                 runtime = +new Date() - config.started,
850                 passed = config.stats.all - config.stats.bad,
851                 html = [
852                         'Tests completed in ',
853                         runtime,
854                         ' milliseconds.<br/>',
855                         '<span class="passed">',
856                         passed,
857                         '</span> tests of <span class="total">',
858                         config.stats.all,
859                         '</span> passed, <span class="failed">',
860                         config.stats.bad,
861                         '</span> failed.'
862                 ].join('');
864         if ( banner ) {
865                 banner.className = (config.stats.bad ? "qunit-fail" : "qunit-pass");
866         }
868         if ( tests ) {
869                 id( "qunit-testresult" ).innerHTML = html;
870         }
872         if ( config.altertitle && typeof document !== "undefined" && document.title ) {
873                 // show ✖ for good, ✔ for bad suite result in title
874                 // use escape sequences in case file gets loaded with non-utf-8-charset
875                 document.title = [
876                         (config.stats.bad ? "\u2716" : "\u2714"),
877                         document.title.replace(/^[\u2714\u2716] /i, "")
878                 ].join(" ");
879         }
881         // clear own sessionStorage items if all tests passed
882         if ( config.reorder && defined.sessionStorage && config.stats.bad === 0 ) {
883                 var key;
884                 for ( var i = 0; i < sessionStorage.length; i++ ) {
885                         key = sessionStorage.key( i++ );
886                         if ( key.indexOf("qunit-test-") === 0 ) {
887                                 sessionStorage.removeItem( key );
888                         }
889                 }
890         }
892         runLoggingCallbacks( 'done', QUnit, {
893                 failed: config.stats.bad,
894                 passed: passed,
895                 total: config.stats.all,
896                 runtime: runtime
897         } );
900 function validTest( name ) {
901         var filter = config.filter,
902                 run = false;
904         if ( !filter ) {
905                 return true;
906         }
908         var not = filter.charAt( 0 ) === "!";
909         if ( not ) {
910                 filter = filter.slice( 1 );
911         }
913         if ( name.indexOf( filter ) !== -1 ) {
914                 return !not;
915         }
917         if ( not ) {
918                 run = true;
919         }
921         return run;
924 // so far supports only Firefox, Chrome and Opera (buggy), Safari (for real exceptions)
925 // Later Safari and IE10 are supposed to support error.stack as well
926 // See also https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Error/Stack
927 function extractStacktrace( e, offset ) {
928         offset = offset || 3;
929         if (e.stacktrace) {
930                 // Opera
931                 return e.stacktrace.split("\n")[offset + 3];
932         } else if (e.stack) {
933                 // Firefox, Chrome
934                 var stack = e.stack.split("\n");
935                 if (/^error$/i.test(stack[0])) {
936                         stack.shift();
937                 }
938                 return stack[offset];
939         } else if (e.sourceURL) {
940                 // Safari, PhantomJS
941                 // hopefully one day Safari provides actual stacktraces
942                 // exclude useless self-reference for generated Error objects
943                 if ( /qunit.js$/.test( e.sourceURL ) ) {
944                         return;
945                 }
946                 // for actual exceptions, this is useful
947                 return e.sourceURL + ":" + e.line;
948         }
950 function sourceFromStacktrace(offset) {
951         try {
952                 throw new Error();
953         } catch ( e ) {
954                 return extractStacktrace( e, offset );
955         }
958 function escapeInnerText(s) {
959         if (!s) {
960                 return "";
961         }
962         s = s + "";
963         return s.replace(/[\&<>]/g, function(s) {
964                 switch(s) {
965                         case "&": return "&amp;";
966                         case "<": return "&lt;";
967                         case ">": return "&gt;";
968                         default: return s;
969                 }
970         });
973 function synchronize( callback, last ) {
974         config.queue.push( callback );
976         if ( config.autorun && !config.blocking ) {
977                 process(last);
978         }
981 function process( last ) {
982         function next() {
983                 process( last );
984         }
985         var start = new Date().getTime();
986         config.depth = config.depth ? config.depth + 1 : 1;
988         while ( config.queue.length && !config.blocking ) {
989                 if ( !defined.setTimeout || config.updateRate <= 0 || ( ( new Date().getTime() - start ) < config.updateRate ) ) {
990                         config.queue.shift()();
991                 } else {
992                         window.setTimeout( next, 13 );
993                         break;
994                 }
995         }
996         config.depth--;
997         if ( last && !config.blocking && !config.queue.length && config.depth === 0 ) {
998                 done();
999         }
1002 function saveGlobal() {
1003         config.pollution = [];
1005         if ( config.noglobals ) {
1006                 for ( var key in window ) {
1007                         if ( !hasOwn.call( window, key ) ) {
1008                                 continue;
1009                         }
1010                         config.pollution.push( key );
1011                 }
1012         }
1015 function checkPollution( name ) {
1016         var old = config.pollution;
1017         saveGlobal();
1019         var newGlobals = diff( config.pollution, old );
1020         if ( newGlobals.length > 0 ) {
1021                 QUnit.pushFailure( "Introduced global variable(s): " + newGlobals.join(", ") );
1022         }
1024         var deletedGlobals = diff( old, config.pollution );
1025         if ( deletedGlobals.length > 0 ) {
1026                 QUnit.pushFailure( "Deleted global variable(s): " + deletedGlobals.join(", ") );
1027         }
1030 // returns a new Array with the elements that are in a but not in b
1031 function diff( a, b ) {
1032         var result = a.slice();
1033         for ( var i = 0; i < result.length; i++ ) {
1034                 for ( var j = 0; j < b.length; j++ ) {
1035                         if ( result[i] === b[j] ) {
1036                                 result.splice(i, 1);
1037                                 i--;
1038                                 break;
1039                         }
1040                 }
1041         }
1042         return result;
1045 function extend(a, b) {
1046         for ( var prop in b ) {
1047                 if ( b[prop] === undefined ) {
1048                         delete a[prop];
1050                 // Avoid "Member not found" error in IE8 caused by setting window.constructor
1051                 } else if ( prop !== "constructor" || a !== window ) {
1052                         a[prop] = b[prop];
1053                 }
1054         }
1056         return a;
1059 function addEvent(elem, type, fn) {
1060         if ( elem.addEventListener ) {
1061                 elem.addEventListener( type, fn, false );
1062         } else if ( elem.attachEvent ) {
1063                 elem.attachEvent( "on" + type, fn );
1064         } else {
1065                 fn();
1066         }
1069 function id(name) {
1070         return !!(typeof document !== "undefined" && document && document.getElementById) &&
1071                 document.getElementById( name );
1074 function registerLoggingCallback(key){
1075         return function(callback){
1076                 config[key].push( callback );
1077         };
1080 // Supports deprecated method of completely overwriting logging callbacks
1081 function runLoggingCallbacks(key, scope, args) {
1082         //debugger;
1083         var callbacks;
1084         if ( QUnit.hasOwnProperty(key) ) {
1085                 QUnit[key].call(scope, args);
1086         } else {
1087                 callbacks = config[key];
1088                 for( var i = 0; i < callbacks.length; i++ ) {
1089                         callbacks[i].call( scope, args );
1090                 }
1091         }
1094 // Test for equality any JavaScript type.
1095 // Author: Philippe Rathé <prathe@gmail.com>
1096 QUnit.equiv = (function() {
1098         var innerEquiv; // the real equiv function
1099         var callers = []; // stack to decide between skip/abort functions
1100         var parents = []; // stack to avoiding loops from circular referencing
1102         // Call the o related callback with the given arguments.
1103         function bindCallbacks(o, callbacks, args) {
1104                 var prop = QUnit.objectType(o);
1105                 if (prop) {
1106                         if (QUnit.objectType(callbacks[prop]) === "function") {
1107                                 return callbacks[prop].apply(callbacks, args);
1108                         } else {
1109                                 return callbacks[prop]; // or undefined
1110                         }
1111                 }
1112         }
1114         var getProto = Object.getPrototypeOf || function (obj) {
1115                 return obj.__proto__;
1116         };
1118         var callbacks = (function () {
1120                 // for string, boolean, number and null
1121                 function useStrictEquality(b, a) {
1122                         if (b instanceof a.constructor || a instanceof b.constructor) {
1123                                 // to catch short annotaion VS 'new' annotation of a
1124                                 // declaration
1125                                 // e.g. var i = 1;
1126                                 // var j = new Number(1);
1127                                 return a == b;
1128                         } else {
1129                                 return a === b;
1130                         }
1131                 }
1133                 return {
1134                         "string" : useStrictEquality,
1135                         "boolean" : useStrictEquality,
1136                         "number" : useStrictEquality,
1137                         "null" : useStrictEquality,
1138                         "undefined" : useStrictEquality,
1140                         "nan" : function(b) {
1141                                 return isNaN(b);
1142                         },
1144                         "date" : function(b, a) {
1145                                 return QUnit.objectType(b) === "date" && a.valueOf() === b.valueOf();
1146                         },
1148                         "regexp" : function(b, a) {
1149                                 return QUnit.objectType(b) === "regexp" &&
1150                                         // the regex itself
1151                                         a.source === b.source &&
1152                                         // and its modifers
1153                                         a.global === b.global &&
1154                                         // (gmi) ...
1155                                         a.ignoreCase === b.ignoreCase &&
1156                                         a.multiline === b.multiline;
1157                         },
1159                         // - skip when the property is a method of an instance (OOP)
1160                         // - abort otherwise,
1161                         // initial === would have catch identical references anyway
1162                         "function" : function() {
1163                                 var caller = callers[callers.length - 1];
1164                                 return caller !== Object && typeof caller !== "undefined";
1165                         },
1167                         "array" : function(b, a) {
1168                                 var i, j, loop;
1169                                 var len;
1171                                 // b could be an object literal here
1172                                 if (QUnit.objectType(b) !== "array") {
1173                                         return false;
1174                                 }
1176                                 len = a.length;
1177                                 if (len !== b.length) { // safe and faster
1178                                         return false;
1179                                 }
1181                                 // track reference to avoid circular references
1182                                 parents.push(a);
1183                                 for (i = 0; i < len; i++) {
1184                                         loop = false;
1185                                         for (j = 0; j < parents.length; j++) {
1186                                                 if (parents[j] === a[i]) {
1187                                                         loop = true;// dont rewalk array
1188                                                 }
1189                                         }
1190                                         if (!loop && !innerEquiv(a[i], b[i])) {
1191                                                 parents.pop();
1192                                                 return false;
1193                                         }
1194                                 }
1195                                 parents.pop();
1196                                 return true;
1197                         },
1199                         "object" : function(b, a) {
1200                                 var i, j, loop;
1201                                 var eq = true; // unless we can proove it
1202                                 var aProperties = [], bProperties = []; // collection of
1203                                                                                                                 // strings
1205                                 // comparing constructors is more strict than using
1206                                 // instanceof
1207                                 if (a.constructor !== b.constructor) {
1208                                         // Allow objects with no prototype to be equivalent to
1209                                         // objects with Object as their constructor.
1210                                         if (!((getProto(a) === null && getProto(b) === Object.prototype) ||
1211                                                 (getProto(b) === null && getProto(a) === Object.prototype)))
1212                                         {
1213                                                 return false;
1214                                         }
1215                                 }
1217                                 // stack constructor before traversing properties
1218                                 callers.push(a.constructor);
1219                                 // track reference to avoid circular references
1220                                 parents.push(a);
1222                                 for (i in a) { // be strict: don't ensures hasOwnProperty
1223                                                                 // and go deep
1224                                         loop = false;
1225                                         for (j = 0; j < parents.length; j++) {
1226                                                 if (parents[j] === a[i]) {
1227                                                         // don't go down the same path twice
1228                                                         loop = true;
1229                                                 }
1230                                         }
1231                                         aProperties.push(i); // collect a's properties
1233                                         if (!loop && !innerEquiv(a[i], b[i])) {
1234                                                 eq = false;
1235                                                 break;
1236                                         }
1237                                 }
1239                                 callers.pop(); // unstack, we are done
1240                                 parents.pop();
1242                                 for (i in b) {
1243                                         bProperties.push(i); // collect b's properties
1244                                 }
1246                                 // Ensures identical properties name
1247                                 return eq && innerEquiv(aProperties.sort(), bProperties.sort());
1248                         }
1249                 };
1250         }());
1252         innerEquiv = function() { // can take multiple arguments
1253                 var args = Array.prototype.slice.apply(arguments);
1254                 if (args.length < 2) {
1255                         return true; // end transition
1256                 }
1258                 return (function(a, b) {
1259                         if (a === b) {
1260                                 return true; // catch the most you can
1261                         } else if (a === null || b === null || typeof a === "undefined" ||
1262                                         typeof b === "undefined" ||
1263                                         QUnit.objectType(a) !== QUnit.objectType(b)) {
1264                                 return false; // don't lose time with error prone cases
1265                         } else {
1266                                 return bindCallbacks(a, callbacks, [ b, a ]);
1267                         }
1269                         // apply transition with (1..n) arguments
1270                 }(args[0], args[1]) && arguments.callee.apply(this, args.splice(1, args.length - 1)));
1271         };
1273         return innerEquiv;
1275 }());
1278  * jsDump Copyright (c) 2008 Ariel Flesler - aflesler(at)gmail(dot)com |
1279  * http://flesler.blogspot.com Licensed under BSD
1280  * (http://www.opensource.org/licenses/bsd-license.php) Date: 5/15/2008
1282  * @projectDescription Advanced and extensible data dumping for Javascript.
1283  * @version 1.0.0
1284  * @author Ariel Flesler
1285  * @link {http://flesler.blogspot.com/2008/05/jsdump-pretty-dump-of-any-javascript.html}
1286  */
1287 QUnit.jsDump = (function() {
1288         function quote( str ) {
1289                 return '"' + str.toString().replace(/"/g, '\\"') + '"';
1290         }
1291         function literal( o ) {
1292                 return o + '';
1293         }
1294         function join( pre, arr, post ) {
1295                 var s = jsDump.separator(),
1296                         base = jsDump.indent(),
1297                         inner = jsDump.indent(1);
1298                 if ( arr.join ) {
1299                         arr = arr.join( ',' + s + inner );
1300                 }
1301                 if ( !arr ) {
1302                         return pre + post;
1303                 }
1304                 return [ pre, inner + arr, base + post ].join(s);
1305         }
1306         function array( arr, stack ) {
1307                 var i = arr.length, ret = new Array(i);
1308                 this.up();
1309                 while ( i-- ) {
1310                         ret[i] = this.parse( arr[i] , undefined , stack);
1311                 }
1312                 this.down();
1313                 return join( '[', ret, ']' );
1314         }
1316         var reName = /^function (\w+)/;
1318         var jsDump = {
1319                 parse: function( obj, type, stack ) { //type is used mostly internally, you can fix a (custom)type in advance
1320                         stack = stack || [ ];
1321                         var parser = this.parsers[ type || this.typeOf(obj) ];
1322                         type = typeof parser;
1323                         var inStack = inArray(obj, stack);
1324                         if (inStack != -1) {
1325                                 return 'recursion('+(inStack - stack.length)+')';
1326                         }
1327                         //else
1328                         if (type == 'function')  {
1329                                         stack.push(obj);
1330                                         var res = parser.call( this, obj, stack );
1331                                         stack.pop();
1332                                         return res;
1333                         }
1334                         // else
1335                         return (type == 'string') ? parser : this.parsers.error;
1336                 },
1337                 typeOf: function( obj ) {
1338                         var type;
1339                         if ( obj === null ) {
1340                                 type = "null";
1341                         } else if (typeof obj === "undefined") {
1342                                 type = "undefined";
1343                         } else if (QUnit.is("RegExp", obj)) {
1344                                 type = "regexp";
1345                         } else if (QUnit.is("Date", obj)) {
1346                                 type = "date";
1347                         } else if (QUnit.is("Function", obj)) {
1348                                 type = "function";
1349                         } else if (typeof obj.setInterval !== undefined && typeof obj.document !== "undefined" && typeof obj.nodeType === "undefined") {
1350                                 type = "window";
1351                         } else if (obj.nodeType === 9) {
1352                                 type = "document";
1353                         } else if (obj.nodeType) {
1354                                 type = "node";
1355                         } else if (
1356                                 // native arrays
1357                                 toString.call( obj ) === "[object Array]" ||
1358                                 // NodeList objects
1359                                 ( typeof obj.length === "number" && typeof obj.item !== "undefined" && ( obj.length ? obj.item(0) === obj[0] : ( obj.item( 0 ) === null && typeof obj[0] === "undefined" ) ) )
1360                         ) {
1361                                 type = "array";
1362                         } else {
1363                                 type = typeof obj;
1364                         }
1365                         return type;
1366                 },
1367                 separator: function() {
1368                         return this.multiline ? this.HTML ? '<br />' : '\n' : this.HTML ? '&nbsp;' : ' ';
1369                 },
1370                 indent: function( extra ) {// extra can be a number, shortcut for increasing-calling-decreasing
1371                         if ( !this.multiline ) {
1372                                 return '';
1373                         }
1374                         var chr = this.indentChar;
1375                         if ( this.HTML ) {
1376                                 chr = chr.replace(/\t/g,'   ').replace(/ /g,'&nbsp;');
1377                         }
1378                         return new Array( this._depth_ + (extra||0) ).join(chr);
1379                 },
1380                 up: function( a ) {
1381                         this._depth_ += a || 1;
1382                 },
1383                 down: function( a ) {
1384                         this._depth_ -= a || 1;
1385                 },
1386                 setParser: function( name, parser ) {
1387                         this.parsers[name] = parser;
1388                 },
1389                 // The next 3 are exposed so you can use them
1390                 quote: quote,
1391                 literal: literal,
1392                 join: join,
1393                 //
1394                 _depth_: 1,
1395                 // This is the list of parsers, to modify them, use jsDump.setParser
1396                 parsers: {
1397                         window: '[Window]',
1398                         document: '[Document]',
1399                         error: '[ERROR]', //when no parser is found, shouldn't happen
1400                         unknown: '[Unknown]',
1401                         'null': 'null',
1402                         'undefined': 'undefined',
1403                         'function': function( fn ) {
1404                                 var ret = 'function',
1405                                         name = 'name' in fn ? fn.name : (reName.exec(fn)||[])[1];//functions never have name in IE
1406                                 if ( name ) {
1407                                         ret += ' ' + name;
1408                                 }
1409                                 ret += '(';
1411                                 ret = [ ret, QUnit.jsDump.parse( fn, 'functionArgs' ), '){'].join('');
1412                                 return join( ret, QUnit.jsDump.parse(fn,'functionCode'), '}' );
1413                         },
1414                         array: array,
1415                         nodelist: array,
1416                         'arguments': array,
1417                         object: function( map, stack ) {
1418                                 var ret = [ ], keys, key, val, i;
1419                                 QUnit.jsDump.up();
1420                                 if (Object.keys) {
1421                                         keys = Object.keys( map );
1422                                 } else {
1423                                         keys = [];
1424                                         for (key in map) { keys.push( key ); }
1425                                 }
1426                                 keys.sort();
1427                                 for (i = 0; i < keys.length; i++) {
1428                                         key = keys[ i ];
1429                                         val = map[ key ];
1430                                         ret.push( QUnit.jsDump.parse( key, 'key' ) + ': ' + QUnit.jsDump.parse( val, undefined, stack ) );
1431                                 }
1432                                 QUnit.jsDump.down();
1433                                 return join( '{', ret, '}' );
1434                         },
1435                         node: function( node ) {
1436                                 var open = QUnit.jsDump.HTML ? '&lt;' : '<',
1437                                         close = QUnit.jsDump.HTML ? '&gt;' : '>';
1439                                 var tag = node.nodeName.toLowerCase(),
1440                                         ret = open + tag;
1442                                 for ( var a in QUnit.jsDump.DOMAttrs ) {
1443                                         var val = node[QUnit.jsDump.DOMAttrs[a]];
1444                                         if ( val ) {
1445                                                 ret += ' ' + a + '=' + QUnit.jsDump.parse( val, 'attribute' );
1446                                         }
1447                                 }
1448                                 return ret + close + open + '/' + tag + close;
1449                         },
1450                         functionArgs: function( fn ) {//function calls it internally, it's the arguments part of the function
1451                                 var l = fn.length;
1452                                 if ( !l ) {
1453                                         return '';
1454                                 }
1456                                 var args = new Array(l);
1457                                 while ( l-- ) {
1458                                         args[l] = String.fromCharCode(97+l);//97 is 'a'
1459                                 }
1460                                 return ' ' + args.join(', ') + ' ';
1461                         },
1462                         key: quote, //object calls it internally, the key part of an item in a map
1463                         functionCode: '[code]', //function calls it internally, it's the content of the function
1464                         attribute: quote, //node calls it internally, it's an html attribute value
1465                         string: quote,
1466                         date: quote,
1467                         regexp: literal, //regex
1468                         number: literal,
1469                         'boolean': literal
1470                 },
1471                 DOMAttrs:{//attributes to dump from nodes, name=>realName
1472                         id:'id',
1473                         name:'name',
1474                         'class':'className'
1475                 },
1476                 HTML:false,//if true, entities are escaped ( <, >, \t, space and \n )
1477                 indentChar:'  ',//indentation unit
1478                 multiline:true //if true, items in a collection, are separated by a \n, else just a space.
1479         };
1481         return jsDump;
1482 }());
1484 // from Sizzle.js
1485 function getText( elems ) {
1486         var ret = "", elem;
1488         for ( var i = 0; elems[i]; i++ ) {
1489                 elem = elems[i];
1491                 // Get the text from text nodes and CDATA nodes
1492                 if ( elem.nodeType === 3 || elem.nodeType === 4 ) {
1493                         ret += elem.nodeValue;
1495                 // Traverse everything else, except comment nodes
1496                 } else if ( elem.nodeType !== 8 ) {
1497                         ret += getText( elem.childNodes );
1498                 }
1499         }
1501         return ret;
1504 //from jquery.js
1505 function inArray( elem, array ) {
1506         if ( array.indexOf ) {
1507                 return array.indexOf( elem );
1508         }
1510         for ( var i = 0, length = array.length; i < length; i++ ) {
1511                 if ( array[ i ] === elem ) {
1512                         return i;
1513                 }
1514         }
1516         return -1;
1520  * Javascript Diff Algorithm
1521  *  By John Resig (http://ejohn.org/)
1522  *  Modified by Chu Alan "sprite"
1524  * Released under the MIT license.
1526  * More Info:
1527  *  http://ejohn.org/projects/javascript-diff-algorithm/
1529  * Usage: QUnit.diff(expected, actual)
1531  * 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"
1532  */
1533 QUnit.diff = (function() {
1534         function diff(o, n) {
1535                 var ns = {};
1536                 var os = {};
1537                 var i;
1539                 for (i = 0; i < n.length; i++) {
1540                         if (ns[n[i]] == null) {
1541                                 ns[n[i]] = {
1542                                         rows: [],
1543                                         o: null
1544                                 };
1545                         }
1546                         ns[n[i]].rows.push(i);
1547                 }
1549                 for (i = 0; i < o.length; i++) {
1550                         if (os[o[i]] == null) {
1551                                 os[o[i]] = {
1552                                         rows: [],
1553                                         n: null
1554                                 };
1555                         }
1556                         os[o[i]].rows.push(i);
1557                 }
1559                 for (i in ns) {
1560                         if ( !hasOwn.call( ns, i ) ) {
1561                                 continue;
1562                         }
1563                         if (ns[i].rows.length == 1 && typeof(os[i]) != "undefined" && os[i].rows.length == 1) {
1564                                 n[ns[i].rows[0]] = {
1565                                         text: n[ns[i].rows[0]],
1566                                         row: os[i].rows[0]
1567                                 };
1568                                 o[os[i].rows[0]] = {
1569                                         text: o[os[i].rows[0]],
1570                                         row: ns[i].rows[0]
1571                                 };
1572                         }
1573                 }
1575                 for (i = 0; i < n.length - 1; i++) {
1576                         if (n[i].text != null && n[i + 1].text == null && n[i].row + 1 < o.length && o[n[i].row + 1].text == null &&
1577                         n[i + 1] == o[n[i].row + 1]) {
1578                                 n[i + 1] = {
1579                                         text: n[i + 1],
1580                                         row: n[i].row + 1
1581                                 };
1582                                 o[n[i].row + 1] = {
1583                                         text: o[n[i].row + 1],
1584                                         row: i + 1
1585                                 };
1586                         }
1587                 }
1589                 for (i = n.length - 1; i > 0; i--) {
1590                         if (n[i].text != null && n[i - 1].text == null && n[i].row > 0 && o[n[i].row - 1].text == null &&
1591                         n[i - 1] == o[n[i].row - 1]) {
1592                                 n[i - 1] = {
1593                                         text: n[i - 1],
1594                                         row: n[i].row - 1
1595                                 };
1596                                 o[n[i].row - 1] = {
1597                                         text: o[n[i].row - 1],
1598                                         row: i - 1
1599                                 };
1600                         }
1601                 }
1603                 return {
1604                         o: o,
1605                         n: n
1606                 };
1607         }
1609         return function(o, n) {
1610                 o = o.replace(/\s+$/, '');
1611                 n = n.replace(/\s+$/, '');
1612                 var out = diff(o === "" ? [] : o.split(/\s+/), n === "" ? [] : n.split(/\s+/));
1614                 var str = "";
1615                 var i;
1617                 var oSpace = o.match(/\s+/g);
1618                 if (oSpace == null) {
1619                         oSpace = [" "];
1620                 }
1621                 else {
1622                         oSpace.push(" ");
1623                 }
1624                 var nSpace = n.match(/\s+/g);
1625                 if (nSpace == null) {
1626                         nSpace = [" "];
1627                 }
1628                 else {
1629                         nSpace.push(" ");
1630                 }
1632                 if (out.n.length === 0) {
1633                         for (i = 0; i < out.o.length; i++) {
1634                                 str += '<del>' + out.o[i] + oSpace[i] + "</del>";
1635                         }
1636                 }
1637                 else {
1638                         if (out.n[0].text == null) {
1639                                 for (n = 0; n < out.o.length && out.o[n].text == null; n++) {
1640                                         str += '<del>' + out.o[n] + oSpace[n] + "</del>";
1641                                 }
1642                         }
1644                         for (i = 0; i < out.n.length; i++) {
1645                                 if (out.n[i].text == null) {
1646                                         str += '<ins>' + out.n[i] + nSpace[i] + "</ins>";
1647                                 }
1648                                 else {
1649                                         var pre = "";
1651                                         for (n = out.n[i].row + 1; n < out.o.length && out.o[n].text == null; n++) {
1652                                                 pre += '<del>' + out.o[n] + oSpace[n] + "</del>";
1653                                         }
1654                                         str += " " + out.n[i].text + nSpace[i] + pre;
1655                                 }
1656                         }
1657                 }
1659                 return str;
1660         };
1661 }());
1663 // for CommonJS enviroments, export everything
1664 if ( typeof exports !== "undefined" || typeof require !== "undefined" ) {
1665         extend(exports, QUnit);
1668 // get at whatever the global object is, like window in browsers
1669 }( (function() {return this;}.call()) ));