teaspoon-qunit 1.18.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. checksums.yaml +7 -0
  2. data/lib/teaspoon-qunit.rb +4 -0
  3. data/lib/teaspoon/qunit/assets/qunit/1.12.0.js +2212 -0
  4. data/lib/teaspoon/qunit/assets/qunit/1.13.0.js +2210 -0
  5. data/lib/teaspoon/qunit/assets/qunit/1.14.0.js +2288 -0
  6. data/lib/teaspoon/qunit/assets/qunit/1.15.0.js +2495 -0
  7. data/lib/teaspoon/qunit/assets/qunit/1.16.0.js +2819 -0
  8. data/lib/teaspoon/qunit/assets/qunit/1.17.1.js +2875 -0
  9. data/lib/teaspoon/qunit/assets/qunit/1.18.0.js +3828 -0
  10. data/lib/teaspoon/qunit/assets/qunit/MIT.LICENSE +21 -0
  11. data/lib/teaspoon/qunit/assets/teaspoon-qunit.js +1529 -0
  12. data/lib/teaspoon/qunit/assets/teaspoon/qunit.coffee +19 -0
  13. data/lib/teaspoon/qunit/assets/teaspoon/qunit/initialize.coffee +11 -0
  14. data/lib/teaspoon/qunit/assets/teaspoon/qunit/reporters/html.coffee +20 -0
  15. data/lib/teaspoon/qunit/assets/teaspoon/qunit/reporters/html/failure_view.coffee +10 -0
  16. data/lib/teaspoon/qunit/assets/teaspoon/qunit/reporters/html/spec_view.coffee +21 -0
  17. data/lib/teaspoon/qunit/assets/teaspoon/qunit/reporters/html/suite_view.coffee +8 -0
  18. data/lib/teaspoon/qunit/assets/teaspoon/qunit/responder.coffee +48 -0
  19. data/lib/teaspoon/qunit/assets/teaspoon/qunit/runner.coffee +12 -0
  20. data/lib/teaspoon/qunit/assets/teaspoon/qunit/spec.coffee +45 -0
  21. data/lib/teaspoon/qunit/assets/teaspoon/qunit/suite.coffee +16 -0
  22. data/lib/teaspoon/qunit/framework.rb +36 -0
  23. data/lib/teaspoon/qunit/templates/test_helper.coffee +29 -0
  24. data/lib/teaspoon/qunit/templates/test_helper.js +30 -0
  25. data/lib/teaspoon/qunit/version.rb +5 -0
  26. data/spec/console_spec.rb +75 -0
  27. data/spec/installation_spec.rb +35 -0
  28. data/spec/integration_spec.rb +73 -0
  29. data/spec/spec_helper.rb +11 -0
  30. data/test/javascripts/integration/_implementation.coffee +1 -0
  31. data/test/javascripts/integration/first_integration.coffee +14 -0
  32. data/test/javascripts/integration/second_integration.coffee +6 -0
  33. data/test/javascripts/integration/test_helper.coffee +9 -0
  34. data/test/javascripts/qunit/fixture_test.coffee +10 -0
  35. data/test/javascripts/qunit/reporters/console_test.coffee +3 -0
  36. data/test/javascripts/qunit/reporters/html/failure_view_test.coffee +3 -0
  37. data/test/javascripts/qunit/reporters/html/spec_view_test.coffee +3 -0
  38. data/test/javascripts/qunit/reporters/html/suite_view_test.coffee +3 -0
  39. data/test/javascripts/qunit/reporters/html_test.coffee +3 -0
  40. data/test/javascripts/qunit/responder_test.coffee +153 -0
  41. data/test/javascripts/qunit/runner_test.coffee +24 -0
  42. data/test/javascripts/qunit/spec_test.coffee +53 -0
  43. data/test/javascripts/qunit/suite_test.coffee +10 -0
  44. data/test/javascripts/test_helper.coffee +4 -0
  45. data/test/teaspoon_env.rb +12 -0
  46. metadata +124 -0
