qunit-rails 0.0.4 → 0.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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
+ })() ));