Home page Forum Wiki Mail Jabber Devel NIX-FILES

AltLUG Git

Git-repositories of Altai Linux User Group

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

Hosting is provided AltSPAHow to use Git
Please direct any questions on jabber alex.wolf@jabber.ru

Valid HTML 4.0 Transitional Valid CSS!

Designer Alexander Wolf