qunit-rails 0.0.4 → 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,11 +1,12 @@
1
- /**
2
- * QUnit v1.12.0 - A JavaScript Unit Testing Framework
3
- *
4
- * http://qunitjs.com
1
+ /*!
2
+ * QUnit 1.13.0
3
+ * http://qunitjs.com/
5
4
  *
6
5
  * Copyright 2013 jQuery Foundation and other contributors
7
- * Released under the MIT license.
8
- * https://jquery.org/license/
6
+ * Released under the MIT license
7
+ * http://jquery.org/license
8
+ *
9
+ * Date: 2014-01-04T17:09Z
9
10
  */
10
11
 
11
12
  (function( window ) {
@@ -22,6 +23,7 @@ var QUnit,
22
23
  Date = window.Date,
23
24
  setTimeout = window.setTimeout,
24
25
  defined = {
26
+ document: typeof window.document !== "undefined",
25
27
  setTimeout: typeof window.setTimeout !== "undefined",
26
28
  sessionStorage: (function() {
27
29
  var x = "qunit-test-string";
@@ -84,1522 +86,1524 @@ var QUnit,
84
86
  return vals;
85
87
  };
86
88
 
87
- function Test( settings ) {
88
- extend( this, settings );
89
- this.assertions = [];
90
- this.testNumber = ++Test.count;
91
- }
92
89
 
93
- Test.count = 0;
90
+ // Root QUnit object.
91
+ // `QUnit` initialized at top of scope
92
+ QUnit = {
94
93
 
95
- Test.prototype = {
96
- init: function() {
97
- var a, b, li,
98
- tests = id( "qunit-tests" );
94
+ // call on start of module test to prepend name to all tests
95
+ module: function( name, testEnvironment ) {
96
+ config.currentModule = name;
97
+ config.currentModuleTestEnvironment = testEnvironment;
98
+ config.modules[name] = true;
99
+ },
99
100
 
100
- if ( tests ) {
101
- b = document.createElement( "strong" );
102
- b.innerHTML = this.nameHtml;
101
+ asyncTest: function( testName, expected, callback ) {
102
+ if ( arguments.length === 2 ) {
103
+ callback = expected;
104
+ expected = null;
105
+ }
103
106
 
104
- // `a` initialized at top of scope
105
- a = document.createElement( "a" );
106
- a.innerHTML = "Rerun";
107
- a.href = QUnit.url({ testNumber: this.testNumber });
107
+ QUnit.test( testName, expected, callback, true );
108
+ },
108
109
 
109
- li = document.createElement( "li" );
110
- li.appendChild( b );
111
- li.appendChild( a );
112
- li.className = "running";
113
- li.id = this.id = "qunit-test-output" + testId++;
110
+ test: function( testName, expected, callback, async ) {
111
+ var test,
112
+ nameHtml = "<span class='test-name'>" + escapeText( testName ) + "</span>";
114
113
 
115
- tests.appendChild( li );
116
- }
117
- },
118
- setup: function() {
119
- if (
120
- // Emit moduleStart when we're switching from one module to another
121
- this.module !== config.previousModule ||
122
- // They could be equal (both undefined) but if the previousModule property doesn't
123
- // yet exist it means this is the first test in a suite that isn't wrapped in a
124
- // module, in which case we'll just emit a moduleStart event for 'undefined'.
125
- // Without this, reporters can get testStart before moduleStart which is a problem.
126
- !hasOwn.call( config, "previousModule" )
127
- ) {
128
- if ( hasOwn.call( config, "previousModule" ) ) {
129
- runLoggingCallbacks( "moduleDone", QUnit, {
130
- name: config.previousModule,
131
- failed: config.moduleStats.bad,
132
- passed: config.moduleStats.all - config.moduleStats.bad,
133
- total: config.moduleStats.all
134
- });
135
- }
136
- config.previousModule = this.module;
137
- config.moduleStats = { all: 0, bad: 0 };
138
- runLoggingCallbacks( "moduleStart", QUnit, {
139
- name: this.module
140
- });
114
+ if ( arguments.length === 2 ) {
115
+ callback = expected;
116
+ expected = null;
141
117
  }
142
118
 
143
- config.current = this;
144
-
145
- this.testEnvironment = extend({
146
- setup: function() {},
147
- teardown: function() {}
148
- }, this.moduleTestEnvironment );
119
+ if ( config.currentModule ) {
120
+ nameHtml = "<span class='module-name'>" + escapeText( config.currentModule ) + "</span>: " + nameHtml;
121
+ }
149
122
 
150
- this.started = +new Date();
151
- runLoggingCallbacks( "testStart", QUnit, {
152
- name: this.testName,
153
- module: this.module
123
+ test = new Test({
124
+ nameHtml: nameHtml,
125
+ testName: testName,
126
+ expected: expected,
127
+ async: async,
128
+ callback: callback,
129
+ module: config.currentModule,
130
+ moduleTestEnvironment: config.currentModuleTestEnvironment,
131
+ stack: sourceFromStacktrace( 2 )
154
132
  });
155
133
 
156
- /*jshint camelcase:false */
157
-
158
-
159
- /**
160
- * Expose the current test environment.
161
- *
162
- * @deprecated since 1.12.0: Use QUnit.config.current.testEnvironment instead.
163
- */
164
- QUnit.current_testEnvironment = this.testEnvironment;
165
-
166
- /*jshint camelcase:true */
167
-
168
- if ( !config.pollution ) {
169
- saveGlobal();
170
- }
171
- if ( config.notrycatch ) {
172
- this.testEnvironment.setup.call( this.testEnvironment, QUnit.assert );
134
+ if ( !validTest( test ) ) {
173
135
  return;
174
136
  }
175
- try {
176
- this.testEnvironment.setup.call( this.testEnvironment, QUnit.assert );
177
- } catch( e ) {
178
- QUnit.pushFailure( "Setup failed on " + this.testName + ": " + ( e.message || e ), extractStacktrace( e, 1 ) );
179
- }
180
- },
181
- run: function() {
182
- config.current = this;
183
-
184
- var running = id( "qunit-testresult" );
185
137
 
186
- if ( running ) {
187
- running.innerHTML = "Running: <br/>" + this.nameHtml;
188
- }
138
+ test.queue();
139
+ },
189
140
 
190
- if ( this.async ) {
191
- QUnit.stop();
141
+ // Specify the number of expected assertions to guarantee that failed test (no assertions are run at all) don't slip through.
142
+ expect: function( asserts ) {
143
+ if (arguments.length === 1) {
144
+ config.current.expected = asserts;
145
+ } else {
146
+ return config.current.expected;
192
147
  }
148
+ },
193
149
 
194
- this.callbackStarted = +new Date();
195
-
196
- if ( config.notrycatch ) {
197
- this.callback.call( this.testEnvironment, QUnit.assert );
198
- this.callbackRuntime = +new Date() - this.callbackStarted;
150
+ start: function( count ) {
151
+ // QUnit hasn't been initialized yet.
152
+ // Note: RequireJS (et al) may delay onLoad
153
+ if ( config.semaphore === undefined ) {
154
+ QUnit.begin(function() {
155
+ // This is triggered at the top of QUnit.load, push start() to the event loop, to allow QUnit.load to finish first
156
+ setTimeout(function() {
157
+ QUnit.start( count );
158
+ });
159
+ });
199
160
  return;
200
161
  }
201
162
 
202
- try {
203
- this.callback.call( this.testEnvironment, QUnit.assert );
204
- this.callbackRuntime = +new Date() - this.callbackStarted;
205
- } catch( e ) {
206
- this.callbackRuntime = +new Date() - this.callbackStarted;
207
-
208
- QUnit.pushFailure( "Died on test #" + (this.assertions.length + 1) + " " + this.stack + ": " + ( e.message || e ), extractStacktrace( e, 0 ) );
209
- // else next test will carry the responsibility
210
- saveGlobal();
211
-
212
- // Restart the tests if they're blocking
213
- if ( config.blocking ) {
214
- QUnit.start();
215
- }
163
+ config.semaphore -= count || 1;
164
+ // don't start until equal number of stop-calls
165
+ if ( config.semaphore > 0 ) {
166
+ return;
216
167
  }
217
- },
218
- teardown: function() {
219
- config.current = this;
220
- if ( config.notrycatch ) {
221
- if ( typeof this.callbackRuntime === "undefined" ) {
222
- this.callbackRuntime = +new Date() - this.callbackStarted;
223
- }
224
- this.testEnvironment.teardown.call( this.testEnvironment, QUnit.assert );
168
+ // ignore if start is called more often then stop
169
+ if ( config.semaphore < 0 ) {
170
+ config.semaphore = 0;
171
+ QUnit.pushFailure( "Called start() while already started (QUnit.config.semaphore was 0 already)", null, sourceFromStacktrace(2) );
225
172
  return;
173
+ }
174
+ // A slight delay, to avoid any current callbacks
175
+ if ( defined.setTimeout ) {
176
+ setTimeout(function() {
177
+ if ( config.semaphore > 0 ) {
178
+ return;
179
+ }
180
+ if ( config.timeout ) {
181
+ clearTimeout( config.timeout );
182
+ }
183
+
184
+ config.blocking = false;
185
+ process( true );
186
+ }, 13);
226
187
  } else {
227
- try {
228
- this.testEnvironment.teardown.call( this.testEnvironment, QUnit.assert );
229
- } catch( e ) {
230
- QUnit.pushFailure( "Teardown failed on " + this.testName + ": " + ( e.message || e ), extractStacktrace( e, 1 ) );
231
- }
188
+ config.blocking = false;
189
+ process( true );
232
190
  }
233
- checkPollution();
234
191
  },
235
- finish: function() {
236
- config.current = this;
237
- if ( config.requireExpects && this.expected === null ) {
238
- QUnit.pushFailure( "Expected number of assertions to be defined, but expect() was not called.", this.stack );
239
- } else if ( this.expected !== null && this.expected !== this.assertions.length ) {
240
- QUnit.pushFailure( "Expected " + this.expected + " assertions, but " + this.assertions.length + " were run", this.stack );
241
- } else if ( this.expected === null && !this.assertions.length ) {
242
- QUnit.pushFailure( "Expected at least one assertion, but none were run - call expect(0) to accept zero assertions.", this.stack );
243
- }
244
192
 
245
- var i, assertion, a, b, time, li, ol,
246
- test = this,
247
- good = 0,
248
- bad = 0,
249
- tests = id( "qunit-tests" );
193
+ stop: function( count ) {
194
+ config.semaphore += count || 1;
195
+ config.blocking = true;
250
196
 
251
- this.runtime = +new Date() - this.started;
252
- config.stats.all += this.assertions.length;
253
- config.moduleStats.all += this.assertions.length;
197
+ if ( config.testTimeout && defined.setTimeout ) {
198
+ clearTimeout( config.timeout );
199
+ config.timeout = setTimeout(function() {
200
+ QUnit.ok( false, "Test timed out" );
201
+ config.semaphore = 1;
202
+ QUnit.start();
203
+ }, config.testTimeout );
204
+ }
205
+ }
206
+ };
254
207
 
255
- if ( tests ) {
256
- ol = document.createElement( "ol" );
257
- ol.className = "qunit-assert-list";
208
+ // We use the prototype to distinguish between properties that should
209
+ // be exposed as globals (and in exports) and those that shouldn't
210
+ (function() {
211
+ function F() {}
212
+ F.prototype = QUnit;
213
+ QUnit = new F();
214
+ // Make F QUnit's constructor so that we can add to the prototype later
215
+ QUnit.constructor = F;
216
+ }());
258
217
 
259
- for ( i = 0; i < this.assertions.length; i++ ) {
260
- assertion = this.assertions[i];
218
+ /**
219
+ * Config object: Maintain internal state
220
+ * Later exposed as QUnit.config
221
+ * `config` initialized at top of scope
222
+ */
223
+ config = {
224
+ // The queue of tests to run
225
+ queue: [],
261
226
 
262
- li = document.createElement( "li" );
263
- li.className = assertion.result ? "pass" : "fail";
264
- li.innerHTML = assertion.message || ( assertion.result ? "okay" : "failed" );
265
- ol.appendChild( li );
227
+ // block until document ready
228
+ blocking: true,
266
229
 
267
- if ( assertion.result ) {
268
- good++;
269
- } else {
270
- bad++;
271
- config.stats.bad++;
272
- config.moduleStats.bad++;
273
- }
274
- }
230
+ // when enabled, show only failing tests
231
+ // gets persisted through sessionStorage and can be changed in UI via checkbox
232
+ hidepassed: false,
275
233
 
276
- // store result when possible
277
- if ( QUnit.config.reorder && defined.sessionStorage ) {
278
- if ( bad ) {
279
- sessionStorage.setItem( "qunit-test-" + this.module + "-" + this.testName, bad );
280
- } else {
281
- sessionStorage.removeItem( "qunit-test-" + this.module + "-" + this.testName );
282
- }
283
- }
234
+ // by default, run previously failed tests first
235
+ // very useful in combination with "Hide passed tests" checked
236
+ reorder: true,
284
237
 
285
- if ( bad === 0 ) {
286
- addClass( ol, "qunit-collapsed" );
287
- }
238
+ // by default, modify document.title when suite is done
239
+ altertitle: true,
288
240
 
289
- // `b` initialized at top of scope
290
- b = document.createElement( "strong" );
291
- b.innerHTML = this.nameHtml + " <b class='counts'>(<b class='failed'>" + bad + "</b>, <b class='passed'>" + good + "</b>, " + this.assertions.length + ")</b>";
241
+ // when enabled, all tests must call expect()
242
+ requireExpects: false,
292
243
 
293
- addEvent(b, "click", function() {
294
- var next = b.parentNode.lastChild,
295
- collapsed = hasClass( next, "qunit-collapsed" );
296
- ( collapsed ? removeClass : addClass )( next, "qunit-collapsed" );
297
- });
244
+ // add checkboxes that are persisted in the query-string
245
+ // when enabled, the id is set to `true` as a `QUnit.config` property
246
+ urlConfig: [
247
+ {
248
+ id: "noglobals",
249
+ label: "Check for Globals",
250
+ tooltip: "Enabling this will test if any test introduces new properties on the `window` object. Stored as query-strings."
251
+ },
252
+ {
253
+ id: "notrycatch",
254
+ label: "No try-catch",
255
+ tooltip: "Enabling this will run tests outside of a try-catch block. Makes debugging exceptions in IE reasonable. Stored as query-strings."
256
+ }
257
+ ],
298
258
 
299
- addEvent(b, "dblclick", function( e ) {
300
- var target = e && e.target ? e.target : window.event.srcElement;
301
- if ( target.nodeName.toLowerCase() === "span" || target.nodeName.toLowerCase() === "b" ) {
302
- target = target.parentNode;
303
- }
304
- if ( window.location && target.nodeName.toLowerCase() === "strong" ) {
305
- window.location = QUnit.url({ testNumber: test.testNumber });
306
- }
307
- });
259
+ // Set of all modules.
260
+ modules: {},
308
261
 
309
- // `time` initialized at top of scope
310
- time = document.createElement( "span" );
311
- time.className = "runtime";
312
- time.innerHTML = this.runtime + " ms";
262
+ // logging callback queues
263
+ begin: [],
264
+ done: [],
265
+ log: [],
266
+ testStart: [],
267
+ testDone: [],
268
+ moduleStart: [],
269
+ moduleDone: []
270
+ };
313
271
 
314
- // `li` initialized at top of scope
315
- li = id( this.id );
316
- li.className = bad ? "fail" : "pass";
317
- li.removeChild( li.firstChild );
318
- a = li.firstChild;
319
- li.appendChild( b );
320
- li.appendChild( a );
321
- li.appendChild( time );
322
- li.appendChild( ol );
272
+ // Initialize more QUnit.config and QUnit.urlParams
273
+ (function() {
274
+ var i,
275
+ location = window.location || { search: "", protocol: "file:" },
276
+ params = location.search.slice( 1 ).split( "&" ),
277
+ length = params.length,
278
+ urlParams = {},
279
+ current;
323
280
 
324
- } else {
325
- for ( i = 0; i < this.assertions.length; i++ ) {
326
- if ( !this.assertions[i].result ) {
327
- bad++;
328
- config.stats.bad++;
329
- config.moduleStats.bad++;
330
- }
331
- }
281
+ if ( params[ 0 ] ) {
282
+ for ( i = 0; i < length; i++ ) {
283
+ current = params[ i ].split( "=" );
284
+ current[ 0 ] = decodeURIComponent( current[ 0 ] );
285
+ // allow just a key to turn on a flag, e.g., test.html?noglobals
286
+ current[ 1 ] = current[ 1 ] ? decodeURIComponent( current[ 1 ] ) : true;
287
+ urlParams[ current[ 0 ] ] = current[ 1 ];
332
288
  }
289
+ }
333
290
 
334
- runLoggingCallbacks( "testDone", QUnit, {
335
- name: this.testName,
336
- module: this.module,
337
- failed: bad,
338
- passed: this.assertions.length - bad,
339
- total: this.assertions.length,
340
- duration: this.runtime
341
- });
291
+ QUnit.urlParams = urlParams;
342
292
 
343
- QUnit.reset();
293
+ // String search anywhere in moduleName+testName
294
+ config.filter = urlParams.filter;
344
295
 
345
- config.current = undefined;
346
- },
296
+ // Exact match of the module name
297
+ config.module = urlParams.module;
347
298
 
348
- queue: function() {
349
- var bad,
350
- test = this;
299
+ config.testNumber = parseInt( urlParams.testNumber, 10 ) || null;
351
300
 
352
- synchronize(function() {
353
- test.init();
354
- });
355
- function run() {
356
- // each of these can by async
357
- synchronize(function() {
358
- test.setup();
359
- });
360
- synchronize(function() {
361
- test.run();
362
- });
363
- synchronize(function() {
364
- test.teardown();
365
- });
366
- synchronize(function() {
367
- test.finish();
368
- });
369
- }
301
+ // Figure out if we're running the tests from a server or not
302
+ QUnit.isLocal = location.protocol === "file:";
303
+ }());
370
304
 
371
- // `bad` initialized at top of scope
372
- // defer when previous test run passed, if storage is available
373
- bad = QUnit.config.reorder && defined.sessionStorage &&
374
- +sessionStorage.getItem( "qunit-test-" + this.module + "-" + this.testName );
305
+ extend( QUnit, {
375
306
 
376
- if ( bad ) {
377
- run();
378
- } else {
379
- synchronize( run, true );
380
- }
381
- }
382
- };
307
+ config: config,
383
308
 
384
- // Root QUnit object.
385
- // `QUnit` initialized at top of scope
386
- QUnit = {
309
+ // Initialize the configuration options
310
+ init: function() {
311
+ extend( config, {
312
+ stats: { all: 0, bad: 0 },
313
+ moduleStats: { all: 0, bad: 0 },
314
+ started: +new Date(),
315
+ updateRate: 1000,
316
+ blocking: false,
317
+ autostart: true,
318
+ autorun: false,
319
+ filter: "",
320
+ queue: [],
321
+ semaphore: 1
322
+ });
387
323
 
388
- // call on start of module test to prepend name to all tests
389
- module: function( name, testEnvironment ) {
390
- config.currentModule = name;
391
- config.currentModuleTestEnvironment = testEnvironment;
392
- config.modules[name] = true;
393
- },
324
+ var tests, banner, result,
325
+ qunit = id( "qunit" );
394
326
 
395
- asyncTest: function( testName, expected, callback ) {
396
- if ( arguments.length === 2 ) {
397
- callback = expected;
398
- expected = null;
327
+ if ( qunit ) {
328
+ qunit.innerHTML =
329
+ "<h1 id='qunit-header'>" + escapeText( document.title ) + "</h1>" +
330
+ "<h2 id='qunit-banner'></h2>" +
331
+ "<div id='qunit-testrunner-toolbar'></div>" +
332
+ "<h2 id='qunit-userAgent'></h2>" +
333
+ "<ol id='qunit-tests'></ol>";
399
334
  }
400
335
 
401
- QUnit.test( testName, expected, callback, true );
402
- },
403
-
404
- test: function( testName, expected, callback, async ) {
405
- var test,
406
- nameHtml = "<span class='test-name'>" + escapeText( testName ) + "</span>";
336
+ tests = id( "qunit-tests" );
337
+ banner = id( "qunit-banner" );
338
+ result = id( "qunit-testresult" );
407
339
 
408
- if ( arguments.length === 2 ) {
409
- callback = expected;
410
- expected = null;
340
+ if ( tests ) {
341
+ tests.innerHTML = "";
411
342
  }
412
343
 
413
- if ( config.currentModule ) {
414
- nameHtml = "<span class='module-name'>" + escapeText( config.currentModule ) + "</span>: " + nameHtml;
344
+ if ( banner ) {
345
+ banner.className = "";
415
346
  }
416
347
 
417
- test = new Test({
418
- nameHtml: nameHtml,
419
- testName: testName,
420
- expected: expected,
421
- async: async,
422
- callback: callback,
423
- module: config.currentModule,
424
- moduleTestEnvironment: config.currentModuleTestEnvironment,
425
- stack: sourceFromStacktrace( 2 )
426
- });
427
-
428
- if ( !validTest( test ) ) {
429
- return;
348
+ if ( result ) {
349
+ result.parentNode.removeChild( result );
430
350
  }
431
351
 
432
- test.queue();
433
- },
434
-
435
- // Specify the number of expected assertions to guarantee that failed test (no assertions are run at all) don't slip through.
436
- expect: function( asserts ) {
437
- if (arguments.length === 1) {
438
- config.current.expected = asserts;
439
- } else {
440
- return config.current.expected;
352
+ if ( tests ) {
353
+ result = document.createElement( "p" );
354
+ result.id = "qunit-testresult";
355
+ result.className = "result";
356
+ tests.parentNode.insertBefore( result, tests );
357
+ result.innerHTML = "Running...<br/>&nbsp;";
441
358
  }
442
359
  },
443
360
 
444
- start: function( count ) {
445
- // QUnit hasn't been initialized yet.
446
- // Note: RequireJS (et al) may delay onLoad
447
- if ( config.semaphore === undefined ) {
448
- QUnit.begin(function() {
449
- // This is triggered at the top of QUnit.load, push start() to the event loop, to allow QUnit.load to finish first
450
- setTimeout(function() {
451
- QUnit.start( count );
452
- });
453
- });
454
- return;
361
+ // Resets the test setup. Useful for tests that modify the DOM.
362
+ /*
363
+ DEPRECATED: Use multiple tests instead of resetting inside a test.
364
+ Use testStart or testDone for custom cleanup.
365
+ This method will throw an error in 2.0, and will be removed in 2.1
366
+ */
367
+ reset: function() {
368
+ var fixture = id( "qunit-fixture" );
369
+ if ( fixture ) {
370
+ fixture.innerHTML = config.fixture;
455
371
  }
372
+ },
456
373
 
457
- config.semaphore -= count || 1;
458
- // don't start until equal number of stop-calls
459
- if ( config.semaphore > 0 ) {
460
- return;
461
- }
462
- // ignore if start is called more often then stop
463
- if ( config.semaphore < 0 ) {
464
- config.semaphore = 0;
465
- QUnit.pushFailure( "Called start() while already started (QUnit.config.semaphore was 0 already)", null, sourceFromStacktrace(2) );
466
- return;
374
+ // Safe object type checking
375
+ is: function( type, obj ) {
376
+ return QUnit.objectType( obj ) === type;
377
+ },
378
+
379
+ objectType: function( obj ) {
380
+ if ( typeof obj === "undefined" ) {
381
+ return "undefined";
467
382
  }
468
- // A slight delay, to avoid any current callbacks
469
- if ( defined.setTimeout ) {
470
- setTimeout(function() {
471
- if ( config.semaphore > 0 ) {
472
- return;
473
- }
474
- if ( config.timeout ) {
475
- clearTimeout( config.timeout );
476
- }
477
383
 
478
- config.blocking = false;
479
- process( true );
480
- }, 13);
481
- } else {
482
- config.blocking = false;
483
- process( true );
384
+ // Consider: typeof null === object
385
+ if ( obj === null ) {
386
+ return "null";
484
387
  }
485
- },
486
388
 
487
- stop: function( count ) {
488
- config.semaphore += count || 1;
489
- config.blocking = true;
389
+ var match = toString.call( obj ).match(/^\[object\s(.*)\]$/),
390
+ type = match && match[1] || "";
490
391
 
491
- if ( config.testTimeout && defined.setTimeout ) {
492
- clearTimeout( config.timeout );
493
- config.timeout = setTimeout(function() {
494
- QUnit.ok( false, "Test timed out" );
495
- config.semaphore = 1;
496
- QUnit.start();
497
- }, config.testTimeout );
392
+ switch ( type ) {
393
+ case "Number":
394
+ if ( isNaN(obj) ) {
395
+ return "nan";
396
+ }
397
+ return "number";
398
+ case "String":
399
+ case "Boolean":
400
+ case "Array":
401
+ case "Date":
402
+ case "RegExp":
403
+ case "Function":
404
+ return type.toLowerCase();
498
405
  }
499
- }
500
- };
406
+ if ( typeof obj === "object" ) {
407
+ return "object";
408
+ }
409
+ return undefined;
410
+ },
501
411
 
502
- // `assert` initialized at top of scope
503
- // Assert helpers
504
- // All of these must either call QUnit.push() or manually do:
505
- // - runLoggingCallbacks( "log", .. );
506
- // - config.current.assertions.push({ .. });
507
- // We attach it to the QUnit object *after* we expose the public API,
508
- // otherwise `assert` will become a global variable in browsers (#341).
509
- assert = {
510
- /**
511
- * Asserts rough true-ish result.
512
- * @name ok
513
- * @function
514
- * @example ok( "asdfasdf".length > 5, "There must be at least 5 chars" );
515
- */
516
- ok: function( result, msg ) {
412
+ push: function( result, actual, expected, message ) {
517
413
  if ( !config.current ) {
518
- throw new Error( "ok() assertion outside test context, was " + sourceFromStacktrace(2) );
414
+ throw new Error( "assertion outside test context, was " + sourceFromStacktrace() );
519
415
  }
520
- result = !!result;
521
- msg = msg || (result ? "okay" : "failed" );
522
416
 
523
- var source,
417
+ var output, source,
524
418
  details = {
525
419
  module: config.current.module,
526
420
  name: config.current.testName,
527
421
  result: result,
528
- message: msg
422
+ message: message,
423
+ actual: actual,
424
+ expected: expected
529
425
  };
530
426
 
531
- msg = "<span class='test-message'>" + escapeText( msg ) + "</span>";
427
+ message = escapeText( message ) || ( result ? "okay" : "failed" );
428
+ message = "<span class='test-message'>" + message + "</span>";
429
+ output = message;
532
430
 
533
431
  if ( !result ) {
534
- source = sourceFromStacktrace( 2 );
432
+ expected = escapeText( QUnit.jsDump.parse(expected) );
433
+ actual = escapeText( QUnit.jsDump.parse(actual) );
434
+ output += "<table><tr class='test-expected'><th>Expected: </th><td><pre>" + expected + "</pre></td></tr>";
435
+
436
+ if ( actual !== expected ) {
437
+ output += "<tr class='test-actual'><th>Result: </th><td><pre>" + actual + "</pre></td></tr>";
438
+ output += "<tr class='test-diff'><th>Diff: </th><td><pre>" + QUnit.diff( expected, actual ) + "</pre></td></tr>";
439
+ }
440
+
441
+ source = sourceFromStacktrace();
442
+
535
443
  if ( source ) {
536
444
  details.source = source;
537
- msg += "<table><tr class='test-source'><th>Source: </th><td><pre>" + escapeText( source ) + "</pre></td></tr></table>";
445
+ output += "<tr class='test-source'><th>Source: </th><td><pre>" + escapeText( source ) + "</pre></td></tr>";
538
446
  }
447
+
448
+ output += "</table>";
539
449
  }
450
+
540
451
  runLoggingCallbacks( "log", QUnit, details );
452
+
541
453
  config.current.assertions.push({
542
- result: result,
543
- message: msg
454
+ result: !!result,
455
+ message: output
544
456
  });
545
457
  },
546
458
 
547
- /**
548
- * Assert that the first two arguments are equal, with an optional message.
549
- * Prints out both actual and expected values.
550
- * @name equal
551
- * @function
552
- * @example equal( format( "Received {0} bytes.", 2), "Received 2 bytes.", "format() replaces {0} with next argument" );
553
- */
554
- equal: function( actual, expected, message ) {
555
- /*jshint eqeqeq:false */
556
- QUnit.push( expected == actual, actual, expected, message );
557
- },
459
+ pushFailure: function( message, source, actual ) {
460
+ if ( !config.current ) {
461
+ throw new Error( "pushFailure() assertion outside test context, was " + sourceFromStacktrace(2) );
462
+ }
558
463
 
559
- /**
560
- * @name notEqual
561
- * @function
562
- */
563
- notEqual: function( actual, expected, message ) {
564
- /*jshint eqeqeq:false */
565
- QUnit.push( expected != actual, actual, expected, message );
566
- },
464
+ var output,
465
+ details = {
466
+ module: config.current.module,
467
+ name: config.current.testName,
468
+ result: false,
469
+ message: message
470
+ };
567
471
 
568
- /**
569
- * @name propEqual
570
- * @function
571
- */
572
- propEqual: function( actual, expected, message ) {
573
- actual = objectValues(actual);
574
- expected = objectValues(expected);
575
- QUnit.push( QUnit.equiv(actual, expected), actual, expected, message );
576
- },
472
+ message = escapeText( message ) || "error";
473
+ message = "<span class='test-message'>" + message + "</span>";
474
+ output = message;
577
475
 
578
- /**
579
- * @name notPropEqual
580
- * @function
581
- */
582
- notPropEqual: function( actual, expected, message ) {
583
- actual = objectValues(actual);
584
- expected = objectValues(expected);
585
- QUnit.push( !QUnit.equiv(actual, expected), actual, expected, message );
586
- },
476
+ output += "<table>";
587
477
 
588
- /**
589
- * @name deepEqual
590
- * @function
591
- */
592
- deepEqual: function( actual, expected, message ) {
593
- QUnit.push( QUnit.equiv(actual, expected), actual, expected, message );
594
- },
478
+ if ( actual ) {
479
+ output += "<tr class='test-actual'><th>Result: </th><td><pre>" + escapeText( actual ) + "</pre></td></tr>";
480
+ }
595
481
 
596
- /**
597
- * @name notDeepEqual
598
- * @function
599
- */
600
- notDeepEqual: function( actual, expected, message ) {
601
- QUnit.push( !QUnit.equiv(actual, expected), actual, expected, message );
602
- },
482
+ if ( source ) {
483
+ details.source = source;
484
+ output += "<tr class='test-source'><th>Source: </th><td><pre>" + escapeText( source ) + "</pre></td></tr>";
485
+ }
603
486
 
604
- /**
605
- * @name strictEqual
606
- * @function
607
- */
608
- strictEqual: function( actual, expected, message ) {
609
- QUnit.push( expected === actual, actual, expected, message );
610
- },
487
+ output += "</table>";
611
488
 
612
- /**
613
- * @name notStrictEqual
614
- * @function
615
- */
616
- notStrictEqual: function( actual, expected, message ) {
617
- QUnit.push( expected !== actual, actual, expected, message );
618
- },
489
+ runLoggingCallbacks( "log", QUnit, details );
619
490
 
620
- "throws": function( block, expected, message ) {
621
- var actual,
622
- expectedOutput = expected,
623
- ok = false;
491
+ config.current.assertions.push({
492
+ result: false,
493
+ message: output
494
+ });
495
+ },
624
496
 
625
- // 'expected' is optional
626
- if ( typeof expected === "string" ) {
627
- message = expected;
628
- expected = null;
629
- }
497
+ url: function( params ) {
498
+ params = extend( extend( {}, QUnit.urlParams ), params );
499
+ var key,
500
+ querystring = "?";
630
501
 
631
- config.current.ignoreGlobalErrors = true;
632
- try {
633
- block.call( config.current.testEnvironment );
634
- } catch (e) {
635
- actual = e;
502
+ for ( key in params ) {
503
+ if ( hasOwn.call( params, key ) ) {
504
+ querystring += encodeURIComponent( key ) + "=" +
505
+ encodeURIComponent( params[ key ] ) + "&";
506
+ }
636
507
  }
637
- config.current.ignoreGlobalErrors = false;
508
+ return window.location.protocol + "//" + window.location.host +
509
+ window.location.pathname + querystring.slice( 0, -1 );
510
+ },
638
511
 
639
- if ( actual ) {
640
- // we don't want to validate thrown error
641
- if ( !expected ) {
642
- ok = true;
643
- expectedOutput = null;
644
- // expected is a regexp
645
- } else if ( QUnit.objectType( expected ) === "regexp" ) {
646
- ok = expected.test( errorString( actual ) );
647
- // expected is a constructor
648
- } else if ( actual instanceof expected ) {
649
- ok = true;
650
- // expected is a validation function which returns true is validation passed
651
- } else if ( expected.call( {}, actual ) === true ) {
652
- expectedOutput = null;
653
- ok = true;
654
- }
655
-
656
- QUnit.push( ok, actual, expectedOutput, message );
657
- } else {
658
- QUnit.pushFailure( message, null, "No exception was thrown." );
659
- }
660
- }
661
- };
662
-
663
- /**
664
- * @deprecated since 1.8.0
665
- * Kept assertion helpers in root for backwards compatibility.
666
- */
667
- extend( QUnit, assert );
668
-
669
- /**
670
- * @deprecated since 1.9.0
671
- * Kept root "raises()" for backwards compatibility.
672
- * (Note that we don't introduce assert.raises).
673
- */
674
- QUnit.raises = assert[ "throws" ];
512
+ extend: extend,
513
+ id: id,
514
+ addEvent: addEvent,
515
+ addClass: addClass,
516
+ hasClass: hasClass,
517
+ removeClass: removeClass
518
+ // load, equiv, jsDump, diff: Attached later
519
+ });
675
520
 
676
521
  /**
677
- * @deprecated since 1.0.0, replaced with error pushes since 1.3.0
678
- * Kept to avoid TypeErrors for undefined methods.
522
+ * @deprecated: Created for backwards compatibility with test runner that set the hook function
523
+ * into QUnit.{hook}, instead of invoking it and passing the hook function.
524
+ * QUnit.constructor is set to the empty F() above so that we can add to it's prototype here.
525
+ * Doing this allows us to tell if the following methods have been overwritten on the actual
526
+ * QUnit object.
679
527
  */
680
- QUnit.equals = function() {
681
- QUnit.push( false, false, false, "QUnit.equals has been deprecated since 2009 (e88049a0), use QUnit.equal instead" );
682
- };
683
- QUnit.same = function() {
684
- QUnit.push( false, false, false, "QUnit.same has been deprecated since 2009 (e88049a0), use QUnit.deepEqual instead" );
685
- };
528
+ extend( QUnit.constructor.prototype, {
686
529
 
687
- // We want access to the constructor's prototype
688
- (function() {
689
- function F() {}
690
- F.prototype = QUnit;
691
- QUnit = new F();
692
- // Make F QUnit's constructor so that we can add to the prototype later
693
- QUnit.constructor = F;
694
- }());
530
+ // Logging callbacks; all receive a single argument with the listed properties
531
+ // run test/logs.html for any related changes
532
+ begin: registerLoggingCallback( "begin" ),
695
533
 
696
- /**
697
- * Config object: Maintain internal state
698
- * Later exposed as QUnit.config
699
- * `config` initialized at top of scope
700
- */
701
- config = {
702
- // The queue of tests to run
703
- queue: [],
534
+ // done: { failed, passed, total, runtime }
535
+ done: registerLoggingCallback( "done" ),
704
536
 
705
- // block until document ready
706
- blocking: true,
537
+ // log: { result, actual, expected, message }
538
+ log: registerLoggingCallback( "log" ),
707
539
 
708
- // when enabled, show only failing tests
709
- // gets persisted through sessionStorage and can be changed in UI via checkbox
710
- hidepassed: false,
540
+ // testStart: { name }
541
+ testStart: registerLoggingCallback( "testStart" ),
711
542
 
712
- // by default, run previously failed tests first
713
- // very useful in combination with "Hide passed tests" checked
714
- reorder: true,
543
+ // testDone: { name, failed, passed, total, runtime }
544
+ testDone: registerLoggingCallback( "testDone" ),
715
545
 
716
- // by default, modify document.title when suite is done
717
- altertitle: true,
546
+ // moduleStart: { name }
547
+ moduleStart: registerLoggingCallback( "moduleStart" ),
718
548
 
719
- // when enabled, all tests must call expect()
720
- requireExpects: false,
549
+ // moduleDone: { name, failed, passed, total }
550
+ moduleDone: registerLoggingCallback( "moduleDone" )
551
+ });
721
552
 
722
- // add checkboxes that are persisted in the query-string
723
- // when enabled, the id is set to `true` as a `QUnit.config` property
724
- urlConfig: [
725
- {
726
- id: "noglobals",
727
- label: "Check for Globals",
728
- tooltip: "Enabling this will test if any test introduces new properties on the `window` object. Stored as query-strings."
729
- },
730
- {
731
- id: "notrycatch",
732
- label: "No try-catch",
733
- tooltip: "Enabling this will run tests outside of a try-catch block. Makes debugging exceptions in IE reasonable. Stored as query-strings."
734
- }
735
- ],
553
+ if ( !defined.document || document.readyState === "complete" ) {
554
+ config.autorun = true;
555
+ }
736
556
 
737
- // Set of all modules.
738
- modules: {},
557
+ QUnit.load = function() {
558
+ runLoggingCallbacks( "begin", QUnit, {} );
739
559
 
740
- // logging callback queues
741
- begin: [],
742
- done: [],
743
- log: [],
744
- testStart: [],
745
- testDone: [],
746
- moduleStart: [],
747
- moduleDone: []
748
- };
560
+ // Initialize the config, saving the execution queue
561
+ var banner, filter, i, label, len, main, ol, toolbar, userAgent, val,
562
+ urlConfigCheckboxesContainer, urlConfigCheckboxes, moduleFilter,
563
+ numModules = 0,
564
+ moduleNames = [],
565
+ moduleFilterHtml = "",
566
+ urlConfigHtml = "",
567
+ oldconfig = extend( {}, config );
749
568
 
750
- // Export global variables, unless an 'exports' object exists,
751
- // in that case we assume we're in CommonJS (dealt with on the bottom of the script)
752
- if ( typeof exports === "undefined" ) {
753
- extend( window, QUnit.constructor.prototype );
569
+ QUnit.init();
570
+ extend(config, oldconfig);
754
571
 
755
- // Expose QUnit object
756
- window.QUnit = QUnit;
757
- }
572
+ config.blocking = false;
758
573
 
759
- // Initialize more QUnit.config and QUnit.urlParams
760
- (function() {
761
- var i,
762
- location = window.location || { search: "", protocol: "file:" },
763
- params = location.search.slice( 1 ).split( "&" ),
764
- length = params.length,
765
- urlParams = {},
766
- current;
574
+ len = config.urlConfig.length;
767
575
 
768
- if ( params[ 0 ] ) {
769
- for ( i = 0; i < length; i++ ) {
770
- current = params[ i ].split( "=" );
771
- current[ 0 ] = decodeURIComponent( current[ 0 ] );
772
- // allow just a key to turn on a flag, e.g., test.html?noglobals
773
- current[ 1 ] = current[ 1 ] ? decodeURIComponent( current[ 1 ] ) : true;
774
- urlParams[ current[ 0 ] ] = current[ 1 ];
576
+ for ( i = 0; i < len; i++ ) {
577
+ val = config.urlConfig[i];
578
+ if ( typeof val === "string" ) {
579
+ val = {
580
+ id: val,
581
+ label: val,
582
+ tooltip: "[no tooltip available]"
583
+ };
584
+ }
585
+ config[ val.id ] = QUnit.urlParams[ val.id ];
586
+ urlConfigHtml += "<input id='qunit-urlconfig-" + escapeText( val.id ) +
587
+ "' name='" + escapeText( val.id ) +
588
+ "' type='checkbox'" + ( config[ val.id ] ? " checked='checked'" : "" ) +
589
+ " title='" + escapeText( val.tooltip ) +
590
+ "'><label for='qunit-urlconfig-" + escapeText( val.id ) +
591
+ "' title='" + escapeText( val.tooltip ) + "'>" + val.label + "</label>";
592
+ }
593
+ for ( i in config.modules ) {
594
+ if ( config.modules.hasOwnProperty( i ) ) {
595
+ moduleNames.push(i);
775
596
  }
776
597
  }
598
+ numModules = moduleNames.length;
599
+ moduleNames.sort( function( a, b ) {
600
+ return a.localeCompare( b );
601
+ });
602
+ moduleFilterHtml += "<label for='qunit-modulefilter'>Module: </label><select id='qunit-modulefilter' name='modulefilter'><option value='' " +
603
+ ( config.module === undefined ? "selected='selected'" : "" ) +
604
+ ">< All Modules ></option>";
777
605
 
778
- QUnit.urlParams = urlParams;
779
-
780
- // String search anywhere in moduleName+testName
781
- config.filter = urlParams.filter;
782
606
 
783
- // Exact match of the module name
784
- config.module = urlParams.module;
607
+ for ( i = 0; i < numModules; i++) {
608
+ moduleFilterHtml += "<option value='" + escapeText( encodeURIComponent(moduleNames[i]) ) + "' " +
609
+ ( config.module === moduleNames[i] ? "selected='selected'" : "" ) +
610
+ ">" + escapeText(moduleNames[i]) + "</option>";
611
+ }
612
+ moduleFilterHtml += "</select>";
785
613
 
786
- config.testNumber = parseInt( urlParams.testNumber, 10 ) || null;
614
+ // `userAgent` initialized at top of scope
615
+ userAgent = id( "qunit-userAgent" );
616
+ if ( userAgent ) {
617
+ userAgent.innerHTML = navigator.userAgent;
618
+ }
787
619
 
788
- // Figure out if we're running the tests from a server or not
789
- QUnit.isLocal = location.protocol === "file:";
790
- }());
620
+ // `banner` initialized at top of scope
621
+ banner = id( "qunit-header" );
622
+ if ( banner ) {
623
+ banner.innerHTML = "<a href='" + QUnit.url({ filter: undefined, module: undefined, testNumber: undefined }) + "'>" + banner.innerHTML + "</a> ";
624
+ }
791
625
 
792
- // Extend QUnit object,
793
- // these after set here because they should not be exposed as global functions
794
- extend( QUnit, {
795
- assert: assert,
626
+ // `toolbar` initialized at top of scope
627
+ toolbar = id( "qunit-testrunner-toolbar" );
628
+ if ( toolbar ) {
629
+ // `filter` initialized at top of scope
630
+ filter = document.createElement( "input" );
631
+ filter.type = "checkbox";
632
+ filter.id = "qunit-filter-pass";
796
633
 
797
- config: config,
634
+ addEvent( filter, "click", function() {
635
+ var tmp,
636
+ ol = id( "qunit-tests" );
798
637
 
799
- // Initialize the configuration options
800
- init: function() {
801
- extend( config, {
802
- stats: { all: 0, bad: 0 },
803
- moduleStats: { all: 0, bad: 0 },
804
- started: +new Date(),
805
- updateRate: 1000,
806
- blocking: false,
807
- autostart: true,
808
- autorun: false,
809
- filter: "",
810
- queue: [],
811
- semaphore: 1
638
+ if ( filter.checked ) {
639
+ ol.className = ol.className + " hidepass";
640
+ } else {
641
+ tmp = " " + ol.className.replace( /[\n\t\r]/g, " " ) + " ";
642
+ ol.className = tmp.replace( / hidepass /, " " );
643
+ }
644
+ if ( defined.sessionStorage ) {
645
+ if (filter.checked) {
646
+ sessionStorage.setItem( "qunit-filter-passed-tests", "true" );
647
+ } else {
648
+ sessionStorage.removeItem( "qunit-filter-passed-tests" );
649
+ }
650
+ }
812
651
  });
813
652
 
814
- var tests, banner, result,
815
- qunit = id( "qunit" );
816
-
817
- if ( qunit ) {
818
- qunit.innerHTML =
819
- "<h1 id='qunit-header'>" + escapeText( document.title ) + "</h1>" +
820
- "<h2 id='qunit-banner'></h2>" +
821
- "<div id='qunit-testrunner-toolbar'></div>" +
822
- "<h2 id='qunit-userAgent'></h2>" +
823
- "<ol id='qunit-tests'></ol>";
653
+ if ( config.hidepassed || defined.sessionStorage && sessionStorage.getItem( "qunit-filter-passed-tests" ) ) {
654
+ filter.checked = true;
655
+ // `ol` initialized at top of scope
656
+ ol = id( "qunit-tests" );
657
+ ol.className = ol.className + " hidepass";
824
658
  }
659
+ toolbar.appendChild( filter );
825
660
 
826
- tests = id( "qunit-tests" );
827
- banner = id( "qunit-banner" );
828
- result = id( "qunit-testresult" );
829
-
830
- if ( tests ) {
831
- tests.innerHTML = "";
832
- }
833
-
834
- if ( banner ) {
835
- banner.className = "";
836
- }
661
+ // `label` initialized at top of scope
662
+ label = document.createElement( "label" );
663
+ label.setAttribute( "for", "qunit-filter-pass" );
664
+ label.setAttribute( "title", "Only show tests and assertions that fail. Stored in sessionStorage." );
665
+ label.innerHTML = "Hide passed tests";
666
+ toolbar.appendChild( label );
837
667
 
838
- if ( result ) {
839
- result.parentNode.removeChild( result );
840
- }
668
+ urlConfigCheckboxesContainer = document.createElement("span");
669
+ urlConfigCheckboxesContainer.innerHTML = urlConfigHtml;
670
+ urlConfigCheckboxes = urlConfigCheckboxesContainer.getElementsByTagName("input");
671
+ // For oldIE support:
672
+ // * Add handlers to the individual elements instead of the container
673
+ // * Use "click" instead of "change"
674
+ // * Fallback from event.target to event.srcElement
675
+ addEvents( urlConfigCheckboxes, "click", function( event ) {
676
+ var params = {},
677
+ target = event.target || event.srcElement;
678
+ params[ target.name ] = target.checked ? true : undefined;
679
+ window.location = QUnit.url( params );
680
+ });
681
+ toolbar.appendChild( urlConfigCheckboxesContainer );
841
682
 
842
- if ( tests ) {
843
- result = document.createElement( "p" );
844
- result.id = "qunit-testresult";
845
- result.className = "result";
846
- tests.parentNode.insertBefore( result, tests );
847
- result.innerHTML = "Running...<br/>&nbsp;";
848
- }
849
- },
683
+ if (numModules > 1) {
684
+ moduleFilter = document.createElement( "span" );
685
+ moduleFilter.setAttribute( "id", "qunit-modulefilter-container" );
686
+ moduleFilter.innerHTML = moduleFilterHtml;
687
+ addEvent( moduleFilter.lastChild, "change", function() {
688
+ var selectBox = moduleFilter.getElementsByTagName("select")[0],
689
+ selectedModule = decodeURIComponent(selectBox.options[selectBox.selectedIndex].value);
850
690
 
851
- // Resets the test setup. Useful for tests that modify the DOM.
852
- /*
853
- DEPRECATED: Use multiple tests instead of resetting inside a test.
854
- Use testStart or testDone for custom cleanup.
855
- This method will throw an error in 2.0, and will be removed in 2.1
856
- */
857
- reset: function() {
858
- var fixture = id( "qunit-fixture" );
859
- if ( fixture ) {
860
- fixture.innerHTML = config.fixture;
691
+ window.location = QUnit.url({
692
+ module: ( selectedModule === "" ) ? undefined : selectedModule,
693
+ // Remove any existing filters
694
+ filter: undefined,
695
+ testNumber: undefined
696
+ });
697
+ });
698
+ toolbar.appendChild(moduleFilter);
861
699
  }
862
- },
700
+ }
863
701
 
864
- // Trigger an event on an element.
865
- // @example triggerEvent( document.body, "click" );
866
- triggerEvent: function( elem, type, event ) {
867
- if ( document.createEvent ) {
868
- event = document.createEvent( "MouseEvents" );
869
- event.initMouseEvent(type, true, true, elem.ownerDocument.defaultView,
870
- 0, 0, 0, 0, 0, false, false, false, false, 0, null);
702
+ // `main` initialized at top of scope
703
+ main = id( "qunit-fixture" );
704
+ if ( main ) {
705
+ config.fixture = main.innerHTML;
706
+ }
871
707
 
872
- elem.dispatchEvent( event );
873
- } else if ( elem.fireEvent ) {
874
- elem.fireEvent( "on" + type );
875
- }
876
- },
708
+ if ( config.autostart ) {
709
+ QUnit.start();
710
+ }
711
+ };
877
712
 
878
- // Safe object type checking
879
- is: function( type, obj ) {
880
- return QUnit.objectType( obj ) === type;
881
- },
713
+ if ( defined.document ) {
714
+ addEvent( window, "load", QUnit.load );
715
+ }
882
716
 
883
- objectType: function( obj ) {
884
- if ( typeof obj === "undefined" ) {
885
- return "undefined";
886
- // consider: typeof null === object
887
- }
888
- if ( obj === null ) {
889
- return "null";
890
- }
717
+ // `onErrorFnPrev` initialized at top of scope
718
+ // Preserve other handlers
719
+ onErrorFnPrev = window.onerror;
891
720
 
892
- var match = toString.call( obj ).match(/^\[object\s(.*)\]$/),
893
- type = match && match[1] || "";
721
+ // Cover uncaught exceptions
722
+ // Returning true will suppress the default browser handler,
723
+ // returning false will let it run.
724
+ window.onerror = function ( error, filePath, linerNr ) {
725
+ var ret = false;
726
+ if ( onErrorFnPrev ) {
727
+ ret = onErrorFnPrev( error, filePath, linerNr );
728
+ }
894
729
 
895
- switch ( type ) {
896
- case "Number":
897
- if ( isNaN(obj) ) {
898
- return "nan";
899
- }
900
- return "number";
901
- case "String":
902
- case "Boolean":
903
- case "Array":
904
- case "Date":
905
- case "RegExp":
906
- case "Function":
907
- return type.toLowerCase();
908
- }
909
- if ( typeof obj === "object" ) {
910
- return "object";
730
+ // Treat return value as window.onerror itself does,
731
+ // Only do our handling if not suppressed.
732
+ if ( ret !== true ) {
733
+ if ( QUnit.config.current ) {
734
+ if ( QUnit.config.current.ignoreGlobalErrors ) {
735
+ return true;
736
+ }
737
+ QUnit.pushFailure( error, filePath + ":" + linerNr );
738
+ } else {
739
+ QUnit.test( "global failure", extend( function() {
740
+ QUnit.pushFailure( error, filePath + ":" + linerNr );
741
+ }, { validTest: validTest } ) );
911
742
  }
912
- return undefined;
913
- },
743
+ return false;
744
+ }
914
745
 
915
- push: function( result, actual, expected, message ) {
916
- if ( !config.current ) {
917
- throw new Error( "assertion outside test context, was " + sourceFromStacktrace() );
918
- }
746
+ return ret;
747
+ };
919
748
 
920
- var output, source,
921
- details = {
922
- module: config.current.module,
923
- name: config.current.testName,
924
- result: result,
925
- message: message,
926
- actual: actual,
927
- expected: expected
928
- };
749
+ function done() {
750
+ config.autorun = true;
929
751
 
930
- message = escapeText( message ) || ( result ? "okay" : "failed" );
931
- message = "<span class='test-message'>" + message + "</span>";
932
- output = message;
752
+ // Log the last module results
753
+ if ( config.previousModule ) {
754
+ runLoggingCallbacks( "moduleDone", QUnit, {
755
+ name: config.previousModule,
756
+ failed: config.moduleStats.bad,
757
+ passed: config.moduleStats.all - config.moduleStats.bad,
758
+ total: config.moduleStats.all
759
+ });
760
+ }
761
+ delete config.previousModule;
933
762
 
934
- if ( !result ) {
935
- expected = escapeText( QUnit.jsDump.parse(expected) );
936
- actual = escapeText( QUnit.jsDump.parse(actual) );
937
- output += "<table><tr class='test-expected'><th>Expected: </th><td><pre>" + expected + "</pre></td></tr>";
763
+ var i, key,
764
+ banner = id( "qunit-banner" ),
765
+ tests = id( "qunit-tests" ),
766
+ runtime = +new Date() - config.started,
767
+ passed = config.stats.all - config.stats.bad,
768
+ html = [
769
+ "Tests completed in ",
770
+ runtime,
771
+ " milliseconds.<br/>",
772
+ "<span class='passed'>",
773
+ passed,
774
+ "</span> assertions of <span class='total'>",
775
+ config.stats.all,
776
+ "</span> passed, <span class='failed'>",
777
+ config.stats.bad,
778
+ "</span> failed."
779
+ ].join( "" );
938
780
 
939
- if ( actual !== expected ) {
940
- output += "<tr class='test-actual'><th>Result: </th><td><pre>" + actual + "</pre></td></tr>";
941
- output += "<tr class='test-diff'><th>Diff: </th><td><pre>" + QUnit.diff( expected, actual ) + "</pre></td></tr>";
942
- }
781
+ if ( banner ) {
782
+ banner.className = ( config.stats.bad ? "qunit-fail" : "qunit-pass" );
783
+ }
943
784
 
944
- source = sourceFromStacktrace();
785
+ if ( tests ) {
786
+ id( "qunit-testresult" ).innerHTML = html;
787
+ }
945
788
 
946
- if ( source ) {
947
- details.source = source;
948
- output += "<tr class='test-source'><th>Source: </th><td><pre>" + escapeText( source ) + "</pre></td></tr>";
949
- }
789
+ if ( config.altertitle && defined.document && document.title ) {
790
+ // show ✖ for good, ✔ for bad suite result in title
791
+ // use escape sequences in case file gets loaded with non-utf-8-charset
792
+ document.title = [
793
+ ( config.stats.bad ? "\u2716" : "\u2714" ),
794
+ document.title.replace( /^[\u2714\u2716] /i, "" )
795
+ ].join( " " );
796
+ }
950
797
 
951
- output += "</table>";
798
+ // clear own sessionStorage items if all tests passed
799
+ if ( config.reorder && defined.sessionStorage && config.stats.bad === 0 ) {
800
+ // `key` & `i` initialized at top of scope
801
+ for ( i = 0; i < sessionStorage.length; i++ ) {
802
+ key = sessionStorage.key( i++ );
803
+ if ( key.indexOf( "qunit-test-" ) === 0 ) {
804
+ sessionStorage.removeItem( key );
805
+ }
952
806
  }
807
+ }
953
808
 
954
- runLoggingCallbacks( "log", QUnit, details );
955
-
956
- config.current.assertions.push({
957
- result: !!result,
958
- message: output
959
- });
960
- },
809
+ // scroll back to top to show results
810
+ if ( window.scrollTo ) {
811
+ window.scrollTo(0, 0);
812
+ }
961
813
 
962
- pushFailure: function( message, source, actual ) {
963
- if ( !config.current ) {
964
- throw new Error( "pushFailure() assertion outside test context, was " + sourceFromStacktrace(2) );
965
- }
814
+ runLoggingCallbacks( "done", QUnit, {
815
+ failed: config.stats.bad,
816
+ passed: passed,
817
+ total: config.stats.all,
818
+ runtime: runtime
819
+ });
820
+ }
966
821
 
967
- var output,
968
- details = {
969
- module: config.current.module,
970
- name: config.current.testName,
971
- result: false,
972
- message: message
973
- };
822
+ /** @return Boolean: true if this test should be ran */
823
+ function validTest( test ) {
824
+ var include,
825
+ filter = config.filter && config.filter.toLowerCase(),
826
+ module = config.module && config.module.toLowerCase(),
827
+ fullName = (test.module + ": " + test.testName).toLowerCase();
974
828
 
975
- message = escapeText( message ) || "error";
976
- message = "<span class='test-message'>" + message + "</span>";
977
- output = message;
829
+ // Internally-generated tests are always valid
830
+ if ( test.callback && test.callback.validTest === validTest ) {
831
+ delete test.callback.validTest;
832
+ return true;
833
+ }
978
834
 
979
- output += "<table>";
835
+ if ( config.testNumber ) {
836
+ return test.testNumber === config.testNumber;
837
+ }
980
838
 
981
- if ( actual ) {
982
- output += "<tr class='test-actual'><th>Result: </th><td><pre>" + escapeText( actual ) + "</pre></td></tr>";
983
- }
839
+ if ( module && ( !test.module || test.module.toLowerCase() !== module ) ) {
840
+ return false;
841
+ }
984
842
 
985
- if ( source ) {
986
- details.source = source;
987
- output += "<tr class='test-source'><th>Source: </th><td><pre>" + escapeText( source ) + "</pre></td></tr>";
988
- }
843
+ if ( !filter ) {
844
+ return true;
845
+ }
989
846
 
990
- output += "</table>";
847
+ include = filter.charAt( 0 ) !== "!";
848
+ if ( !include ) {
849
+ filter = filter.slice( 1 );
850
+ }
991
851
 
992
- runLoggingCallbacks( "log", QUnit, details );
852
+ // If the filter matches, we need to honour include
853
+ if ( fullName.indexOf( filter ) !== -1 ) {
854
+ return include;
855
+ }
993
856
 
994
- config.current.assertions.push({
995
- result: false,
996
- message: output
997
- });
998
- },
857
+ // Otherwise, do the opposite
858
+ return !include;
859
+ }
999
860
 
1000
- url: function( params ) {
1001
- params = extend( extend( {}, QUnit.urlParams ), params );
1002
- var key,
1003
- querystring = "?";
861
+ // so far supports only Firefox, Chrome and Opera (buggy), Safari (for real exceptions)
862
+ // Later Safari and IE10 are supposed to support error.stack as well
863
+ // See also https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Error/Stack
864
+ function extractStacktrace( e, offset ) {
865
+ offset = offset === undefined ? 3 : offset;
1004
866
 
1005
- for ( key in params ) {
1006
- if ( hasOwn.call( params, key ) ) {
1007
- querystring += encodeURIComponent( key ) + "=" +
1008
- encodeURIComponent( params[ key ] ) + "&";
867
+ var stack, include, i;
868
+
869
+ if ( e.stacktrace ) {
870
+ // Opera
871
+ return e.stacktrace.split( "\n" )[ offset + 3 ];
872
+ } else if ( e.stack ) {
873
+ // Firefox, Chrome
874
+ stack = e.stack.split( "\n" );
875
+ if (/^error$/i.test( stack[0] ) ) {
876
+ stack.shift();
877
+ }
878
+ if ( fileName ) {
879
+ include = [];
880
+ for ( i = offset; i < stack.length; i++ ) {
881
+ if ( stack[ i ].indexOf( fileName ) !== -1 ) {
882
+ break;
883
+ }
884
+ include.push( stack[ i ] );
885
+ }
886
+ if ( include.length ) {
887
+ return include.join( "\n" );
1009
888
  }
1010
889
  }
1011
- return window.location.protocol + "//" + window.location.host +
1012
- window.location.pathname + querystring.slice( 0, -1 );
1013
- },
1014
-
1015
- extend: extend,
1016
- id: id,
1017
- addEvent: addEvent,
1018
- addClass: addClass,
1019
- hasClass: hasClass,
1020
- removeClass: removeClass
1021
- // load, equiv, jsDump, diff: Attached later
1022
- });
890
+ return stack[ offset ];
891
+ } else if ( e.sourceURL ) {
892
+ // Safari, PhantomJS
893
+ // hopefully one day Safari provides actual stacktraces
894
+ // exclude useless self-reference for generated Error objects
895
+ if ( /qunit.js$/.test( e.sourceURL ) ) {
896
+ return;
897
+ }
898
+ // for actual exceptions, this is useful
899
+ return e.sourceURL + ":" + e.line;
900
+ }
901
+ }
902
+ function sourceFromStacktrace( offset ) {
903
+ try {
904
+ throw new Error();
905
+ } catch ( e ) {
906
+ return extractStacktrace( e, offset );
907
+ }
908
+ }
1023
909
 
1024
910
  /**
1025
- * @deprecated: Created for backwards compatibility with test runner that set the hook function
1026
- * into QUnit.{hook}, instead of invoking it and passing the hook function.
1027
- * QUnit.constructor is set to the empty F() above so that we can add to it's prototype here.
1028
- * Doing this allows us to tell if the following methods have been overwritten on the actual
1029
- * QUnit object.
911
+ * Escape text for attribute or text content.
1030
912
  */
1031
- extend( QUnit.constructor.prototype, {
1032
-
1033
- // Logging callbacks; all receive a single argument with the listed properties
1034
- // run test/logs.html for any related changes
1035
- begin: registerLoggingCallback( "begin" ),
1036
-
1037
- // done: { failed, passed, total, runtime }
1038
- done: registerLoggingCallback( "done" ),
913
+ function escapeText( s ) {
914
+ if ( !s ) {
915
+ return "";
916
+ }
917
+ s = s + "";
918
+ // Both single quotes and double quotes (for attributes)
919
+ return s.replace( /['"<>&]/g, function( s ) {
920
+ switch( s ) {
921
+ case "'":
922
+ return "&#039;";
923
+ case "\"":
924
+ return "&quot;";
925
+ case "<":
926
+ return "&lt;";
927
+ case ">":
928
+ return "&gt;";
929
+ case "&":
930
+ return "&amp;";
931
+ }
932
+ });
933
+ }
1039
934
 
1040
- // log: { result, actual, expected, message }
1041
- log: registerLoggingCallback( "log" ),
935
+ function synchronize( callback, last ) {
936
+ config.queue.push( callback );
1042
937
 
1043
- // testStart: { name }
1044
- testStart: registerLoggingCallback( "testStart" ),
938
+ if ( config.autorun && !config.blocking ) {
939
+ process( last );
940
+ }
941
+ }
1045
942
 
1046
- // testDone: { name, failed, passed, total, duration }
1047
- testDone: registerLoggingCallback( "testDone" ),
943
+ function process( last ) {
944
+ function next() {
945
+ process( last );
946
+ }
947
+ var start = new Date().getTime();
948
+ config.depth = config.depth ? config.depth + 1 : 1;
1048
949
 
1049
- // moduleStart: { name }
1050
- moduleStart: registerLoggingCallback( "moduleStart" ),
950
+ while ( config.queue.length && !config.blocking ) {
951
+ if ( !defined.setTimeout || config.updateRate <= 0 || ( ( new Date().getTime() - start ) < config.updateRate ) ) {
952
+ config.queue.shift()();
953
+ } else {
954
+ setTimeout( next, 13 );
955
+ break;
956
+ }
957
+ }
958
+ config.depth--;
959
+ if ( last && !config.blocking && !config.queue.length && config.depth === 0 ) {
960
+ done();
961
+ }
962
+ }
1051
963
 
1052
- // moduleDone: { name, failed, passed, total }
1053
- moduleDone: registerLoggingCallback( "moduleDone" )
1054
- });
964
+ function saveGlobal() {
965
+ config.pollution = [];
1055
966
 
1056
- if ( typeof document === "undefined" || document.readyState === "complete" ) {
1057
- config.autorun = true;
967
+ if ( config.noglobals ) {
968
+ for ( var key in window ) {
969
+ if ( hasOwn.call( window, key ) ) {
970
+ // in Opera sometimes DOM element ids show up here, ignore them
971
+ if ( /^qunit-test-output/.test( key ) ) {
972
+ continue;
973
+ }
974
+ config.pollution.push( key );
975
+ }
976
+ }
977
+ }
1058
978
  }
1059
979
 
1060
- QUnit.load = function() {
1061
- runLoggingCallbacks( "begin", QUnit, {} );
980
+ function checkPollution() {
981
+ var newGlobals,
982
+ deletedGlobals,
983
+ old = config.pollution;
1062
984
 
1063
- // Initialize the config, saving the execution queue
1064
- var banner, filter, i, label, len, main, ol, toolbar, userAgent, val,
1065
- urlConfigCheckboxesContainer, urlConfigCheckboxes, moduleFilter,
1066
- numModules = 0,
1067
- moduleNames = [],
1068
- moduleFilterHtml = "",
1069
- urlConfigHtml = "",
1070
- oldconfig = extend( {}, config );
985
+ saveGlobal();
1071
986
 
1072
- QUnit.init();
1073
- extend(config, oldconfig);
987
+ newGlobals = diff( config.pollution, old );
988
+ if ( newGlobals.length > 0 ) {
989
+ QUnit.pushFailure( "Introduced global variable(s): " + newGlobals.join(", ") );
990
+ }
1074
991
 
1075
- config.blocking = false;
992
+ deletedGlobals = diff( old, config.pollution );
993
+ if ( deletedGlobals.length > 0 ) {
994
+ QUnit.pushFailure( "Deleted global variable(s): " + deletedGlobals.join(", ") );
995
+ }
996
+ }
1076
997
 
1077
- len = config.urlConfig.length;
998
+ // returns a new Array with the elements that are in a but not in b
999
+ function diff( a, b ) {
1000
+ var i, j,
1001
+ result = a.slice();
1078
1002
 
1079
- for ( i = 0; i < len; i++ ) {
1080
- val = config.urlConfig[i];
1081
- if ( typeof val === "string" ) {
1082
- val = {
1083
- id: val,
1084
- label: val,
1085
- tooltip: "[no tooltip available]"
1086
- };
1003
+ for ( i = 0; i < result.length; i++ ) {
1004
+ for ( j = 0; j < b.length; j++ ) {
1005
+ if ( result[i] === b[j] ) {
1006
+ result.splice( i, 1 );
1007
+ i--;
1008
+ break;
1009
+ }
1087
1010
  }
1088
- config[ val.id ] = QUnit.urlParams[ val.id ];
1089
- urlConfigHtml += "<input id='qunit-urlconfig-" + escapeText( val.id ) +
1090
- "' name='" + escapeText( val.id ) +
1091
- "' type='checkbox'" + ( config[ val.id ] ? " checked='checked'" : "" ) +
1092
- " title='" + escapeText( val.tooltip ) +
1093
- "'><label for='qunit-urlconfig-" + escapeText( val.id ) +
1094
- "' title='" + escapeText( val.tooltip ) + "'>" + val.label + "</label>";
1095
1011
  }
1096
- for ( i in config.modules ) {
1097
- if ( config.modules.hasOwnProperty( i ) ) {
1098
- moduleNames.push(i);
1012
+ return result;
1013
+ }
1014
+
1015
+ function extend( a, b ) {
1016
+ for ( var prop in b ) {
1017
+ if ( hasOwn.call( b, prop ) ) {
1018
+ // Avoid "Member not found" error in IE8 caused by messing with window.constructor
1019
+ if ( !( prop === "constructor" && a === window ) ) {
1020
+ if ( b[ prop ] === undefined ) {
1021
+ delete a[ prop ];
1022
+ } else {
1023
+ a[ prop ] = b[ prop ];
1024
+ }
1025
+ }
1099
1026
  }
1100
1027
  }
1101
- numModules = moduleNames.length;
1102
- moduleNames.sort( function( a, b ) {
1103
- return a.localeCompare( b );
1104
- });
1105
- moduleFilterHtml += "<label for='qunit-modulefilter'>Module: </label><select id='qunit-modulefilter' name='modulefilter'><option value='' " +
1106
- ( config.module === undefined ? "selected='selected'" : "" ) +
1107
- ">< All Modules ></option>";
1108
1028
 
1029
+ return a;
1030
+ }
1109
1031
 
1110
- for ( i = 0; i < numModules; i++) {
1111
- moduleFilterHtml += "<option value='" + escapeText( encodeURIComponent(moduleNames[i]) ) + "' " +
1112
- ( config.module === moduleNames[i] ? "selected='selected'" : "" ) +
1113
- ">" + escapeText(moduleNames[i]) + "</option>";
1114
- }
1115
- moduleFilterHtml += "</select>";
1032
+ /**
1033
+ * @param {HTMLElement} elem
1034
+ * @param {string} type
1035
+ * @param {Function} fn
1036
+ */
1037
+ function addEvent( elem, type, fn ) {
1038
+ if ( elem.addEventListener ) {
1116
1039
 
1117
- // `userAgent` initialized at top of scope
1118
- userAgent = id( "qunit-userAgent" );
1119
- if ( userAgent ) {
1120
- userAgent.innerHTML = navigator.userAgent;
1121
- }
1040
+ // Standards-based browsers
1041
+ elem.addEventListener( type, fn, false );
1042
+ } else if ( elem.attachEvent ) {
1122
1043
 
1123
- // `banner` initialized at top of scope
1124
- banner = id( "qunit-header" );
1125
- if ( banner ) {
1126
- banner.innerHTML = "<a href='" + QUnit.url({ filter: undefined, module: undefined, testNumber: undefined }) + "'>" + banner.innerHTML + "</a> ";
1127
- }
1044
+ // support: IE <9
1045
+ elem.attachEvent( "on" + type, fn );
1046
+ } else {
1128
1047
 
1129
- // `toolbar` initialized at top of scope
1130
- toolbar = id( "qunit-testrunner-toolbar" );
1131
- if ( toolbar ) {
1132
- // `filter` initialized at top of scope
1133
- filter = document.createElement( "input" );
1134
- filter.type = "checkbox";
1135
- filter.id = "qunit-filter-pass";
1048
+ // Caller must ensure support for event listeners is present
1049
+ throw new Error( "addEvent() was called in a context without event listener support" );
1050
+ }
1051
+ }
1136
1052
 
1137
- addEvent( filter, "click", function() {
1138
- var tmp,
1139
- ol = document.getElementById( "qunit-tests" );
1053
+ /**
1054
+ * @param {Array|NodeList} elems
1055
+ * @param {string} type
1056
+ * @param {Function} fn
1057
+ */
1058
+ function addEvents( elems, type, fn ) {
1059
+ var i = elems.length;
1060
+ while ( i-- ) {
1061
+ addEvent( elems[i], type, fn );
1062
+ }
1063
+ }
1140
1064
 
1141
- if ( filter.checked ) {
1142
- ol.className = ol.className + " hidepass";
1143
- } else {
1144
- tmp = " " + ol.className.replace( /[\n\t\r]/g, " " ) + " ";
1145
- ol.className = tmp.replace( / hidepass /, " " );
1146
- }
1147
- if ( defined.sessionStorage ) {
1148
- if (filter.checked) {
1149
- sessionStorage.setItem( "qunit-filter-passed-tests", "true" );
1150
- } else {
1151
- sessionStorage.removeItem( "qunit-filter-passed-tests" );
1152
- }
1153
- }
1154
- });
1065
+ function hasClass( elem, name ) {
1066
+ return (" " + elem.className + " ").indexOf(" " + name + " ") > -1;
1067
+ }
1155
1068
 
1156
- if ( config.hidepassed || defined.sessionStorage && sessionStorage.getItem( "qunit-filter-passed-tests" ) ) {
1157
- filter.checked = true;
1158
- // `ol` initialized at top of scope
1159
- ol = document.getElementById( "qunit-tests" );
1160
- ol.className = ol.className + " hidepass";
1161
- }
1162
- toolbar.appendChild( filter );
1069
+ function addClass( elem, name ) {
1070
+ if ( !hasClass( elem, name ) ) {
1071
+ elem.className += (elem.className ? " " : "") + name;
1072
+ }
1073
+ }
1163
1074
 
1164
- // `label` initialized at top of scope
1165
- label = document.createElement( "label" );
1166
- label.setAttribute( "for", "qunit-filter-pass" );
1167
- label.setAttribute( "title", "Only show tests and assertions that fail. Stored in sessionStorage." );
1168
- label.innerHTML = "Hide passed tests";
1169
- toolbar.appendChild( label );
1075
+ function removeClass( elem, name ) {
1076
+ var set = " " + elem.className + " ";
1077
+ // Class name may appear multiple times
1078
+ while ( set.indexOf(" " + name + " ") > -1 ) {
1079
+ set = set.replace(" " + name + " " , " ");
1080
+ }
1081
+ // If possible, trim it for prettiness, but not necessarily
1082
+ elem.className = typeof set.trim === "function" ? set.trim() : set.replace(/^\s+|\s+$/g, "");
1083
+ }
1170
1084
 
1171
- urlConfigCheckboxesContainer = document.createElement("span");
1172
- urlConfigCheckboxesContainer.innerHTML = urlConfigHtml;
1173
- urlConfigCheckboxes = urlConfigCheckboxesContainer.getElementsByTagName("input");
1174
- // For oldIE support:
1175
- // * Add handlers to the individual elements instead of the container
1176
- // * Use "click" instead of "change"
1177
- // * Fallback from event.target to event.srcElement
1178
- addEvents( urlConfigCheckboxes, "click", function( event ) {
1179
- var params = {},
1180
- target = event.target || event.srcElement;
1181
- params[ target.name ] = target.checked ? true : undefined;
1182
- window.location = QUnit.url( params );
1183
- });
1184
- toolbar.appendChild( urlConfigCheckboxesContainer );
1085
+ function id( name ) {
1086
+ return defined.document && document.getElementById && document.getElementById( name );
1087
+ }
1185
1088
 
1186
- if (numModules > 1) {
1187
- moduleFilter = document.createElement( "span" );
1188
- moduleFilter.setAttribute( "id", "qunit-modulefilter-container" );
1189
- moduleFilter.innerHTML = moduleFilterHtml;
1190
- addEvent( moduleFilter.lastChild, "change", function() {
1191
- var selectBox = moduleFilter.getElementsByTagName("select")[0],
1192
- selectedModule = decodeURIComponent(selectBox.options[selectBox.selectedIndex].value);
1089
+ function registerLoggingCallback( key ) {
1090
+ return function( callback ) {
1091
+ config[key].push( callback );
1092
+ };
1093
+ }
1193
1094
 
1194
- window.location = QUnit.url({
1195
- module: ( selectedModule === "" ) ? undefined : selectedModule,
1196
- // Remove any existing filters
1197
- filter: undefined,
1198
- testNumber: undefined
1199
- });
1200
- });
1201
- toolbar.appendChild(moduleFilter);
1095
+ // Supports deprecated method of completely overwriting logging callbacks
1096
+ function runLoggingCallbacks( key, scope, args ) {
1097
+ var i, callbacks;
1098
+ if ( QUnit.hasOwnProperty( key ) ) {
1099
+ QUnit[ key ].call(scope, args );
1100
+ } else {
1101
+ callbacks = config[ key ];
1102
+ for ( i = 0; i < callbacks.length; i++ ) {
1103
+ callbacks[ i ].call( scope, args );
1202
1104
  }
1203
1105
  }
1106
+ }
1204
1107
 
1205
- // `main` initialized at top of scope
1206
- main = id( "qunit-fixture" );
1207
- if ( main ) {
1208
- config.fixture = main.innerHTML;
1108
+ // from jquery.js
1109
+ function inArray( elem, array ) {
1110
+ if ( array.indexOf ) {
1111
+ return array.indexOf( elem );
1209
1112
  }
1210
1113
 
1211
- if ( config.autostart ) {
1212
- QUnit.start();
1114
+ for ( var i = 0, length = array.length; i < length; i++ ) {
1115
+ if ( array[ i ] === elem ) {
1116
+ return i;
1117
+ }
1213
1118
  }
1214
- };
1215
1119
 
1216
- addEvent( window, "load", QUnit.load );
1120
+ return -1;
1121
+ }
1122
+
1123
+ function Test( settings ) {
1124
+ extend( this, settings );
1125
+ this.assertions = [];
1126
+ this.testNumber = ++Test.count;
1127
+ }
1217
1128
 
1218
- // `onErrorFnPrev` initialized at top of scope
1219
- // Preserve other handlers
1220
- onErrorFnPrev = window.onerror;
1129
+ Test.count = 0;
1221
1130
 
1222
- // Cover uncaught exceptions
1223
- // Returning true will suppress the default browser handler,
1224
- // returning false will let it run.
1225
- window.onerror = function ( error, filePath, linerNr ) {
1226
- var ret = false;
1227
- if ( onErrorFnPrev ) {
1228
- ret = onErrorFnPrev( error, filePath, linerNr );
1229
- }
1131
+ Test.prototype = {
1132
+ init: function() {
1133
+ var a, b, li,
1134
+ tests = id( "qunit-tests" );
1230
1135
 
1231
- // Treat return value as window.onerror itself does,
1232
- // Only do our handling if not suppressed.
1233
- if ( ret !== true ) {
1234
- if ( QUnit.config.current ) {
1235
- if ( QUnit.config.current.ignoreGlobalErrors ) {
1236
- return true;
1136
+ if ( tests ) {
1137
+ b = document.createElement( "strong" );
1138
+ b.innerHTML = this.nameHtml;
1139
+
1140
+ // `a` initialized at top of scope
1141
+ a = document.createElement( "a" );
1142
+ a.innerHTML = "Rerun";
1143
+ a.href = QUnit.url({ testNumber: this.testNumber });
1144
+
1145
+ li = document.createElement( "li" );
1146
+ li.appendChild( b );
1147
+ li.appendChild( a );
1148
+ li.className = "running";
1149
+ li.id = this.id = "qunit-test-output" + testId++;
1150
+
1151
+ tests.appendChild( li );
1152
+ }
1153
+ },
1154
+ setup: function() {
1155
+ if (
1156
+ // Emit moduleStart when we're switching from one module to another
1157
+ this.module !== config.previousModule ||
1158
+ // They could be equal (both undefined) but if the previousModule property doesn't
1159
+ // yet exist it means this is the first test in a suite that isn't wrapped in a
1160
+ // module, in which case we'll just emit a moduleStart event for 'undefined'.
1161
+ // Without this, reporters can get testStart before moduleStart which is a problem.
1162
+ !hasOwn.call( config, "previousModule" )
1163
+ ) {
1164
+ if ( hasOwn.call( config, "previousModule" ) ) {
1165
+ runLoggingCallbacks( "moduleDone", QUnit, {
1166
+ name: config.previousModule,
1167
+ failed: config.moduleStats.bad,
1168
+ passed: config.moduleStats.all - config.moduleStats.bad,
1169
+ total: config.moduleStats.all
1170
+ });
1237
1171
  }
1238
- QUnit.pushFailure( error, filePath + ":" + linerNr );
1239
- } else {
1240
- QUnit.test( "global failure", extend( function() {
1241
- QUnit.pushFailure( error, filePath + ":" + linerNr );
1242
- }, { validTest: validTest } ) );
1172
+ config.previousModule = this.module;
1173
+ config.moduleStats = { all: 0, bad: 0 };
1174
+ runLoggingCallbacks( "moduleStart", QUnit, {
1175
+ name: this.module
1176
+ });
1243
1177
  }
1244
- return false;
1245
- }
1246
1178
 
1247
- return ret;
1248
- };
1179
+ config.current = this;
1249
1180
 
1250
- function done() {
1251
- config.autorun = true;
1181
+ this.testEnvironment = extend({
1182
+ setup: function() {},
1183
+ teardown: function() {}
1184
+ }, this.moduleTestEnvironment );
1252
1185
 
1253
- // Log the last module results
1254
- if ( config.currentModule ) {
1255
- runLoggingCallbacks( "moduleDone", QUnit, {
1256
- name: config.currentModule,
1257
- failed: config.moduleStats.bad,
1258
- passed: config.moduleStats.all - config.moduleStats.bad,
1259
- total: config.moduleStats.all
1186
+ this.started = +new Date();
1187
+ runLoggingCallbacks( "testStart", QUnit, {
1188
+ name: this.testName,
1189
+ module: this.module
1260
1190
  });
1261
- }
1262
- delete config.previousModule;
1263
1191
 
1264
- var i, key,
1265
- banner = id( "qunit-banner" ),
1266
- tests = id( "qunit-tests" ),
1267
- runtime = +new Date() - config.started,
1268
- passed = config.stats.all - config.stats.bad,
1269
- html = [
1270
- "Tests completed in ",
1271
- runtime,
1272
- " milliseconds.<br/>",
1273
- "<span class='passed'>",
1274
- passed,
1275
- "</span> assertions of <span class='total'>",
1276
- config.stats.all,
1277
- "</span> passed, <span class='failed'>",
1278
- config.stats.bad,
1279
- "</span> failed."
1280
- ].join( "" );
1192
+ /*jshint camelcase:false */
1281
1193
 
1282
- if ( banner ) {
1283
- banner.className = ( config.stats.bad ? "qunit-fail" : "qunit-pass" );
1284
- }
1285
1194
 
1286
- if ( tests ) {
1287
- id( "qunit-testresult" ).innerHTML = html;
1288
- }
1195
+ /**
1196
+ * Expose the current test environment.
1197
+ *
1198
+ * @deprecated since 1.12.0: Use QUnit.config.current.testEnvironment instead.
1199
+ */
1200
+ QUnit.current_testEnvironment = this.testEnvironment;
1289
1201
 
1290
- if ( config.altertitle && typeof document !== "undefined" && document.title ) {
1291
- // show ✖ for good, ✔ for bad suite result in title
1292
- // use escape sequences in case file gets loaded with non-utf-8-charset
1293
- document.title = [
1294
- ( config.stats.bad ? "\u2716" : "\u2714" ),
1295
- document.title.replace( /^[\u2714\u2716] /i, "" )
1296
- ].join( " " );
1297
- }
1202
+ /*jshint camelcase:true */
1298
1203
 
1299
- // clear own sessionStorage items if all tests passed
1300
- if ( config.reorder && defined.sessionStorage && config.stats.bad === 0 ) {
1301
- // `key` & `i` initialized at top of scope
1302
- for ( i = 0; i < sessionStorage.length; i++ ) {
1303
- key = sessionStorage.key( i++ );
1304
- if ( key.indexOf( "qunit-test-" ) === 0 ) {
1305
- sessionStorage.removeItem( key );
1306
- }
1204
+ if ( !config.pollution ) {
1205
+ saveGlobal();
1307
1206
  }
1308
- }
1207
+ if ( config.notrycatch ) {
1208
+ this.testEnvironment.setup.call( this.testEnvironment, QUnit.assert );
1209
+ return;
1210
+ }
1211
+ try {
1212
+ this.testEnvironment.setup.call( this.testEnvironment, QUnit.assert );
1213
+ } catch( e ) {
1214
+ QUnit.pushFailure( "Setup failed on " + this.testName + ": " + ( e.message || e ), extractStacktrace( e, 1 ) );
1215
+ }
1216
+ },
1217
+ run: function() {
1218
+ config.current = this;
1309
1219
 
1310
- // scroll back to top to show results
1311
- if ( window.scrollTo ) {
1312
- window.scrollTo(0, 0);
1313
- }
1220
+ var running = id( "qunit-testresult" );
1314
1221
 
1315
- runLoggingCallbacks( "done", QUnit, {
1316
- failed: config.stats.bad,
1317
- passed: passed,
1318
- total: config.stats.all,
1319
- runtime: runtime
1320
- });
1321
- }
1222
+ if ( running ) {
1223
+ running.innerHTML = "Running: <br/>" + this.nameHtml;
1224
+ }
1322
1225
 
1323
- /** @return Boolean: true if this test should be ran */
1324
- function validTest( test ) {
1325
- var include,
1326
- filter = config.filter && config.filter.toLowerCase(),
1327
- module = config.module && config.module.toLowerCase(),
1328
- fullName = (test.module + ": " + test.testName).toLowerCase();
1226
+ if ( this.async ) {
1227
+ QUnit.stop();
1228
+ }
1329
1229
 
1330
- // Internally-generated tests are always valid
1331
- if ( test.callback && test.callback.validTest === validTest ) {
1332
- delete test.callback.validTest;
1333
- return true;
1334
- }
1230
+ this.callbackStarted = +new Date();
1335
1231
 
1336
- if ( config.testNumber ) {
1337
- return test.testNumber === config.testNumber;
1338
- }
1232
+ if ( config.notrycatch ) {
1233
+ this.callback.call( this.testEnvironment, QUnit.assert );
1234
+ this.callbackRuntime = +new Date() - this.callbackStarted;
1235
+ return;
1236
+ }
1339
1237
 
1340
- if ( module && ( !test.module || test.module.toLowerCase() !== module ) ) {
1341
- return false;
1342
- }
1238
+ try {
1239
+ this.callback.call( this.testEnvironment, QUnit.assert );
1240
+ this.callbackRuntime = +new Date() - this.callbackStarted;
1241
+ } catch( e ) {
1242
+ this.callbackRuntime = +new Date() - this.callbackStarted;
1343
1243
 
1344
- if ( !filter ) {
1345
- return true;
1346
- }
1244
+ QUnit.pushFailure( "Died on test #" + (this.assertions.length + 1) + " " + this.stack + ": " + ( e.message || e ), extractStacktrace( e, 0 ) );
1245
+ // else next test will carry the responsibility
1246
+ saveGlobal();
1347
1247
 
1348
- include = filter.charAt( 0 ) !== "!";
1349
- if ( !include ) {
1350
- filter = filter.slice( 1 );
1351
- }
1248
+ // Restart the tests if they're blocking
1249
+ if ( config.blocking ) {
1250
+ QUnit.start();
1251
+ }
1252
+ }
1253
+ },
1254
+ teardown: function() {
1255
+ config.current = this;
1256
+ if ( config.notrycatch ) {
1257
+ if ( typeof this.callbackRuntime === "undefined" ) {
1258
+ this.callbackRuntime = +new Date() - this.callbackStarted;
1259
+ }
1260
+ this.testEnvironment.teardown.call( this.testEnvironment, QUnit.assert );
1261
+ return;
1262
+ } else {
1263
+ try {
1264
+ this.testEnvironment.teardown.call( this.testEnvironment, QUnit.assert );
1265
+ } catch( e ) {
1266
+ QUnit.pushFailure( "Teardown failed on " + this.testName + ": " + ( e.message || e ), extractStacktrace( e, 1 ) );
1267
+ }
1268
+ }
1269
+ checkPollution();
1270
+ },
1271
+ finish: function() {
1272
+ config.current = this;
1273
+ if ( config.requireExpects && this.expected === null ) {
1274
+ QUnit.pushFailure( "Expected number of assertions to be defined, but expect() was not called.", this.stack );
1275
+ } else if ( this.expected !== null && this.expected !== this.assertions.length ) {
1276
+ QUnit.pushFailure( "Expected " + this.expected + " assertions, but " + this.assertions.length + " were run", this.stack );
1277
+ } else if ( this.expected === null && !this.assertions.length ) {
1278
+ QUnit.pushFailure( "Expected at least one assertion, but none were run - call expect(0) to accept zero assertions.", this.stack );
1279
+ }
1352
1280
 
1353
- // If the filter matches, we need to honour include
1354
- if ( fullName.indexOf( filter ) !== -1 ) {
1355
- return include;
1356
- }
1281
+ var i, assertion, a, b, time, li, ol,
1282
+ test = this,
1283
+ good = 0,
1284
+ bad = 0,
1285
+ tests = id( "qunit-tests" );
1357
1286
 
1358
- // Otherwise, do the opposite
1359
- return !include;
1360
- }
1287
+ this.runtime = +new Date() - this.started;
1288
+ config.stats.all += this.assertions.length;
1289
+ config.moduleStats.all += this.assertions.length;
1361
1290
 
1362
- // so far supports only Firefox, Chrome and Opera (buggy), Safari (for real exceptions)
1363
- // Later Safari and IE10 are supposed to support error.stack as well
1364
- // See also https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Error/Stack
1365
- function extractStacktrace( e, offset ) {
1366
- offset = offset === undefined ? 3 : offset;
1291
+ if ( tests ) {
1292
+ ol = document.createElement( "ol" );
1293
+ ol.className = "qunit-assert-list";
1367
1294
 
1368
- var stack, include, i;
1295
+ for ( i = 0; i < this.assertions.length; i++ ) {
1296
+ assertion = this.assertions[i];
1369
1297
 
1370
- if ( e.stacktrace ) {
1371
- // Opera
1372
- return e.stacktrace.split( "\n" )[ offset + 3 ];
1373
- } else if ( e.stack ) {
1374
- // Firefox, Chrome
1375
- stack = e.stack.split( "\n" );
1376
- if (/^error$/i.test( stack[0] ) ) {
1377
- stack.shift();
1378
- }
1379
- if ( fileName ) {
1380
- include = [];
1381
- for ( i = offset; i < stack.length; i++ ) {
1382
- if ( stack[ i ].indexOf( fileName ) !== -1 ) {
1383
- break;
1298
+ li = document.createElement( "li" );
1299
+ li.className = assertion.result ? "pass" : "fail";
1300
+ li.innerHTML = assertion.message || ( assertion.result ? "okay" : "failed" );
1301
+ ol.appendChild( li );
1302
+
1303
+ if ( assertion.result ) {
1304
+ good++;
1305
+ } else {
1306
+ bad++;
1307
+ config.stats.bad++;
1308
+ config.moduleStats.bad++;
1384
1309
  }
1385
- include.push( stack[ i ] );
1386
1310
  }
1387
- if ( include.length ) {
1388
- return include.join( "\n" );
1311
+
1312
+ // store result when possible
1313
+ if ( QUnit.config.reorder && defined.sessionStorage ) {
1314
+ if ( bad ) {
1315
+ sessionStorage.setItem( "qunit-test-" + this.module + "-" + this.testName, bad );
1316
+ } else {
1317
+ sessionStorage.removeItem( "qunit-test-" + this.module + "-" + this.testName );
1318
+ }
1319
+ }
1320
+
1321
+ if ( bad === 0 ) {
1322
+ addClass( ol, "qunit-collapsed" );
1323
+ }
1324
+
1325
+ // `b` initialized at top of scope
1326
+ b = document.createElement( "strong" );
1327
+ b.innerHTML = this.nameHtml + " <b class='counts'>(<b class='failed'>" + bad + "</b>, <b class='passed'>" + good + "</b>, " + this.assertions.length + ")</b>";
1328
+
1329
+ addEvent(b, "click", function() {
1330
+ var next = b.parentNode.lastChild,
1331
+ collapsed = hasClass( next, "qunit-collapsed" );
1332
+ ( collapsed ? removeClass : addClass )( next, "qunit-collapsed" );
1333
+ });
1334
+
1335
+ addEvent(b, "dblclick", function( e ) {
1336
+ var target = e && e.target ? e.target : window.event.srcElement;
1337
+ if ( target.nodeName.toLowerCase() === "span" || target.nodeName.toLowerCase() === "b" ) {
1338
+ target = target.parentNode;
1339
+ }
1340
+ if ( window.location && target.nodeName.toLowerCase() === "strong" ) {
1341
+ window.location = QUnit.url({ testNumber: test.testNumber });
1342
+ }
1343
+ });
1344
+
1345
+ // `time` initialized at top of scope
1346
+ time = document.createElement( "span" );
1347
+ time.className = "runtime";
1348
+ time.innerHTML = this.runtime + " ms";
1349
+
1350
+ // `li` initialized at top of scope
1351
+ li = id( this.id );
1352
+ li.className = bad ? "fail" : "pass";
1353
+ li.removeChild( li.firstChild );
1354
+ a = li.firstChild;
1355
+ li.appendChild( b );
1356
+ li.appendChild( a );
1357
+ li.appendChild( time );
1358
+ li.appendChild( ol );
1359
+
1360
+ } else {
1361
+ for ( i = 0; i < this.assertions.length; i++ ) {
1362
+ if ( !this.assertions[i].result ) {
1363
+ bad++;
1364
+ config.stats.bad++;
1365
+ config.moduleStats.bad++;
1366
+ }
1389
1367
  }
1390
1368
  }
1391
- return stack[ offset ];
1392
- } else if ( e.sourceURL ) {
1393
- // Safari, PhantomJS
1394
- // hopefully one day Safari provides actual stacktraces
1395
- // exclude useless self-reference for generated Error objects
1396
- if ( /qunit.js$/.test( e.sourceURL ) ) {
1397
- return;
1369
+
1370
+ runLoggingCallbacks( "testDone", QUnit, {
1371
+ name: this.testName,
1372
+ module: this.module,
1373
+ failed: bad,
1374
+ passed: this.assertions.length - bad,
1375
+ total: this.assertions.length,
1376
+ runtime: this.runtime,
1377
+ // DEPRECATED: this property will be removed in 2.0.0, use runtime instead
1378
+ duration: this.runtime,
1379
+ });
1380
+
1381
+ QUnit.reset();
1382
+
1383
+ config.current = undefined;
1384
+ },
1385
+
1386
+ queue: function() {
1387
+ var bad,
1388
+ test = this;
1389
+
1390
+ synchronize(function() {
1391
+ test.init();
1392
+ });
1393
+ function run() {
1394
+ // each of these can by async
1395
+ synchronize(function() {
1396
+ test.setup();
1397
+ });
1398
+ synchronize(function() {
1399
+ test.run();
1400
+ });
1401
+ synchronize(function() {
1402
+ test.teardown();
1403
+ });
1404
+ synchronize(function() {
1405
+ test.finish();
1406
+ });
1398
1407
  }
1399
- // for actual exceptions, this is useful
1400
- return e.sourceURL + ":" + e.line;
1401
- }
1402
- }
1403
- function sourceFromStacktrace( offset ) {
1404
- try {
1405
- throw new Error();
1406
- } catch ( e ) {
1407
- return extractStacktrace( e, offset );
1408
- }
1409
- }
1410
1408
 
1411
- /**
1412
- * Escape text for attribute or text content.
1413
- */
1414
- function escapeText( s ) {
1415
- if ( !s ) {
1416
- return "";
1409
+ // `bad` initialized at top of scope
1410
+ // defer when previous test run passed, if storage is available
1411
+ bad = QUnit.config.reorder && defined.sessionStorage &&
1412
+ +sessionStorage.getItem( "qunit-test-" + this.module + "-" + this.testName );
1413
+
1414
+ if ( bad ) {
1415
+ run();
1416
+ } else {
1417
+ synchronize( run, true );
1418
+ }
1417
1419
  }
1418
- s = s + "";
1419
- // Both single quotes and double quotes (for attributes)
1420
- return s.replace( /['"<>&]/g, function( s ) {
1421
- switch( s ) {
1422
- case "'":
1423
- return "&#039;";
1424
- case "\"":
1425
- return "&quot;";
1426
- case "<":
1427
- return "&lt;";
1428
- case ">":
1429
- return "&gt;";
1430
- case "&":
1431
- return "&amp;";
1420
+ };
1421
+
1422
+ // `assert` initialized at top of scope
1423
+ // Assert helpers
1424
+ // All of these must either call QUnit.push() or manually do:
1425
+ // - runLoggingCallbacks( "log", .. );
1426
+ // - config.current.assertions.push({ .. });
1427
+ assert = QUnit.assert = {
1428
+ /**
1429
+ * Asserts rough true-ish result.
1430
+ * @name ok
1431
+ * @function
1432
+ * @example ok( "asdfasdf".length > 5, "There must be at least 5 chars" );
1433
+ */
1434
+ ok: function( result, msg ) {
1435
+ if ( !config.current ) {
1436
+ throw new Error( "ok() assertion outside test context, was " + sourceFromStacktrace(2) );
1432
1437
  }
1433
- });
1434
- }
1438
+ result = !!result;
1439
+ msg = msg || ( result ? "okay" : "failed" );
1435
1440
 
1436
- function synchronize( callback, last ) {
1437
- config.queue.push( callback );
1441
+ var source,
1442
+ details = {
1443
+ module: config.current.module,
1444
+ name: config.current.testName,
1445
+ result: result,
1446
+ message: msg
1447
+ };
1438
1448
 
1439
- if ( config.autorun && !config.blocking ) {
1440
- process( last );
1441
- }
1442
- }
1449
+ msg = "<span class='test-message'>" + escapeText( msg ) + "</span>";
1450
+
1451
+ if ( !result ) {
1452
+ source = sourceFromStacktrace( 2 );
1453
+ if ( source ) {
1454
+ details.source = source;
1455
+ msg += "<table><tr class='test-source'><th>Source: </th><td><pre>" +
1456
+ escapeText( source ) +
1457
+ "</pre></td></tr></table>";
1458
+ }
1459
+ }
1460
+ runLoggingCallbacks( "log", QUnit, details );
1461
+ config.current.assertions.push({
1462
+ result: result,
1463
+ message: msg
1464
+ });
1465
+ },
1466
+
1467
+ /**
1468
+ * Assert that the first two arguments are equal, with an optional message.
1469
+ * Prints out both actual and expected values.
1470
+ * @name equal
1471
+ * @function
1472
+ * @example equal( format( "Received {0} bytes.", 2), "Received 2 bytes.", "format() replaces {0} with next argument" );
1473
+ */
1474
+ equal: function( actual, expected, message ) {
1475
+ /*jshint eqeqeq:false */
1476
+ QUnit.push( expected == actual, actual, expected, message );
1477
+ },
1443
1478
 
1444
- function process( last ) {
1445
- function next() {
1446
- process( last );
1447
- }
1448
- var start = new Date().getTime();
1449
- config.depth = config.depth ? config.depth + 1 : 1;
1479
+ /**
1480
+ * @name notEqual
1481
+ * @function
1482
+ */
1483
+ notEqual: function( actual, expected, message ) {
1484
+ /*jshint eqeqeq:false */
1485
+ QUnit.push( expected != actual, actual, expected, message );
1486
+ },
1450
1487
 
1451
- while ( config.queue.length && !config.blocking ) {
1452
- if ( !defined.setTimeout || config.updateRate <= 0 || ( ( new Date().getTime() - start ) < config.updateRate ) ) {
1453
- config.queue.shift()();
1454
- } else {
1455
- setTimeout( next, 13 );
1456
- break;
1457
- }
1458
- }
1459
- config.depth--;
1460
- if ( last && !config.blocking && !config.queue.length && config.depth === 0 ) {
1461
- done();
1462
- }
1463
- }
1488
+ /**
1489
+ * @name propEqual
1490
+ * @function
1491
+ */
1492
+ propEqual: function( actual, expected, message ) {
1493
+ actual = objectValues(actual);
1494
+ expected = objectValues(expected);
1495
+ QUnit.push( QUnit.equiv(actual, expected), actual, expected, message );
1496
+ },
1464
1497
 
1465
- function saveGlobal() {
1466
- config.pollution = [];
1498
+ /**
1499
+ * @name notPropEqual
1500
+ * @function
1501
+ */
1502
+ notPropEqual: function( actual, expected, message ) {
1503
+ actual = objectValues(actual);
1504
+ expected = objectValues(expected);
1505
+ QUnit.push( !QUnit.equiv(actual, expected), actual, expected, message );
1506
+ },
1467
1507
 
1468
- if ( config.noglobals ) {
1469
- for ( var key in window ) {
1470
- if ( hasOwn.call( window, key ) ) {
1471
- // in Opera sometimes DOM element ids show up here, ignore them
1472
- if ( /^qunit-test-output/.test( key ) ) {
1473
- continue;
1474
- }
1475
- config.pollution.push( key );
1476
- }
1477
- }
1478
- }
1479
- }
1508
+ /**
1509
+ * @name deepEqual
1510
+ * @function
1511
+ */
1512
+ deepEqual: function( actual, expected, message ) {
1513
+ QUnit.push( QUnit.equiv(actual, expected), actual, expected, message );
1514
+ },
1480
1515
 
1481
- function checkPollution() {
1482
- var newGlobals,
1483
- deletedGlobals,
1484
- old = config.pollution;
1516
+ /**
1517
+ * @name notDeepEqual
1518
+ * @function
1519
+ */
1520
+ notDeepEqual: function( actual, expected, message ) {
1521
+ QUnit.push( !QUnit.equiv(actual, expected), actual, expected, message );
1522
+ },
1485
1523
 
1486
- saveGlobal();
1524
+ /**
1525
+ * @name strictEqual
1526
+ * @function
1527
+ */
1528
+ strictEqual: function( actual, expected, message ) {
1529
+ QUnit.push( expected === actual, actual, expected, message );
1530
+ },
1487
1531
 
1488
- newGlobals = diff( config.pollution, old );
1489
- if ( newGlobals.length > 0 ) {
1490
- QUnit.pushFailure( "Introduced global variable(s): " + newGlobals.join(", ") );
1491
- }
1532
+ /**
1533
+ * @name notStrictEqual
1534
+ * @function
1535
+ */
1536
+ notStrictEqual: function( actual, expected, message ) {
1537
+ QUnit.push( expected !== actual, actual, expected, message );
1538
+ },
1492
1539
 
1493
- deletedGlobals = diff( old, config.pollution );
1494
- if ( deletedGlobals.length > 0 ) {
1495
- QUnit.pushFailure( "Deleted global variable(s): " + deletedGlobals.join(", ") );
1496
- }
1497
- }
1540
+ "throws": function( block, expected, message ) {
1541
+ var actual,
1542
+ expectedOutput = expected,
1543
+ ok = false;
1498
1544
 
1499
- // returns a new Array with the elements that are in a but not in b
1500
- function diff( a, b ) {
1501
- var i, j,
1502
- result = a.slice();
1545
+ // 'expected' is optional
1546
+ if ( typeof expected === "string" ) {
1547
+ message = expected;
1548
+ expected = null;
1549
+ }
1503
1550
 
1504
- for ( i = 0; i < result.length; i++ ) {
1505
- for ( j = 0; j < b.length; j++ ) {
1506
- if ( result[i] === b[j] ) {
1507
- result.splice( i, 1 );
1508
- i--;
1509
- break;
1510
- }
1551
+ config.current.ignoreGlobalErrors = true;
1552
+ try {
1553
+ block.call( config.current.testEnvironment );
1554
+ } catch (e) {
1555
+ actual = e;
1511
1556
  }
1512
- }
1513
- return result;
1514
- }
1557
+ config.current.ignoreGlobalErrors = false;
1515
1558
 
1516
- function extend( a, b ) {
1517
- for ( var prop in b ) {
1518
- if ( hasOwn.call( b, prop ) ) {
1519
- // Avoid "Member not found" error in IE8 caused by messing with window.constructor
1520
- if ( !( prop === "constructor" && a === window ) ) {
1521
- if ( b[ prop ] === undefined ) {
1522
- delete a[ prop ];
1523
- } else {
1524
- a[ prop ] = b[ prop ];
1525
- }
1559
+ if ( actual ) {
1560
+ // we don't want to validate thrown error
1561
+ if ( !expected ) {
1562
+ ok = true;
1563
+ expectedOutput = null;
1564
+ // expected is a regexp
1565
+ } else if ( QUnit.objectType( expected ) === "regexp" ) {
1566
+ ok = expected.test( errorString( actual ) );
1567
+ // expected is a constructor
1568
+ } else if ( actual instanceof expected ) {
1569
+ ok = true;
1570
+ // expected is a validation function which returns true is validation passed
1571
+ } else if ( expected.call( {}, actual ) === true ) {
1572
+ expectedOutput = null;
1573
+ ok = true;
1526
1574
  }
1575
+
1576
+ QUnit.push( ok, actual, expectedOutput, message );
1577
+ } else {
1578
+ QUnit.pushFailure( message, null, "No exception was thrown." );
1527
1579
  }
1528
1580
  }
1529
-
1530
- return a;
1531
- }
1581
+ };
1532
1582
 
1533
1583
  /**
1534
- * @param {HTMLElement} elem
1535
- * @param {string} type
1536
- * @param {Function} fn
1584
+ * @deprecated since 1.8.0
1585
+ * Kept assertion helpers in root for backwards compatibility.
1537
1586
  */
1538
- function addEvent( elem, type, fn ) {
1539
- // Standards-based browsers
1540
- if ( elem.addEventListener ) {
1541
- elem.addEventListener( type, fn, false );
1542
- // IE
1543
- } else {
1544
- elem.attachEvent( "on" + type, fn );
1545
- }
1546
- }
1587
+ extend( QUnit.constructor.prototype, assert );
1547
1588
 
1548
1589
  /**
1549
- * @param {Array|NodeList} elems
1550
- * @param {string} type
1551
- * @param {Function} fn
1590
+ * @deprecated since 1.9.0
1591
+ * Kept to avoid TypeErrors for undefined methods.
1552
1592
  */
1553
- function addEvents( elems, type, fn ) {
1554
- var i = elems.length;
1555
- while ( i-- ) {
1556
- addEvent( elems[i], type, fn );
1557
- }
1558
- }
1559
-
1560
- function hasClass( elem, name ) {
1561
- return (" " + elem.className + " ").indexOf(" " + name + " ") > -1;
1562
- }
1563
-
1564
- function addClass( elem, name ) {
1565
- if ( !hasClass( elem, name ) ) {
1566
- elem.className += (elem.className ? " " : "") + name;
1567
- }
1568
- }
1569
-
1570
- function removeClass( elem, name ) {
1571
- var set = " " + elem.className + " ";
1572
- // Class name may appear multiple times
1573
- while ( set.indexOf(" " + name + " ") > -1 ) {
1574
- set = set.replace(" " + name + " " , " ");
1575
- }
1576
- // If possible, trim it for prettiness, but not necessarily
1577
- elem.className = typeof set.trim === "function" ? set.trim() : set.replace(/^\s+|\s+$/g, "");
1578
- }
1579
-
1580
- function id( name ) {
1581
- return !!( typeof document !== "undefined" && document && document.getElementById ) &&
1582
- document.getElementById( name );
1583
- }
1584
-
1585
- function registerLoggingCallback( key ) {
1586
- return function( callback ) {
1587
- config[key].push( callback );
1588
- };
1589
- }
1593
+ QUnit.constructor.prototype.raises = function() {
1594
+ QUnit.push( false, false, false, "QUnit.raises has been deprecated since 2012 (fad3c1ea), use QUnit.throws instead" );
1595
+ };
1590
1596
 
1591
- // Supports deprecated method of completely overwriting logging callbacks
1592
- function runLoggingCallbacks( key, scope, args ) {
1593
- var i, callbacks;
1594
- if ( QUnit.hasOwnProperty( key ) ) {
1595
- QUnit[ key ].call(scope, args );
1596
- } else {
1597
- callbacks = config[ key ];
1598
- for ( i = 0; i < callbacks.length; i++ ) {
1599
- callbacks[ i ].call( scope, args );
1600
- }
1601
- }
1602
- }
1597
+ /**
1598
+ * @deprecated since 1.0.0, replaced with error pushes since 1.3.0
1599
+ * Kept to avoid TypeErrors for undefined methods.
1600
+ */
1601
+ QUnit.constructor.prototype.equals = function() {
1602
+ QUnit.push( false, false, false, "QUnit.equals has been deprecated since 2009 (e88049a0), use QUnit.equal instead" );
1603
+ };
1604
+ QUnit.constructor.prototype.same = function() {
1605
+ QUnit.push( false, false, false, "QUnit.same has been deprecated since 2009 (e88049a0), use QUnit.deepEqual instead" );
1606
+ };
1603
1607
 
1604
1608
  // Test for equality any JavaScript type.
1605
1609
  // Author: Philippe Rathé <prathe@gmail.com>
@@ -2040,21 +2044,6 @@ QUnit.jsDump = (function() {
2040
2044
  return jsDump;
2041
2045
  }());
2042
2046
 
2043
- // from jquery.js
2044
- function inArray( elem, array ) {
2045
- if ( array.indexOf ) {
2046
- return array.indexOf( elem );
2047
- }
2048
-
2049
- for ( var i = 0, length = array.length; i < length; i++ ) {
2050
- if ( array[ i ] === elem ) {
2051
- return i;
2052
- }
2053
- }
2054
-
2055
- return -1;
2056
- }
2057
-
2058
2047
  /*
2059
2048
  * Javascript Diff Algorithm
2060
2049
  * By John Resig (http://ejohn.org/)
@@ -2203,10 +2192,19 @@ QUnit.diff = (function() {
2203
2192
  };
2204
2193
  }());
2205
2194
 
2206
- // for CommonJS environments, export everything
2207
- if ( typeof exports !== "undefined" ) {
2208
- extend( exports, QUnit.constructor.prototype );
2195
+ // For browser, export only select globals
2196
+ if ( typeof window !== "undefined" ) {
2197
+ extend( window, QUnit.constructor.prototype );
2198
+ window.QUnit = QUnit;
2199
+ }
2200
+
2201
+ // For CommonJS environments, export everything
2202
+ if ( typeof module !== "undefined" && module.exports ) {
2203
+ module.exports = QUnit;
2209
2204
  }
2210
2205
 
2211
- // get at whatever the global object is, like window in browsers
2212
- }( (function() {return this;}.call()) ));
2206
+
2207
+ // Get a reference to the global object, like window in browsers
2208
+ }( (function() {
2209
+ return this;
2210
+ })() ));