Projet

Général

Profil

Paste
Télécharger (32,4 ko) Statistiques
| Branche: | Révision:

root / htmltest / sites / all / modules / jquery_update / replace / ui / external / qunit.js @ dc45a079

1
/*
2
 * QUnit - A JavaScript Unit Testing Framework
3
 * 
4
 * http://docs.jquery.com/QUnit
5
 *
6
 * Copyright (c) 2009 John Resig, Jörn Zaefferer
7
 * Dual licensed under the MIT (MIT-LICENSE.txt)
8
 * and GPL (GPL-LICENSE.txt) licenses.
9
 */
10

    
11
(function(window) {
12

    
13
var QUnit = {
14

    
15
        // call on start of module test to prepend name to all tests
16
        module: function(name, testEnvironment) {
17
                config.currentModule = name;
18

    
19
                synchronize(function() {
20
                        if ( config.currentModule ) {
21
                                QUnit.moduleDone( config.currentModule, config.moduleStats.bad, config.moduleStats.all );
22
                        }
23

    
24
                        config.currentModule = name;
25
                        config.moduleTestEnvironment = testEnvironment;
26
                        config.moduleStats = { all: 0, bad: 0 };
27

    
28
                        QUnit.moduleStart( name, testEnvironment );
29
                });
30
        },
31

    
32
        asyncTest: function(testName, expected, callback) {
33
                if ( arguments.length === 2 ) {
34
                        callback = expected;
35
                        expected = 0;
36
                }
37

    
38
                QUnit.test(testName, expected, callback, true);
39
        },
40
        
41
        test: function(testName, expected, callback, async) {
42
                var name = '<span class="test-name">' + testName + '</span>', testEnvironment, testEnvironmentArg;
43

    
44
                if ( arguments.length === 2 ) {
45
                        callback = expected;
46
                        expected = null;
47
                }
48
                // is 2nd argument a testEnvironment?
49
                if ( expected && typeof expected === 'object') {
50
                        testEnvironmentArg =  expected;
51
                        expected = null;
52
                }
53

    
54
                if ( config.currentModule ) {
55
                        name = '<span class="module-name">' + config.currentModule + "</span>: " + name;
56
                }
57

    
58
                if ( !validTest(config.currentModule + ": " + testName) ) {
59
                        return;
60
                }
61

    
62
                synchronize(function() {
63

    
64
                        testEnvironment = extend({
65
                                setup: function() {},
66
                                teardown: function() {}
67
                        }, config.moduleTestEnvironment);
68
                        if (testEnvironmentArg) {
69
                                extend(testEnvironment,testEnvironmentArg);
70
                        }
71

    
72
                        QUnit.testStart( testName, testEnvironment );
73

    
74
                        // allow utility functions to access the current test environment
75
                        QUnit.current_testEnvironment = testEnvironment;
76
                        
77
                        config.assertions = [];
78
                        config.expected = expected;
79
                        
80
                        var tests = id("qunit-tests");
81
                        if (tests) {
82
                                var b = document.createElement("strong");
83
                                        b.innerHTML = "Running " + name;
84
                                var li = document.createElement("li");
85
                                        li.appendChild( b );
86
                                        li.id = "current-test-output";
87
                                tests.appendChild( li )
88
                        }
89

    
90
                        try {
91
                                if ( !config.pollution ) {
92
                                        saveGlobal();
93
                                }
94

    
95
                                testEnvironment.setup.call(testEnvironment);
96
                        } catch(e) {
97
                                QUnit.ok( false, "Setup failed on " + name + ": " + e.message );
98
                        }
99
            });
100
        
101
            synchronize(function() {
102
                        if ( async ) {
103
                                QUnit.stop();
104
                        }
105

    
106
                        try {
107
                                callback.call(testEnvironment);
108
                        } catch(e) {
109
                                fail("Test " + name + " died, exception and test follows", e, callback);
110
                                QUnit.ok( false, "Died on test #" + (config.assertions.length + 1) + ": " + e.message );
111
                                // else next test will carry the responsibility
112
                                saveGlobal();
113

    
114
                                // Restart the tests if they're blocking
115
                                if ( config.blocking ) {
116
                                        start();
117
                                }
118
                        }
119
                });
120

    
121
                synchronize(function() {
122
                        try {
123
                                checkPollution();
124
                                testEnvironment.teardown.call(testEnvironment);
125
                        } catch(e) {
126
                                QUnit.ok( false, "Teardown failed on " + name + ": " + e.message );
127
                        }
128
            });
129
        
130
            synchronize(function() {
131
                        try {
132
                                QUnit.reset();
133
                        } catch(e) {
134
                                fail("reset() failed, following Test " + name + ", exception and reset fn follows", e, reset);
135
                        }
136

    
137
                        if ( config.expected && config.expected != config.assertions.length ) {
138
                                QUnit.ok( false, "Expected " + config.expected + " assertions, but " + config.assertions.length + " were run" );
139
                        }
140

    
141
                        var good = 0, bad = 0,
142
                                tests = id("qunit-tests");
143

    
144
                        config.stats.all += config.assertions.length;
145
                        config.moduleStats.all += config.assertions.length;
146

    
147
                        if ( tests ) {
148
                                var ol  = document.createElement("ol");
149

    
150
                                for ( var i = 0; i < config.assertions.length; i++ ) {
151
                                        var assertion = config.assertions[i];
152

    
153
                                        var li = document.createElement("li");
154
                                        li.className = assertion.result ? "pass" : "fail";
155
                                        li.innerHTML = assertion.message || "(no message)";
156
                                        ol.appendChild( li );
157

    
158
                                        if ( assertion.result ) {
159
                                                good++;
160
                                        } else {
161
                                                bad++;
162
                                                config.stats.bad++;
163
                                                config.moduleStats.bad++;
164
                                        }
165
                                }
166
                                if (bad == 0) {
167
                                        ol.style.display = "none";
168
                                }
169

    
170
                                var b = document.createElement("strong");
171
                                b.innerHTML = name + " <b style='color:black;'>(<b class='fail'>" + bad + "</b>, <b class='pass'>" + good + "</b>, " + config.assertions.length + ")</b>";
172
                                
173
                                addEvent(b, "click", function() {
174
                                        var next = b.nextSibling, display = next.style.display;
175
                                        next.style.display = display === "none" ? "block" : "none";
176
                                });
177
                                
178
                                addEvent(b, "dblclick", function(e) {
179
                                        var target = e && e.target ? e.target : window.event.srcElement;
180
                                        if ( target.nodeName.toLowerCase() == "span" || target.nodeName.toLowerCase() == "b" ) {
181
                                                target = target.parentNode;
182
                                        }
183
                                        if ( window.location && target.nodeName.toLowerCase() === "strong" ) {
184
                                                window.location.search = "?" + encodeURIComponent(getText([target]).replace(/\(.+\)$/, "").replace(/(^\s*|\s*$)/g, ""));
185
                                        }
186
                                });
187

    
188
                                var li = id("current-test-output");
189
                                li.id = "";
190
                                li.className = bad ? "fail" : "pass";
191
                                li.removeChild( li.firstChild );
192
                                li.appendChild( b );
193
                                li.appendChild( ol );
194

    
195
                                if ( bad ) {
196
                                        var toolbar = id("qunit-testrunner-toolbar");
197
                                        if ( toolbar ) {
198
                                                toolbar.style.display = "block";
199
                                                id("qunit-filter-pass").disabled = null;
200
                                                id("qunit-filter-missing").disabled = null;
201
                                        }
202
                                }
203

    
204
                        } else {
205
                                for ( var i = 0; i < config.assertions.length; i++ ) {
206
                                        if ( !config.assertions[i].result ) {
207
                                                bad++;
208
                                                config.stats.bad++;
209
                                                config.moduleStats.bad++;
210
                                        }
211
                                }
212
                        }
213

    
214
                        QUnit.testDone( testName, bad, config.assertions.length );
215

    
216
                        if ( !window.setTimeout && !config.queue.length ) {
217
                                done();
218
                        }
219
                });
220

    
221
                synchronize( done );
222
        },
223
        
224
        /**
225
         * Specify the number of expected assertions to gurantee that failed test (no assertions are run at all) don't slip through.
226
         */
227
        expect: function(asserts) {
228
                config.expected = asserts;
229
        },
230

    
231
        /**
232
         * Asserts true.
233
         * @example ok( "asdfasdf".length > 5, "There must be at least 5 chars" );
234
         */
235
        ok: function(a, msg) {
236
                msg = escapeHtml(msg);
237
                QUnit.log(a, msg);
238

    
239
                config.assertions.push({
240
                        result: !!a,
241
                        message: msg
242
                });
243
        },
244

    
245
        /**
246
         * Checks that the first two arguments are equal, with an optional message.
247
         * Prints out both actual and expected values.
248
         *
249
         * Prefered to ok( actual == expected, message )
250
         *
251
         * @example equal( format("Received {0} bytes.", 2), "Received 2 bytes." );
252
         *
253
         * @param Object actual
254
         * @param Object expected
255
         * @param String message (optional)
256
         */
257
        equal: function(actual, expected, message) {
258
                push(expected == actual, actual, expected, message);
259
        },
260

    
261
        notEqual: function(actual, expected, message) {
262
                push(expected != actual, actual, expected, message);
263
        },
264
        
265
        deepEqual: function(actual, expected, message) {
266
                push(QUnit.equiv(actual, expected), actual, expected, message);
267
        },
268

    
269
        notDeepEqual: function(actual, expected, message) {
270
                push(!QUnit.equiv(actual, expected), actual, expected, message);
271
        },
272

    
273
        strictEqual: function(actual, expected, message) {
274
                push(expected === actual, actual, expected, message);
275
        },
276

    
277
        notStrictEqual: function(actual, expected, message) {
278
                push(expected !== actual, actual, expected, message);
279
        },
280

    
281
        raises: function(fn,  message) {
282
                try {
283
                        fn();
284
                        ok( false, message );
285
                }
286
                catch (e) {
287
                        ok( true, message );
288
                }
289
        },
290

    
291
        start: function() {
292
                // A slight delay, to avoid any current callbacks
293
                if ( window.setTimeout ) {
294
                        window.setTimeout(function() {
295
                                if ( config.timeout ) {
296
                                        clearTimeout(config.timeout);
297
                                }
298

    
299
                                config.blocking = false;
300
                                process();
301
                        }, 13);
302
                } else {
303
                        config.blocking = false;
304
                        process();
305
                }
306
        },
307
        
308
        stop: function(timeout) {
309
                config.blocking = true;
310

    
311
                if ( timeout && window.setTimeout ) {
312
                        config.timeout = window.setTimeout(function() {
313
                                QUnit.ok( false, "Test timed out" );
314
                                QUnit.start();
315
                        }, timeout);
316
                }
317
        }
318

    
319
};
320

    
321
// Backwards compatibility, deprecated
322
QUnit.equals = QUnit.equal;
323
QUnit.same = QUnit.deepEqual;
324

    
325
// Maintain internal state
326
var config = {
327
        // The queue of tests to run
328
        queue: [],
329

    
330
        // block until document ready
331
        blocking: true
332
};
333

    
334
// Load paramaters
335
(function() {
336
        var location = window.location || { search: "", protocol: "file:" },
337
                GETParams = location.search.slice(1).split('&');
338

    
339
        for ( var i = 0; i < GETParams.length; i++ ) {
340
                GETParams[i] = decodeURIComponent( GETParams[i] );
341
                if ( GETParams[i] === "noglobals" ) {
342
                        GETParams.splice( i, 1 );
343
                        i--;
344
                        config.noglobals = true;
345
                } else if ( GETParams[i].search('=') > -1 ) {
346
                        GETParams.splice( i, 1 );
347
                        i--;
348
                }
349
        }
350
        
351
        // restrict modules/tests by get parameters
352
        config.filters = GETParams;
353
        
354
        // Figure out if we're running the tests from a server or not
355
        QUnit.isLocal = !!(location.protocol === 'file:');
356
})();
357

    
358
// Expose the API as global variables, unless an 'exports'
359
// object exists, in that case we assume we're in CommonJS
360
if ( typeof exports === "undefined" || typeof require === "undefined" ) {
361
        extend(window, QUnit);
362
        window.QUnit = QUnit;
363
} else {
364
        extend(exports, QUnit);
365
        exports.QUnit = QUnit;
366
}
367

    
368
// define these after exposing globals to keep them in these QUnit namespace only
369
extend(QUnit, {
370
        config: config,
371

    
372
        // Initialize the configuration options
373
        init: function() {
374
                extend(config, {
375
                        stats: { all: 0, bad: 0 },
376
                        moduleStats: { all: 0, bad: 0 },
377
                        started: +new Date,
378
                        updateRate: 1000,
379
                        blocking: false,
380
                        autostart: true,
381
                        autorun: false,
382
                        assertions: [],
383
                        filters: [],
384
                        queue: []
385
                });
386

    
387
                var tests = id("qunit-tests"),
388
                        banner = id("qunit-banner"),
389
                        result = id("qunit-testresult");
390

    
391
                if ( tests ) {
392
                        tests.innerHTML = "";
393
                }
394

    
395
                if ( banner ) {
396
                        banner.className = "";
397
                }
398

    
399
                if ( result ) {
400
                        result.parentNode.removeChild( result );
401
                }
402
        },
403
        
404
        /**
405
         * Resets the test setup. Useful for tests that modify the DOM.
406
         */
407
        reset: function() {
408
                if ( window.jQuery ) {
409
                        jQuery("#main, #qunit-fixture").html( config.fixture );
410
                }
411
        },
412
        
413
        /**
414
         * Trigger an event on an element.
415
         *
416
         * @example triggerEvent( document.body, "click" );
417
         *
418
         * @param DOMElement elem
419
         * @param String type
420
         */
421
        triggerEvent: function( elem, type, event ) {
422
                if ( document.createEvent ) {
423
                        event = document.createEvent("MouseEvents");
424
                        event.initMouseEvent(type, true, true, elem.ownerDocument.defaultView,
425
                                0, 0, 0, 0, 0, false, false, false, false, 0, null);
426
                        elem.dispatchEvent( event );
427

    
428
                } else if ( elem.fireEvent ) {
429
                        elem.fireEvent("on"+type);
430
                }
431
        },
432
        
433
        // Safe object type checking
434
        is: function( type, obj ) {
435
                return QUnit.objectType( obj ) == type;
436
        },
437
        
438
        objectType: function( obj ) {
439
                if (typeof obj === "undefined") {
440
                                return "undefined";
441

    
442
                // consider: typeof null === object
443
                }
444
                if (obj === null) {
445
                                return "null";
446
                }
447

    
448
                var type = Object.prototype.toString.call( obj )
449
                        .match(/^\[object\s(.*)\]$/)[1] || '';
450

    
451
                switch (type) {
452
                                case 'Number':
453
                                                if (isNaN(obj)) {
454
                                                                return "nan";
455
                                                } else {
456
                                                                return "number";
457
                                                }
458
                                case 'String':
459
                                case 'Boolean':
460
                                case 'Array':
461
                                case 'Date':
462
                                case 'RegExp':
463
                                case 'Function':
464
                                                return type.toLowerCase();
465
                }
466
                if (typeof obj === "object") {
467
                                return "object";
468
                }
469
                return undefined;
470
        },
471
        
472
        // Logging callbacks
473
        begin: function() {},
474
        done: function(failures, total) {},
475
        log: function(result, message) {},
476
        testStart: function(name, testEnvironment) {},
477
        testDone: function(name, failures, total) {},
478
        moduleStart: function(name, testEnvironment) {},
479
        moduleDone: function(name, failures, total) {}
480
});
481

    
482
if ( typeof document === "undefined" || document.readyState === "complete" ) {
483
        config.autorun = true;
484
}
485

    
486
addEvent(window, "load", function() {
487
        QUnit.begin();
488
        
489
        // Initialize the config, saving the execution queue
490
        var oldconfig = extend({}, config);
491
        QUnit.init();
492
        extend(config, oldconfig);
493

    
494
        config.blocking = false;
495

    
496
        var userAgent = id("qunit-userAgent");
497
        if ( userAgent ) {
498
                userAgent.innerHTML = navigator.userAgent;
499
        }
500
        var banner = id("qunit-header");
501
        if ( banner ) {
502
                banner.innerHTML = '<a href="' + location.href + '">' + banner.innerHTML + '</a>'; 
503
        }
504
        
505
        var toolbar = id("qunit-testrunner-toolbar");
506
        if ( toolbar ) {
507
                toolbar.style.display = "none";
508
                
509
                var filter = document.createElement("input");
510
                filter.type = "checkbox";
511
                filter.id = "qunit-filter-pass";
512
                filter.disabled = true;
513
                addEvent( filter, "click", function() {
514
                        var li = document.getElementsByTagName("li");
515
                        for ( var i = 0; i < li.length; i++ ) {
516
                                if ( li[i].className.indexOf("pass") > -1 ) {
517
                                        li[i].style.display = filter.checked ? "none" : "";
518
                                }
519
                        }
520
                });
521
                toolbar.appendChild( filter );
522

    
523
                var label = document.createElement("label");
524
                label.setAttribute("for", "qunit-filter-pass");
525
                label.innerHTML = "Hide passed tests";
526
                toolbar.appendChild( label );
527

    
528
                var missing = document.createElement("input");
529
                missing.type = "checkbox";
530
                missing.id = "qunit-filter-missing";
531
                missing.disabled = true;
532
                addEvent( missing, "click", function() {
533
                        var li = document.getElementsByTagName("li");
534
                        for ( var i = 0; i < li.length; i++ ) {
535
                                if ( li[i].className.indexOf("fail") > -1 && li[i].innerHTML.indexOf('missing test - untested code is broken code') > - 1 ) {
536
                                        li[i].parentNode.parentNode.style.display = missing.checked ? "none" : "block";
537
                                }
538
                        }
539
                });
540
                toolbar.appendChild( missing );
541

    
542
                label = document.createElement("label");
543
                label.setAttribute("for", "qunit-filter-missing");
544
                label.innerHTML = "Hide missing tests (untested code is broken code)";
545
                toolbar.appendChild( label );
546
        }
547

    
548
        var main = id('main') || id('qunit-fixture');
549
        if ( main ) {
550
                config.fixture = main.innerHTML;
551
        }
552

    
553
        if (config.autostart) {
554
                QUnit.start();
555
        }
556
});
557

    
558
function done() {
559
        if ( config.doneTimer && window.clearTimeout ) {
560
                window.clearTimeout( config.doneTimer );
561
                config.doneTimer = null;
562
        }
563

    
564
        if ( config.queue.length ) {
565
                config.doneTimer = window.setTimeout(function(){
566
                        if ( !config.queue.length ) {
567
                                done();
568
                        } else {
569
                                synchronize( done );
570
                        }
571
                }, 13);
572

    
573
                return;
574
        }
575

    
576
        config.autorun = true;
577

    
578
        // Log the last module results
579
        if ( config.currentModule ) {
580
                QUnit.moduleDone( config.currentModule, config.moduleStats.bad, config.moduleStats.all );
581
        }
582

    
583
        var banner = id("qunit-banner"),
584
                tests = id("qunit-tests"),
585
                html = ['Tests completed in ',
586
                +new Date - config.started, ' milliseconds.<br/>',
587
                '<span class="passed">', config.stats.all - config.stats.bad, '</span> tests of <span class="total">', config.stats.all, '</span> passed, <span class="failed">', config.stats.bad,'</span> failed.'].join('');
588

    
589
        if ( banner ) {
590
                banner.className = (config.stats.bad ? "qunit-fail" : "qunit-pass");
591
        }
592

    
593
        if ( tests ) {        
594
                var result = id("qunit-testresult");
595

    
596
                if ( !result ) {
597
                        result = document.createElement("p");
598
                        result.id = "qunit-testresult";
599
                        result.className = "result";
600
                        tests.parentNode.insertBefore( result, tests.nextSibling );
601
                }
602

    
603
                result.innerHTML = html;
604
        }
605

    
606
        QUnit.done( config.stats.bad, config.stats.all );
607
}
608

    
609
function validTest( name ) {
610
        var i = config.filters.length,
611
                run = false;
612

    
613
        if ( !i ) {
614
                return true;
615
        }
616
        
617
        while ( i-- ) {
618
                var filter = config.filters[i],
619
                        not = filter.charAt(0) == '!';
620

    
621
                if ( not ) {
622
                        filter = filter.slice(1);
623
                }
624

    
625
                if ( name.indexOf(filter) !== -1 ) {
626
                        return !not;
627
                }
628

    
629
                if ( not ) {
630
                        run = true;
631
                }
632
        }
633

    
634
        return run;
635
}
636

    
637
function escapeHtml(s) {
638
        s = s === null ? "" : s + "";
639
        return s.replace(/[\&"<>\\]/g, function(s) {
640
                switch(s) {
641
                        case "&": return "&amp;";
642
                        case "\\": return "\\\\";
643
                        case '"': return '\"';
644
                        case "<": return "&lt;";
645
                        case ">": return "&gt;";
646
                        default: return s;
647
                }
648
        });
649
}
650

    
651
function push(result, actual, expected, message) {
652
        message = escapeHtml(message) || (result ? "okay" : "failed");
653
        message = '<span class="test-message">' + message + "</span>";
654
        expected = escapeHtml(QUnit.jsDump.parse(expected));
655
        actual = escapeHtml(QUnit.jsDump.parse(actual));
656
        var output = message + ', expected: <span class="test-expected">' + expected + '</span>';
657
        if (actual != expected) {
658
                output += ' result: <span class="test-actual">' + actual + '</span>, diff: ' + QUnit.diff(expected, actual);
659
        }
660
        
661
        // can't use ok, as that would double-escape messages
662
        QUnit.log(result, output);
663
        config.assertions.push({
664
                result: !!result,
665
                message: output
666
        });
667
}
668

    
669
function synchronize( callback ) {
670
        config.queue.push( callback );
671

    
672
        if ( config.autorun && !config.blocking ) {
673
                process();
674
        }
675
}
676

    
677
function process() {
678
        var start = (new Date()).getTime();
679

    
680
        while ( config.queue.length && !config.blocking ) {
681
                if ( config.updateRate <= 0 || (((new Date()).getTime() - start) < config.updateRate) ) {
682
                        config.queue.shift()();
683

    
684
                } else {
685
                        setTimeout( process, 13 );
686
                        break;
687
                }
688
        }
689
}
690

    
691
function saveGlobal() {
692
        config.pollution = [];
693
        
694
        if ( config.noglobals ) {
695
                for ( var key in window ) {
696
                        config.pollution.push( key );
697
                }
698
        }
699
}
700

    
701
function checkPollution( name ) {
702
        var old = config.pollution;
703
        saveGlobal();
704
        
705
        var newGlobals = diff( old, config.pollution );
706
        if ( newGlobals.length > 0 ) {
707
                ok( false, "Introduced global variable(s): " + newGlobals.join(", ") );
708
                config.expected++;
709
        }
710

    
711
        var deletedGlobals = diff( config.pollution, old );
712
        if ( deletedGlobals.length > 0 ) {
713
                ok( false, "Deleted global variable(s): " + deletedGlobals.join(", ") );
714
                config.expected++;
715
        }
716
}
717

    
718
// returns a new Array with the elements that are in a but not in b
719
function diff( a, b ) {
720
        var result = a.slice();
721
        for ( var i = 0; i < result.length; i++ ) {
722
                for ( var j = 0; j < b.length; j++ ) {
723
                        if ( result[i] === b[j] ) {
724
                                result.splice(i, 1);
725
                                i--;
726
                                break;
727
                        }
728
                }
729
        }
730
        return result;
731
}
732

    
733
function fail(message, exception, callback) {
734
        if ( typeof console !== "undefined" && console.error && console.warn ) {
735
                console.error(message);
736
                console.error(exception);
737
                console.warn(callback.toString());
738

    
739
        } else if ( window.opera && opera.postError ) {
740
                opera.postError(message, exception, callback.toString);
741
        }
742
}
743

    
744
function extend(a, b) {
745
        for ( var prop in b ) {
746
                a[prop] = b[prop];
747
        }
748

    
749
        return a;
750
}
751

    
752
function addEvent(elem, type, fn) {
753
        if ( elem.addEventListener ) {
754
                elem.addEventListener( type, fn, false );
755
        } else if ( elem.attachEvent ) {
756
                elem.attachEvent( "on" + type, fn );
757
        } else {
758
                fn();
759
        }
760
}
761

    
762
function id(name) {
763
        return !!(typeof document !== "undefined" && document && document.getElementById) &&
764
                document.getElementById( name );
765
}
766

    
767
// Test for equality any JavaScript type.
768
// Discussions and reference: http://philrathe.com/articles/equiv
769
// Test suites: http://philrathe.com/tests/equiv
770
// Author: Philippe Rathé <prathe@gmail.com>
771
QUnit.equiv = function () {
772

    
773
    var innerEquiv; // the real equiv function
774
    var callers = []; // stack to decide between skip/abort functions
775
    var parents = []; // stack to avoiding loops from circular referencing
776

    
777
    // Call the o related callback with the given arguments.
778
    function bindCallbacks(o, callbacks, args) {
779
        var prop = QUnit.objectType(o);
780
        if (prop) {
781
            if (QUnit.objectType(callbacks[prop]) === "function") {
782
                return callbacks[prop].apply(callbacks, args);
783
            } else {
784
                return callbacks[prop]; // or undefined
785
            }
786
        }
787
    }
788
    
789
    var callbacks = function () {
790

    
791
        // for string, boolean, number and null
792
        function useStrictEquality(b, a) {
793
            if (b instanceof a.constructor || a instanceof b.constructor) {
794
                // to catch short annotaion VS 'new' annotation of a declaration
795
                // e.g. var i = 1;
796
                //      var j = new Number(1);
797
                return a == b;
798
            } else {
799
                return a === b;
800
            }
801
        }
802

    
803
        return {
804
            "string": useStrictEquality,
805
            "boolean": useStrictEquality,
806
            "number": useStrictEquality,
807
            "null": useStrictEquality,
808
            "undefined": useStrictEquality,
809

    
810
            "nan": function (b) {
811
                return isNaN(b);
812
            },
813

    
814
            "date": function (b, a) {
815
                return QUnit.objectType(b) === "date" && a.valueOf() === b.valueOf();
816
            },
817

    
818
            "regexp": function (b, a) {
819
                return QUnit.objectType(b) === "regexp" &&
820
                    a.source === b.source && // the regex itself
821
                    a.global === b.global && // and its modifers (gmi) ...
822
                    a.ignoreCase === b.ignoreCase &&
823
                    a.multiline === b.multiline;
824
            },
825

    
826
            // - skip when the property is a method of an instance (OOP)
827
            // - abort otherwise,
828
            //   initial === would have catch identical references anyway
829
            "function": function () {
830
                var caller = callers[callers.length - 1];
831
                return caller !== Object &&
832
                        typeof caller !== "undefined";
833
            },
834

    
835
            "array": function (b, a) {
836
                var i, j, loop;
837
                var len;
838

    
839
                // b could be an object literal here
840
                if ( ! (QUnit.objectType(b) === "array")) {
841
                    return false;
842
                }   
843
                
844
                len = a.length;
845
                if (len !== b.length) { // safe and faster
846
                    return false;
847
                }
848
                
849
                //track reference to avoid circular references
850
                parents.push(a);
851
                for (i = 0; i < len; i++) {
852
                    loop = false;
853
                    for(j=0;j<parents.length;j++){
854
                        if(parents[j] === a[i]){
855
                            loop = true;//dont rewalk array
856
                        }
857
                    }
858
                    if (!loop && ! innerEquiv(a[i], b[i])) {
859
                        parents.pop();
860
                        return false;
861
                    }
862
                }
863
                parents.pop();
864
                return true;
865
            },
866

    
867
            "object": function (b, a) {
868
                var i, j, loop;
869
                var eq = true; // unless we can proove it
870
                var aProperties = [], bProperties = []; // collection of strings
871

    
872
                // comparing constructors is more strict than using instanceof
873
                if ( a.constructor !== b.constructor) {
874
                    return false;
875
                }
876

    
877
                // stack constructor before traversing properties
878
                callers.push(a.constructor);
879
                //track reference to avoid circular references
880
                parents.push(a);
881
                
882
                for (i in a) { // be strict: don't ensures hasOwnProperty and go deep
883
                    loop = false;
884
                    for(j=0;j<parents.length;j++){
885
                        if(parents[j] === a[i])
886
                            loop = true; //don't go down the same path twice
887
                    }
888
                    aProperties.push(i); // collect a's properties
889

    
890
                    if (!loop && ! innerEquiv(a[i], b[i])) {
891
                        eq = false;
892
                        break;
893
                    }
894
                }
895

    
896
                callers.pop(); // unstack, we are done
897
                parents.pop();
898

    
899
                for (i in b) {
900
                    bProperties.push(i); // collect b's properties
901
                }
902

    
903
                // Ensures identical properties name
904
                return eq && innerEquiv(aProperties.sort(), bProperties.sort());
905
            }
906
        };
907
    }();
908

    
909
    innerEquiv = function () { // can take multiple arguments
910
        var args = Array.prototype.slice.apply(arguments);
911
        if (args.length < 2) {
912
            return true; // end transition
913
        }
914

    
915
        return (function (a, b) {
916
            if (a === b) {
917
                return true; // catch the most you can
918
            } else if (a === null || b === null || typeof a === "undefined" || typeof b === "undefined" || QUnit.objectType(a) !== QUnit.objectType(b)) {
919
                return false; // don't lose time with error prone cases
920
            } else {
921
                return bindCallbacks(a, callbacks, [b, a]);
922
            }
923

    
924
        // apply transition with (1..n) arguments
925
        })(args[0], args[1]) && arguments.callee.apply(this, args.splice(1, args.length -1));
926
    };
927

    
928
    return innerEquiv;
929

    
930
}();
931

    
932
/**
933
 * jsDump
934
 * Copyright (c) 2008 Ariel Flesler - aflesler(at)gmail(dot)com | http://flesler.blogspot.com
935
 * Licensed under BSD (http://www.opensource.org/licenses/bsd-license.php)
936
 * Date: 5/15/2008
937
 * @projectDescription Advanced and extensible data dumping for Javascript.
938
 * @version 1.0.0
939
 * @author Ariel Flesler
940
 * @link {http://flesler.blogspot.com/2008/05/jsdump-pretty-dump-of-any-javascript.html}
941
 */
942
QUnit.jsDump = (function() {
943
        function quote( str ) {
944
                return '"' + str.toString().replace(/"/g, '\\"') + '"';
945
        };
946
        function literal( o ) {
947
                return o + '';        
948
        };
949
        function join( pre, arr, post ) {
950
                var s = jsDump.separator(),
951
                        base = jsDump.indent(),
952
                        inner = jsDump.indent(1);
953
                if ( arr.join )
954
                        arr = arr.join( ',' + s + inner );
955
                if ( !arr )
956
                        return pre + post;
957
                return [ pre, inner + arr, base + post ].join(s);
958
        };
959
        function array( arr ) {
960
                var i = arr.length,        ret = Array(i);                                        
961
                this.up();
962
                while ( i-- )
963
                        ret[i] = this.parse( arr[i] );                                
964
                this.down();
965
                return join( '[', ret, ']' );
966
        };
967
        
968
        var reName = /^function (\w+)/;
969
        
970
        var jsDump = {
971
                parse:function( obj, type ) { //type is used mostly internally, you can fix a (custom)type in advance
972
                        var        parser = this.parsers[ type || this.typeOf(obj) ];
973
                        type = typeof parser;                        
974
                        
975
                        return type == 'function' ? parser.call( this, obj ) :
976
                                   type == 'string' ? parser :
977
                                   this.parsers.error;
978
                },
979
                typeOf:function( obj ) {
980
                        var type;
981
                        if ( obj === null ) {
982
                                type = "null";
983
                        } else if (typeof obj === "undefined") {
984
                                type = "undefined";
985
                        } else if (QUnit.is("RegExp", obj)) {
986
                                type = "regexp";
987
                        } else if (QUnit.is("Date", obj)) {
988
                                type = "date";
989
                        } else if (QUnit.is("Function", obj)) {
990
                                type = "function";
991
                        } else if (obj.setInterval && obj.document && !obj.nodeType) {
992
                                type = "window";
993
                        } else if (obj.nodeType === 9) {
994
                                type = "document";
995
                        } else if (obj.nodeType) {
996
                                type = "node";
997
                        } else if (typeof obj === "object" && typeof obj.length === "number" && obj.length >= 0) {
998
                                type = "array";
999
                        } else {
1000
                                type = typeof obj;
1001
                        }
1002
                        return type;
1003
                },
1004
                separator:function() {
1005
                        return this.multiline ?        this.HTML ? '<br />' : '\n' : this.HTML ? '&nbsp;' : ' ';
1006
                },
1007
                indent:function( extra ) {// extra can be a number, shortcut for increasing-calling-decreasing
1008
                        if ( !this.multiline )
1009
                                return '';
1010
                        var chr = this.indentChar;
1011
                        if ( this.HTML )
1012
                                chr = chr.replace(/\t/g,'   ').replace(/ /g,'&nbsp;');
1013
                        return Array( this._depth_ + (extra||0) ).join(chr);
1014
                },
1015
                up:function( a ) {
1016
                        this._depth_ += a || 1;
1017
                },
1018
                down:function( a ) {
1019
                        this._depth_ -= a || 1;
1020
                },
1021
                setParser:function( name, parser ) {
1022
                        this.parsers[name] = parser;
1023
                },
1024
                // The next 3 are exposed so you can use them
1025
                quote:quote, 
1026
                literal:literal,
1027
                join:join,
1028
                //
1029
                _depth_: 1,
1030
                // This is the list of parsers, to modify them, use jsDump.setParser
1031
                parsers:{
1032
                        window: '[Window]',
1033
                        document: '[Document]',
1034
                        error:'[ERROR]', //when no parser is found, shouldn't happen
1035
                        unknown: '[Unknown]',
1036
                        'null':'null',
1037
                        undefined:'undefined',
1038
                        'function':function( fn ) {
1039
                                var ret = 'function',
1040
                                        name = 'name' in fn ? fn.name : (reName.exec(fn)||[])[1];//functions never have name in IE
1041
                                if ( name )
1042
                                        ret += ' ' + name;
1043
                                ret += '(';
1044
                                
1045
                                ret = [ ret, this.parse( fn, 'functionArgs' ), '){'].join('');
1046
                                return join( ret, this.parse(fn,'functionCode'), '}' );
1047
                        },
1048
                        array: array,
1049
                        nodelist: array,
1050
                        arguments: array,
1051
                        object:function( map ) {
1052
                                var ret = [ ];
1053
                                this.up();
1054
                                for ( var key in map )
1055
                                        ret.push( this.parse(key,'key') + ': ' + this.parse(map[key]) );
1056
                                this.down();
1057
                                return join( '{', ret, '}' );
1058
                        },
1059
                        node:function( node ) {
1060
                                var open = this.HTML ? '&lt;' : '<',
1061
                                        close = this.HTML ? '&gt;' : '>';
1062
                                        
1063
                                var tag = node.nodeName.toLowerCase(),
1064
                                        ret = open + tag;
1065
                                        
1066
                                for ( var a in this.DOMAttrs ) {
1067
                                        var val = node[this.DOMAttrs[a]];
1068
                                        if ( val )
1069
                                                ret += ' ' + a + '=' + this.parse( val, 'attribute' );
1070
                                }
1071
                                return ret + close + open + '/' + tag + close;
1072
                        },
1073
                        functionArgs:function( fn ) {//function calls it internally, it's the arguments part of the function
1074
                                var l = fn.length;
1075
                                if ( !l ) return '';                                
1076
                                
1077
                                var args = Array(l);
1078
                                while ( l-- )
1079
                                        args[l] = String.fromCharCode(97+l);//97 is 'a'
1080
                                return ' ' + args.join(', ') + ' ';
1081
                        },
1082
                        key:quote, //object calls it internally, the key part of an item in a map
1083
                        functionCode:'[code]', //function calls it internally, it's the content of the function
1084
                        attribute:quote, //node calls it internally, it's an html attribute value
1085
                        string:quote,
1086
                        date:quote,
1087
                        regexp:literal, //regex
1088
                        number:literal,
1089
                        'boolean':literal
1090
                },
1091
                DOMAttrs:{//attributes to dump from nodes, name=>realName
1092
                        id:'id',
1093
                        name:'name',
1094
                        'class':'className'
1095
                },
1096
                HTML:false,//if true, entities are escaped ( <, >, \t, space and \n )
1097
                indentChar:'   ',//indentation unit
1098
                multiline:false //if true, items in a collection, are separated by a \n, else just a space.
1099
        };
1100

    
1101
        return jsDump;
1102
})();
1103

    
1104
// from Sizzle.js
1105
function getText( elems ) {
1106
        var ret = "", elem;
1107

    
1108
        for ( var i = 0; elems[i]; i++ ) {
1109
                elem = elems[i];
1110

    
1111
                // Get the text from text nodes and CDATA nodes
1112
                if ( elem.nodeType === 3 || elem.nodeType === 4 ) {
1113
                        ret += elem.nodeValue;
1114

    
1115
                // Traverse everything else, except comment nodes
1116
                } else if ( elem.nodeType !== 8 ) {
1117
                        ret += getText( elem.childNodes );
1118
                }
1119
        }
1120

    
1121
        return ret;
1122
};
1123

    
1124
/*
1125
 * Javascript Diff Algorithm
1126
 *  By John Resig (http://ejohn.org/)
1127
 *  Modified by Chu Alan "sprite"
1128
 *
1129
 * Released under the MIT license.
1130
 *
1131
 * More Info:
1132
 *  http://ejohn.org/projects/javascript-diff-algorithm/
1133
 *  
1134
 * Usage: QUnit.diff(expected, actual)
1135
 * 
1136
 * 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"
1137
 */
1138
QUnit.diff = (function() {
1139
        function diff(o, n){
1140
                var ns = new Object();
1141
                var os = new Object();
1142
                
1143
                for (var i = 0; i < n.length; i++) {
1144
                        if (ns[n[i]] == null) 
1145
                                ns[n[i]] = {
1146
                                        rows: new Array(),
1147
                                        o: null
1148
                                };
1149
                        ns[n[i]].rows.push(i);
1150
                }
1151
                
1152
                for (var i = 0; i < o.length; i++) {
1153
                        if (os[o[i]] == null) 
1154
                                os[o[i]] = {
1155
                                        rows: new Array(),
1156
                                        n: null
1157
                                };
1158
                        os[o[i]].rows.push(i);
1159
                }
1160
                
1161
                for (var i in ns) {
1162
                        if (ns[i].rows.length == 1 && typeof(os[i]) != "undefined" && os[i].rows.length == 1) {
1163
                                n[ns[i].rows[0]] = {
1164
                                        text: n[ns[i].rows[0]],
1165
                                        row: os[i].rows[0]
1166
                                };
1167
                                o[os[i].rows[0]] = {
1168
                                        text: o[os[i].rows[0]],
1169
                                        row: ns[i].rows[0]
1170
                                };
1171
                        }
1172
                }
1173
                
1174
                for (var i = 0; i < n.length - 1; i++) {
1175
                        if (n[i].text != null && n[i + 1].text == null && n[i].row + 1 < o.length && o[n[i].row + 1].text == null &&
1176
                        n[i + 1] == o[n[i].row + 1]) {
1177
                                n[i + 1] = {
1178
                                        text: n[i + 1],
1179
                                        row: n[i].row + 1
1180
                                };
1181
                                o[n[i].row + 1] = {
1182
                                        text: o[n[i].row + 1],
1183
                                        row: i + 1
1184
                                };
1185
                        }
1186
                }
1187
                
1188
                for (var i = n.length - 1; i > 0; i--) {
1189
                        if (n[i].text != null && n[i - 1].text == null && n[i].row > 0 && o[n[i].row - 1].text == null &&
1190
                        n[i - 1] == o[n[i].row - 1]) {
1191
                                n[i - 1] = {
1192
                                        text: n[i - 1],
1193
                                        row: n[i].row - 1
1194
                                };
1195
                                o[n[i].row - 1] = {
1196
                                        text: o[n[i].row - 1],
1197
                                        row: i - 1
1198
                                };
1199
                        }
1200
                }
1201
                
1202
                return {
1203
                        o: o,
1204
                        n: n
1205
                };
1206
        }
1207
        
1208
        return function(o, n){
1209
                o = o.replace(/\s+$/, '');
1210
                n = n.replace(/\s+$/, '');
1211
                var out = diff(o == "" ? [] : o.split(/\s+/), n == "" ? [] : n.split(/\s+/));
1212

    
1213
                var str = "";
1214
                
1215
                var oSpace = o.match(/\s+/g);
1216
                if (oSpace == null) {
1217
                        oSpace = [" "];
1218
                }
1219
                else {
1220
                        oSpace.push(" ");
1221
                }
1222
                var nSpace = n.match(/\s+/g);
1223
                if (nSpace == null) {
1224
                        nSpace = [" "];
1225
                }
1226
                else {
1227
                        nSpace.push(" ");
1228
                }
1229
                
1230
                if (out.n.length == 0) {
1231
                        for (var i = 0; i < out.o.length; i++) {
1232
                                str += '<del>' + out.o[i] + oSpace[i] + "</del>";
1233
                        }
1234
                }
1235
                else {
1236
                        if (out.n[0].text == null) {
1237
                                for (n = 0; n < out.o.length && out.o[n].text == null; n++) {
1238
                                        str += '<del>' + out.o[n] + oSpace[n] + "</del>";
1239
                                }
1240
                        }
1241
                        
1242
                        for (var i = 0; i < out.n.length; i++) {
1243
                                if (out.n[i].text == null) {
1244
                                        str += '<ins>' + out.n[i] + nSpace[i] + "</ins>";
1245
                                }
1246
                                else {
1247
                                        var pre = "";
1248
                                        
1249
                                        for (n = out.n[i].row + 1; n < out.o.length && out.o[n].text == null; n++) {
1250
                                                pre += '<del>' + out.o[n] + oSpace[n] + "</del>";
1251
                                        }
1252
                                        str += " " + out.n[i].text + nSpace[i] + pre;
1253
                                }
1254
                        }
1255
                }
1256
                
1257
                return str;
1258
        }
1259
})();
1260

    
1261
})(this);