@@ -0,0 +1,2495 @@
1
+ /*!
2
+ * QUnit 1.15.0
3
+ * http://qunitjs.com/
4
+ *
5
+ * Copyright 2014 jQuery Foundation and other contributors
6
+ * Released under the MIT license
7
+ * http://jquery.org/license
8
+ *
9
+ * Date: 2014-08-08T16:00Z
10
+ */
11
+ ;_qunit_version = "1.15.0";
12
+ (function( window ) {
13
+
14
+ var QUnit,
15
+ config,
16
+ onErrorFnPrev,
17
+ fileName = ( sourceFromStacktrace( 0 ) || "" ).replace( /(:\d+)+\)?/, "" ).replace( /.+\//, "" ),
18
+ toString = Object.prototype.toString,
19
+ hasOwn = Object.prototype.hasOwnProperty,
20
+ // Keep a local reference to Date (GH-283)
21
+ Date = window.Date,
22
+ now = Date.now || function() {
23
+ return new Date().getTime();
24
+ },
25
+ setTimeout = window.setTimeout,
26
+ clearTimeout = window.clearTimeout,
27
+ defined = {
28
+ document: typeof window.document !== "undefined",
29
+ setTimeout: typeof window.setTimeout !== "undefined",
30
+ sessionStorage: (function() {
31
+ var x = "qunit-test-string";
32
+ try {
33
+ sessionStorage.setItem( x, x );
34
+ sessionStorage.removeItem( x );
35
+ return true;
36
+ } catch ( e ) {
37
+ return false;
38
+ }
39
+ }())
40
+ },
41
+ /**
42
+ * Provides a normalized error string, correcting an issue
43
+ * with IE 7 (and prior) where Error.prototype.toString is
44
+ * not properly implemented
45
+ *
46
+ * Based on http://es5.github.com/#x15.11.4.4
47
+ *
48
+ * @param {String|Error} error
49
+ * @return {String} error message
50
+ */
51
+ errorString = function( error ) {
52
+ var name, message,
53
+ errorString = error.toString();
54
+ if ( errorString.substring( 0, 7 ) === "[object" ) {
55
+ name = error.name ? error.name.toString() : "Error";
56
+ message = error.message ? error.message.toString() : "";
57
+ if ( name && message ) {
58
+ return name + ": " + message;
59
+ } else if ( name ) {
60
+ return name;
61
+ } else if ( message ) {
62
+ return message;
63
+ } else {
64
+ return "Error";
65
+ }
66
+ } else {
67
+ return errorString;
68
+ }
69
+ },
70
+ /**
71
+ * Makes a clone of an object using only Array or Object as base,
72
+ * and copies over the own enumerable properties.
73
+ *
74
+ * @param {Object} obj
75
+ * @return {Object} New object with only the own properties (recursively).
76
+ */
77
+ objectValues = function( obj ) {
78
+ var key, val,
79
+ vals = QUnit.is( "array", obj ) ? [] : {};
80
+ for ( key in obj ) {
81
+ if ( hasOwn.call( obj, key ) ) {
82
+ val = obj[ key ];
83
+ vals[ key ] = val === Object( val ) ? objectValues( val ) : val;
84
+ }
85
+ }
86
+ return vals;
87
+ };
88
+
89
+ // Root QUnit object.
90
+ // `QUnit` initialized at top of scope
91
+ QUnit = {
92
+
93
+ // call on start of module test to prepend name to all tests
94
+ module: function( name, testEnvironment ) {
95
+ config.currentModule = name;
96
+ config.currentModuleTestEnvironment = testEnvironment;
97
+ config.modules[ name ] = true;
98
+ },
99
+
100
+ asyncTest: function( testName, expected, callback ) {
101
+ if ( arguments.length === 2 ) {
102
+ callback = expected;
103
+ expected = null;
104
+ }
105
+
106
+ QUnit.test( testName, expected, callback, true );
107
+ },
108
+
109
+ test: function( testName, expected, callback, async ) {
110
+ var test;
111
+
112
+ if ( arguments.length === 2 ) {
113
+ callback = expected;
114
+ expected = null;
115
+ }
116
+
117
+ test = new Test({
118
+ testName: testName,
119
+ expected: expected,
120
+ async: async,
121
+ callback: callback,
122
+ module: config.currentModule,
123
+ moduleTestEnvironment: config.currentModuleTestEnvironment,
124
+ stack: sourceFromStacktrace( 2 )
125
+ });
126
+
127
+ if ( !validTest( test ) ) {
128
+ return;
129
+ }
130
+
131
+ test.queue();
132
+ },
133
+
134
+ start: function( count ) {
135
+ var message;
136
+
137
+ // QUnit hasn't been initialized yet.
138
+ // Note: RequireJS (et al) may delay onLoad
139
+ if ( config.semaphore === undefined ) {
140
+ QUnit.begin(function() {
141
+ // This is triggered at the top of QUnit.load, push start() to the event loop, to allow QUnit.load to finish first
142
+ setTimeout(function() {
143
+ QUnit.start( count );
144
+ });
145
+ });
146
+ return;
147
+ }
148
+
149
+ config.semaphore -= count || 1;
150
+ // don't start until equal number of stop-calls
151
+ if ( config.semaphore > 0 ) {
152
+ return;
153
+ }
154
+
155
+ // Set the starting time when the first test is run
156
+ QUnit.config.started = QUnit.config.started || now();
157
+ // ignore if start is called more often then stop
158
+ if ( config.semaphore < 0 ) {
159
+ config.semaphore = 0;
160
+
161
+ message = "Called start() while already started (QUnit.config.semaphore was 0 already)";
162
+
163
+ if ( config.current ) {
164
+ QUnit.pushFailure( message, sourceFromStacktrace( 2 ) );
165
+ } else {
166
+ throw new Error( message );
167
+ }
168
+
169
+ return;
170
+ }
171
+ // A slight delay, to avoid any current callbacks
172
+ if ( defined.setTimeout ) {
173
+ setTimeout(function() {
174
+ if ( config.semaphore > 0 ) {
175
+ return;
176
+ }
177
+ if ( config.timeout ) {
178
+ clearTimeout( config.timeout );
179
+ }
180
+
181
+ config.blocking = false;
182
+ process( true );
183
+ }, 13 );
184
+ } else {
185
+ config.blocking = false;
186
+ process( true );
187
+ }
188
+ },
189
+
190
+ stop: function( count ) {
191
+ config.semaphore += count || 1;
192
+ config.blocking = true;
193
+
194
+ if ( config.testTimeout && defined.setTimeout ) {
195
+ clearTimeout( config.timeout );
196
+ config.timeout = setTimeout(function() {
197
+ QUnit.ok( false, "Test timed out" );
198
+ config.semaphore = 1;
199
+ QUnit.start();
200
+ }, config.testTimeout );
201
+ }
202
+ }
203
+ };
204
+
205
+ // We use the prototype to distinguish between properties that should
206
+ // be exposed as globals (and in exports) and those that shouldn't
207
+ (function() {
208
+ function F() {}
209
+ F.prototype = QUnit;
210
+ QUnit = new F();
211
+
212
+ // Make F QUnit's constructor so that we can add to the prototype later
213
+ QUnit.constructor = F;
214
+ }());
215
+
216
+ /**
217
+ * Config object: Maintain internal state
218
+ * Later exposed as QUnit.config
219
+ * `config` initialized at top of scope
220
+ */
221
+ config = {
222
+ // The queue of tests to run
223
+ queue: [],
224
+
225
+ // block until document ready
226
+ blocking: true,
227
+
228
+ // when enabled, show only failing tests
229
+ // gets persisted through sessionStorage and can be changed in UI via checkbox
230
+ hidepassed: false,
231
+
232
+ // by default, run previously failed tests first
233
+ // very useful in combination with "Hide passed tests" checked
234
+ reorder: true,
235
+
236
+ // by default, modify document.title when suite is done
237
+ altertitle: true,
238
+
239
+ // by default, scroll to top of the page when suite is done
240
+ scrolltop: true,
241
+
242
+ // when enabled, all tests must call expect()
243
+ requireExpects: false,
244
+
245
+ // add checkboxes that are persisted in the query-string
246
+ // when enabled, the id is set to `true` as a `QUnit.config` property
247
+ urlConfig: [
248
+ {
249
+ id: "noglobals",
250
+ label: "Check for Globals",
251
+ tooltip: "Enabling this will test if any test introduces new properties on the `window` object. Stored as query-strings."
252
+ },
253
+ {
254
+ id: "notrycatch",
255
+ label: "No try-catch",
256
+ tooltip: "Enabling this will run tests outside of a try-catch block. Makes debugging exceptions in IE reasonable. Stored as query-strings."
257
+ }
258
+ ],
259
+
260
+ // Set of all modules.
261
+ modules: {},
262
+
263
+ callbacks: {}
264
+ };
265
+
266
+ // Initialize more QUnit.config and QUnit.urlParams
267
+ (function() {
268
+ var i, current,
269
+ location = window.location || { search: "", protocol: "file:" },
270
+ params = location.search.slice( 1 ).split( "&" ),
271
+ length = params.length,
272
+ urlParams = {};
273
+
274
+ if ( params[ 0 ] ) {
275
+ for ( i = 0; i < length; i++ ) {
276
+ current = params[ i ].split( "=" );
277
+ current[ 0 ] = decodeURIComponent( current[ 0 ] );
278
+
279
+ // allow just a key to turn on a flag, e.g., test.html?noglobals
280
+ current[ 1 ] = current[ 1 ] ? decodeURIComponent( current[ 1 ] ) : true;
281
+ if ( urlParams[ current[ 0 ] ] ) {
282
+ urlParams[ current[ 0 ] ] = [].concat( urlParams[ current[ 0 ] ], current[ 1 ] );
283
+ } else {
284
+ urlParams[ current[ 0 ] ] = current[ 1 ];
285
+ }
286
+ }
287
+ }
288
+
289
+ QUnit.urlParams = urlParams;
290
+
291
+ // String search anywhere in moduleName+testName
292
+ config.filter = urlParams.filter;
293
+
294
+ // Exact match of the module name
295
+ config.module = urlParams.module;
296
+
297
+ config.testNumber = [];
298
+ if ( urlParams.testNumber ) {
299
+
300
+ // Ensure that urlParams.testNumber is an array
301
+ urlParams.testNumber = [].concat( urlParams.testNumber );
302
+ for ( i = 0; i < urlParams.testNumber.length; i++ ) {
303
+ current = urlParams.testNumber[ i ];
304
+ config.testNumber.push( parseInt( current, 10 ) );
305
+ }
306
+ }
307
+
308
+ // Figure out if we're running the tests from a server or not
309
+ QUnit.isLocal = location.protocol === "file:";
310
+ }());
311
+
312
+ extend( QUnit, {
313
+
314
+ config: config,
315
+
316
+ // Safe object type checking
317
+ is: function( type, obj ) {
318
+ return QUnit.objectType( obj ) === type;
319
+ },
320
+
321
+ objectType: function( obj ) {
322
+ if ( typeof obj === "undefined" ) {
323
+ return "undefined";
324
+ }
325
+
326
+ // Consider: typeof null === object
327
+ if ( obj === null ) {
328
+ return "null";
329
+ }
330
+
331
+ var match = toString.call( obj ).match( /^\[object\s(.*)\]$/ ),
332
+ type = match && match[ 1 ] || "";
333
+
334
+ switch ( type ) {
335
+ case "Number":
336
+ if ( isNaN( obj ) ) {
337
+ return "nan";
338
+ }
339
+ return "number";
340
+ case "String":
341
+ case "Boolean":
342
+ case "Array":
343
+ case "Date":
344
+ case "RegExp":
345
+ case "Function":
346
+ return type.toLowerCase();
347
+ }
348
+ if ( typeof obj === "object" ) {
349
+ return "object";
350
+ }
351
+ return undefined;
352
+ },
353
+
354
+ url: function( params ) {
355
+ params = extend( extend( {}, QUnit.urlParams ), params );
356
+ var key,
357
+ querystring = "?";
358
+
359
+ for ( key in params ) {
360
+ if ( hasOwn.call( params, key ) ) {
361
+ querystring += encodeURIComponent( key ) + "=" +
362
+ encodeURIComponent( params[ key ] ) + "&";
363
+ }
364
+ }
365
+ return window.location.protocol + "//" + window.location.host +
366
+ window.location.pathname + querystring.slice( 0, -1 );
367
+ },
368
+
369
+ extend: extend
370
+ });
371
+
372
+ /**
373
+ * @deprecated: Created for backwards compatibility with test runner that set the hook function
374
+ * into QUnit.{hook}, instead of invoking it and passing the hook function.
375
+ * QUnit.constructor is set to the empty F() above so that we can add to it's prototype here.
376
+ * Doing this allows us to tell if the following methods have been overwritten on the actual
377
+ * QUnit object.
378
+ */
379
+ extend( QUnit.constructor.prototype, {
380
+
381
+ // Logging callbacks; all receive a single argument with the listed properties
382
+ // run test/logs.html for any related changes
383
+ begin: registerLoggingCallback( "begin" ),
384
+
385
+ // done: { failed, passed, total, runtime }
386
+ done: registerLoggingCallback( "done" ),
387
+
388
+ // log: { result, actual, expected, message }
389
+ log: registerLoggingCallback( "log" ),
390
+
391
+ // testStart: { name }
392
+ testStart: registerLoggingCallback( "testStart" ),
393
+
394
+ // testDone: { name, failed, passed, total, runtime }
395
+ testDone: registerLoggingCallback( "testDone" ),
396
+
397
+ // moduleStart: { name }
398
+ moduleStart: registerLoggingCallback( "moduleStart" ),
399
+
400
+ // moduleDone: { name, failed, passed, total }
401
+ moduleDone: registerLoggingCallback( "moduleDone" )
402
+ });
403
+
404
+ QUnit.load = function() {
405
+ runLoggingCallbacks( "begin", {
406
+ totalTests: Test.count
407
+ });
408
+
409
+ // Initialize the configuration options
410
+ extend( config, {
411
+ stats: { all: 0, bad: 0 },
412
+ moduleStats: { all: 0, bad: 0 },
413
+ started: 0,
414
+ updateRate: 1000,
415
+ autostart: true,
416
+ filter: "",
417
+ semaphore: 1
418
+ }, true );
419
+
420
+ config.blocking = false;
421
+
422
+ if ( config.autostart ) {
423
+ QUnit.start();
424
+ }
425
+ };
426
+
427
+ // `onErrorFnPrev` initialized at top of scope
428
+ // Preserve other handlers
429
+ onErrorFnPrev = window.onerror;
430
+
431
+ // Cover uncaught exceptions
432
+ // Returning true will suppress the default browser handler,
433
+ // returning false will let it run.
434
+ window.onerror = function( error, filePath, linerNr ) {
435
+ var ret = false;
436
+ if ( onErrorFnPrev ) {
437
+ ret = onErrorFnPrev( error, filePath, linerNr );
438
+ }
439
+
440
+ // Treat return value as window.onerror itself does,
441
+ // Only do our handling if not suppressed.
442
+ if ( ret !== true ) {
443
+ if ( QUnit.config.current ) {
444
+ if ( QUnit.config.current.ignoreGlobalErrors ) {
445
+ return true;
446
+ }
447
+ QUnit.pushFailure( error, filePath + ":" + linerNr );
448
+ } else {
449
+ QUnit.test( "global failure", extend(function() {
450
+ QUnit.pushFailure( error, filePath + ":" + linerNr );
451
+ }, { validTest: validTest } ) );
452
+ }
453
+ return false;
454
+ }
455
+
456
+ return ret;
457
+ };
458
+
459
+ function done() {
460
+ config.autorun = true;
461
+
462
+ // Log the last module results
463
+ if ( config.previousModule ) {
464
+ runLoggingCallbacks( "moduleDone", {
465
+ name: config.previousModule,
466
+ failed: config.moduleStats.bad,
467
+ passed: config.moduleStats.all - config.moduleStats.bad,
468
+ total: config.moduleStats.all
469
+ });
470
+ }
471
+ delete config.previousModule;
472
+
473
+ var runtime = now() - config.started,
474
+ passed = config.stats.all - config.stats.bad;
475
+
476
+ runLoggingCallbacks( "done", {
477
+ failed: config.stats.bad,
478
+ passed: passed,
479
+ total: config.stats.all,
480
+ runtime: runtime
481
+ });
482
+ }
483
+
484
+ /** @return Boolean: true if this test should be ran */
485
+ function validTest( test ) {
486
+ var include,
487
+ filter = config.filter && config.filter.toLowerCase(),
488
+ module = config.module && config.module.toLowerCase(),
489
+ fullName = ( test.module + ": " + test.testName ).toLowerCase();
490
+
491
+ // Internally-generated tests are always valid
492
+ if ( test.callback && test.callback.validTest === validTest ) {
493
+ delete test.callback.validTest;
494
+ return true;
495
+ }
496
+
497
+ if ( config.testNumber.length > 0 ) {
498
+ if ( inArray( test.testNumber, config.testNumber ) < 0 ) {
499
+ return false;
500
+ }
501
+ }
502
+
503
+ if ( module && ( !test.module || test.module.toLowerCase() !== module ) ) {
504
+ return false;
505
+ }
506
+
507
+ if ( !filter ) {
508
+ return true;
509
+ }
510
+
511
+ include = filter.charAt( 0 ) !== "!";
512
+ if ( !include ) {
513
+ filter = filter.slice( 1 );
514
+ }
515
+
516
+ // If the filter matches, we need to honour include
517
+ if ( fullName.indexOf( filter ) !== -1 ) {
518
+ return include;
519
+ }
520
+
521
+ // Otherwise, do the opposite
522
+ return !include;
523
+ }
524
+
525
+ // Doesn't support IE6 to IE9
526
+ // See also https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Error/Stack
527
+ function extractStacktrace( e, offset ) {
528
+ offset = offset === undefined ? 4 : offset;
529
+
530
+ var stack, include, i;
531
+
532
+ if ( e.stacktrace ) {
533
+
534
+ // Opera 12.x
535
+ return e.stacktrace.split( "\n" )[ offset + 3 ];
536
+ } else if ( e.stack ) {
537
+
538
+ // Firefox, Chrome, Safari 6+, IE10+, PhantomJS and Node
539
+ stack = e.stack.split( "\n" );
540
+ if ( /^error$/i.test( stack[ 0 ] ) ) {
541
+ stack.shift();
542
+ }
543
+ if ( fileName ) {
544
+ include = [];
545
+ for ( i = offset; i < stack.length; i++ ) {
546
+ if ( stack[ i ].indexOf( fileName ) !== -1 ) {
547
+ break;
548
+ }
549
+ include.push( stack[ i ] );
550
+ }
551
+ if ( include.length ) {
552
+ return include.join( "\n" );
553
+ }
554
+ }
555
+ return stack[ offset ];
556
+ } else if ( e.sourceURL ) {
557
+
558
+ // Safari < 6
559
+ // exclude useless self-reference for generated Error objects
560
+ if ( /qunit.js$/.test( e.sourceURL ) ) {
561
+ return;
562
+ }
563
+
564
+ // for actual exceptions, this is useful
565
+ return e.sourceURL + ":" + e.line;
566
+ }
567
+ }
568
+ function sourceFromStacktrace( offset ) {
569
+ try {
570
+ throw new Error();
571
+ } catch ( e ) {
572
+ return extractStacktrace( e, offset );
573
+ }
574
+ }
575
+
576
+ function synchronize( callback, last ) {
577
+ config.queue.push( callback );
578
+
579
+ if ( config.autorun && !config.blocking ) {
580
+ process( last );
581
+ }
582
+ }
583
+
584
+ function process( last ) {
585
+ function next() {
586
+ process( last );
587
+ }
588
+ var start = now();
589
+ config.depth = config.depth ? config.depth + 1 : 1;
590
+
591
+ while ( config.queue.length && !config.blocking ) {
592
+ if ( !defined.setTimeout || config.updateRate <= 0 || ( ( now() - start ) < config.updateRate ) ) {
593
+ config.queue.shift()();
594
+ } else {
595
+ setTimeout( next, 13 );
596
+ break;
597
+ }
598
+ }
599
+ config.depth--;
600
+ if ( last && !config.blocking && !config.queue.length && config.depth === 0 ) {
601
+ done();
602
+ }
603
+ }
604
+
605
+ function saveGlobal() {
606
+ config.pollution = [];
607
+
608
+ if ( config.noglobals ) {
609
+ for ( var key in window ) {
610
+ if ( hasOwn.call( window, key ) ) {
611
+ // in Opera sometimes DOM element ids show up here, ignore them
612
+ if ( /^qunit-test-output/.test( key ) ) {
613
+ continue;
614
+ }
615
+ config.pollution.push( key );
616
+ }
617
+ }
618
+ }
619
+ }
620
+
621
+ function checkPollution() {
622
+ var newGlobals,
623
+ deletedGlobals,
624
+ old = config.pollution;
625
+
626
+ saveGlobal();
627
+
628
+ newGlobals = diff( config.pollution, old );
629
+ if ( newGlobals.length > 0 ) {
630
+ QUnit.pushFailure( "Introduced global variable(s): " + newGlobals.join( ", " ) );
631
+ }
632
+
633
+ deletedGlobals = diff( old, config.pollution );
634
+ if ( deletedGlobals.length > 0 ) {
635
+ QUnit.pushFailure( "Deleted global variable(s): " + deletedGlobals.join( ", " ) );
636
+ }
637
+ }
638
+
639
+ // returns a new Array with the elements that are in a but not in b
640
+ function diff( a, b ) {
641
+ var i, j,
642
+ result = a.slice();
643
+
644
+ for ( i = 0; i < result.length; i++ ) {
645
+ for ( j = 0; j < b.length; j++ ) {
646
+ if ( result[ i ] === b[ j ] ) {
647
+ result.splice( i, 1 );
648
+ i--;
649
+ break;
650
+ }
651
+ }
652
+ }
653
+ return result;
654
+ }
655
+
656
+ function extend( a, b, undefOnly ) {
657
+ for ( var prop in b ) {
658
+ if ( hasOwn.call( b, prop ) ) {
659
+
660
+ // Avoid "Member not found" error in IE8 caused by messing with window.constructor
661
+ if ( !( prop === "constructor" && a === window ) ) {
662
+ if ( b[ prop ] === undefined ) {
663
+ delete a[ prop ];
664
+ } else if ( !( undefOnly && typeof a[ prop ] !== "undefined" ) ) {
665
+ a[ prop ] = b[ prop ];
666
+ }
667
+ }
668
+ }
669
+ }
670
+
671
+ return a;
672
+ }
673
+
674
+ function registerLoggingCallback( key ) {
675
+
676
+ // Initialize key collection of logging callback
677
+ if ( QUnit.objectType( config.callbacks[ key ] ) === "undefined" ) {
678
+ config.callbacks[ key ] = [];
679
+ }
680
+
681
+ return function( callback ) {
682
+ config.callbacks[ key ].push( callback );
683
+ };
684
+ }
685
+
686
+ function runLoggingCallbacks( key, args ) {
687
+ var i, l, callbacks;
688
+
689
+ callbacks = config.callbacks[ key ];
690
+ for ( i = 0, l = callbacks.length; i < l; i++ ) {
691
+ callbacks[ i ]( args );
692
+ }
693
+ }
694
+
695
+ // from jquery.js
696
+ function inArray( elem, array ) {
697
+ if ( array.indexOf ) {
698
+ return array.indexOf( elem );
699
+ }
700
+
701
+ for ( var i = 0, length = array.length; i < length; i++ ) {
702
+ if ( array[ i ] === elem ) {
703
+ return i;
704
+ }
705
+ }
706
+
707
+ return -1;
708
+ }
709
+
710
+ function Test( settings ) {
711
+ extend( this, settings );
712
+ this.assert = new Assert( this );
713
+ this.assertions = [];
714
+ this.testNumber = ++Test.count;
715
+ }
716
+
717
+ Test.count = 0;
718
+
719
+ Test.prototype = {
720
+ setup: function() {
721
+ if (
722
+
723
+ // Emit moduleStart when we're switching from one module to another
724
+ this.module !== config.previousModule ||
725
+
726
+ // They could be equal (both undefined) but if the previousModule property doesn't
727
+ // yet exist it means this is the first test in a suite that isn't wrapped in a
728
+ // module, in which case we'll just emit a moduleStart event for 'undefined'.
729
+ // Without this, reporters can get testStart before moduleStart which is a problem.
730
+ !hasOwn.call( config, "previousModule" )
731
+ ) {
732
+ if ( hasOwn.call( config, "previousModule" ) ) {
733
+ runLoggingCallbacks( "moduleDone", {
734
+ name: config.previousModule,
735
+ failed: config.moduleStats.bad,
736
+ passed: config.moduleStats.all - config.moduleStats.bad,
737
+ total: config.moduleStats.all
738
+ });
739
+ }
740
+ config.previousModule = this.module;
741
+ config.moduleStats = { all: 0, bad: 0 };
742
+ runLoggingCallbacks( "moduleStart", {
743
+ name: this.module
744
+ });
745
+ }
746
+
747
+ config.current = this;
748
+
749
+ this.testEnvironment = extend({
750
+ setup: function() {},
751
+ teardown: function() {}
752
+ }, this.moduleTestEnvironment );
753
+
754
+ this.started = now();
755
+ runLoggingCallbacks( "testStart", {
756
+ name: this.testName,
757
+ module: this.module,
758
+ testNumber: this.testNumber
759
+ });
760
+
761
+ if ( !config.pollution ) {
762
+ saveGlobal();
763
+ }
764
+ if ( config.notrycatch ) {
765
+ this.testEnvironment.setup.call( this.testEnvironment, this.assert );
766
+ return;
767
+ }
768
+ try {
769
+ this.testEnvironment.setup.call( this.testEnvironment, this.assert );
770
+ } catch ( e ) {
771
+ this.pushFailure( "Setup failed on " + this.testName + ": " + ( e.message || e ), extractStacktrace( e, 0 ) );
772
+ }
773
+ },
774
+ run: function() {
775
+ config.current = this;
776
+
777
+ if ( this.async ) {
778
+ QUnit.stop();
779
+ }
780
+
781
+ this.callbackStarted = now();
782
+
783
+ if ( config.notrycatch ) {
784
+ this.callback.call( this.testEnvironment, this.assert );
785
+ this.callbackRuntime = now() - this.callbackStarted;
786
+ return;
787
+ }
788
+
789
+ try {
790
+ this.callback.call( this.testEnvironment, this.assert );
791
+ this.callbackRuntime = now() - this.callbackStarted;
792
+ } catch ( e ) {
793
+ this.callbackRuntime = now() - this.callbackStarted;
794
+
795
+ this.pushFailure( "Died on test #" + ( this.assertions.length + 1 ) + " " + this.stack + ": " + ( e.message || e ), extractStacktrace( e, 0 ) );
796
+
797
+ // else next test will carry the responsibility
798
+ saveGlobal();
799
+
800
+ // Restart the tests if they're blocking
801
+ if ( config.blocking ) {
802
+ QUnit.start();
803
+ }
804
+ }
805
+ },
806
+ teardown: function() {
807
+ config.current = this;
808
+ if ( config.notrycatch ) {
809
+ if ( typeof this.callbackRuntime === "undefined" ) {
810
+ this.callbackRuntime = now() - this.callbackStarted;
811
+ }
812
+ this.testEnvironment.teardown.call( this.testEnvironment, this.assert );
813
+ return;
814
+ } else {
815
+ try {
816
+ this.testEnvironment.teardown.call( this.testEnvironment, this.assert );
817
+ } catch ( e ) {
818
+ this.pushFailure( "Teardown failed on " + this.testName + ": " + ( e.message || e ), extractStacktrace( e, 0 ) );
819
+ }
820
+ }
821
+ checkPollution();
822
+ },
823
+ finish: function() {
824
+ config.current = this;
825
+ if ( config.requireExpects && this.expected === null ) {
826
+ this.pushFailure( "Expected number of assertions to be defined, but expect() was not called.", this.stack );
827
+ } else if ( this.expected !== null && this.expected !== this.assertions.length ) {
828
+ this.pushFailure( "Expected " + this.expected + " assertions, but " + this.assertions.length + " were run", this.stack );
829
+ } else if ( this.expected === null && !this.assertions.length ) {
830
+ this.pushFailure( "Expected at least one assertion, but none were run - call expect(0) to accept zero assertions.", this.stack );
831
+ }
832
+
833
+ var i,
834
+ bad = 0;
835
+
836
+ this.runtime = now() - this.started;
837
+ config.stats.all += this.assertions.length;
838
+ config.moduleStats.all += this.assertions.length;
839
+
840
+ for ( i = 0; i < this.assertions.length; i++ ) {
841
+ if ( !this.assertions[ i ].result ) {
842
+ bad++;
843
+ config.stats.bad++;
844
+ config.moduleStats.bad++;
845
+ }
846
+ }
847
+
848
+ runLoggingCallbacks( "testDone", {
849
+ name: this.testName,
850
+ module: this.module,
851
+ failed: bad,
852
+ passed: this.assertions.length - bad,
853
+ total: this.assertions.length,
854
+ runtime: this.runtime,
855
+
856
+ // HTML Reporter use
857
+ assertions: this.assertions,
858
+ testNumber: this.testNumber,
859
+
860
+ // DEPRECATED: this property will be removed in 2.0.0, use runtime instead
861
+ duration: this.runtime
862
+ });
863
+
864
+ config.current = undefined;
865
+ },
866
+
867
+ queue: function() {
868
+ var bad,
869
+ test = this;
870
+
871
+ function run() {
872
+ // each of these can by async
873
+ synchronize(function() {
874
+ test.setup();
875
+ });
876
+ synchronize(function() {
877
+ test.run();
878
+ });
879
+ synchronize(function() {
880
+ test.teardown();
881
+ });
882
+ synchronize(function() {
883
+ test.finish();
884
+ });
885
+ }
886
+
887
+ // `bad` initialized at top of scope
888
+ // defer when previous test run passed, if storage is available
889
+ bad = QUnit.config.reorder && defined.sessionStorage &&
890
+ +sessionStorage.getItem( "qunit-test-" + this.module + "-" + this.testName );
891
+
892
+ if ( bad ) {
893
+ run();
894
+ } else {
895
+ synchronize( run, true );
896
+ }
897
+ },
898
+
899
+ push: function( result, actual, expected, message ) {
900
+ var source,
901
+ details = {
902
+ module: this.module,
903
+ name: this.testName,
904
+ result: result,
905
+ message: message,
906
+ actual: actual,
907
+ expected: expected,
908
+ testNumber: this.testNumber
909
+ };
910
+
911
+ if ( !result ) {
912
+ source = sourceFromStacktrace();
913
+
914
+ if ( source ) {
915
+ details.source = source;
916
+ }
917
+ }
918
+
919
+ runLoggingCallbacks( "log", details );
920
+
921
+ this.assertions.push({
922
+ result: !!result,
923
+ message: message
924
+ });
925
+ },
926
+
927
+ pushFailure: function( message, source, actual ) {
928
+ if ( !this instanceof Test ) {
929
+ throw new Error( "pushFailure() assertion outside test context, was " + sourceFromStacktrace( 2 ) );
930
+ }
931
+
932
+ var details = {
933
+ module: this.module,
934
+ name: this.testName,
935
+ result: false,
936
+ message: message || "error",
937
+ actual: actual || null,
938
+ testNumber: this.testNumber
939
+ };
940
+
941
+ if ( source ) {
942
+ details.source = source;
943
+ }
944
+
945
+ runLoggingCallbacks( "log", details );
946
+
947
+ this.assertions.push({
948
+ result: false,
949
+ message: message
950
+ });
951
+ }
952
+ };
953
+
954
+ QUnit.pushFailure = function() {
955
+ if ( !QUnit.config.current ) {
956
+ throw new Error( "pushFailure() assertion outside test context, in " + sourceFromStacktrace( 2 ) );
957
+ }
958
+
959
+ // Gets current test obj
960
+ var currentTest = QUnit.config.current.assert.test;
961
+
962
+ return currentTest.pushFailure.apply( currentTest, arguments );
963
+ };
964
+
965
+ function Assert( testContext ) {
966
+ this.test = testContext;
967
+ }
968
+
969
+ // Assert helpers
970
+ QUnit.assert = Assert.prototype = {
971
+
972
+ // Specify the number of expected assertions to guarantee that failed test (no assertions are run at all) don't slip through.
973
+ expect: function( asserts ) {
974
+ if ( arguments.length === 1 ) {
975
+ this.test.expected = asserts;
976
+ } else {
977
+ return this.test.expected;
978
+ }
979
+ },
980
+
981
+ // Exports test.push() to the user API
982
+ push: function() {
983
+ var assert = this;
984
+
985
+ // Backwards compatibility fix.
986
+ // Allows the direct use of global exported assertions and QUnit.assert.*
987
+ // Although, it's use is not recommended as it can leak assertions
988
+ // to other tests from async tests, because we only get a reference to the current test,
989
+ // not exactly the test where assertion were intended to be called.
990
+ if ( !QUnit.config.current ) {
991
+ throw new Error( "assertion outside test context, in " + sourceFromStacktrace( 2 ) );
992
+ }
993
+ if ( !( assert instanceof Assert ) ) {
994
+ assert = QUnit.config.current.assert;
995
+ }
996
+ return assert.test.push.apply( assert.test, arguments );
997
+ },
998
+
999
+ /**
1000
+ * Asserts rough true-ish result.
1001
+ * @name ok
1002
+ * @function
1003
+ * @example ok( "asdfasdf".length > 5, "There must be at least 5 chars" );
1004
+ */
1005
+ ok: function( result, message ) {
1006
+ message = message || ( result ? "okay" : "failed, expected argument to be truthy, was: " +
1007
+ QUnit.dump.parse( result ) );
1008
+ if ( !!result ) {
1009
+ this.push( true, result, true, message );
1010
+ } else {
1011
+ this.test.pushFailure( message, null, result );
1012
+ }
1013
+ },
1014
+
1015
+ /**
1016
+ * Assert that the first two arguments are equal, with an optional message.
1017
+ * Prints out both actual and expected values.
1018
+ * @name equal
1019
+ * @function
1020
+ * @example equal( format( "Received {0} bytes.", 2), "Received 2 bytes.", "format() replaces {0} with next argument" );
1021
+ */
1022
+ equal: function( actual, expected, message ) {
1023
+ /*jshint eqeqeq:false */
1024
+ this.push( expected == actual, actual, expected, message );
1025
+ },
1026
+
1027
+ /**
1028
+ * @name notEqual
1029
+ * @function
1030
+ */
1031
+ notEqual: function( actual, expected, message ) {
1032
+ /*jshint eqeqeq:false */
1033
+ this.push( expected != actual, actual, expected, message );
1034
+ },
1035
+
1036
+ /**
1037
+ * @name propEqual
1038
+ * @function
1039
+ */
1040
+ propEqual: function( actual, expected, message ) {
1041
+ actual = objectValues( actual );
1042
+ expected = objectValues( expected );
1043
+ this.push( QUnit.equiv( actual, expected ), actual, expected, message );
1044
+ },
1045
+
1046
+ /**
1047
+ * @name notPropEqual
1048
+ * @function
1049
+ */
1050
+ notPropEqual: function( actual, expected, message ) {
1051
+ actual = objectValues( actual );
1052
+ expected = objectValues( expected );
1053
+ this.push( !QUnit.equiv( actual, expected ), actual, expected, message );
1054
+ },
1055
+
1056
+ /**
1057
+ * @name deepEqual
1058
+ * @function
1059
+ */
1060
+ deepEqual: function( actual, expected, message ) {
1061
+ this.push( QUnit.equiv( actual, expected ), actual, expected, message );
1062
+ },
1063
+
1064
+ /**
1065
+ * @name notDeepEqual
1066
+ * @function
1067
+ */
1068
+ notDeepEqual: function( actual, expected, message ) {
1069
+ this.push( !QUnit.equiv( actual, expected ), actual, expected, message );
1070
+ },
1071
+
1072
+ /**
1073
+ * @name strictEqual
1074
+ * @function
1075
+ */
1076
+ strictEqual: function( actual, expected, message ) {
1077
+ this.push( expected === actual, actual, expected, message );
1078
+ },
1079
+
1080
+ /**
1081
+ * @name notStrictEqual
1082
+ * @function
1083
+ */
1084
+ notStrictEqual: function( actual, expected, message ) {
1085
+ this.push( expected !== actual, actual, expected, message );
1086
+ },
1087
+
1088
+ "throws": function( block, expected, message ) {
1089
+ var actual, expectedType,
1090
+ expectedOutput = expected,
1091
+ ok = false;
1092
+
1093
+ // 'expected' is optional unless doing string comparison
1094
+ if ( message == null && typeof expected === "string" ) {
1095
+ message = expected;
1096
+ expected = null;
1097
+ }
1098
+
1099
+ this.test.ignoreGlobalErrors = true;
1100
+ try {
1101
+ block.call( this.test.testEnvironment );
1102
+ } catch (e) {
1103
+ actual = e;
1104
+ }
1105
+ this.test.ignoreGlobalErrors = false;
1106
+
1107
+ if ( actual ) {
1108
+ expectedType = QUnit.objectType( expected );
1109
+
1110
+ // we don't want to validate thrown error
1111
+ if ( !expected ) {
1112
+ ok = true;
1113
+ expectedOutput = null;
1114
+
1115
+ // expected is a regexp
1116
+ } else if ( expectedType === "regexp" ) {
1117
+ ok = expected.test( errorString( actual ) );
1118
+
1119
+ // expected is a string
1120
+ } else if ( expectedType === "string" ) {
1121
+ ok = expected === errorString( actual );
1122
+
1123
+ // expected is a constructor, maybe an Error constructor
1124
+ } else if ( expectedType === "function" && actual instanceof expected ) {
1125
+ ok = true;
1126
+
1127
+ // expected is an Error object
1128
+ } else if ( expectedType === "object" ) {
1129
+ ok = actual instanceof expected.constructor &&
1130
+ actual.name === expected.name &&
1131
+ actual.message === expected.message;
1132
+
1133
+ // expected is a validation function which returns true if validation passed
1134
+ } else if ( expectedType === "function" && expected.call( {}, actual ) === true ) {
1135
+ expectedOutput = null;
1136
+ ok = true;
1137
+ }
1138
+
1139
+ this.push( ok, actual, expectedOutput, message );
1140
+ } else {
1141
+ this.test.pushFailure( message, null, "No exception was thrown." );
1142
+ }
1143
+ }
1144
+ };
1145
+
1146
+ // Test for equality any JavaScript type.
1147
+ // Author: Philippe Rathé <prathe@gmail.com>
1148
+ QUnit.equiv = (function() {
1149
+
1150
+ // Call the o related callback with the given arguments.
1151
+ function bindCallbacks( o, callbacks, args ) {
1152
+ var prop = QUnit.objectType( o );
1153
+ if ( prop ) {
1154
+ if ( QUnit.objectType( callbacks[ prop ] ) === "function" ) {
1155
+ return callbacks[ prop ].apply( callbacks, args );
1156
+ } else {
1157
+ return callbacks[ prop ]; // or undefined
1158
+ }
1159
+ }
1160
+ }
1161
+
1162
+ // the real equiv function
1163
+ var innerEquiv,
1164
+
1165
+ // stack to decide between skip/abort functions
1166
+ callers = [],
1167
+
1168
+ // stack to avoiding loops from circular referencing
1169
+ parents = [],
1170
+ parentsB = [],
1171
+
1172
+ getProto = Object.getPrototypeOf || function( obj ) {
1173
+ /* jshint camelcase: false, proto: true */
1174
+ return obj.__proto__;
1175
+ },
1176
+ callbacks = (function() {
1177
+
1178
+ // for string, boolean, number and null
1179
+ function useStrictEquality( b, a ) {
1180
+
1181
+ /*jshint eqeqeq:false */
1182
+ if ( b instanceof a.constructor || a instanceof b.constructor ) {
1183
+
1184
+ // to catch short annotation VS 'new' annotation of a
1185
+ // declaration
1186
+ // e.g. var i = 1;
1187
+ // var j = new Number(1);
1188
+ return a == b;
1189
+ } else {
1190
+ return a === b;
1191
+ }
1192
+ }
1193
+
1194
+ return {
1195
+ "string": useStrictEquality,
1196
+ "boolean": useStrictEquality,
1197
+ "number": useStrictEquality,
1198
+ "null": useStrictEquality,
1199
+ "undefined": useStrictEquality,
1200
+
1201
+ "nan": function( b ) {
1202
+ return isNaN( b );
1203
+ },
1204
+
1205
+ "date": function( b, a ) {
1206
+ return QUnit.objectType( b ) === "date" && a.valueOf() === b.valueOf();
1207
+ },
1208
+
1209
+ "regexp": function( b, a ) {
1210
+ return QUnit.objectType( b ) === "regexp" &&
1211
+
1212
+ // the regex itself
1213
+ a.source === b.source &&
1214
+
1215
+ // and its modifiers
1216
+ a.global === b.global &&
1217
+
1218
+ // (gmi) ...
1219
+ a.ignoreCase === b.ignoreCase &&
1220
+ a.multiline === b.multiline &&
1221
+ a.sticky === b.sticky;
1222
+ },
1223
+
1224
+ // - skip when the property is a method of an instance (OOP)
1225
+ // - abort otherwise,
1226
+ // initial === would have catch identical references anyway
1227
+ "function": function() {
1228
+ var caller = callers[ callers.length - 1 ];
1229
+ return caller !== Object && typeof caller !== "undefined";
1230
+ },
1231
+
1232
+ "array": function( b, a ) {
1233
+ var i, j, len, loop, aCircular, bCircular;
1234
+
1235
+ // b could be an object literal here
1236
+ if ( QUnit.objectType( b ) !== "array" ) {
1237
+ return false;
1238
+ }
1239
+
1240
+ len = a.length;
1241
+ if ( len !== b.length ) {
1242
+ // safe and faster
1243
+ return false;
1244
+ }
1245
+
1246
+ // track reference to avoid circular references
1247
+ parents.push( a );
1248
+ parentsB.push( b );
1249
+ for ( i = 0; i < len; i++ ) {
1250
+ loop = false;
1251
+ for ( j = 0; j < parents.length; j++ ) {
1252
+ aCircular = parents[ j ] === a[ i ];
1253
+ bCircular = parentsB[ j ] === b[ i ];
1254
+ if ( aCircular || bCircular ) {
1255
+ if ( a[ i ] === b[ i ] || aCircular && bCircular ) {
1256
+ loop = true;
1257
+ } else {
1258
+ parents.pop();
1259
+ parentsB.pop();
1260
+ return false;
1261
+ }
1262
+ }
1263
+ }
1264
+ if ( !loop && !innerEquiv( a[ i ], b[ i ] ) ) {
1265
+ parents.pop();
1266
+ parentsB.pop();
1267
+ return false;
1268
+ }
1269
+ }
1270
+ parents.pop();
1271
+ parentsB.pop();
1272
+ return true;
1273
+ },
1274
+
1275
+ "object": function( b, a ) {
1276
+
1277
+ /*jshint forin:false */
1278
+ var i, j, loop, aCircular, bCircular,
1279
+ // Default to true
1280
+ eq = true,
1281
+ aProperties = [],
1282
+ bProperties = [];
1283
+
1284
+ // comparing constructors is more strict than using
1285
+ // instanceof
1286
+ if ( a.constructor !== b.constructor ) {
1287
+
1288
+ // Allow objects with no prototype to be equivalent to
1289
+ // objects with Object as their constructor.
1290
+ if ( !( ( getProto( a ) === null && getProto( b ) === Object.prototype ) ||
1291
+ ( getProto( b ) === null && getProto( a ) === Object.prototype ) ) ) {
1292
+ return false;
1293
+ }
1294
+ }
1295
+
1296
+ // stack constructor before traversing properties
1297
+ callers.push( a.constructor );
1298
+
1299
+ // track reference to avoid circular references
1300
+ parents.push( a );
1301
+ parentsB.push( b );
1302
+
1303
+ // be strict: don't ensure hasOwnProperty and go deep
1304
+ for ( i in a ) {
1305
+ loop = false;
1306
+ for ( j = 0; j < parents.length; j++ ) {
1307
+ aCircular = parents[ j ] === a[ i ];
1308
+ bCircular = parentsB[ j ] === b[ i ];
1309
+ if ( aCircular || bCircular ) {
1310
+ if ( a[ i ] === b[ i ] || aCircular && bCircular ) {
1311
+ loop = true;
1312
+ } else {
1313
+ eq = false;
1314
+ break;
1315
+ }
1316
+ }
1317
+ }
1318
+ aProperties.push( i );
1319
+ if ( !loop && !innerEquiv( a[ i ], b[ i ] ) ) {
1320
+ eq = false;
1321
+ break;
1322
+ }
1323
+ }
1324
+
1325
+ parents.pop();
1326
+ parentsB.pop();
1327
+ callers.pop(); // unstack, we are done
1328
+
1329
+ for ( i in b ) {
1330
+ bProperties.push( i ); // collect b's properties
1331
+ }
1332
+
1333
+ // Ensures identical properties name
1334
+ return eq && innerEquiv( aProperties.sort(), bProperties.sort() );
1335
+ }
1336
+ };
1337
+ }());
1338
+
1339
+ innerEquiv = function() { // can take multiple arguments
1340
+ var args = [].slice.apply( arguments );
1341
+ if ( args.length < 2 ) {
1342
+ return true; // end transition
1343
+ }
1344
+
1345
+ return ( (function( a, b ) {
1346
+ if ( a === b ) {
1347
+ return true; // catch the most you can
1348
+ } else if ( a === null || b === null || typeof a === "undefined" ||
1349
+ typeof b === "undefined" ||
1350
+ QUnit.objectType( a ) !== QUnit.objectType( b ) ) {
1351
+
1352
+ // don't lose time with error prone cases
1353
+ return false;
1354
+ } else {
1355
+ return bindCallbacks( a, callbacks, [ b, a ] );
1356
+ }
1357
+
1358
+ // apply transition with (1..n) arguments
1359
+ }( args[ 0 ], args[ 1 ] ) ) && innerEquiv.apply( this, args.splice( 1, args.length - 1 ) ) );
1360
+ };
1361
+
1362
+ return innerEquiv;
1363
+ }());
1364
+
1365
+ // Based on jsDump by Ariel Flesler
1366
+ // http://flesler.blogspot.com/2008/05/jsdump-pretty-dump-of-any-javascript.html
1367
+ QUnit.dump = (function() {
1368
+ function quote( str ) {
1369
+ return "\"" + str.toString().replace( /"/g, "\\\"" ) + "\"";
1370
+ }
1371
+ function literal( o ) {
1372
+ return o + "";
1373
+ }
1374
+ function join( pre, arr, post ) {
1375
+ var s = dump.separator(),
1376
+ base = dump.indent(),
1377
+ inner = dump.indent( 1 );
1378
+ if ( arr.join ) {
1379
+ arr = arr.join( "," + s + inner );
1380
+ }
1381
+ if ( !arr ) {
1382
+ return pre + post;
1383
+ }
1384
+ return [ pre, inner + arr, base + post ].join( s );
1385
+ }
1386
+ function array( arr, stack ) {
1387
+ var i = arr.length,
1388
+ ret = new Array( i );
1389
+ this.up();
1390
+ while ( i-- ) {
1391
+ ret[ i ] = this.parse( arr[ i ], undefined, stack );
1392
+ }
1393
+ this.down();
1394
+ return join( "[", ret, "]" );
1395
+ }
1396
+
1397
+ var reName = /^function (\w+)/,
1398
+ dump = {
1399
+ // type is used mostly internally, you can fix a (custom)type in advance
1400
+ parse: function( obj, type, stack ) {
1401
+ stack = stack || [];
1402
+ var inStack, res,
1403
+ parser = this.parsers[ type || this.typeOf( obj ) ];
1404
+
1405
+ type = typeof parser;
1406
+ inStack = inArray( obj, stack );
1407
+
1408
+ if ( inStack !== -1 ) {
1409
+ return "recursion(" + ( inStack - stack.length ) + ")";
1410
+ }
1411
+ if ( type === "function" ) {
1412
+ stack.push( obj );
1413
+ res = parser.call( this, obj, stack );
1414
+ stack.pop();
1415
+ return res;
1416
+ }
1417
+ return ( type === "string" ) ? parser : this.parsers.error;
1418
+ },
1419
+ typeOf: function( obj ) {
1420
+ var type;
1421
+ if ( obj === null ) {
1422
+ type = "null";
1423
+ } else if ( typeof obj === "undefined" ) {
1424
+ type = "undefined";
1425
+ } else if ( QUnit.is( "regexp", obj ) ) {
1426
+ type = "regexp";
1427
+ } else if ( QUnit.is( "date", obj ) ) {
1428
+ type = "date";
1429
+ } else if ( QUnit.is( "function", obj ) ) {
1430
+ type = "function";
1431
+ } else if ( typeof obj.setInterval !== undefined && typeof obj.document !== "undefined" && typeof obj.nodeType === "undefined" ) {
1432
+ type = "window";
1433
+ } else if ( obj.nodeType === 9 ) {
1434
+ type = "document";
1435
+ } else if ( obj.nodeType ) {
1436
+ type = "node";
1437
+ } else if (
1438
+
1439
+ // native arrays
1440
+ toString.call( obj ) === "[object Array]" ||
1441
+
1442
+ // NodeList objects
1443
+ ( typeof obj.length === "number" && typeof obj.item !== "undefined" && ( obj.length ? obj.item( 0 ) === obj[ 0 ] : ( obj.item( 0 ) === null && typeof obj[ 0 ] === "undefined" ) ) )
1444
+ ) {
1445
+ type = "array";
1446
+ } else if ( obj.constructor === Error.prototype.constructor ) {
1447
+ type = "error";
1448
+ } else {
1449
+ type = typeof obj;
1450
+ }
1451
+ return type;
1452
+ },
1453
+ separator: function() {
1454
+ return this.multiline ? this.HTML ? "<br />" : "\n" : this.HTML ? "&nbsp;" : " ";
1455
+ },
1456
+ // extra can be a number, shortcut for increasing-calling-decreasing
1457
+ indent: function( extra ) {
1458
+ if ( !this.multiline ) {
1459
+ return "";
1460
+ }
1461
+ var chr = this.indentChar;
1462
+ if ( this.HTML ) {
1463
+ chr = chr.replace( /\t/g, " " ).replace( / /g, "&nbsp;" );
1464
+ }
1465
+ return new Array( this.depth + ( extra || 0 ) ).join( chr );
1466
+ },
1467
+ up: function( a ) {
1468
+ this.depth += a || 1;
1469
+ },
1470
+ down: function( a ) {
1471
+ this.depth -= a || 1;
1472
+ },
1473
+ setParser: function( name, parser ) {
1474
+ this.parsers[ name ] = parser;
1475
+ },
1476
+ // The next 3 are exposed so you can use them
1477
+ quote: quote,
1478
+ literal: literal,
1479
+ join: join,
1480
+ //
1481
+ depth: 1,
1482
+ // This is the list of parsers, to modify them, use dump.setParser
1483
+ parsers: {
1484
+ window: "[Window]",
1485
+ document: "[Document]",
1486
+ error: function( error ) {
1487
+ return "Error(\"" + error.message + "\")";
1488
+ },
1489
+ unknown: "[Unknown]",
1490
+ "null": "null",
1491
+ "undefined": "undefined",
1492
+ "function": function( fn ) {
1493
+ var ret = "function",
1494
+ // functions never have name in IE
1495
+ name = "name" in fn ? fn.name : ( reName.exec( fn ) || [] )[ 1 ];
1496
+
1497
+ if ( name ) {
1498
+ ret += " " + name;
1499
+ }
1500
+ ret += "( ";
1501
+
1502
+ ret = [ ret, dump.parse( fn, "functionArgs" ), "){" ].join( "" );
1503
+ return join( ret, dump.parse( fn, "functionCode" ), "}" );
1504
+ },
1505
+ array: array,
1506
+ nodelist: array,
1507
+ "arguments": array,
1508
+ object: function( map, stack ) {
1509
+ /*jshint forin:false */
1510
+ var ret = [], keys, key, val, i, nonEnumerableProperties;
1511
+ dump.up();
1512
+ keys = [];
1513
+ for ( key in map ) {
1514
+ keys.push( key );
1515
+ }
1516
+
1517
+ // Some properties are not always enumerable on Error objects.
1518
+ nonEnumerableProperties = [ "message", "name" ];
1519
+ for ( i in nonEnumerableProperties ) {
1520
+ key = nonEnumerableProperties[ i ];
1521
+ if ( key in map && !( key in keys ) ) {
1522
+ keys.push( key );
1523
+ }
1524
+ }
1525
+ keys.sort();
1526
+ for ( i = 0; i < keys.length; i++ ) {
1527
+ key = keys[ i ];
1528
+ val = map[ key ];
1529
+ ret.push( dump.parse( key, "key" ) + ": " + dump.parse( val, undefined, stack ) );
1530
+ }
1531
+ dump.down();
1532
+ return join( "{", ret, "}" );
1533
+ },
1534
+ node: function( node ) {
1535
+ var len, i, val,
1536
+ open = dump.HTML ? "&lt;" : "<",
1537
+ close = dump.HTML ? "&gt;" : ">",
1538
+ tag = node.nodeName.toLowerCase(),
1539
+ ret = open + tag,
1540
+ attrs = node.attributes;
1541
+
1542
+ if ( attrs ) {
1543
+ for ( i = 0, len = attrs.length; i < len; i++ ) {
1544
+ val = attrs[ i ].nodeValue;
1545
+
1546
+ // IE6 includes all attributes in .attributes, even ones not explicitly set.
1547
+ // Those have values like undefined, null, 0, false, "" or "inherit".
1548
+ if ( val && val !== "inherit" ) {
1549
+ ret += " " + attrs[ i ].nodeName + "=" + dump.parse( val, "attribute" );
1550
+ }
1551
+ }
1552
+ }
1553
+ ret += close;
1554
+
1555
+ // Show content of TextNode or CDATASection
1556
+ if ( node.nodeType === 3 || node.nodeType === 4 ) {
1557
+ ret += node.nodeValue;
1558
+ }
1559
+
1560
+ return ret + open + "/" + tag + close;
1561
+ },
1562
+
1563
+ // function calls it internally, it's the arguments part of the function
1564
+ functionArgs: function( fn ) {
1565
+ var args,
1566
+ l = fn.length;
1567
+
1568
+ if ( !l ) {
1569
+ return "";
1570
+ }
1571
+
1572
+ args = new Array( l );
1573
+ while ( l-- ) {
1574
+
1575
+ // 97 is 'a'
1576
+ args[ l ] = String.fromCharCode( 97 + l );
1577
+ }
1578
+ return " " + args.join( ", " ) + " ";
1579
+ },
1580
+ // object calls it internally, the key part of an item in a map
1581
+ key: quote,
1582
+ // function calls it internally, it's the content of the function
1583
+ functionCode: "[code]",
1584
+ // node calls it internally, it's an html attribute value
1585
+ attribute: quote,
1586
+ string: quote,
1587
+ date: quote,
1588
+ regexp: literal,
1589
+ number: literal,
1590
+ "boolean": literal
1591
+ },
1592
+ // if true, entities are escaped ( <, >, \t, space and \n )
1593
+ HTML: false,
1594
+ // indentation unit
1595
+ indentChar: " ",
1596
+ // if true, items in a collection, are separated by a \n, else just a space.
1597
+ multiline: true
1598
+ };
1599
+
1600
+ return dump;
1601
+ }());
1602
+
1603
+ // back compat
1604
+ QUnit.jsDump = QUnit.dump;
1605
+
1606
+ // For browser, export only select globals
1607
+ if ( typeof window !== "undefined" ) {
1608
+
1609
+ // Deprecated
1610
+ // Extend assert methods to QUnit and Global scope through Backwards compatibility
1611
+ (function() {
1612
+ var i,
1613
+ assertions = Assert.prototype;
1614
+
1615
+ function applyCurrent( current ) {
1616
+ return function() {
1617
+ var assert = new Assert( QUnit.config.current );
1618
+ current.apply( assert, arguments );
1619
+ };
1620
+ }
1621
+
1622
+ for ( i in assertions ) {
1623
+ QUnit[ i ] = applyCurrent( assertions[ i ] );
1624
+ }
1625
+ })();
1626
+
1627
+ (function() {
1628
+ var i, l,
1629
+ keys = [
1630
+ "test",
1631
+ "module",
1632
+ "expect",
1633
+ "asyncTest",
1634
+ "start",
1635
+ "stop",
1636
+ "ok",
1637
+ "equal",
1638
+ "notEqual",
1639
+ "propEqual",
1640
+ "notPropEqual",
1641
+ "deepEqual",
1642
+ "notDeepEqual",
1643
+ "strictEqual",
1644
+ "notStrictEqual",
1645
+ "throws"
1646
+ ];
1647
+
1648
+ for ( i = 0, l = keys.length; i < l; i++ ) {
1649
+ window[ keys[ i ] ] = QUnit[ keys[ i ] ];
1650
+ }
1651
+ })();
1652
+
1653
+ window.QUnit = QUnit;
1654
+ }
1655
+
1656
+ // For CommonJS environments, export everything
1657
+ if ( typeof module !== "undefined" && module.exports ) {
1658
+ module.exports = QUnit;
1659
+ }
1660
+
1661
+ // Get a reference to the global object, like window in browsers
1662
+ }( (function() {
1663
+ return this;
1664
+ })() ));
1665
+
1666
+ /*istanbul ignore next */
1667
+ /*
1668
+ * Javascript Diff Algorithm
1669
+ * By John Resig (http://ejohn.org/)
1670
+ * Modified by Chu Alan "sprite"
1671
+ *
1672
+ * Released under the MIT license.
1673
+ *
1674
+ * More Info:
1675
+ * http://ejohn.org/projects/javascript-diff-algorithm/
1676
+ *
1677
+ * Usage: QUnit.diff(expected, actual)
1678
+ *
1679
+ * 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"
1680
+ */
1681
+ QUnit.diff = (function() {
1682
+ var hasOwn = Object.prototype.hasOwnProperty;
1683
+
1684
+ /*jshint eqeqeq:false, eqnull:true */
1685
+ function diff( o, n ) {
1686
+ var i,
1687
+ ns = {},
1688
+ os = {};
1689
+
1690
+ for ( i = 0; i < n.length; i++ ) {
1691
+ if ( !hasOwn.call( ns, n[ i ] ) ) {
1692
+ ns[ n[ i ] ] = {
1693
+ rows: [],
1694
+ o: null
1695
+ };
1696
+ }
1697
+ ns[ n[ i ] ].rows.push( i );
1698
+ }
1699
+
1700
+ for ( i = 0; i < o.length; i++ ) {
1701
+ if ( !hasOwn.call( os, o[ i ] ) ) {
1702
+ os[ o[ i ] ] = {
1703
+ rows: [],
1704
+ n: null
1705
+ };
1706
+ }
1707
+ os[ o[ i ] ].rows.push( i );
1708
+ }
1709
+
1710
+ for ( i in ns ) {
1711
+ if ( hasOwn.call( ns, i ) ) {
1712
+ if ( ns[ i ].rows.length === 1 && hasOwn.call( os, i ) && os[ i ].rows.length === 1 ) {
1713
+ n[ ns[ i ].rows[ 0 ] ] = {
1714
+ text: n[ ns[ i ].rows[ 0 ] ],
1715
+ row: os[ i ].rows[ 0 ]
1716
+ };
1717
+ o[ os[ i ].rows[ 0 ] ] = {
1718
+ text: o[ os[ i ].rows[ 0 ] ],
1719
+ row: ns[ i ].rows[ 0 ]
1720
+ };
1721
+ }
1722
+ }
1723
+ }
1724
+
1725
+ for ( i = 0; i < n.length - 1; i++ ) {
1726
+ if ( n[ i ].text != null && n[ i + 1 ].text == null && n[ i ].row + 1 < o.length && o[ n[ i ].row + 1 ].text == null &&
1727
+ n[ i + 1 ] == o[ n[ i ].row + 1 ] ) {
1728
+
1729
+ n[ i + 1 ] = {
1730
+ text: n[ i + 1 ],
1731
+ row: n[ i ].row + 1
1732
+ };
1733
+ o[ n[ i ].row + 1 ] = {
1734
+ text: o[ n[ i ].row + 1 ],
1735
+ row: i + 1
1736
+ };
1737
+ }
1738
+ }
1739
+
1740
+ for ( i = n.length - 1; i > 0; i-- ) {
1741
+ if ( n[ i ].text != null && n[ i - 1 ].text == null && n[ i ].row > 0 && o[ n[ i ].row - 1 ].text == null &&
1742
+ n[ i - 1 ] == o[ n[ i ].row - 1 ] ) {
1743
+
1744
+ n[ i - 1 ] = {
1745
+ text: n[ i - 1 ],
1746
+ row: n[ i ].row - 1
1747
+ };
1748
+ o[ n[ i ].row - 1 ] = {
1749
+ text: o[ n[ i ].row - 1 ],
1750
+ row: i - 1
1751
+ };
1752
+ }
1753
+ }
1754
+
1755
+ return {
1756
+ o: o,
1757
+ n: n
1758
+ };
1759
+ }
1760
+
1761
+ return function( o, n ) {
1762
+ o = o.replace( /\s+$/, "" );
1763
+ n = n.replace( /\s+$/, "" );
1764
+
1765
+ var i, pre,
1766
+ str = "",
1767
+ out = diff( o === "" ? [] : o.split( /\s+/ ), n === "" ? [] : n.split( /\s+/ ) ),
1768
+ oSpace = o.match( /\s+/g ),
1769
+ nSpace = n.match( /\s+/g );
1770
+
1771
+ if ( oSpace == null ) {
1772
+ oSpace = [ " " ];
1773
+ } else {
1774
+ oSpace.push( " " );
1775
+ }
1776
+
1777
+ if ( nSpace == null ) {
1778
+ nSpace = [ " " ];
1779
+ } else {
1780
+ nSpace.push( " " );
1781
+ }
1782
+
1783
+ if ( out.n.length === 0 ) {
1784
+ for ( i = 0; i < out.o.length; i++ ) {
1785
+ str += "<del>" + out.o[ i ] + oSpace[ i ] + "</del>";
1786
+ }
1787
+ } else {
1788
+ if ( out.n[ 0 ].text == null ) {
1789
+ for ( n = 0; n < out.o.length && out.o[ n ].text == null; n++ ) {
1790
+ str += "<del>" + out.o[ n ] + oSpace[ n ] + "</del>";
1791
+ }
1792
+ }
1793
+
1794
+ for ( i = 0; i < out.n.length; i++ ) {
1795
+ if ( out.n[ i ].text == null ) {
1796
+ str += "<ins>" + out.n[ i ] + nSpace[ i ] + "</ins>";
1797
+ } else {
1798
+
1799
+ // `pre` initialized at top of scope
1800
+ pre = "";
1801
+
1802
+ for ( n = out.n[ i ].row + 1; n < out.o.length && out.o[ n ].text == null; n++ ) {
1803
+ pre += "<del>" + out.o[ n ] + oSpace[ n ] + "</del>";
1804
+ }
1805
+ str += " " + out.n[ i ].text + nSpace[ i ] + pre;
1806
+ }
1807
+ }
1808
+ }
1809
+
1810
+ return str;
1811
+ };
1812
+ }());
1813
+
1814
+ (function() {
1815
+
1816
+ // Deprecated QUnit.init - Ref #530
1817
+ // Re-initialize the configuration options
1818
+ QUnit.init = function() {
1819
+ var tests, banner, result, qunit,
1820
+ config = QUnit.config;
1821
+
1822
+ config.stats = { all: 0, bad: 0 };
1823
+ config.moduleStats = { all: 0, bad: 0 };
1824
+ config.started = 0;
1825
+ config.updateRate = 1000;
1826
+ config.blocking = false;
1827
+ config.autostart = true;
1828
+ config.autorun = false;
1829
+ config.filter = "";
1830
+ config.queue = [];
1831
+ config.semaphore = 1;
1832
+
1833
+ // Return on non-browser environments
1834
+ // This is necessary to not break on node tests
1835
+ if ( typeof window === "undefined" ) {
1836
+ return;
1837
+ }
1838
+
1839
+ qunit = id( "qunit" );
1840
+ if ( qunit ) {
1841
+ qunit.innerHTML =
1842
+ "<h1 id='qunit-header'>" + escapeText( document.title ) + "</h1>" +
1843
+ "<h2 id='qunit-banner'></h2>" +
1844
+ "<div id='qunit-testrunner-toolbar'></div>" +
1845
+ "<h2 id='qunit-userAgent'></h2>" +
1846
+ "<ol id='qunit-tests'></ol>";
1847
+ }
1848
+
1849
+ tests = id( "qunit-tests" );
1850
+ banner = id( "qunit-banner" );
1851
+ result = id( "qunit-testresult" );
1852
+
1853
+ if ( tests ) {
1854
+ tests.innerHTML = "";
1855
+ }
1856
+
1857
+ if ( banner ) {
1858
+ banner.className = "";
1859
+ }
1860
+
1861
+ if ( result ) {
1862
+ result.parentNode.removeChild( result );
1863
+ }
1864
+
1865
+ if ( tests ) {
1866
+ result = document.createElement( "p" );
1867
+ result.id = "qunit-testresult";
1868
+ result.className = "result";
1869
+ tests.parentNode.insertBefore( result, tests );
1870
+ result.innerHTML = "Running...<br/>&nbsp;";
1871
+ }
1872
+ };
1873
+
1874
+ // Resets the test setup. Useful for tests that modify the DOM.
1875
+ /*
1876
+ DEPRECATED: Use multiple tests instead of resetting inside a test.
1877
+ Use testStart or testDone for custom cleanup.
1878
+ This method will throw an error in 2.0, and will be removed in 2.1
1879
+ */
1880
+ QUnit.reset = function() {
1881
+
1882
+ // Return on non-browser environments
1883
+ // This is necessary to not break on node tests
1884
+ if ( typeof window === "undefined" ) {
1885
+ return;
1886
+ }
1887
+
1888
+ var fixture = id( "qunit-fixture" );
1889
+ if ( fixture ) {
1890
+ fixture.innerHTML = config.fixture;
1891
+ }
1892
+ };
1893
+
1894
+ // Don't load the HTML Reporter on non-Browser environments
1895
+ if ( typeof window === "undefined" ) {
1896
+ return;
1897
+ }
1898
+
1899
+ var config = QUnit.config,
1900
+ hasOwn = Object.prototype.hasOwnProperty,
1901
+ defined = {
1902
+ document: typeof window.document !== "undefined",
1903
+ sessionStorage: (function() {
1904
+ var x = "qunit-test-string";
1905
+ try {
1906
+ sessionStorage.setItem( x, x );
1907
+ sessionStorage.removeItem( x );
1908
+ return true;
1909
+ } catch ( e ) {
1910
+ return false;
1911
+ }
1912
+ }())
1913
+ };
1914
+
1915
+ /**
1916
+ * Escape text for attribute or text content.
1917
+ */
1918
+ function escapeText( s ) {
1919
+ if ( !s ) {
1920
+ return "";
1921
+ }
1922
+ s = s + "";
1923
+
1924
+ // Both single quotes and double quotes (for attributes)
1925
+ return s.replace( /['"<>&]/g, function( s ) {
1926
+ switch ( s ) {
1927
+ case "'":
1928
+ return "&#039;";
1929
+ case "\"":
1930
+ return "&quot;";
1931
+ case "<":
1932
+ return "&lt;";
1933
+ case ">":
1934
+ return "&gt;";
1935
+ case "&":
1936
+ return "&amp;";
1937
+ }
1938
+ });
1939
+ }
1940
+
1941
+ /**
1942
+ * @param {HTMLElement} elem
1943
+ * @param {string} type
1944
+ * @param {Function} fn
1945
+ */
1946
+ function addEvent( elem, type, fn ) {
1947
+ if ( elem.addEventListener ) {
1948
+
1949
+ // Standards-based browsers
1950
+ elem.addEventListener( type, fn, false );
1951
+ } else if ( elem.attachEvent ) {
1952
+
1953
+ // support: IE <9
1954
+ elem.attachEvent( "on" + type, fn );
1955
+ }
1956
+ }
1957
+
1958
+ /**
1959
+ * @param {Array|NodeList} elems
1960
+ * @param {string} type
1961
+ * @param {Function} fn
1962
+ */
1963
+ function addEvents( elems, type, fn ) {
1964
+ var i = elems.length;
1965
+ while ( i-- ) {
1966
+ addEvent( elems[ i ], type, fn );
1967
+ }
1968
+ }
1969
+
1970
+ function hasClass( elem, name ) {
1971
+ return ( " " + elem.className + " " ).indexOf( " " + name + " " ) >= 0;
1972
+ }
1973
+
1974
+ function addClass( elem, name ) {
1975
+ if ( !hasClass( elem, name ) ) {
1976
+ elem.className += ( elem.className ? " " : "" ) + name;
1977
+ }
1978
+ }
1979
+
1980
+ function toggleClass( elem, name ) {
1981
+ if ( hasClass( elem, name ) ) {
1982
+ removeClass( elem, name );
1983
+ } else {
1984
+ addClass( elem, name );
1985
+ }
1986
+ }
1987
+
1988
+ function removeClass( elem, name ) {
1989
+ var set = " " + elem.className + " ";
1990
+
1991
+ // Class name may appear multiple times
1992
+ while ( set.indexOf( " " + name + " " ) >= 0 ) {
1993
+ set = set.replace( " " + name + " ", " " );
1994
+ }
1995
+
1996
+ // trim for prettiness
1997
+ elem.className = typeof set.trim === "function" ? set.trim() : set.replace( /^\s+|\s+$/g, "" );
1998
+ }
1999
+
2000
+ function id( name ) {
2001
+ return defined.document && document.getElementById && document.getElementById( name );
2002
+ }
2003
+
2004
+ function getUrlConfigHtml() {
2005
+ var i, j, val,
2006
+ escaped, escapedTooltip,
2007
+ selection = false,
2008
+ len = config.urlConfig.length,
2009
+ urlConfigHtml = "";
2010
+
2011
+ for ( i = 0; i < len; i++ ) {
2012
+ val = config.urlConfig[ i ];
2013
+ if ( typeof val === "string" ) {
2014
+ val = {
2015
+ id: val,
2016
+ label: val
2017
+ };
2018
+ }
2019
+
2020
+ escaped = escapeText( val.id );
2021
+ escapedTooltip = escapeText( val.tooltip );
2022
+
2023
+ config[ val.id ] = QUnit.urlParams[ val.id ];
2024
+ if ( !val.value || typeof val.value === "string" ) {
2025
+ urlConfigHtml += "<input id='qunit-urlconfig-" + escaped +
2026
+ "' name='" + escaped + "' type='checkbox'" +
2027
+ ( val.value ? " value='" + escapeText( val.value ) + "'" : "" ) +
2028
+ ( config[ val.id ] ? " checked='checked'" : "" ) +
2029
+ " title='" + escapedTooltip + "'><label for='qunit-urlconfig-" + escaped +
2030
+ "' title='" + escapedTooltip + "'>" + val.label + "</label>";
2031
+ } else {
2032
+ urlConfigHtml += "<label for='qunit-urlconfig-" + escaped +
2033
+ "' title='" + escapedTooltip + "'>" + val.label +
2034
+ ": </label><select id='qunit-urlconfig-" + escaped +
2035
+ "' name='" + escaped + "' title='" + escapedTooltip + "'><option></option>";
2036
+
2037
+ if ( QUnit.is( "array", val.value ) ) {
2038
+ for ( j = 0; j < val.value.length; j++ ) {
2039
+ escaped = escapeText( val.value[ j ] );
2040
+ urlConfigHtml += "<option value='" + escaped + "'" +
2041
+ ( config[ val.id ] === val.value[ j ] ?
2042
+ ( selection = true ) && " selected='selected'" : "" ) +
2043
+ ">" + escaped + "</option>";
2044
+ }
2045
+ } else {
2046
+ for ( j in val.value ) {
2047
+ if ( hasOwn.call( val.value, j ) ) {
2048
+ urlConfigHtml += "<option value='" + escapeText( j ) + "'" +
2049
+ ( config[ val.id ] === j ?
2050
+ ( selection = true ) && " selected='selected'" : "" ) +
2051
+ ">" + escapeText( val.value[ j ] ) + "</option>";
2052
+ }
2053
+ }
2054
+ }
2055
+ if ( config[ val.id ] && !selection ) {
2056
+ escaped = escapeText( config[ val.id ] );
2057
+ urlConfigHtml += "<option value='" + escaped +
2058
+ "' selected='selected' disabled='disabled'>" + escaped + "</option>";
2059
+ }
2060
+ urlConfigHtml += "</select>";
2061
+ }
2062
+ }
2063
+
2064
+ return urlConfigHtml;
2065
+ }
2066
+
2067
+ function toolbarUrlConfigContainer() {
2068
+ var urlConfigContainer = document.createElement( "span" );
2069
+
2070
+ urlConfigContainer.innerHTML = getUrlConfigHtml();
2071
+
2072
+ // For oldIE support:
2073
+ // * Add handlers to the individual elements instead of the container
2074
+ // * Use "click" instead of "change" for checkboxes
2075
+ // * Fallback from event.target to event.srcElement
2076
+ addEvents( urlConfigContainer.getElementsByTagName( "input" ), "click", function( event ) {
2077
+ var params = {},
2078
+ target = event.target || event.srcElement;
2079
+ params[ target.name ] = target.checked ?
2080
+ target.defaultValue || true :
2081
+ undefined;
2082
+ window.location = QUnit.url( params );
2083
+ });
2084
+ addEvents( urlConfigContainer.getElementsByTagName( "select" ), "change", function( event ) {
2085
+ var params = {},
2086
+ target = event.target || event.srcElement;
2087
+ params[ target.name ] = target.options[ target.selectedIndex ].value || undefined;
2088
+ window.location = QUnit.url( params );
2089
+ });
2090
+
2091
+ return urlConfigContainer;
2092
+ }
2093
+
2094
+ function getModuleNames() {
2095
+ var i,
2096
+ moduleNames = [];
2097
+
2098
+ for ( i in config.modules ) {
2099
+ if ( config.modules.hasOwnProperty( i ) ) {
2100
+ moduleNames.push( i );
2101
+ }
2102
+ }
2103
+
2104
+ moduleNames.sort(function( a, b ) {
2105
+ return a.localeCompare( b );
2106
+ });
2107
+
2108
+ return moduleNames;
2109
+ }
2110
+
2111
+ function toolbarModuleFilterHtml() {
2112
+ var i,
2113
+ moduleFilterHtml = "",
2114
+ moduleNames = getModuleNames();
2115
+
2116
+ if ( moduleNames.length <= 1 ) {
2117
+ return false;
2118
+ }
2119
+
2120
+ moduleFilterHtml += "<label for='qunit-modulefilter'>Module: </label>" +
2121
+ "<select id='qunit-modulefilter' name='modulefilter'><option value='' " +
2122
+ ( config.module === undefined ? "selected='selected'" : "" ) +
2123
+ ">< All Modules ></option>";
2124
+
2125
+ for ( i = 0; i < moduleNames.length; i++ ) {
2126
+ moduleFilterHtml += "<option value='" +
2127
+ escapeText( encodeURIComponent( moduleNames[ i ] ) ) + "' " +
2128
+ ( config.module === moduleNames[ i ] ? "selected='selected'" : "" ) +
2129
+ ">" + escapeText( moduleNames[ i ] ) + "</option>";
2130
+ }
2131
+ moduleFilterHtml += "</select>";
2132
+
2133
+ return moduleFilterHtml;
2134
+ }
2135
+
2136
+ function toolbarModuleFilter() {
2137
+ var moduleFilter = document.createElement( "span" ),
2138
+ moduleFilterHtml = toolbarModuleFilterHtml();
2139
+
2140
+ if ( !moduleFilterHtml ) {
2141
+ return false;
2142
+ }
2143
+
2144
+ moduleFilter.setAttribute( "id", "qunit-modulefilter-container" );
2145
+ moduleFilter.innerHTML = moduleFilterHtml;
2146
+
2147
+ addEvent( moduleFilter.lastChild, "change", function() {
2148
+ var selectBox = moduleFilter.getElementsByTagName( "select" )[ 0 ],
2149
+ selectedModule = decodeURIComponent( selectBox.options[ selectBox.selectedIndex ].value );
2150
+
2151
+ window.location = QUnit.url({
2152
+ module: ( selectedModule === "" ) ? undefined : selectedModule,
2153
+
2154
+ // Remove any existing filters
2155
+ filter: undefined,
2156
+ testNumber: undefined
2157
+ });
2158
+ });
2159
+
2160
+ return moduleFilter;
2161
+ }
2162
+
2163
+ function toolbarFilter() {
2164
+ var testList = id( "qunit-tests" ),
2165
+ filter = document.createElement( "input" );
2166
+
2167
+ filter.type = "checkbox";
2168
+ filter.id = "qunit-filter-pass";
2169
+
2170
+ addEvent( filter, "click", function() {
2171
+ if ( filter.checked ) {
2172
+ addClass( testList, "hidepass" );
2173
+ if ( defined.sessionStorage ) {
2174
+ sessionStorage.setItem( "qunit-filter-passed-tests", "true" );
2175
+ }
2176
+ } else {
2177
+ removeClass( testList, "hidepass" );
2178
+ if ( defined.sessionStorage ) {
2179
+ sessionStorage.removeItem( "qunit-filter-passed-tests" );
2180
+ }
2181
+ }
2182
+ });
2183
+
2184
+ if ( config.hidepassed || defined.sessionStorage &&
2185
+ sessionStorage.getItem( "qunit-filter-passed-tests" ) ) {
2186
+ filter.checked = true;
2187
+
2188
+ addClass( testList, "hidepass" );
2189
+ }
2190
+
2191
+ return filter;
2192
+ }
2193
+
2194
+ function toolbarLabel() {
2195
+ var label = document.createElement( "label" );
2196
+ label.setAttribute( "for", "qunit-filter-pass" );
2197
+ label.setAttribute( "title", "Only show tests and assertions that fail. Stored in sessionStorage." );
2198
+ label.innerHTML = "Hide passed tests";
2199
+
2200
+ return label;
2201
+ }
2202
+
2203
+ function appendToolbar() {
2204
+ var moduleFilter,
2205
+ toolbar = id( "qunit-testrunner-toolbar" );
2206
+
2207
+ if ( toolbar ) {
2208
+ toolbar.appendChild( toolbarFilter() );
2209
+ toolbar.appendChild( toolbarLabel() );
2210
+ toolbar.appendChild( toolbarUrlConfigContainer() );
2211
+
2212
+ moduleFilter = toolbarModuleFilter();
2213
+ if ( moduleFilter ) {
2214
+ toolbar.appendChild( moduleFilter );
2215
+ }
2216
+ }
2217
+ }
2218
+
2219
+ function appendBanner() {
2220
+ var banner = id( "qunit-banner" );
2221
+
2222
+ if ( banner ) {
2223
+ banner.className = "";
2224
+ banner.innerHTML = "<a href='" +
2225
+ QUnit.url({ filter: undefined, module: undefined, testNumber: undefined }) +
2226
+ "'>" + banner.innerHTML + "</a> ";
2227
+ }
2228
+ }
2229
+
2230
+ function appendTestResults() {
2231
+ var tests = id( "qunit-tests" ),
2232
+ result = id( "qunit-testresult" );
2233
+
2234
+ if ( result ) {
2235
+ result.parentNode.removeChild( result );
2236
+ }
2237
+
2238
+ if ( tests ) {
2239
+ tests.innerHTML = "";
2240
+ result = document.createElement( "p" );
2241
+ result.id = "qunit-testresult";
2242
+ result.className = "result";
2243
+ tests.parentNode.insertBefore( result, tests );
2244
+ result.innerHTML = "Running...<br>&nbsp;";
2245
+ }
2246
+ }
2247
+
2248
+ function storeFixture() {
2249
+ var fixture = id( "qunit-fixture" );
2250
+ if ( fixture ) {
2251
+ config.fixture = fixture.innerHTML;
2252
+ }
2253
+ }
2254
+
2255
+ function appendUserAgent() {
2256
+ var userAgent = id( "qunit-userAgent" );
2257
+ if ( userAgent ) {
2258
+ userAgent.innerHTML = navigator.userAgent;
2259
+ }
2260
+ }
2261
+
2262
+ // HTML Reporter initialization and load
2263
+ QUnit.begin(function() {
2264
+ var qunit = id( "qunit" );
2265
+
2266
+ if ( qunit ) {
2267
+ qunit.innerHTML =
2268
+ "<h1 id='qunit-header'>" + escapeText( document.title ) + "</h1>" +
2269
+ "<h2 id='qunit-banner'></h2>" +
2270
+ "<div id='qunit-testrunner-toolbar'></div>" +
2271
+ "<h2 id='qunit-userAgent'></h2>" +
2272
+ "<ol id='qunit-tests'></ol>";
2273
+ }
2274
+
2275
+ appendBanner();
2276
+ appendTestResults();
2277
+ appendUserAgent();
2278
+ appendToolbar();
2279
+ storeFixture();
2280
+ });
2281
+
2282
+ QUnit.done(function( details ) {
2283
+ var i, key,
2284
+ banner = id( "qunit-banner" ),
2285
+ tests = id( "qunit-tests" ),
2286
+ html = [
2287
+ "Tests completed in ",
2288
+ details.runtime,
2289
+ " milliseconds.<br>",
2290
+ "<span class='passed'>",
2291
+ details.passed,
2292
+ "</span> assertions of <span class='total'>",
2293
+ details.total,
2294
+ "</span> passed, <span class='failed'>",
2295
+ details.failed,
2296
+ "</span> failed."
2297
+ ].join( "" );
2298
+
2299
+ if ( banner ) {
2300
+ banner.className = details.failed ? "qunit-fail" : "qunit-pass";
2301
+ }
2302
+
2303
+ if ( tests ) {
2304
+ id( "qunit-testresult" ).innerHTML = html;
2305
+ }
2306
+
2307
+ if ( config.altertitle && defined.document && document.title ) {
2308
+
2309
+ // show ✖ for good, ✔ for bad suite result in title
2310
+ // use escape sequences in case file gets loaded with non-utf-8-charset
2311
+ document.title = [
2312
+ ( details.failed ? "\u2716" : "\u2714" ),
2313
+ document.title.replace( /^[\u2714\u2716] /i, "" )
2314
+ ].join( " " );
2315
+ }
2316
+
2317
+ // clear own sessionStorage items if all tests passed
2318
+ if ( config.reorder && defined.sessionStorage && details.failed === 0 ) {
2319
+ for ( i = 0; i < sessionStorage.length; i++ ) {
2320
+ key = sessionStorage.key( i++ );
2321
+ if ( key.indexOf( "qunit-test-" ) === 0 ) {
2322
+ sessionStorage.removeItem( key );
2323
+ }
2324
+ }
2325
+ }
2326
+
2327
+ // scroll back to top to show results
2328
+ if ( config.scrolltop && window.scrollTo ) {
2329
+ window.scrollTo( 0, 0 );
2330
+ }
2331
+ });
2332
+
2333
+ function getNameHtml( name, module ) {
2334
+ var nameHtml = "";
2335
+
2336
+ if ( module ) {
2337
+ nameHtml = "<span class='module-name'>" + escapeText( module ) + "</span>: ";
2338
+ }
2339
+
2340
+ nameHtml += "<span class='test-name'>" + escapeText( name ) + "</span>";
2341
+
2342
+ return nameHtml;
2343
+ }
2344
+
2345
+ QUnit.testStart(function( details ) {
2346
+ var a, b, li, running, assertList,
2347
+ name = getNameHtml( details.name, details.module ),
2348
+ tests = id( "qunit-tests" );
2349
+
2350
+ if ( tests ) {
2351
+ b = document.createElement( "strong" );
2352
+ b.innerHTML = name;
2353
+
2354
+ a = document.createElement( "a" );
2355
+ a.innerHTML = "Rerun";
2356
+ a.href = QUnit.url({ testNumber: details.testNumber });
2357
+
2358
+ li = document.createElement( "li" );
2359
+ li.appendChild( b );
2360
+ li.appendChild( a );
2361
+ li.className = "running";
2362
+ li.id = "qunit-test-output" + details.testNumber;
2363
+
2364
+ assertList = document.createElement( "ol" );
2365
+ assertList.className = "qunit-assert-list";
2366
+
2367
+ li.appendChild( assertList );
2368
+
2369
+ tests.appendChild( li );
2370
+ }
2371
+
2372
+ running = id( "qunit-testresult" );
2373
+ if ( running ) {
2374
+ running.innerHTML = "Running: <br>" + name;
2375
+ }
2376
+
2377
+ });
2378
+
2379
+ QUnit.log(function( details ) {
2380
+ var assertList, assertLi,
2381
+ message, expected, actual,
2382
+ testItem = id( "qunit-test-output" + details.testNumber );
2383
+
2384
+ if ( !testItem ) {
2385
+ return;
2386
+ }
2387
+
2388
+ message = escapeText( details.message ) || ( details.result ? "okay" : "failed" );
2389
+ message = "<span class='test-message'>" + message + "</span>";
2390
+
2391
+ // pushFailure doesn't provide details.expected
2392
+ // when it calls, it's implicit to also not show expected and diff stuff
2393
+ // Also, we need to check details.expected existence, as it can exist and be undefined
2394
+ if ( !details.result && hasOwn.call( details, "expected" ) ) {
2395
+ expected = escapeText( QUnit.dump.parse( details.expected ) );
2396
+ actual = escapeText( QUnit.dump.parse( details.actual ) );
2397
+ message += "<table><tr class='test-expected'><th>Expected: </th><td><pre>" +
2398
+ expected +
2399
+ "</pre></td></tr>";
2400
+
2401
+ if ( actual !== expected ) {
2402
+ message += "<tr class='test-actual'><th>Result: </th><td><pre>" +
2403
+ actual + "</pre></td></tr>" +
2404
+ "<tr class='test-diff'><th>Diff: </th><td><pre>" +
2405
+ QUnit.diff( expected, actual ) + "</pre></td></tr>";
2406
+ }
2407
+
2408
+ if ( details.source ) {
2409
+ message += "<tr class='test-source'><th>Source: </th><td><pre>" +
2410
+ escapeText( details.source ) + "</pre></td></tr>";
2411
+ }
2412
+
2413
+ message += "</table>";
2414
+
2415
+ // this occours when pushFailure is set and we have an extracted stack trace
2416
+ } else if ( !details.result && details.source ) {
2417
+ message += "<table>" +
2418
+ "<tr class='test-source'><th>Source: </th><td><pre>" +
2419
+ escapeText( details.source ) + "</pre></td></tr>" +
2420
+ "</table>";
2421
+ }
2422
+
2423
+ assertList = testItem.getElementsByTagName( "ol" )[ 0 ];
2424
+
2425
+ assertLi = document.createElement( "li" );
2426
+ assertLi.className = details.result ? "pass" : "fail";
2427
+ assertLi.innerHTML = message;
2428
+ assertList.appendChild( assertLi );
2429
+ });
2430
+
2431
+ QUnit.testDone(function( details ) {
2432
+ var testTitle, time, testItem, assertList,
2433
+ good, bad, testCounts,
2434
+ tests = id( "qunit-tests" );
2435
+
2436
+ // QUnit.reset() is deprecated and will be replaced for a new
2437
+ // fixture reset function on QUnit 2.0/2.1.
2438
+ // It's still called here for backwards compatibility handling
2439
+ QUnit.reset();
2440
+
2441
+ if ( !tests ) {
2442
+ return;
2443
+ }
2444
+
2445
+ testItem = id( "qunit-test-output" + details.testNumber );
2446
+ assertList = testItem.getElementsByTagName( "ol" )[ 0 ];
2447
+
2448
+ good = details.passed;
2449
+ bad = details.failed;
2450
+
2451
+ // store result when possible
2452
+ if ( config.reorder && defined.sessionStorage ) {
2453
+ if ( bad ) {
2454
+ sessionStorage.setItem( "qunit-test-" + details.module + "-" + details.name, bad );
2455
+ } else {
2456
+ sessionStorage.removeItem( "qunit-test-" + details.module + "-" + details.name );
2457
+ }
2458
+ }
2459
+
2460
+ if ( bad === 0 ) {
2461
+ addClass( assertList, "qunit-collapsed" );
2462
+ }
2463
+
2464
+ // testItem.firstChild is the test name
2465
+ testTitle = testItem.firstChild;
2466
+
2467
+ testCounts = bad ?
2468
+ "<b class='failed'>" + bad + "</b>, " + "<b class='passed'>" + good + "</b>, " :
2469
+ "";
2470
+
2471
+ testTitle.innerHTML += " <b class='counts'>(" + testCounts +
2472
+ details.assertions.length + ")</b>";
2473
+
2474
+ addEvent( testTitle, "click", function() {
2475
+ toggleClass( assertList, "qunit-collapsed" );
2476
+ });
2477
+
2478
+ time = document.createElement( "span" );
2479
+ time.className = "runtime";
2480
+ time.innerHTML = details.runtime + " ms";
2481
+
2482
+ testItem.className = bad ? "fail" : "pass";
2483
+
2484
+ testItem.insertBefore( time, assertList );
2485
+ });
2486
+
2487
+ if ( !defined.document || document.readyState === "complete" ) {
2488
+ config.autorun = true;
2489
+ }
2490
+
2491
+ if ( defined.document ) {
2492
+ addEvent( window, "load", QUnit.load );
2493
+ }
2494
+
2495
+ })();