qunit-rails 0.0.2 → 0.0.3

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,35 +1,87 @@
1
1
  /**
2
- * QUnit v1.9.0 - A JavaScript Unit Testing Framework
2
+ * QUnit v1.11.0 - A JavaScript Unit Testing Framework
3
3
  *
4
- * http://docs.jquery.com/QUnit
4
+ * http://qunitjs.com
5
5
  *
6
- * Copyright (c) 2012 John Resig, Jörn Zaefferer
7
- * Dual licensed under the MIT (MIT-LICENSE.txt)
8
- * or GPL (GPL-LICENSE.txt) licenses.
6
+ * Copyright 2012 jQuery Foundation and other contributors
7
+ * Released under the MIT license.
8
+ * http://jquery.org/license
9
9
  */
10
10
 
11
11
  (function( window ) {
12
12
 
13
13
  var QUnit,
14
+ assert,
14
15
  config,
15
16
  onErrorFnPrev,
16
17
  testId = 0,
17
18
  fileName = (sourceFromStacktrace( 0 ) || "" ).replace(/(:\d+)+\)?/, "").replace(/.+\//, ""),
18
19
  toString = Object.prototype.toString,
19
20
  hasOwn = Object.prototype.hasOwnProperty,
21
+ // Keep a local reference to Date (GH-283)
22
+ Date = window.Date,
20
23
  defined = {
21
- setTimeout: typeof window.setTimeout !== "undefined",
22
- sessionStorage: (function() {
23
- var x = "qunit-test-string";
24
- try {
25
- sessionStorage.setItem( x, x );
26
- sessionStorage.removeItem( x );
27
- return true;
28
- } catch( e ) {
29
- return false;
24
+ setTimeout: typeof window.setTimeout !== "undefined",
25
+ sessionStorage: (function() {
26
+ var x = "qunit-test-string";
27
+ try {
28
+ sessionStorage.setItem( x, x );
29
+ sessionStorage.removeItem( x );
30
+ return true;
31
+ } catch( e ) {
32
+ return false;
33
+ }
34
+ }())
35
+ },
36
+ /**
37
+ * Provides a normalized error string, correcting an issue
38
+ * with IE 7 (and prior) where Error.prototype.toString is
39
+ * not properly implemented
40
+ *
41
+ * Based on http://es5.github.com/#x15.11.4.4
42
+ *
43
+ * @param {String|Error} error
44
+ * @return {String} error message
45
+ */
46
+ errorString = function( error ) {
47
+ var name, message,
48
+ errorString = error.toString();
49
+ if ( errorString.substring( 0, 7 ) === "[object" ) {
50
+ name = error.name ? error.name.toString() : "Error";
51
+ message = error.message ? error.message.toString() : "";
52
+ if ( name && message ) {
53
+ return name + ": " + message;
54
+ } else if ( name ) {
55
+ return name;
56
+ } else if ( message ) {
57
+ return message;
58
+ } else {
59
+ return "Error";
60
+ }
61
+ } else {
62
+ return errorString;
30
63
  }
31
- }())
32
- };
64
+ },
65
+ /**
66
+ * Makes a clone of an object using only Array or Object as base,
67
+ * and copies over the own enumerable properties.
68
+ *
69
+ * @param {Object} obj
70
+ * @return {Object} New object with only the own properties (recursively).
71
+ */
72
+ objectValues = function( obj ) {
73
+ // Grunt 0.3.x uses an older version of jshint that still has jshint/jshint#392.
74
+ /*jshint newcap: false */
75
+ var key, val,
76
+ vals = QUnit.is( "array", obj ) ? [] : {};
77
+ for ( key in obj ) {
78
+ if ( hasOwn.call( obj, key ) ) {
79
+ val = obj[key];
80
+ vals[key] = val === Object(val) ? objectValues(val) : val;
81
+ }
82
+ }
83
+ return vals;
84
+ };
33
85
 
34
86
  function Test( settings ) {
35
87
  extend( this, settings );
@@ -42,11 +94,11 @@ Test.count = 0;
42
94
  Test.prototype = {
43
95
  init: function() {
44
96
  var a, b, li,
45
- tests = id( "qunit-tests" );
97
+ tests = id( "qunit-tests" );
46
98
 
47
99
  if ( tests ) {
48
100
  b = document.createElement( "strong" );
49
- b.innerHTML = this.name;
101
+ b.innerHTML = this.nameHtml;
50
102
 
51
103
  // `a` initialized at top of scope
52
104
  a = document.createElement( "a" );
@@ -90,6 +142,7 @@ Test.prototype = {
90
142
  teardown: function() {}
91
143
  }, this.moduleTestEnvironment );
92
144
 
145
+ this.started = +new Date();
93
146
  runLoggingCallbacks( "testStart", QUnit, {
94
147
  name: this.testName,
95
148
  module: this.module
@@ -109,7 +162,7 @@ Test.prototype = {
109
162
  try {
110
163
  this.testEnvironment.setup.call( this.testEnvironment );
111
164
  } catch( e ) {
112
- QUnit.pushFailure( "Setup failed on " + this.testName + ": " + e.message, extractStacktrace( e, 1 ) );
165
+ QUnit.pushFailure( "Setup failed on " + this.testName + ": " + ( e.message || e ), extractStacktrace( e, 1 ) );
113
166
  }
114
167
  },
115
168
  run: function() {
@@ -118,22 +171,28 @@ Test.prototype = {
118
171
  var running = id( "qunit-testresult" );
119
172
 
120
173
  if ( running ) {
121
- running.innerHTML = "Running: <br/>" + this.name;
174
+ running.innerHTML = "Running: <br/>" + this.nameHtml;
122
175
  }
123
176
 
124
177
  if ( this.async ) {
125
178
  QUnit.stop();
126
179
  }
127
180
 
181
+ this.callbackStarted = +new Date();
182
+
128
183
  if ( config.notrycatch ) {
129
184
  this.callback.call( this.testEnvironment, QUnit.assert );
185
+ this.callbackRuntime = +new Date() - this.callbackStarted;
130
186
  return;
131
187
  }
132
188
 
133
189
  try {
134
190
  this.callback.call( this.testEnvironment, QUnit.assert );
191
+ this.callbackRuntime = +new Date() - this.callbackStarted;
135
192
  } catch( e ) {
136
- QUnit.pushFailure( "Died on test #" + (this.assertions.length + 1) + " " + this.stack + ": " + e.message, extractStacktrace( e, 0 ) );
193
+ this.callbackRuntime = +new Date() - this.callbackStarted;
194
+
195
+ QUnit.pushFailure( "Died on test #" + (this.assertions.length + 1) + " " + this.stack + ": " + ( e.message || e ), extractStacktrace( e, 0 ) );
137
196
  // else next test will carry the responsibility
138
197
  saveGlobal();
139
198
 
@@ -146,38 +205,43 @@ Test.prototype = {
146
205
  teardown: function() {
147
206
  config.current = this;
148
207
  if ( config.notrycatch ) {
208
+ if ( typeof this.callbackRuntime === "undefined" ) {
209
+ this.callbackRuntime = +new Date() - this.callbackStarted;
210
+ }
149
211
  this.testEnvironment.teardown.call( this.testEnvironment );
150
212
  return;
151
213
  } else {
152
214
  try {
153
215
  this.testEnvironment.teardown.call( this.testEnvironment );
154
216
  } catch( e ) {
155
- QUnit.pushFailure( "Teardown failed on " + this.testName + ": " + e.message, extractStacktrace( e, 1 ) );
217
+ QUnit.pushFailure( "Teardown failed on " + this.testName + ": " + ( e.message || e ), extractStacktrace( e, 1 ) );
156
218
  }
157
219
  }
158
220
  checkPollution();
159
221
  },
160
222
  finish: function() {
161
223
  config.current = this;
162
- if ( config.requireExpects && this.expected == null ) {
224
+ if ( config.requireExpects && this.expected === null ) {
163
225
  QUnit.pushFailure( "Expected number of assertions to be defined, but expect() was not called.", this.stack );
164
- } else if ( this.expected != null && this.expected != this.assertions.length ) {
226
+ } else if ( this.expected !== null && this.expected !== this.assertions.length ) {
165
227
  QUnit.pushFailure( "Expected " + this.expected + " assertions, but " + this.assertions.length + " were run", this.stack );
166
- } else if ( this.expected == null && !this.assertions.length ) {
228
+ } else if ( this.expected === null && !this.assertions.length ) {
167
229
  QUnit.pushFailure( "Expected at least one assertion, but none were run - call expect(0) to accept zero assertions.", this.stack );
168
230
  }
169
231
 
170
- var assertion, a, b, i, li, ol,
232
+ var i, assertion, a, b, time, li, ol,
171
233
  test = this,
172
234
  good = 0,
173
235
  bad = 0,
174
236
  tests = id( "qunit-tests" );
175
237
 
238
+ this.runtime = +new Date() - this.started;
176
239
  config.stats.all += this.assertions.length;
177
240
  config.moduleStats.all += this.assertions.length;
178
241
 
179
242
  if ( tests ) {
180
243
  ol = document.createElement( "ol" );
244
+ ol.className = "qunit-assert-list";
181
245
 
182
246
  for ( i = 0; i < this.assertions.length; i++ ) {
183
247
  assertion = this.assertions[i];
@@ -206,22 +270,22 @@ Test.prototype = {
206
270
  }
207
271
 
208
272
  if ( bad === 0 ) {
209
- ol.style.display = "none";
273
+ addClass( ol, "qunit-collapsed" );
210
274
  }
211
275
 
212
276
  // `b` initialized at top of scope
213
277
  b = document.createElement( "strong" );
214
- b.innerHTML = this.name + " <b class='counts'>(<b class='failed'>" + bad + "</b>, <b class='passed'>" + good + "</b>, " + this.assertions.length + ")</b>";
278
+ b.innerHTML = this.nameHtml + " <b class='counts'>(<b class='failed'>" + bad + "</b>, <b class='passed'>" + good + "</b>, " + this.assertions.length + ")</b>";
215
279
 
216
280
  addEvent(b, "click", function() {
217
- var next = b.nextSibling.nextSibling,
218
- display = next.style.display;
219
- next.style.display = display === "none" ? "block" : "none";
281
+ var next = b.parentNode.lastChild,
282
+ collapsed = hasClass( next, "qunit-collapsed" );
283
+ ( collapsed ? removeClass : addClass )( next, "qunit-collapsed" );
220
284
  });
221
285
 
222
286
  addEvent(b, "dblclick", function( e ) {
223
287
  var target = e && e.target ? e.target : window.event.srcElement;
224
- if ( target.nodeName.toLowerCase() == "span" || target.nodeName.toLowerCase() == "b" ) {
288
+ if ( target.nodeName.toLowerCase() === "span" || target.nodeName.toLowerCase() === "b" ) {
225
289
  target = target.parentNode;
226
290
  }
227
291
  if ( window.location && target.nodeName.toLowerCase() === "strong" ) {
@@ -229,13 +293,19 @@ Test.prototype = {
229
293
  }
230
294
  });
231
295
 
296
+ // `time` initialized at top of scope
297
+ time = document.createElement( "span" );
298
+ time.className = "runtime";
299
+ time.innerHTML = this.runtime + " ms";
300
+
232
301
  // `li` initialized at top of scope
233
302
  li = id( this.id );
234
303
  li.className = bad ? "fail" : "pass";
235
304
  li.removeChild( li.firstChild );
236
305
  a = li.firstChild;
237
306
  li.appendChild( b );
238
- li.appendChild ( a );
307
+ li.appendChild( a );
308
+ li.appendChild( time );
239
309
  li.appendChild( ol );
240
310
 
241
311
  } else {
@@ -253,7 +323,8 @@ Test.prototype = {
253
323
  module: this.module,
254
324
  failed: bad,
255
325
  passed: this.assertions.length - bad,
256
- total: this.assertions.length
326
+ total: this.assertions.length,
327
+ duration: this.runtime
257
328
  });
258
329
 
259
330
  QUnit.reset();
@@ -304,7 +375,8 @@ QUnit = {
304
375
  // call on start of module test to prepend name to all tests
305
376
  module: function( name, testEnvironment ) {
306
377
  config.currentModule = name;
307
- config.currentModuleTestEnviroment = testEnvironment;
378
+ config.currentModuleTestEnvironment = testEnvironment;
379
+ config.modules[name] = true;
308
380
  },
309
381
 
310
382
  asyncTest: function( testName, expected, callback ) {
@@ -318,7 +390,7 @@ QUnit = {
318
390
 
319
391
  test: function( testName, expected, callback, async ) {
320
392
  var test,
321
- name = "<span class='test-name'>" + escapeInnerText( testName ) + "</span>";
393
+ nameHtml = "<span class='test-name'>" + escapeText( testName ) + "</span>";
322
394
 
323
395
  if ( arguments.length === 2 ) {
324
396
  callback = expected;
@@ -326,17 +398,17 @@ QUnit = {
326
398
  }
327
399
 
328
400
  if ( config.currentModule ) {
329
- name = "<span class='module-name'>" + config.currentModule + "</span>: " + name;
401
+ nameHtml = "<span class='module-name'>" + escapeText( config.currentModule ) + "</span>: " + nameHtml;
330
402
  }
331
403
 
332
404
  test = new Test({
333
- name: name,
405
+ nameHtml: nameHtml,
334
406
  testName: testName,
335
407
  expected: expected,
336
408
  async: async,
337
409
  callback: callback,
338
410
  module: config.currentModule,
339
- moduleTestEnvironment: config.currentModuleTestEnviroment,
411
+ moduleTestEnvironment: config.currentModuleTestEnvironment,
340
412
  stack: sourceFromStacktrace( 2 )
341
413
  });
342
414
 
@@ -349,10 +421,26 @@ QUnit = {
349
421
 
350
422
  // Specify the number of expected assertions to gurantee that failed test (no assertions are run at all) don't slip through.
351
423
  expect: function( asserts ) {
352
- config.current.expected = asserts;
424
+ if (arguments.length === 1) {
425
+ config.current.expected = asserts;
426
+ } else {
427
+ return config.current.expected;
428
+ }
353
429
  },
354
430
 
355
431
  start: function( count ) {
432
+ // QUnit hasn't been initialized yet.
433
+ // Note: RequireJS (et al) may delay onLoad
434
+ if ( config.semaphore === undefined ) {
435
+ QUnit.begin(function() {
436
+ // This is triggered at the top of QUnit.load, push start() to the event loop, to allow QUnit.load to finish first
437
+ setTimeout(function() {
438
+ QUnit.start( count );
439
+ });
440
+ });
441
+ return;
442
+ }
443
+
356
444
  config.semaphore -= count || 1;
357
445
  // don't start until equal number of stop-calls
358
446
  if ( config.semaphore > 0 ) {
@@ -361,6 +449,8 @@ QUnit = {
361
449
  // ignore if start is called more often then stop
362
450
  if ( config.semaphore < 0 ) {
363
451
  config.semaphore = 0;
452
+ QUnit.pushFailure( "Called start() while already started (QUnit.config.semaphore was 0 already)", null, sourceFromStacktrace(2) );
453
+ return;
364
454
  }
365
455
  // A slight delay, to avoid any current callbacks
366
456
  if ( defined.setTimeout ) {
@@ -396,11 +486,14 @@ QUnit = {
396
486
  }
397
487
  };
398
488
 
489
+ // `assert` initialized at top of scope
399
490
  // Asssert helpers
400
- // All of these must call either QUnit.push() or manually do:
491
+ // All of these must either call QUnit.push() or manually do:
401
492
  // - runLoggingCallbacks( "log", .. );
402
493
  // - config.current.assertions.push({ .. });
403
- QUnit.assert = {
494
+ // We attach it to the QUnit object *after* we expose the public API,
495
+ // otherwise `assert` will become a global variable in browsers (#341).
496
+ assert = {
404
497
  /**
405
498
  * Asserts rough true-ish result.
406
499
  * @name ok
@@ -415,18 +508,20 @@ QUnit.assert = {
415
508
 
416
509
  var source,
417
510
  details = {
511
+ module: config.current.module,
512
+ name: config.current.testName,
418
513
  result: result,
419
514
  message: msg
420
515
  };
421
516
 
422
- msg = escapeInnerText( msg || (result ? "okay" : "failed" ) );
517
+ msg = escapeText( msg || (result ? "okay" : "failed" ) );
423
518
  msg = "<span class='test-message'>" + msg + "</span>";
424
519
 
425
520
  if ( !result ) {
426
521
  source = sourceFromStacktrace( 2 );
427
522
  if ( source ) {
428
523
  details.source = source;
429
- msg += "<table><tr class='test-source'><th>Source: </th><td><pre>" + escapeInnerText( source ) + "</pre></td></tr></table>";
524
+ msg += "<table><tr class='test-source'><th>Source: </th><td><pre>" + escapeText( source ) + "</pre></td></tr></table>";
430
525
  }
431
526
  }
432
527
  runLoggingCallbacks( "log", QUnit, details );
@@ -444,6 +539,7 @@ QUnit.assert = {
444
539
  * @example equal( format( "Received {0} bytes.", 2), "Received 2 bytes.", "format() replaces {0} with next argument" );
445
540
  */
446
541
  equal: function( actual, expected, message ) {
542
+ /*jshint eqeqeq:false */
447
543
  QUnit.push( expected == actual, actual, expected, message );
448
544
  },
449
545
 
@@ -452,9 +548,30 @@ QUnit.assert = {
452
548
  * @function
453
549
  */
454
550
  notEqual: function( actual, expected, message ) {
551
+ /*jshint eqeqeq:false */
455
552
  QUnit.push( expected != actual, actual, expected, message );
456
553
  },
457
554
 
555
+ /**
556
+ * @name propEqual
557
+ * @function
558
+ */
559
+ propEqual: function( actual, expected, message ) {
560
+ actual = objectValues(actual);
561
+ expected = objectValues(expected);
562
+ QUnit.push( QUnit.equiv(actual, expected), actual, expected, message );
563
+ },
564
+
565
+ /**
566
+ * @name notPropEqual
567
+ * @function
568
+ */
569
+ notPropEqual: function( actual, expected, message ) {
570
+ actual = objectValues(actual);
571
+ expected = objectValues(expected);
572
+ QUnit.push( !QUnit.equiv(actual, expected), actual, expected, message );
573
+ },
574
+
458
575
  /**
459
576
  * @name deepEqual
460
577
  * @function
@@ -487,8 +604,9 @@ QUnit.assert = {
487
604
  QUnit.push( expected !== actual, actual, expected, message );
488
605
  },
489
606
 
490
- throws: function( block, expected, message ) {
607
+ "throws": function( block, expected, message ) {
491
608
  var actual,
609
+ expectedOutput = expected,
492
610
  ok = false;
493
611
 
494
612
  // 'expected' is optional
@@ -509,18 +627,20 @@ QUnit.assert = {
509
627
  // we don't want to validate thrown error
510
628
  if ( !expected ) {
511
629
  ok = true;
630
+ expectedOutput = null;
512
631
  // expected is a regexp
513
632
  } else if ( QUnit.objectType( expected ) === "regexp" ) {
514
- ok = expected.test( actual );
633
+ ok = expected.test( errorString( actual ) );
515
634
  // expected is a constructor
516
635
  } else if ( actual instanceof expected ) {
517
636
  ok = true;
518
637
  // expected is a validation function which returns true is validation passed
519
638
  } else if ( expected.call( {}, actual ) === true ) {
639
+ expectedOutput = null;
520
640
  ok = true;
521
641
  }
522
642
 
523
- QUnit.push( ok, actual, null, message );
643
+ QUnit.push( ok, actual, expectedOutput, message );
524
644
  } else {
525
645
  QUnit.pushFailure( message, null, 'No exception was thrown.' );
526
646
  }
@@ -529,15 +649,16 @@ QUnit.assert = {
529
649
 
530
650
  /**
531
651
  * @deprecate since 1.8.0
532
- * Kept assertion helpers in root for backwards compatibility
652
+ * Kept assertion helpers in root for backwards compatibility.
533
653
  */
534
- extend( QUnit, QUnit.assert );
654
+ extend( QUnit, assert );
535
655
 
536
656
  /**
537
657
  * @deprecated since 1.9.0
538
- * Kept global "raises()" for backwards compatibility
658
+ * Kept root "raises()" for backwards compatibility.
659
+ * (Note that we don't introduce assert.raises).
539
660
  */
540
- QUnit.raises = QUnit.assert.throws;
661
+ QUnit.raises = assert[ "throws" ];
541
662
 
542
663
  /**
543
664
  * @deprecated since 1.0.0, replaced with error pushes since 1.3.0
@@ -600,6 +721,9 @@ config = {
600
721
  }
601
722
  ],
602
723
 
724
+ // Set of all modules.
725
+ modules: {},
726
+
603
727
  // logging callback queues
604
728
  begin: [],
605
729
  done: [],
@@ -610,6 +734,15 @@ config = {
610
734
  moduleDone: []
611
735
  };
612
736
 
737
+ // Export global variables, unless an 'exports' object exists,
738
+ // in that case we assume we're in CommonJS (dealt with on the bottom of the script)
739
+ if ( typeof exports === "undefined" ) {
740
+ extend( window, QUnit );
741
+
742
+ // Expose QUnit object
743
+ window.QUnit = QUnit;
744
+ }
745
+
613
746
  // Initialize more QUnit.config and QUnit.urlParams
614
747
  (function() {
615
748
  var i,
@@ -643,18 +776,11 @@ config = {
643
776
  QUnit.isLocal = location.protocol === "file:";
644
777
  }());
645
778
 
646
- // Export global variables, unless an 'exports' object exists,
647
- // in that case we assume we're in CommonJS (dealt with on the bottom of the script)
648
- if ( typeof exports === "undefined" ) {
649
- extend( window, QUnit );
650
-
651
- // Expose QUnit object
652
- window.QUnit = QUnit;
653
- }
654
-
655
779
  // Extend QUnit object,
656
780
  // these after set here because they should not be exposed as global functions
657
781
  extend( QUnit, {
782
+ assert: assert,
783
+
658
784
  config: config,
659
785
 
660
786
  // Initialize the configuration options
@@ -669,7 +795,7 @@ extend( QUnit, {
669
795
  autorun: false,
670
796
  filter: "",
671
797
  queue: [],
672
- semaphore: 0
798
+ semaphore: 1
673
799
  });
674
800
 
675
801
  var tests, banner, result,
@@ -677,7 +803,7 @@ extend( QUnit, {
677
803
 
678
804
  if ( qunit ) {
679
805
  qunit.innerHTML =
680
- "<h1 id='qunit-header'>" + escapeInnerText( document.title ) + "</h1>" +
806
+ "<h1 id='qunit-header'>" + escapeText( document.title ) + "</h1>" +
681
807
  "<h2 id='qunit-banner'></h2>" +
682
808
  "<div id='qunit-testrunner-toolbar'></div>" +
683
809
  "<h2 id='qunit-userAgent'></h2>" +
@@ -710,17 +836,10 @@ extend( QUnit, {
710
836
  },
711
837
 
712
838
  // Resets the test setup. Useful for tests that modify the DOM.
713
- // If jQuery is available, uses jQuery's html(), otherwise just innerHTML.
714
839
  reset: function() {
715
- var fixture;
716
-
717
- if ( window.jQuery ) {
718
- jQuery( "#qunit-fixture" ).html( config.fixture );
719
- } else {
720
- fixture = id( "qunit-fixture" );
721
- if ( fixture ) {
722
- fixture.innerHTML = config.fixture;
723
- }
840
+ var fixture = id( "qunit-fixture" );
841
+ if ( fixture ) {
842
+ fixture.innerHTML = config.fixture;
724
843
  }
725
844
  },
726
845
 
@@ -740,7 +859,7 @@ extend( QUnit, {
740
859
 
741
860
  // Safe object type checking
742
861
  is: function( type, obj ) {
743
- return QUnit.objectType( obj ) == type;
862
+ return QUnit.objectType( obj ) === type;
744
863
  },
745
864
 
746
865
  objectType: function( obj ) {
@@ -752,7 +871,8 @@ extend( QUnit, {
752
871
  return "null";
753
872
  }
754
873
 
755
- var type = toString.call( obj ).match(/^\[object\s(.*)\]$/)[1] || "";
874
+ var match = toString.call( obj ).match(/^\[object\s(.*)\]$/),
875
+ type = match && match[1] || "";
756
876
 
757
877
  switch ( type ) {
758
878
  case "Number":
@@ -781,22 +901,24 @@ extend( QUnit, {
781
901
 
782
902
  var output, source,
783
903
  details = {
904
+ module: config.current.module,
905
+ name: config.current.testName,
784
906
  result: result,
785
907
  message: message,
786
908
  actual: actual,
787
909
  expected: expected
788
910
  };
789
911
 
790
- message = escapeInnerText( message ) || ( result ? "okay" : "failed" );
912
+ message = escapeText( message ) || ( result ? "okay" : "failed" );
791
913
  message = "<span class='test-message'>" + message + "</span>";
792
914
  output = message;
793
915
 
794
916
  if ( !result ) {
795
- expected = escapeInnerText( QUnit.jsDump.parse(expected) );
796
- actual = escapeInnerText( QUnit.jsDump.parse(actual) );
917
+ expected = escapeText( QUnit.jsDump.parse(expected) );
918
+ actual = escapeText( QUnit.jsDump.parse(actual) );
797
919
  output += "<table><tr class='test-expected'><th>Expected: </th><td><pre>" + expected + "</pre></td></tr>";
798
920
 
799
- if ( actual != expected ) {
921
+ if ( actual !== expected ) {
800
922
  output += "<tr class='test-actual'><th>Result: </th><td><pre>" + actual + "</pre></td></tr>";
801
923
  output += "<tr class='test-diff'><th>Diff: </th><td><pre>" + QUnit.diff( expected, actual ) + "</pre></td></tr>";
802
924
  }
@@ -805,7 +927,7 @@ extend( QUnit, {
805
927
 
806
928
  if ( source ) {
807
929
  details.source = source;
808
- output += "<tr class='test-source'><th>Source: </th><td><pre>" + escapeInnerText( source ) + "</pre></td></tr>";
930
+ output += "<tr class='test-source'><th>Source: </th><td><pre>" + escapeText( source ) + "</pre></td></tr>";
809
931
  }
810
932
 
811
933
  output += "</table>";
@@ -826,23 +948,25 @@ extend( QUnit, {
826
948
 
827
949
  var output,
828
950
  details = {
951
+ module: config.current.module,
952
+ name: config.current.testName,
829
953
  result: false,
830
954
  message: message
831
955
  };
832
956
 
833
- message = escapeInnerText( message ) || "error";
957
+ message = escapeText( message ) || "error";
834
958
  message = "<span class='test-message'>" + message + "</span>";
835
959
  output = message;
836
960
 
837
961
  output += "<table>";
838
962
 
839
963
  if ( actual ) {
840
- output += "<tr class='test-actual'><th>Result: </th><td><pre>" + escapeInnerText( actual ) + "</pre></td></tr>";
964
+ output += "<tr class='test-actual'><th>Result: </th><td><pre>" + escapeText( actual ) + "</pre></td></tr>";
841
965
  }
842
966
 
843
967
  if ( source ) {
844
968
  details.source = source;
845
- output += "<tr class='test-source'><th>Source: </th><td><pre>" + escapeInnerText( source ) + "</pre></td></tr>";
969
+ output += "<tr class='test-source'><th>Source: </th><td><pre>" + escapeText( source ) + "</pre></td></tr>";
846
970
  }
847
971
 
848
972
  output += "</table>";
@@ -867,7 +991,8 @@ extend( QUnit, {
867
991
  querystring += encodeURIComponent( key ) + "=" +
868
992
  encodeURIComponent( params[ key ] ) + "&";
869
993
  }
870
- return window.location.pathname + querystring.slice( 0, -1 );
994
+ return window.location.protocol + "//" + window.location.host +
995
+ window.location.pathname + querystring.slice( 0, -1 );
871
996
  },
872
997
 
873
998
  extend: extend,
@@ -898,7 +1023,7 @@ extend( QUnit.constructor.prototype, {
898
1023
  // testStart: { name }
899
1024
  testStart: registerLoggingCallback( "testStart" ),
900
1025
 
901
- // testDone: { name, failed, passed, total }
1026
+ // testDone: { name, failed, passed, total, duration }
902
1027
  testDone: registerLoggingCallback( "testDone" ),
903
1028
 
904
1029
  // moduleStart: { name }
@@ -916,7 +1041,10 @@ QUnit.load = function() {
916
1041
  runLoggingCallbacks( "begin", QUnit, {} );
917
1042
 
918
1043
  // Initialize the config, saving the execution queue
919
- var banner, filter, i, label, len, main, ol, toolbar, userAgent, val, urlConfigCheckboxes,
1044
+ var banner, filter, i, label, len, main, ol, toolbar, userAgent, val,
1045
+ urlConfigCheckboxesContainer, urlConfigCheckboxes, moduleFilter,
1046
+ numModules = 0,
1047
+ moduleFilterHtml = "",
920
1048
  urlConfigHtml = "",
921
1049
  oldconfig = extend( {}, config );
922
1050
 
@@ -937,9 +1065,28 @@ QUnit.load = function() {
937
1065
  };
938
1066
  }
939
1067
  config[ val.id ] = QUnit.urlParams[ val.id ];
940
- urlConfigHtml += "<input id='qunit-urlconfig-" + val.id + "' name='" + val.id + "' type='checkbox'" + ( config[ val.id ] ? " checked='checked'" : "" ) + " title='" + val.tooltip + "'><label for='qunit-urlconfig-" + val.id + "' title='" + val.tooltip + "'>" + val.label + "</label>";
1068
+ urlConfigHtml += "<input id='qunit-urlconfig-" + escapeText( val.id ) +
1069
+ "' name='" + escapeText( val.id ) +
1070
+ "' type='checkbox'" + ( config[ val.id ] ? " checked='checked'" : "" ) +
1071
+ " title='" + escapeText( val.tooltip ) +
1072
+ "'><label for='qunit-urlconfig-" + escapeText( val.id ) +
1073
+ "' title='" + escapeText( val.tooltip ) + "'>" + val.label + "</label>";
941
1074
  }
942
1075
 
1076
+ moduleFilterHtml += "<label for='qunit-modulefilter'>Module: </label><select id='qunit-modulefilter' name='modulefilter'><option value='' " +
1077
+ ( config.module === undefined ? "selected='selected'" : "" ) +
1078
+ ">< All Modules ></option>";
1079
+
1080
+ for ( i in config.modules ) {
1081
+ if ( config.modules.hasOwnProperty( i ) ) {
1082
+ numModules += 1;
1083
+ moduleFilterHtml += "<option value='" + escapeText( encodeURIComponent(i) ) + "' " +
1084
+ ( config.module === i ? "selected='selected'" : "" ) +
1085
+ ">" + escapeText(i) + "</option>";
1086
+ }
1087
+ }
1088
+ moduleFilterHtml += "</select>";
1089
+
943
1090
  // `userAgent` initialized at top of scope
944
1091
  userAgent = id( "qunit-userAgent" );
945
1092
  if ( userAgent ) {
@@ -994,14 +1141,33 @@ QUnit.load = function() {
994
1141
  label.innerHTML = "Hide passed tests";
995
1142
  toolbar.appendChild( label );
996
1143
 
997
- urlConfigCheckboxes = document.createElement( 'span' );
998
- urlConfigCheckboxes.innerHTML = urlConfigHtml;
999
- addEvent( urlConfigCheckboxes, "change", function( event ) {
1000
- var params = {};
1001
- params[ event.target.name ] = event.target.checked ? true : undefined;
1144
+ urlConfigCheckboxesContainer = document.createElement("span");
1145
+ urlConfigCheckboxesContainer.innerHTML = urlConfigHtml;
1146
+ urlConfigCheckboxes = urlConfigCheckboxesContainer.getElementsByTagName("input");
1147
+ // For oldIE support:
1148
+ // * Add handlers to the individual elements instead of the container
1149
+ // * Use "click" instead of "change"
1150
+ // * Fallback from event.target to event.srcElement
1151
+ addEvents( urlConfigCheckboxes, "click", function( event ) {
1152
+ var params = {},
1153
+ target = event.target || event.srcElement;
1154
+ params[ target.name ] = target.checked ? true : undefined;
1002
1155
  window.location = QUnit.url( params );
1003
1156
  });
1004
- toolbar.appendChild( urlConfigCheckboxes );
1157
+ toolbar.appendChild( urlConfigCheckboxesContainer );
1158
+
1159
+ if (numModules > 1) {
1160
+ moduleFilter = document.createElement( 'span' );
1161
+ moduleFilter.setAttribute( 'id', 'qunit-modulefilter-container' );
1162
+ moduleFilter.innerHTML = moduleFilterHtml;
1163
+ addEvent( moduleFilter.lastChild, "change", function() {
1164
+ var selectBox = moduleFilter.getElementsByTagName("select")[0],
1165
+ selectedModule = decodeURIComponent(selectBox.options[selectBox.selectedIndex].value);
1166
+
1167
+ window.location = QUnit.url( { module: ( selectedModule === "" ) ? undefined : selectedModule } );
1168
+ });
1169
+ toolbar.appendChild(moduleFilter);
1170
+ }
1005
1171
  }
1006
1172
 
1007
1173
  // `main` initialized at top of scope
@@ -1039,9 +1205,9 @@ window.onerror = function ( error, filePath, linerNr ) {
1039
1205
  }
1040
1206
  QUnit.pushFailure( error, filePath + ":" + linerNr );
1041
1207
  } else {
1042
- QUnit.test( "global failure", function() {
1208
+ QUnit.test( "global failure", extend( function() {
1043
1209
  QUnit.pushFailure( error, filePath + ":" + linerNr );
1044
- });
1210
+ }, { validTest: validTest } ) );
1045
1211
  }
1046
1212
  return false;
1047
1213
  }
@@ -1073,7 +1239,7 @@ function done() {
1073
1239
  " milliseconds.<br/>",
1074
1240
  "<span class='passed'>",
1075
1241
  passed,
1076
- "</span> tests of <span class='total'>",
1242
+ "</span> assertions of <span class='total'>",
1077
1243
  config.stats.all,
1078
1244
  "</span> passed, <span class='failed'>",
1079
1245
  config.stats.bad,
@@ -1108,6 +1274,11 @@ function done() {
1108
1274
  }
1109
1275
  }
1110
1276
 
1277
+ // scroll back to top to show results
1278
+ if ( window.scrollTo ) {
1279
+ window.scrollTo(0, 0);
1280
+ }
1281
+
1111
1282
  runLoggingCallbacks( "done", QUnit, {
1112
1283
  failed: config.stats.bad,
1113
1284
  passed: passed,
@@ -1123,6 +1294,12 @@ function validTest( test ) {
1123
1294
  module = config.module && config.module.toLowerCase(),
1124
1295
  fullName = (test.module + ": " + test.testName).toLowerCase();
1125
1296
 
1297
+ // Internally-generated tests are always valid
1298
+ if ( test.callback && test.callback.validTest === validTest ) {
1299
+ delete test.callback.validTest;
1300
+ return true;
1301
+ }
1302
+
1126
1303
  if ( config.testNumber ) {
1127
1304
  return test.testNumber === config.testNumber;
1128
1305
  }
@@ -1155,7 +1332,7 @@ function validTest( test ) {
1155
1332
  function extractStacktrace( e, offset ) {
1156
1333
  offset = offset === undefined ? 3 : offset;
1157
1334
 
1158
- var stack, include, i, regex;
1335
+ var stack, include, i;
1159
1336
 
1160
1337
  if ( e.stacktrace ) {
1161
1338
  // Opera
@@ -1169,7 +1346,7 @@ function extractStacktrace( e, offset ) {
1169
1346
  if ( fileName ) {
1170
1347
  include = [];
1171
1348
  for ( i = offset; i < stack.length; i++ ) {
1172
- if ( stack[ i ].indexOf( fileName ) != -1 ) {
1349
+ if ( stack[ i ].indexOf( fileName ) !== -1 ) {
1173
1350
  break;
1174
1351
  }
1175
1352
  include.push( stack[ i ] );
@@ -1198,17 +1375,27 @@ function sourceFromStacktrace( offset ) {
1198
1375
  }
1199
1376
  }
1200
1377
 
1201
- function escapeInnerText( s ) {
1378
+ /**
1379
+ * Escape text for attribute or text content.
1380
+ */
1381
+ function escapeText( s ) {
1202
1382
  if ( !s ) {
1203
1383
  return "";
1204
1384
  }
1205
1385
  s = s + "";
1206
- return s.replace( /[\&<>]/g, function( s ) {
1386
+ // Both single quotes and double quotes (for attributes)
1387
+ return s.replace( /['"<>&]/g, function( s ) {
1207
1388
  switch( s ) {
1208
- case "&": return "&amp;";
1209
- case "<": return "&lt;";
1210
- case ">": return "&gt;";
1211
- default: return s;
1389
+ case '\'':
1390
+ return '&#039;';
1391
+ case '"':
1392
+ return '&quot;';
1393
+ case '<':
1394
+ return '&lt;';
1395
+ case '>':
1396
+ return '&gt;';
1397
+ case '&':
1398
+ return '&amp;';
1212
1399
  }
1213
1400
  });
1214
1401
  }
@@ -1256,7 +1443,7 @@ function saveGlobal() {
1256
1443
  }
1257
1444
  }
1258
1445
 
1259
- function checkPollution( name ) {
1446
+ function checkPollution() {
1260
1447
  var newGlobals,
1261
1448
  deletedGlobals,
1262
1449
  old = config.pollution;
@@ -1305,16 +1492,53 @@ function extend( a, b ) {
1305
1492
  return a;
1306
1493
  }
1307
1494
 
1495
+ /**
1496
+ * @param {HTMLElement} elem
1497
+ * @param {string} type
1498
+ * @param {Function} fn
1499
+ */
1308
1500
  function addEvent( elem, type, fn ) {
1501
+ // Standards-based browsers
1309
1502
  if ( elem.addEventListener ) {
1310
1503
  elem.addEventListener( type, fn, false );
1311
- } else if ( elem.attachEvent ) {
1312
- elem.attachEvent( "on" + type, fn );
1504
+ // IE
1313
1505
  } else {
1314
- fn();
1506
+ elem.attachEvent( "on" + type, fn );
1315
1507
  }
1316
1508
  }
1317
1509
 
1510
+ /**
1511
+ * @param {Array|NodeList} elems
1512
+ * @param {string} type
1513
+ * @param {Function} fn
1514
+ */
1515
+ function addEvents( elems, type, fn ) {
1516
+ var i = elems.length;
1517
+ while ( i-- ) {
1518
+ addEvent( elems[i], type, fn );
1519
+ }
1520
+ }
1521
+
1522
+ function hasClass( elem, name ) {
1523
+ return (" " + elem.className + " ").indexOf(" " + name + " ") > -1;
1524
+ }
1525
+
1526
+ function addClass( elem, name ) {
1527
+ if ( !hasClass( elem, name ) ) {
1528
+ elem.className += (elem.className ? " " : "") + name;
1529
+ }
1530
+ }
1531
+
1532
+ function removeClass( elem, name ) {
1533
+ var set = " " + elem.className + " ";
1534
+ // Class name may appear multiple times
1535
+ while ( set.indexOf(" " + name + " ") > -1 ) {
1536
+ set = set.replace(" " + name + " " , " ");
1537
+ }
1538
+ // If possible, trim it for prettiness, but not neccecarily
1539
+ elem.className = window.jQuery ? jQuery.trim( set ) : ( set.trim ? set.trim() : set );
1540
+ }
1541
+
1318
1542
  function id( name ) {
1319
1543
  return !!( typeof document !== "undefined" && document && document.getElementById ) &&
1320
1544
  document.getElementById( name );
@@ -1328,7 +1552,6 @@ function registerLoggingCallback( key ) {
1328
1552
 
1329
1553
  // Supports deprecated method of completely overwriting logging callbacks
1330
1554
  function runLoggingCallbacks( key, scope, args ) {
1331
- //debugger;
1332
1555
  var i, callbacks;
1333
1556
  if ( QUnit.hasOwnProperty( key ) ) {
1334
1557
  QUnit[ key ].call(scope, args );
@@ -1370,6 +1593,7 @@ QUnit.equiv = (function() {
1370
1593
 
1371
1594
  // for string, boolean, number and null
1372
1595
  function useStrictEquality( b, a ) {
1596
+ /*jshint eqeqeq:false */
1373
1597
  if ( b instanceof a.constructor || a instanceof b.constructor ) {
1374
1598
  // to catch short annotaion VS 'new' annotation of a
1375
1599
  // declaration
@@ -1404,7 +1628,8 @@ QUnit.equiv = (function() {
1404
1628
  a.global === b.global &&
1405
1629
  // (gmi) ...
1406
1630
  a.ignoreCase === b.ignoreCase &&
1407
- a.multiline === b.multiline;
1631
+ a.multiline === b.multiline &&
1632
+ a.sticky === b.sticky;
1408
1633
  },
1409
1634
 
1410
1635
  // - skip when the property is a method of an instance (OOP)
@@ -1565,7 +1790,8 @@ QUnit.jsDump = (function() {
1565
1790
 
1566
1791
  var reName = /^function (\w+)/,
1567
1792
  jsDump = {
1568
- parse: function( obj, type, stack ) { //type is used mostly internally, you can fix a (custom)type in advance
1793
+ // type is used mostly internally, you can fix a (custom)type in advance
1794
+ parse: function( obj, type, stack ) {
1569
1795
  stack = stack || [ ];
1570
1796
  var inStack, res,
1571
1797
  parser = this.parsers[ type || this.typeOf(obj) ];
@@ -1573,18 +1799,16 @@ QUnit.jsDump = (function() {
1573
1799
  type = typeof parser;
1574
1800
  inStack = inArray( obj, stack );
1575
1801
 
1576
- if ( inStack != -1 ) {
1802
+ if ( inStack !== -1 ) {
1577
1803
  return "recursion(" + (inStack - stack.length) + ")";
1578
1804
  }
1579
- //else
1580
- if ( type == "function" ) {
1805
+ if ( type === "function" ) {
1581
1806
  stack.push( obj );
1582
1807
  res = parser.call( this, obj, stack );
1583
1808
  stack.pop();
1584
1809
  return res;
1585
1810
  }
1586
- // else
1587
- return ( type == "string" ) ? parser : this.parsers.error;
1811
+ return ( type === "string" ) ? parser : this.parsers.error;
1588
1812
  },
1589
1813
  typeOf: function( obj ) {
1590
1814
  var type;
@@ -1611,6 +1835,8 @@ QUnit.jsDump = (function() {
1611
1835
  ( typeof obj.length === "number" && typeof obj.item !== "undefined" && ( obj.length ? obj.item(0) === obj[0] : ( obj.item( 0 ) === null && typeof obj[0] === "undefined" ) ) )
1612
1836
  ) {
1613
1837
  type = "array";
1838
+ } else if ( obj.constructor === Error.prototype.constructor ) {
1839
+ type = "error";
1614
1840
  } else {
1615
1841
  type = typeof obj;
1616
1842
  }
@@ -1619,7 +1845,8 @@ QUnit.jsDump = (function() {
1619
1845
  separator: function() {
1620
1846
  return this.multiline ? this.HTML ? "<br />" : "\n" : this.HTML ? "&nbsp;" : " ";
1621
1847
  },
1622
- indent: function( extra ) {// extra can be a number, shortcut for increasing-calling-decreasing
1848
+ // extra can be a number, shortcut for increasing-calling-decreasing
1849
+ indent: function( extra ) {
1623
1850
  if ( !this.multiline ) {
1624
1851
  return "";
1625
1852
  }
@@ -1648,13 +1875,16 @@ QUnit.jsDump = (function() {
1648
1875
  parsers: {
1649
1876
  window: "[Window]",
1650
1877
  document: "[Document]",
1651
- error: "[ERROR]", //when no parser is found, shouldn"t happen
1878
+ error: function(error) {
1879
+ return "Error(\"" + error.message + "\")";
1880
+ },
1652
1881
  unknown: "[Unknown]",
1653
1882
  "null": "null",
1654
1883
  "undefined": "undefined",
1655
1884
  "function": function( fn ) {
1656
1885
  var ret = "function",
1657
- name = "name" in fn ? fn.name : (reName.exec(fn) || [])[1];//functions never have name in IE
1886
+ // functions never have name in IE
1887
+ name = "name" in fn ? fn.name : (reName.exec(fn) || [])[1];
1658
1888
 
1659
1889
  if ( name ) {
1660
1890
  ret += " " + name;
@@ -1670,13 +1900,9 @@ QUnit.jsDump = (function() {
1670
1900
  object: function( map, stack ) {
1671
1901
  var ret = [ ], keys, key, val, i;
1672
1902
  QUnit.jsDump.up();
1673
- if ( Object.keys ) {
1674
- keys = Object.keys( map );
1675
- } else {
1676
- keys = [];
1677
- for ( key in map ) {
1678
- keys.push( key );
1679
- }
1903
+ keys = [];
1904
+ for ( key in map ) {
1905
+ keys.push( key );
1680
1906
  }
1681
1907
  keys.sort();
1682
1908
  for ( i = 0; i < keys.length; i++ ) {
@@ -1688,21 +1914,34 @@ QUnit.jsDump = (function() {
1688
1914
  return join( "{", ret, "}" );
1689
1915
  },
1690
1916
  node: function( node ) {
1691
- var a, val,
1917
+ var len, i, val,
1692
1918
  open = QUnit.jsDump.HTML ? "&lt;" : "<",
1693
1919
  close = QUnit.jsDump.HTML ? "&gt;" : ">",
1694
1920
  tag = node.nodeName.toLowerCase(),
1695
- ret = open + tag;
1696
-
1697
- for ( a in QUnit.jsDump.DOMAttrs ) {
1698
- val = node[ QUnit.jsDump.DOMAttrs[a] ];
1699
- if ( val ) {
1700
- ret += " " + a + "=" + QUnit.jsDump.parse( val, "attribute" );
1921
+ ret = open + tag,
1922
+ attrs = node.attributes;
1923
+
1924
+ if ( attrs ) {
1925
+ for ( i = 0, len = attrs.length; i < len; i++ ) {
1926
+ val = attrs[i].nodeValue;
1927
+ // IE6 includes all attributes in .attributes, even ones not explicitly set.
1928
+ // Those have values like undefined, null, 0, false, "" or "inherit".
1929
+ if ( val && val !== "inherit" ) {
1930
+ ret += " " + attrs[i].nodeName + "=" + QUnit.jsDump.parse( val, "attribute" );
1931
+ }
1701
1932
  }
1702
1933
  }
1703
- return ret + close + open + "/" + tag + close;
1934
+ ret += close;
1935
+
1936
+ // Show content of TextNode or CDATASection
1937
+ if ( node.nodeType === 3 || node.nodeType === 4 ) {
1938
+ ret += node.nodeValue;
1939
+ }
1940
+
1941
+ return ret + open + "/" + tag + close;
1704
1942
  },
1705
- functionArgs: function( fn ) {//function calls it internally, it's the arguments part of the function
1943
+ // function calls it internally, it's the arguments part of the function
1944
+ functionArgs: function( fn ) {
1706
1945
  var args,
1707
1946
  l = fn.length;
1708
1947
 
@@ -1712,54 +1951,34 @@ QUnit.jsDump = (function() {
1712
1951
 
1713
1952
  args = new Array(l);
1714
1953
  while ( l-- ) {
1715
- args[l] = String.fromCharCode(97+l);//97 is 'a'
1954
+ // 97 is 'a'
1955
+ args[l] = String.fromCharCode(97+l);
1716
1956
  }
1717
1957
  return " " + args.join( ", " ) + " ";
1718
1958
  },
1719
- key: quote, //object calls it internally, the key part of an item in a map
1720
- functionCode: "[code]", //function calls it internally, it's the content of the function
1721
- attribute: quote, //node calls it internally, it's an html attribute value
1959
+ // object calls it internally, the key part of an item in a map
1960
+ key: quote,
1961
+ // function calls it internally, it's the content of the function
1962
+ functionCode: "[code]",
1963
+ // node calls it internally, it's an html attribute value
1964
+ attribute: quote,
1722
1965
  string: quote,
1723
1966
  date: quote,
1724
- regexp: literal, //regex
1967
+ regexp: literal,
1725
1968
  number: literal,
1726
1969
  "boolean": literal
1727
1970
  },
1728
- DOMAttrs: {
1729
- //attributes to dump from nodes, name=>realName
1730
- id: "id",
1731
- name: "name",
1732
- "class": "className"
1733
- },
1734
- HTML: false,//if true, entities are escaped ( <, >, \t, space and \n )
1735
- indentChar: " ",//indentation unit
1736
- multiline: true //if true, items in a collection, are separated by a \n, else just a space.
1971
+ // if true, entities are escaped ( <, >, \t, space and \n )
1972
+ HTML: false,
1973
+ // indentation unit
1974
+ indentChar: " ",
1975
+ // if true, items in a collection, are separated by a \n, else just a space.
1976
+ multiline: true
1737
1977
  };
1738
1978
 
1739
1979
  return jsDump;
1740
1980
  }());
1741
1981
 
1742
- // from Sizzle.js
1743
- function getText( elems ) {
1744
- var i, elem,
1745
- ret = "";
1746
-
1747
- for ( i = 0; elems[i]; i++ ) {
1748
- elem = elems[i];
1749
-
1750
- // Get the text from text nodes and CDATA nodes
1751
- if ( elem.nodeType === 3 || elem.nodeType === 4 ) {
1752
- ret += elem.nodeValue;
1753
-
1754
- // Traverse everything else, except comment nodes
1755
- } else if ( elem.nodeType !== 8 ) {
1756
- ret += getText( elem.childNodes );
1757
- }
1758
- }
1759
-
1760
- return ret;
1761
- }
1762
-
1763
1982
  // from jquery.js
1764
1983
  function inArray( elem, array ) {
1765
1984
  if ( array.indexOf ) {
@@ -1790,13 +2009,14 @@ function inArray( elem, array ) {
1790
2009
  * QUnit.diff( "the quick brown fox jumped over", "the quick fox jumps over" ) == "the quick <del>brown </del> fox <del>jumped </del><ins>jumps </ins> over"
1791
2010
  */
1792
2011
  QUnit.diff = (function() {
2012
+ /*jshint eqeqeq:false, eqnull:true */
1793
2013
  function diff( o, n ) {
1794
2014
  var i,
1795
2015
  ns = {},
1796
2016
  os = {};
1797
2017
 
1798
2018
  for ( i = 0; i < n.length; i++ ) {
1799
- if ( ns[ n[i] ] == null ) {
2019
+ if ( !hasOwn.call( ns, n[i] ) ) {
1800
2020
  ns[ n[i] ] = {
1801
2021
  rows: [],
1802
2022
  o: null
@@ -1806,7 +2026,7 @@ QUnit.diff = (function() {
1806
2026
  }
1807
2027
 
1808
2028
  for ( i = 0; i < o.length; i++ ) {
1809
- if ( os[ o[i] ] == null ) {
2029
+ if ( !hasOwn.call( os, o[i] ) ) {
1810
2030
  os[ o[i] ] = {
1811
2031
  rows: [],
1812
2032
  n: null
@@ -1819,7 +2039,7 @@ QUnit.diff = (function() {
1819
2039
  if ( !hasOwn.call( ns, i ) ) {
1820
2040
  continue;
1821
2041
  }
1822
- if ( ns[i].rows.length == 1 && typeof os[i] != "undefined" && os[i].rows.length == 1 ) {
2042
+ if ( ns[i].rows.length === 1 && hasOwn.call( os, i ) && os[i].rows.length === 1 ) {
1823
2043
  n[ ns[i].rows[0] ] = {
1824
2044
  text: n[ ns[i].rows[0] ],
1825
2045
  row: os[i].rows[0]
@@ -1925,7 +2145,7 @@ QUnit.diff = (function() {
1925
2145
 
1926
2146
  // for CommonJS enviroments, export everything
1927
2147
  if ( typeof exports !== "undefined" ) {
1928
- extend(exports, QUnit);
2148
+ extend( exports, QUnit );
1929
2149
  }
1930
2150
 
1931
2151
  // get at whatever the global object is, like window in browsers