source: Dev/branches/jQueryUI/client/js/jquery/external/qunit.js @ 249

Last change on this file since 249 was 249, checked in by hendrikvanantwerpen, 13 years ago

This one's for Subversion, because it's so close...

First widget (stripped down sequencer).
Seperated client and server code in two direcotry trees.

File size: 37.4 KB
Line 
1/**
2 * QUnit - A JavaScript Unit Testing Framework
3 *
4 * http://docs.jquery.com/QUnit
5 *
6 * Copyright (c) 2011 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
13var defined = {
14        setTimeout: typeof window.setTimeout !== "undefined",
15        sessionStorage: (function() {
16                try {
17                        return !!sessionStorage.getItem;
18                } catch(e){
19                        return false;
20                }
21  })()
22};
23
24var testId = 0;
25
26var Test = function(name, testName, expected, testEnvironmentArg, async, callback) {
27        this.name = name;
28        this.testName = testName;
29        this.expected = expected;
30        this.testEnvironmentArg = testEnvironmentArg;
31        this.async = async;
32        this.callback = callback;
33        this.assertions = [];
34};
35Test.prototype = {
36        init: function() {
37                var tests = id("qunit-tests");
38                if (tests) {
39                        var b = document.createElement("strong");
40                                b.innerHTML = "Running " + this.name;
41                        var li = document.createElement("li");
42                                li.appendChild( b );
43                                li.className = "running";
44                                li.id = this.id = "test-output" + testId++;
45                        tests.appendChild( li );
46                }
47        },
48        setup: function() {
49                if (this.module != config.previousModule) {
50                        if ( config.previousModule ) {
51                                QUnit.moduleDone( {
52                                        name: config.previousModule,
53                                        failed: config.moduleStats.bad,
54                                        passed: config.moduleStats.all - config.moduleStats.bad,
55                                        total: config.moduleStats.all
56                                } );
57                        }
58                        config.previousModule = this.module;
59                        config.moduleStats = { all: 0, bad: 0 };
60                        QUnit.moduleStart( {
61                                name: this.module
62                        } );
63                }
64
65                config.current = this;
66                this.testEnvironment = extend({
67                        setup: function() {},
68                        teardown: function() {}
69                }, this.moduleTestEnvironment);
70                if (this.testEnvironmentArg) {
71                        extend(this.testEnvironment, this.testEnvironmentArg);
72                }
73
74                QUnit.testStart( {
75                        name: this.testName
76                } );
77
78                // allow utility functions to access the current test environment
79                // TODO why??
80                QUnit.current_testEnvironment = this.testEnvironment;
81
82                try {
83                        if ( !config.pollution ) {
84                                saveGlobal();
85                        }
86
87                        this.testEnvironment.setup.call(this.testEnvironment);
88                } catch(e) {
89                        QUnit.ok( false, "Setup failed on " + this.testName + ": " + e.message );
90                }
91        },
92        run: function() {
93                if ( this.async ) {
94                        QUnit.stop();
95                }
96
97                if ( config.notrycatch ) {
98                        this.callback.call(this.testEnvironment);
99                        return;
100                }
101                try {
102                        this.callback.call(this.testEnvironment);
103                } catch(e) {
104                        fail("Test " + this.testName + " died, exception and test follows", e, this.callback);
105                        QUnit.ok( false, "Died on test #" + (this.assertions.length + 1) + ": " + e.message + " - " + QUnit.jsDump.parse(e) );
106                        // else next test will carry the responsibility
107                        saveGlobal();
108
109                        // Restart the tests if they're blocking
110                        if ( config.blocking ) {
111                                start();
112                        }
113                }
114        },
115        teardown: function() {
116                try {
117                        this.testEnvironment.teardown.call(this.testEnvironment);
118                        checkPollution();
119                } catch(e) {
120                        QUnit.ok( false, "Teardown failed on " + this.testName + ": " + e.message );
121                }
122        },
123        finish: function() {
124                if ( this.expected && this.expected != this.assertions.length ) {
125                        QUnit.ok( false, "Expected " + this.expected + " assertions, but " + this.assertions.length + " were run" );
126                }
127
128                var good = 0, bad = 0,
129                        tests = id("qunit-tests");
130
131                config.stats.all += this.assertions.length;
132                config.moduleStats.all += this.assertions.length;
133
134                if ( tests ) {
135                        var ol  = document.createElement("ol");
136
137                        for ( var i = 0; i < this.assertions.length; i++ ) {
138                                var assertion = this.assertions[i];
139
140                                var li = document.createElement("li");
141                                li.className = assertion.result ? "pass" : "fail";
142                                li.innerHTML = assertion.message || (assertion.result ? "okay" : "failed");
143                                ol.appendChild( li );
144
145                                if ( assertion.result ) {
146                                        good++;
147                                } else {
148                                        bad++;
149                                        config.stats.bad++;
150                                        config.moduleStats.bad++;
151                                }
152                        }
153
154                        // store result when possible
155                        if ( QUnit.config.reorder && defined.sessionStorage ) {
156                                if (bad) {
157                                        sessionStorage.setItem("qunit-" + this.module + "-" + this.testName, bad);
158                                } else {
159                                        sessionStorage.removeItem("qunit-" + this.module + "-" + this.testName);
160                                }
161                        }
162
163                        if (bad == 0) {
164                                ol.style.display = "none";
165                        }
166
167                        var b = document.createElement("strong");
168                        b.innerHTML = this.name + " <b class='counts'>(<b class='failed'>" + bad + "</b>, <b class='passed'>" + good + "</b>, " + this.assertions.length + ")</b>";
169
170                        var a = document.createElement("a");
171                        a.innerHTML = "Rerun";
172                        a.href = QUnit.url({ filter: getText([b]).replace(/\([^)]+\)$/, "").replace(/(^\s*|\s*$)/g, "") });
173
174                        addEvent(b, "click", function() {
175                                var next = b.nextSibling.nextSibling,
176                                        display = next.style.display;
177                                next.style.display = display === "none" ? "block" : "none";
178                        });
179
180                        addEvent(b, "dblclick", function(e) {
181                                var target = e && e.target ? e.target : window.event.srcElement;
182                                if ( target.nodeName.toLowerCase() == "span" || target.nodeName.toLowerCase() == "b" ) {
183                                        target = target.parentNode;
184                                }
185                                if ( window.location && target.nodeName.toLowerCase() === "strong" ) {
186                                        window.location = QUnit.url({ filter: getText([target]).replace(/\([^)]+\)$/, "").replace(/(^\s*|\s*$)/g, "") });
187                                }
188                        });
189
190                        var li = id(this.id);
191                        li.className = bad ? "fail" : "pass";
192                        li.removeChild( li.firstChild );
193                        li.appendChild( b );
194                        li.appendChild( a );
195                        li.appendChild( ol );
196
197                } else {
198                        for ( var i = 0; i < this.assertions.length; i++ ) {
199                                if ( !this.assertions[i].result ) {
200                                        bad++;
201                                        config.stats.bad++;
202                                        config.moduleStats.bad++;
203                                }
204                        }
205                }
206
207                try {
208                        QUnit.reset();
209                } catch(e) {
210                        fail("reset() failed, following Test " + this.testName + ", exception and reset fn follows", e, QUnit.reset);
211                }
212
213                QUnit.testDone( {
214                        name: this.testName,
215                        failed: bad,
216                        passed: this.assertions.length - bad,
217                        total: this.assertions.length
218                } );
219        },
220
221        queue: function() {
222                var test = this;
223                synchronize(function() {
224                        test.init();
225                });
226                function run() {
227                        // each of these can by async
228                        synchronize(function() {
229                                test.setup();
230                        });
231                        synchronize(function() {
232                                test.run();
233                        });
234                        synchronize(function() {
235                                test.teardown();
236                        });
237                        synchronize(function() {
238                                test.finish();
239                        });
240                }
241                // defer when previous test run passed, if storage is available
242                var bad = QUnit.config.reorder && defined.sessionStorage && +sessionStorage.getItem("qunit-" + this.module + "-" + this.testName);
243                if (bad) {
244                        run();
245                } else {
246                        synchronize(run);
247                };
248        }
249
250};
251
252var QUnit = {
253
254        // call on start of module test to prepend name to all tests
255        module: function(name, testEnvironment) {
256                config.currentModule = name;
257                config.currentModuleTestEnviroment = testEnvironment;
258        },
259
260        asyncTest: function(testName, expected, callback) {
261                if ( arguments.length === 2 ) {
262                        callback = expected;
263                        expected = 0;
264                }
265
266                QUnit.test(testName, expected, callback, true);
267        },
268
269        test: function(testName, expected, callback, async) {
270                var name = '<span class="test-name">' + testName + '</span>', testEnvironmentArg;
271
272                if ( arguments.length === 2 ) {
273                        callback = expected;
274                        expected = null;
275                }
276                // is 2nd argument a testEnvironment?
277                if ( expected && typeof expected === 'object') {
278                        testEnvironmentArg =  expected;
279                        expected = null;
280                }
281
282                if ( config.currentModule ) {
283                        name = '<span class="module-name">' + config.currentModule + "</span>: " + name;
284                }
285
286                if ( !validTest(config.currentModule + ": " + testName) ) {
287                        return;
288                }
289
290                var test = new Test(name, testName, expected, testEnvironmentArg, async, callback);
291                test.module = config.currentModule;
292                test.moduleTestEnvironment = config.currentModuleTestEnviroment;
293                test.queue();
294        },
295
296        /**
297         * Specify the number of expected assertions to gurantee that failed test (no assertions are run at all) don't slip through.
298         */
299        expect: function(asserts) {
300                config.current.expected = asserts;
301        },
302
303        /**
304         * Asserts true.
305         * @example ok( "asdfasdf".length > 5, "There must be at least 5 chars" );
306         */
307        ok: function(a, msg) {
308                a = !!a;
309                var details = {
310                        result: a,
311                        message: msg
312                };
313                msg = escapeHtml(msg);
314                QUnit.log(details);
315                config.current.assertions.push({
316                        result: a,
317                        message: msg
318                });
319        },
320
321        /**
322         * Checks that the first two arguments are equal, with an optional message.
323         * Prints out both actual and expected values.
324         *
325         * Prefered to ok( actual == expected, message )
326         *
327         * @example equal( format("Received {0} bytes.", 2), "Received 2 bytes." );
328         *
329         * @param Object actual
330         * @param Object expected
331         * @param String message (optional)
332         */
333        equal: function(actual, expected, message) {
334                QUnit.push(expected == actual, actual, expected, message);
335        },
336
337        notEqual: function(actual, expected, message) {
338                QUnit.push(expected != actual, actual, expected, message);
339        },
340
341        deepEqual: function(actual, expected, message) {
342                QUnit.push(QUnit.equiv(actual, expected), actual, expected, message);
343        },
344
345        notDeepEqual: function(actual, expected, message) {
346                QUnit.push(!QUnit.equiv(actual, expected), actual, expected, message);
347        },
348
349        strictEqual: function(actual, expected, message) {
350                QUnit.push(expected === actual, actual, expected, message);
351        },
352
353        notStrictEqual: function(actual, expected, message) {
354                QUnit.push(expected !== actual, actual, expected, message);
355        },
356
357        raises: function(block, expected, message) {
358                var actual, ok = false;
359
360                if (typeof expected === 'string') {
361                        message = expected;
362                        expected = null;
363                }
364
365                try {
366                        block();
367                } catch (e) {
368                        actual = e;
369                }
370
371                if (actual) {
372                        // we don't want to validate thrown error
373                        if (!expected) {
374                                ok = true;
375                        // expected is a regexp
376                        } else if (QUnit.objectType(expected) === "regexp") {
377                                ok = expected.test(actual);
378                        // expected is a constructor
379                        } else if (actual instanceof expected) {
380                                ok = true;
381                        // expected is a validation function which returns true is validation passed
382                        } else if (expected.call({}, actual) === true) {
383                                ok = true;
384                        }
385                }
386
387                QUnit.ok(ok, message);
388        },
389
390        start: function() {
391                config.semaphore--;
392                if (config.semaphore > 0) {
393                        // don't start until equal number of stop-calls
394                        return;
395                }
396                if (config.semaphore < 0) {
397                        // ignore if start is called more often then stop
398                        config.semaphore = 0;
399                }
400                // A slight delay, to avoid any current callbacks
401                if ( defined.setTimeout ) {
402                        window.setTimeout(function() {
403                                if ( config.timeout ) {
404                                        clearTimeout(config.timeout);
405                                }
406
407                                config.blocking = false;
408                                process();
409                        }, 13);
410                } else {
411                        config.blocking = false;
412                        process();
413                }
414        },
415
416        stop: function(timeout) {
417                config.semaphore++;
418                config.blocking = true;
419
420                if ( timeout && defined.setTimeout ) {
421                        clearTimeout(config.timeout);
422                        config.timeout = window.setTimeout(function() {
423                                QUnit.ok( false, "Test timed out" );
424                                QUnit.start();
425                        }, timeout);
426                }
427        }
428};
429
430// Backwards compatibility, deprecated
431QUnit.equals = QUnit.equal;
432QUnit.same = QUnit.deepEqual;
433
434// Maintain internal state
435var config = {
436        // The queue of tests to run
437        queue: [],
438
439        // block until document ready
440        blocking: true,
441
442        // by default, run previously failed tests first
443        // very useful in combination with "Hide passed tests" checked
444        reorder: true,
445
446        noglobals: false,
447        notrycatch: false
448};
449
450// Load paramaters
451(function() {
452        var location = window.location || { search: "", protocol: "file:" },
453                params = location.search.slice( 1 ).split( "&" ),
454                length = params.length,
455                urlParams = {},
456                current;
457
458        if ( params[ 0 ] ) {
459                for ( var i = 0; i < length; i++ ) {
460                        current = params[ i ].split( "=" );
461                        current[ 0 ] = decodeURIComponent( current[ 0 ] );
462                        // allow just a key to turn on a flag, e.g., test.html?noglobals
463                        current[ 1 ] = current[ 1 ] ? decodeURIComponent( current[ 1 ] ) : true;
464                        urlParams[ current[ 0 ] ] = current[ 1 ];
465                        if ( current[ 0 ] in config ) {
466                                config[ current[ 0 ] ] = current[ 1 ];
467                        }
468                }
469        }
470
471        QUnit.urlParams = urlParams;
472        config.filter = urlParams.filter;
473
474        // Figure out if we're running the tests from a server or not
475        QUnit.isLocal = !!(location.protocol === 'file:');
476})();
477
478// Expose the API as global variables, unless an 'exports'
479// object exists, in that case we assume we're in CommonJS
480if ( typeof exports === "undefined" || typeof require === "undefined" ) {
481        extend(window, QUnit);
482        window.QUnit = QUnit;
483} else {
484        extend(exports, QUnit);
485        exports.QUnit = QUnit;
486}
487
488// define these after exposing globals to keep them in these QUnit namespace only
489extend(QUnit, {
490        config: config,
491
492        // Initialize the configuration options
493        init: function() {
494                extend(config, {
495                        stats: { all: 0, bad: 0 },
496                        moduleStats: { all: 0, bad: 0 },
497                        started: +new Date,
498                        updateRate: 1000,
499                        blocking: false,
500                        autostart: true,
501                        autorun: false,
502                        filter: "",
503                        queue: [],
504                        semaphore: 0
505                });
506
507                var tests = id( "qunit-tests" ),
508                        banner = id( "qunit-banner" ),
509                        result = id( "qunit-testresult" );
510
511                if ( tests ) {
512                        tests.innerHTML = "";
513                }
514
515                if ( banner ) {
516                        banner.className = "";
517                }
518
519                if ( result ) {
520                        result.parentNode.removeChild( result );
521                }
522
523                if ( tests ) {
524                        result = document.createElement( "p" );
525                        result.id = "qunit-testresult";
526                        result.className = "result";
527                        tests.parentNode.insertBefore( result, tests );
528                        result.innerHTML = 'Running...<br/>&nbsp;';
529                }
530        },
531
532        /**
533         * Resets the test setup. Useful for tests that modify the DOM.
534         *
535         * If jQuery is available, uses jQuery's html(), otherwise just innerHTML.
536         */
537        reset: function() {
538                if ( window.jQuery ) {
539                        jQuery( "#qunit-fixture" ).html( config.fixture );
540                } else {
541                        var main = id( 'qunit-fixture' );
542                        if ( main ) {
543                                main.innerHTML = config.fixture;
544                        }
545                }
546        },
547
548        /**
549         * Trigger an event on an element.
550         *
551         * @example triggerEvent( document.body, "click" );
552         *
553         * @param DOMElement elem
554         * @param String type
555         */
556        triggerEvent: function( elem, type, event ) {
557                if ( document.createEvent ) {
558                        event = document.createEvent("MouseEvents");
559                        event.initMouseEvent(type, true, true, elem.ownerDocument.defaultView,
560                                0, 0, 0, 0, 0, false, false, false, false, 0, null);
561                        elem.dispatchEvent( event );
562
563                } else if ( elem.fireEvent ) {
564                        elem.fireEvent("on"+type);
565                }
566        },
567
568        // Safe object type checking
569        is: function( type, obj ) {
570                return QUnit.objectType( obj ) == type;
571        },
572
573        objectType: function( obj ) {
574                if (typeof obj === "undefined") {
575                                return "undefined";
576
577                // consider: typeof null === object
578                }
579                if (obj === null) {
580                                return "null";
581                }
582
583                var type = Object.prototype.toString.call( obj )
584                        .match(/^\[object\s(.*)\]$/)[1] || '';
585
586                switch (type) {
587                                case 'Number':
588                                                if (isNaN(obj)) {
589                                                                return "nan";
590                                                } else {
591                                                                return "number";
592                                                }
593                                case 'String':
594                                case 'Boolean':
595                                case 'Array':
596                                case 'Date':
597                                case 'RegExp':
598                                case 'Function':
599                                                return type.toLowerCase();
600                }
601                if (typeof obj === "object") {
602                                return "object";
603                }
604                return undefined;
605        },
606
607        push: function(result, actual, expected, message) {
608                var details = {
609                        result: result,
610                        message: message,
611                        actual: actual,
612                        expected: expected
613                };
614
615                message = escapeHtml(message) || (result ? "okay" : "failed");
616                message = '<span class="test-message">' + message + "</span>";
617                expected = escapeHtml(QUnit.jsDump.parse(expected));
618                actual = escapeHtml(QUnit.jsDump.parse(actual));
619                var output = message + '<table><tr class="test-expected"><th>Expected: </th><td><pre>' + expected + '</pre></td></tr>';
620                if (actual != expected) {
621                        output += '<tr class="test-actual"><th>Result: </th><td><pre>' + actual + '</pre></td></tr>';
622                        output += '<tr class="test-diff"><th>Diff: </th><td><pre>' + QUnit.diff(expected, actual) +'</pre></td></tr>';
623                }
624                if (!result) {
625                        var source = sourceFromStacktrace();
626                        if (source) {
627                                details.source = source;
628                                output += '<tr class="test-source"><th>Source: </th><td><pre>' + escapeHtml(source) + '</pre></td></tr>';
629                        }
630                }
631                output += "</table>";
632
633                QUnit.log(details);
634
635                config.current.assertions.push({
636                        result: !!result,
637                        message: output
638                });
639        },
640
641        url: function( params ) {
642                params = extend( extend( {}, QUnit.urlParams ), params );
643                var querystring = "?",
644                        key;
645                for ( key in params ) {
646                        querystring += encodeURIComponent( key ) + "=" +
647                                encodeURIComponent( params[ key ] ) + "&";
648                }
649                return window.location.pathname + querystring.slice( 0, -1 );
650        },
651
652        // Logging callbacks; all receive a single argument with the listed properties
653        // run test/logs.html for any related changes
654        begin: function() {},
655        // done: { failed, passed, total, runtime }
656        done: function() {},
657        // log: { result, actual, expected, message }
658        log: function() {},
659        // testStart: { name }
660        testStart: function() {},
661        // testDone: { name, failed, passed, total }
662        testDone: function() {},
663        // moduleStart: { name }
664        moduleStart: function() {},
665        // moduleDone: { name, failed, passed, total }
666        moduleDone: function() {}
667});
668
669if ( typeof document === "undefined" || document.readyState === "complete" ) {
670        config.autorun = true;
671}
672
673addEvent(window, "load", function() {
674        QUnit.begin({});
675
676        // Initialize the config, saving the execution queue
677        var oldconfig = extend({}, config);
678        QUnit.init();
679        extend(config, oldconfig);
680
681        config.blocking = false;
682
683        var userAgent = id("qunit-userAgent");
684        if ( userAgent ) {
685                userAgent.innerHTML = navigator.userAgent;
686        }
687        var banner = id("qunit-header");
688        if ( banner ) {
689                banner.innerHTML = '<a href="' + QUnit.url({ filter: undefined }) + '"> ' + banner.innerHTML + '</a> ' +
690                        '<label><input name="noglobals" type="checkbox"' + ( config.noglobals ? ' checked="checked"' : '' ) + '>noglobals</label>' +
691                        '<label><input name="notrycatch" type="checkbox"' + ( config.notrycatch ? ' checked="checked"' : '' ) + '>notrycatch</label>';
692                addEvent( banner, "change", function( event ) {
693                        var params = {};
694                        params[ event.target.name ] = event.target.checked ? true : undefined;
695                        window.location = QUnit.url( params );
696                });
697        }
698
699        var toolbar = id("qunit-testrunner-toolbar");
700        if ( toolbar ) {
701                var filter = document.createElement("input");
702                filter.type = "checkbox";
703                filter.id = "qunit-filter-pass";
704                addEvent( filter, "click", function() {
705                        var ol = document.getElementById("qunit-tests");
706                        if ( filter.checked ) {
707                                ol.className = ol.className + " hidepass";
708                        } else {
709                                var tmp = " " + ol.className.replace( /[\n\t\r]/g, " " ) + " ";
710                                ol.className = tmp.replace(/ hidepass /, " ");
711                        }
712                        if ( defined.sessionStorage ) {
713                                if (filter.checked) {
714                                        sessionStorage.setItem("qunit-filter-passed-tests",  "true");
715                                } else {
716                                        sessionStorage.removeItem("qunit-filter-passed-tests");
717                                }
718                        }
719                });
720                if ( defined.sessionStorage && sessionStorage.getItem("qunit-filter-passed-tests") ) {
721                        filter.checked = true;
722                        var ol = document.getElementById("qunit-tests");
723                        ol.className = ol.className + " hidepass";
724                }
725                toolbar.appendChild( filter );
726
727                var label = document.createElement("label");
728                label.setAttribute("for", "qunit-filter-pass");
729                label.innerHTML = "Hide passed tests";
730                toolbar.appendChild( label );
731        }
732
733        var main = id('qunit-fixture');
734        if ( main ) {
735                config.fixture = main.innerHTML;
736        }
737
738        if (config.autostart) {
739                QUnit.start();
740        }
741});
742
743function done() {
744        config.autorun = true;
745
746        // Log the last module results
747        if ( config.currentModule ) {
748                QUnit.moduleDone( {
749                        name: config.currentModule,
750                        failed: config.moduleStats.bad,
751                        passed: config.moduleStats.all - config.moduleStats.bad,
752                        total: config.moduleStats.all
753                } );
754        }
755
756        var banner = id("qunit-banner"),
757                tests = id("qunit-tests"),
758                runtime = +new Date - config.started,
759                passed = config.stats.all - config.stats.bad,
760                html = [
761                        'Tests completed in ',
762                        runtime,
763                        ' milliseconds.<br/>',
764                        '<span class="passed">',
765                        passed,
766                        '</span> tests of <span class="total">',
767                        config.stats.all,
768                        '</span> passed, <span class="failed">',
769                        config.stats.bad,
770                        '</span> failed.'
771                ].join('');
772
773        if ( banner ) {
774                banner.className = (config.stats.bad ? "qunit-fail" : "qunit-pass");
775        }
776
777        if ( tests ) {
778                id( "qunit-testresult" ).innerHTML = html;
779        }
780
781        if ( typeof document !== "undefined" && document.title ) {
782                // show ✖ for bad, ✔ for good suite result in title
783                // use escape sequences in case file gets loaded with non-utf-8-charset
784                document.title = (config.stats.bad ? "\u2716" : "\u2714") + " " + document.title;
785        }
786
787        QUnit.done( {
788                failed: config.stats.bad,
789                passed: passed,
790                total: config.stats.all,
791                runtime: runtime
792        } );
793}
794
795function validTest( name ) {
796        var filter = config.filter,
797                run = false;
798
799        if ( !filter ) {
800                return true;
801        }
802
803        var not = filter.charAt( 0 ) === "!";
804        if ( not ) {
805                filter = filter.slice( 1 );
806        }
807
808        if ( name.indexOf( filter ) !== -1 ) {
809                return !not;
810        }
811
812        if ( not ) {
813                run = true;
814        }
815
816        return run;
817}
818
819// so far supports only Firefox, Chrome and Opera (buggy)
820// could be extended in the future to use something like https://github.com/csnover/TraceKit
821function sourceFromStacktrace() {
822        try {
823                throw new Error();
824        } catch ( e ) {
825                if (e.stacktrace) {
826                        // Opera
827                        return e.stacktrace.split("\n")[6];
828                } else if (e.stack) {
829                        // Firefox, Chrome
830                        return e.stack.split("\n")[4];
831                }
832        }
833}
834
835function escapeHtml(s) {
836        if (!s) {
837                return "";
838        }
839        s = s + "";
840        return s.replace(/[\&"<>\\]/g, function(s) {
841                switch(s) {
842                        case "&": return "&amp;";
843                        case "\\": return "\\\\";
844                        case '"': return '\"';
845                        case "<": return "&lt;";
846                        case ">": return "&gt;";
847                        default: return s;
848                }
849        });
850}
851
852function synchronize( callback ) {
853        config.queue.push( callback );
854
855        if ( config.autorun && !config.blocking ) {
856                process();
857        }
858}
859
860function process() {
861        var start = (new Date()).getTime();
862
863        while ( config.queue.length && !config.blocking ) {
864                if ( config.updateRate <= 0 || (((new Date()).getTime() - start) < config.updateRate) ) {
865                        config.queue.shift()();
866                } else {
867                        window.setTimeout( process, 13 );
868                        break;
869                }
870        }
871  if (!config.blocking && !config.queue.length) {
872    done();
873  }
874}
875
876function saveGlobal() {
877        config.pollution = [];
878
879        if ( config.noglobals ) {
880                for ( var key in window ) {
881                        config.pollution.push( key );
882                }
883        }
884}
885
886function checkPollution( name ) {
887        var old = config.pollution;
888        saveGlobal();
889
890        var newGlobals = diff( config.pollution, old );
891        if ( newGlobals.length > 0 ) {
892                ok( false, "Introduced global variable(s): " + newGlobals.join(", ") );
893        }
894
895        var deletedGlobals = diff( old, config.pollution );
896        if ( deletedGlobals.length > 0 ) {
897                ok( false, "Deleted global variable(s): " + deletedGlobals.join(", ") );
898        }
899}
900
901// returns a new Array with the elements that are in a but not in b
902function diff( a, b ) {
903        var result = a.slice();
904        for ( var i = 0; i < result.length; i++ ) {
905                for ( var j = 0; j < b.length; j++ ) {
906                        if ( result[i] === b[j] ) {
907                                result.splice(i, 1);
908                                i--;
909                                break;
910                        }
911                }
912        }
913        return result;
914}
915
916function fail(message, exception, callback) {
917        if ( typeof console !== "undefined" && console.error && console.warn ) {
918                console.error(message);
919                console.error(exception);
920                console.warn(callback.toString());
921
922        } else if ( window.opera && opera.postError ) {
923                opera.postError(message, exception, callback.toString);
924        }
925}
926
927function extend(a, b) {
928        for ( var prop in b ) {
929                if ( b[prop] === undefined ) {
930                        delete a[prop];
931                } else {
932                        a[prop] = b[prop];
933                }
934        }
935
936        return a;
937}
938
939function addEvent(elem, type, fn) {
940        if ( elem.addEventListener ) {
941                elem.addEventListener( type, fn, false );
942        } else if ( elem.attachEvent ) {
943                elem.attachEvent( "on" + type, fn );
944        } else {
945                fn();
946        }
947}
948
949function id(name) {
950        return !!(typeof document !== "undefined" && document && document.getElementById) &&
951                document.getElementById( name );
952}
953
954// Test for equality any JavaScript type.
955// Discussions and reference: http://philrathe.com/articles/equiv
956// Test suites: http://philrathe.com/tests/equiv
957// Author: Philippe Rathé <prathe@gmail.com>
958QUnit.equiv = function () {
959
960    var innerEquiv; // the real equiv function
961    var callers = []; // stack to decide between skip/abort functions
962    var parents = []; // stack to avoiding loops from circular referencing
963
964    // Call the o related callback with the given arguments.
965    function bindCallbacks(o, callbacks, args) {
966        var prop = QUnit.objectType(o);
967        if (prop) {
968            if (QUnit.objectType(callbacks[prop]) === "function") {
969                return callbacks[prop].apply(callbacks, args);
970            } else {
971                return callbacks[prop]; // or undefined
972            }
973        }
974    }
975
976    var callbacks = function () {
977
978        // for string, boolean, number and null
979        function useStrictEquality(b, a) {
980            if (b instanceof a.constructor || a instanceof b.constructor) {
981                // to catch short annotaion VS 'new' annotation of a declaration
982                // e.g. var i = 1;
983                //      var j = new Number(1);
984                return a == b;
985            } else {
986                return a === b;
987            }
988        }
989
990        return {
991            "string": useStrictEquality,
992            "boolean": useStrictEquality,
993            "number": useStrictEquality,
994            "null": useStrictEquality,
995            "undefined": useStrictEquality,
996
997            "nan": function (b) {
998                return isNaN(b);
999            },
1000
1001            "date": function (b, a) {
1002                return QUnit.objectType(b) === "date" && a.valueOf() === b.valueOf();
1003            },
1004
1005            "regexp": function (b, a) {
1006                return QUnit.objectType(b) === "regexp" &&
1007                    a.source === b.source && // the regex itself
1008                    a.global === b.global && // and its modifers (gmi) ...
1009                    a.ignoreCase === b.ignoreCase &&
1010                    a.multiline === b.multiline;
1011            },
1012
1013            // - skip when the property is a method of an instance (OOP)
1014            // - abort otherwise,
1015            //   initial === would have catch identical references anyway
1016            "function": function () {
1017                var caller = callers[callers.length - 1];
1018                return caller !== Object &&
1019                        typeof caller !== "undefined";
1020            },
1021
1022            "array": function (b, a) {
1023                var i, j, loop;
1024                var len;
1025
1026                // b could be an object literal here
1027                if ( ! (QUnit.objectType(b) === "array")) {
1028                    return false;
1029                }
1030
1031                len = a.length;
1032                if (len !== b.length) { // safe and faster
1033                    return false;
1034                }
1035
1036                //track reference to avoid circular references
1037                parents.push(a);
1038                for (i = 0; i < len; i++) {
1039                    loop = false;
1040                    for(j=0;j<parents.length;j++){
1041                        if(parents[j] === a[i]){
1042                            loop = true;//dont rewalk array
1043                        }
1044                    }
1045                    if (!loop && ! innerEquiv(a[i], b[i])) {
1046                        parents.pop();
1047                        return false;
1048                    }
1049                }
1050                parents.pop();
1051                return true;
1052            },
1053
1054            "object": function (b, a) {
1055                var i, j, loop;
1056                var eq = true; // unless we can proove it
1057                var aProperties = [], bProperties = []; // collection of strings
1058
1059                // comparing constructors is more strict than using instanceof
1060                if ( a.constructor !== b.constructor) {
1061                    return false;
1062                }
1063
1064                // stack constructor before traversing properties
1065                callers.push(a.constructor);
1066                //track reference to avoid circular references
1067                parents.push(a);
1068
1069                for (i in a) { // be strict: don't ensures hasOwnProperty and go deep
1070                    loop = false;
1071                    for(j=0;j<parents.length;j++){
1072                        if(parents[j] === a[i])
1073                            loop = true; //don't go down the same path twice
1074                    }
1075                    aProperties.push(i); // collect a's properties
1076
1077                    if (!loop && ! innerEquiv(a[i], b[i])) {
1078                        eq = false;
1079                        break;
1080                    }
1081                }
1082
1083                callers.pop(); // unstack, we are done
1084                parents.pop();
1085
1086                for (i in b) {
1087                    bProperties.push(i); // collect b's properties
1088                }
1089
1090                // Ensures identical properties name
1091                return eq && innerEquiv(aProperties.sort(), bProperties.sort());
1092            }
1093        };
1094    }();
1095
1096    innerEquiv = function () { // can take multiple arguments
1097        var args = Array.prototype.slice.apply(arguments);
1098        if (args.length < 2) {
1099            return true; // end transition
1100        }
1101
1102        return (function (a, b) {
1103            if (a === b) {
1104                return true; // catch the most you can
1105            } else if (a === null || b === null || typeof a === "undefined" || typeof b === "undefined" || QUnit.objectType(a) !== QUnit.objectType(b)) {
1106                return false; // don't lose time with error prone cases
1107            } else {
1108                return bindCallbacks(a, callbacks, [b, a]);
1109            }
1110
1111        // apply transition with (1..n) arguments
1112        })(args[0], args[1]) && arguments.callee.apply(this, args.splice(1, args.length -1));
1113    };
1114
1115    return innerEquiv;
1116
1117}();
1118
1119/**
1120 * jsDump
1121 * Copyright (c) 2008 Ariel Flesler - aflesler(at)gmail(dot)com | http://flesler.blogspot.com
1122 * Licensed under BSD (http://www.opensource.org/licenses/bsd-license.php)
1123 * Date: 5/15/2008
1124 * @projectDescription Advanced and extensible data dumping for Javascript.
1125 * @version 1.0.0
1126 * @author Ariel Flesler
1127 * @link {http://flesler.blogspot.com/2008/05/jsdump-pretty-dump-of-any-javascript.html}
1128 */
1129QUnit.jsDump = (function() {
1130        function quote( str ) {
1131                return '"' + str.toString().replace(/"/g, '\\"') + '"';
1132        };
1133        function literal( o ) {
1134                return o + '';
1135        };
1136        function join( pre, arr, post ) {
1137                var s = jsDump.separator(),
1138                        base = jsDump.indent(),
1139                        inner = jsDump.indent(1);
1140                if ( arr.join )
1141                        arr = arr.join( ',' + s + inner );
1142                if ( !arr )
1143                        return pre + post;
1144                return [ pre, inner + arr, base + post ].join(s);
1145        };
1146        function array( arr ) {
1147                var i = arr.length,     ret = Array(i);
1148                this.up();
1149                while ( i-- )
1150                        ret[i] = this.parse( arr[i] );
1151                this.down();
1152                return join( '[', ret, ']' );
1153        };
1154
1155        var reName = /^function (\w+)/;
1156
1157        var jsDump = {
1158                parse:function( obj, type ) { //type is used mostly internally, you can fix a (custom)type in advance
1159                        var     parser = this.parsers[ type || this.typeOf(obj) ];
1160                        type = typeof parser;
1161
1162                        return type == 'function' ? parser.call( this, obj ) :
1163                                   type == 'string' ? parser :
1164                                   this.parsers.error;
1165                },
1166                typeOf:function( obj ) {
1167                        var type;
1168                        if ( obj === null ) {
1169                                type = "null";
1170                        } else if (typeof obj === "undefined") {
1171                                type = "undefined";
1172                        } else if (QUnit.is("RegExp", obj)) {
1173                                type = "regexp";
1174                        } else if (QUnit.is("Date", obj)) {
1175                                type = "date";
1176                        } else if (QUnit.is("Function", obj)) {
1177                                type = "function";
1178                        } else if (typeof obj.setInterval !== undefined && typeof obj.document !== "undefined" && typeof obj.nodeType === "undefined") {
1179                                type = "window";
1180                        } else if (obj.nodeType === 9) {
1181                                type = "document";
1182                        } else if (obj.nodeType) {
1183                                type = "node";
1184                        } else if (typeof obj === "object" && typeof obj.length === "number" && obj.length >= 0) {
1185                                type = "array";
1186                        } else {
1187                                type = typeof obj;
1188                        }
1189                        return type;
1190                },
1191                separator:function() {
1192                        return this.multiline ? this.HTML ? '<br />' : '\n' : this.HTML ? '&nbsp;' : ' ';
1193                },
1194                indent:function( extra ) {// extra can be a number, shortcut for increasing-calling-decreasing
1195                        if ( !this.multiline )
1196                                return '';
1197                        var chr = this.indentChar;
1198                        if ( this.HTML )
1199                                chr = chr.replace(/\t/g,'   ').replace(/ /g,'&nbsp;');
1200                        return Array( this._depth_ + (extra||0) ).join(chr);
1201                },
1202                up:function( a ) {
1203                        this._depth_ += a || 1;
1204                },
1205                down:function( a ) {
1206                        this._depth_ -= a || 1;
1207                },
1208                setParser:function( name, parser ) {
1209                        this.parsers[name] = parser;
1210                },
1211                // The next 3 are exposed so you can use them
1212                quote:quote,
1213                literal:literal,
1214                join:join,
1215                //
1216                _depth_: 1,
1217                // This is the list of parsers, to modify them, use jsDump.setParser
1218                parsers:{
1219                        window: '[Window]',
1220                        document: '[Document]',
1221                        error:'[ERROR]', //when no parser is found, shouldn't happen
1222                        unknown: '[Unknown]',
1223                        'null':'null',
1224                        'undefined':'undefined',
1225                        'function':function( fn ) {
1226                                var ret = 'function',
1227                                        name = 'name' in fn ? fn.name : (reName.exec(fn)||[])[1];//functions never have name in IE
1228                                if ( name )
1229                                        ret += ' ' + name;
1230                                ret += '(';
1231
1232                                ret = [ ret, QUnit.jsDump.parse( fn, 'functionArgs' ), '){'].join('');
1233                                return join( ret, QUnit.jsDump.parse(fn,'functionCode'), '}' );
1234                        },
1235                        array: array,
1236                        nodelist: array,
1237                        arguments: array,
1238                        object:function( map ) {
1239                                var ret = [ ];
1240                                QUnit.jsDump.up();
1241                                for ( var key in map )
1242                                        ret.push( QUnit.jsDump.parse(key,'key') + ': ' + QUnit.jsDump.parse(map[key]) );
1243                                QUnit.jsDump.down();
1244                                return join( '{', ret, '}' );
1245                        },
1246                        node:function( node ) {
1247                                var open = QUnit.jsDump.HTML ? '&lt;' : '<',
1248                                        close = QUnit.jsDump.HTML ? '&gt;' : '>';
1249
1250                                var tag = node.nodeName.toLowerCase(),
1251                                        ret = open + tag;
1252
1253                                for ( var a in QUnit.jsDump.DOMAttrs ) {
1254                                        var val = node[QUnit.jsDump.DOMAttrs[a]];
1255                                        if ( val )
1256                                                ret += ' ' + a + '=' + QUnit.jsDump.parse( val, 'attribute' );
1257                                }
1258                                return ret + close + open + '/' + tag + close;
1259                        },
1260                        functionArgs:function( fn ) {//function calls it internally, it's the arguments part of the function
1261                                var l = fn.length;
1262                                if ( !l ) return '';
1263
1264                                var args = Array(l);
1265                                while ( l-- )
1266                                        args[l] = String.fromCharCode(97+l);//97 is 'a'
1267                                return ' ' + args.join(', ') + ' ';
1268                        },
1269                        key:quote, //object calls it internally, the key part of an item in a map
1270                        functionCode:'[code]', //function calls it internally, it's the content of the function
1271                        attribute:quote, //node calls it internally, it's an html attribute value
1272                        string:quote,
1273                        date:quote,
1274                        regexp:literal, //regex
1275                        number:literal,
1276                        'boolean':literal
1277                },
1278                DOMAttrs:{//attributes to dump from nodes, name=>realName
1279                        id:'id',
1280                        name:'name',
1281                        'class':'className'
1282                },
1283                HTML:false,//if true, entities are escaped ( <, >, \t, space and \n )
1284                indentChar:'  ',//indentation unit
1285                multiline:true //if true, items in a collection, are separated by a \n, else just a space.
1286        };
1287
1288        return jsDump;
1289})();
1290
1291// from Sizzle.js
1292function getText( elems ) {
1293        var ret = "", elem;
1294
1295        for ( var i = 0; elems[i]; i++ ) {
1296                elem = elems[i];
1297
1298                // Get the text from text nodes and CDATA nodes
1299                if ( elem.nodeType === 3 || elem.nodeType === 4 ) {
1300                        ret += elem.nodeValue;
1301
1302                // Traverse everything else, except comment nodes
1303                } else if ( elem.nodeType !== 8 ) {
1304                        ret += getText( elem.childNodes );
1305                }
1306        }
1307
1308        return ret;
1309};
1310
1311/*
1312 * Javascript Diff Algorithm
1313 *  By John Resig (http://ejohn.org/)
1314 *  Modified by Chu Alan "sprite"
1315 *
1316 * Released under the MIT license.
1317 *
1318 * More Info:
1319 *  http://ejohn.org/projects/javascript-diff-algorithm/
1320 *
1321 * Usage: QUnit.diff(expected, actual)
1322 *
1323 * 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"
1324 */
1325QUnit.diff = (function() {
1326        function diff(o, n){
1327                var ns = new Object();
1328                var os = new Object();
1329
1330                for (var i = 0; i < n.length; i++) {
1331                        if (ns[n[i]] == null)
1332                                ns[n[i]] = {
1333                                        rows: new Array(),
1334                                        o: null
1335                                };
1336                        ns[n[i]].rows.push(i);
1337                }
1338
1339                for (var i = 0; i < o.length; i++) {
1340                        if (os[o[i]] == null)
1341                                os[o[i]] = {
1342                                        rows: new Array(),
1343                                        n: null
1344                                };
1345                        os[o[i]].rows.push(i);
1346                }
1347
1348                for (var i in ns) {
1349                        if (ns[i].rows.length == 1 && typeof(os[i]) != "undefined" && os[i].rows.length == 1) {
1350                                n[ns[i].rows[0]] = {
1351                                        text: n[ns[i].rows[0]],
1352                                        row: os[i].rows[0]
1353                                };
1354                                o[os[i].rows[0]] = {
1355                                        text: o[os[i].rows[0]],
1356                                        row: ns[i].rows[0]
1357                                };
1358                        }
1359                }
1360
1361                for (var i = 0; i < n.length - 1; i++) {
1362                        if (n[i].text != null && n[i + 1].text == null && n[i].row + 1 < o.length && o[n[i].row + 1].text == null &&
1363                        n[i + 1] == o[n[i].row + 1]) {
1364                                n[i + 1] = {
1365                                        text: n[i + 1],
1366                                        row: n[i].row + 1
1367                                };
1368                                o[n[i].row + 1] = {
1369                                        text: o[n[i].row + 1],
1370                                        row: i + 1
1371                                };
1372                        }
1373                }
1374
1375                for (var i = n.length - 1; i > 0; i--) {
1376                        if (n[i].text != null && n[i - 1].text == null && n[i].row > 0 && o[n[i].row - 1].text == null &&
1377                        n[i - 1] == o[n[i].row - 1]) {
1378                                n[i - 1] = {
1379                                        text: n[i - 1],
1380                                        row: n[i].row - 1
1381                                };
1382                                o[n[i].row - 1] = {
1383                                        text: o[n[i].row - 1],
1384                                        row: i - 1
1385                                };
1386                        }
1387                }
1388
1389                return {
1390                        o: o,
1391                        n: n
1392                };
1393        }
1394
1395        return function(o, n){
1396                o = o.replace(/\s+$/, '');
1397                n = n.replace(/\s+$/, '');
1398                var out = diff(o == "" ? [] : o.split(/\s+/), n == "" ? [] : n.split(/\s+/));
1399
1400                var str = "";
1401
1402                var oSpace = o.match(/\s+/g);
1403                if (oSpace == null) {
1404                        oSpace = [" "];
1405                }
1406                else {
1407                        oSpace.push(" ");
1408                }
1409                var nSpace = n.match(/\s+/g);
1410                if (nSpace == null) {
1411                        nSpace = [" "];
1412                }
1413                else {
1414                        nSpace.push(" ");
1415                }
1416
1417                if (out.n.length == 0) {
1418                        for (var i = 0; i < out.o.length; i++) {
1419                                str += '<del>' + out.o[i] + oSpace[i] + "</del>";
1420                        }
1421                }
1422                else {
1423                        if (out.n[0].text == null) {
1424                                for (n = 0; n < out.o.length && out.o[n].text == null; n++) {
1425                                        str += '<del>' + out.o[n] + oSpace[n] + "</del>";
1426                                }
1427                        }
1428
1429                        for (var i = 0; i < out.n.length; i++) {
1430                                if (out.n[i].text == null) {
1431                                        str += '<ins>' + out.n[i] + nSpace[i] + "</ins>";
1432                                }
1433                                else {
1434                                        var pre = "";
1435
1436                                        for (n = out.n[i].row + 1; n < out.o.length && out.o[n].text == null; n++) {
1437                                                pre += '<del>' + out.o[n] + oSpace[n] + "</del>";
1438                                        }
1439                                        str += " " + out.n[i].text + nSpace[i] + pre;
1440                                }
1441                        }
1442                }
1443
1444                return str;
1445        };
1446})();
1447
1448})(this);
Note: See TracBrowser for help on using the repository browser.