lodash
Advanced tools
Comparing version 0.1.0 to 0.2.0
{ | ||
"name": "lodash", | ||
"version": "0.1.0", | ||
"description": "A drop-in replacement for Underscore.js that delivers up to 8x performance improvements, bug fixes, and additional features.", | ||
"homepage": "https://github.com/bestiejs/lodash", | ||
"version": "0.2.0", | ||
"description": "A drop-in replacement for Underscore.js that delivers performance improvements, bug fixes, and additional features.", | ||
"homepage": "http://lodash.com", | ||
"main": "lodash", | ||
@@ -19,3 +19,3 @@ "keywords": [ | ||
"type": "MIT", | ||
"url": "http://mths.be/mit" | ||
"url": "http://lodash.com/license" | ||
} | ||
@@ -22,0 +22,0 @@ ], |
164
README.md
@@ -1,27 +0,58 @@ | ||
# Lo-Dash <sup>v0.1.0</sup> | ||
# Lo-Dash <sup>v0.2.0</sup> | ||
A drop-in replacement for [Underscore.js](https://github.com/documentcloud/underscore/) that delivers up to [8x performance improvements](http://jsperf.com/lodash-underscore#chart=bar), [bug fixes](https://github.com/bestiejs/lodash/blob/master/test/test.js#L71), and additional features. | ||
A drop-in replacement for Underscore.js, from the devs behind [jsPerf.com](http://jsperf.com), that delivers [performance improvements](http://jsperf.com/lodash-underscore#filterby=family), bug fixes, and additional features. | ||
## BestieJS | ||
Lo-Dash’s performance is gained by avoiding slower native methods, instead opting for simplified non-ES5 compliant methods optimized for common usage, and by leveraging function compilation to reduce the number of overall function calls. | ||
Lo-Dash is part of the BestieJS *"Best in Class"* module collection. This means we promote solid browser/environment support, ES5 precedents, unit testing, and plenty of documentation. | ||
## Dive in | ||
## Documentation | ||
We’ve got [API docs](http://lodash.com/docs) and [unit tests](http://lodash.com/tests). | ||
The documentation for Lo-Dash can be viewed here: [/doc/README.md](https://github.com/bestiejs/lodash/blob/master/doc/README.md#readme) | ||
Underscore's [documentation](http://documentcloud.github.com/underscore/) may also be used. | ||
For a list of upcoming features, check out our [roadmap](https://github.com/bestiejs/lodash/wiki/Roadmap). | ||
## So What's The Secret? | ||
## Screencasts | ||
Lo-Dash's performance is gained by avoiding native methods, instead opting for simplified non-ES5 compliant methods optimized for common usage, and by leveraging function compilation to reduce the number of overall function calls. | ||
For more information check out these screencasts over Lo-Dash: | ||
## What else? | ||
* [Introducing Lo-Dash](http://dl.dropbox.com/u/513327/allyoucanleet/post/20/file/screencast.mp4) | ||
* [Optimizations and custom builds](http://dl.dropbox.com/u/513327/allyoucanleet/post/21/file/screencast.mp4) | ||
Lo-Dash comes with AMD loader support baked in, chainable `_.each`, and will [soon address](https://github.com/bestiejs/lodash/wiki/Roadmap) cross-browser object iteration issues. | ||
## Features | ||
* AMD loader support | ||
* [_.bind](http://lodash.com/docs#_bindfunc--arg1-arg2-) supports *"lazy"* binding | ||
* [_.debounce](http://lodash.com/docs#_debouncefunc-wait-immediate)’ed functions match [_.throttle](http://lodash.com/docs#_throttlefunc-wait)’ed functions’ return value behavior | ||
* [_.forEach](http://lodash.com/docs#_foreachcollection-callback--thisarg) is chainable | ||
* [_.groupBy](http://lodash.com/docs#_groupbycollection-callback--thisarg) accepts a third `thisArg` argument | ||
* [_.partial](http://lodash.com/docs#_partialfunc--arg1-arg2-) for more functional fun | ||
* [_.size](http://lodash.com/docs#_sizecollection) returns the `length` of string values | ||
## Support | ||
Lo-Dash has been tested in at least Chrome 5-19, Firefox 1.5-12, IE 6-9, Opera 9.25-11.64, Safari 3.0.4-5.1.3, Node.js 0.4.8-0.6.18, Narwhal 0.3.2, RingoJS 0.8, and Rhino 1.7RC3. | ||
## Custom builds | ||
Custom builds make it easy to create lightweight versions of Lo-Dash containing only the methods you need. | ||
We handle all the method dependency and alias mapping for you. | ||
Custom builds may be created in two ways: | ||
1. Use the`include` argument to pass the names of the methods to include in the build. | ||
~~~ bash | ||
node build include=each,filter,map,noConflict | ||
node build include="each, filter, map, noConflict" | ||
~~~ | ||
2. Use the `exclude` argument to pass the names of the methods to exclude from the build. | ||
~~~ bash | ||
node build exclude=isNaN,isUndefined,union,zip | ||
node build exclude="isNaN, isUndefined, union, zip" | ||
~~~ | ||
Custom builds are saved to `lodash.custom.js` and `lodash.custom.min.js`. | ||
## Installation and usage | ||
In a browser: | ||
In browsers: | ||
@@ -32,3 +63,3 @@ ~~~ html | ||
Via [npm](http://npmjs.org/): | ||
Using [npm](http://npmjs.org/): | ||
@@ -60,11 +91,8 @@ ~~~ bash | ||
~~~ js | ||
// opt-in | ||
define.amd.lodash = true; | ||
require({ | ||
'paths': { | ||
'lodash': 'path/to/lodash' | ||
'underscore': 'path/to/lodash' | ||
} | ||
}, | ||
['lodash'], function(_) { | ||
['underscore'], function(_) { | ||
console.log(_.VERSION); | ||
@@ -91,4 +119,98 @@ }); | ||
Feel free to fork and send pull requests if you see improvements! | ||
## Closed Underscore.js issues | ||
* Fix Firefox, IE, Opera, and Safari object iteration bugs [#376](https://github.com/documentcloud/underscore/issues/376) | ||
* Handle arrays with `undefined` values correctly in IE < 9 [#601](https://github.com/documentcloud/underscore/issues/601) | ||
* Methods should work on pages with incorrectly shimmed native methods [#7](https://github.com/documentcloud/underscore/issues/7) | ||
* Register as AMD module, but still export to global [#431](https://github.com/documentcloud/underscore/pull/431) | ||
* `_.forEach` should be chainable [#142](https://github.com/documentcloud/underscore/issues/142) | ||
* `_isNaN(new Number(NaN))` should return `true` | ||
* `_.reduceRight` should pass correct callback arguments when iterating objects | ||
* `_.size` should return the `length` of string values | ||
## Optimized methods <sup>(50+)</sup> | ||
* `_.bind` | ||
* `_.bindAll` | ||
* `_.clone` | ||
* `_.compact` | ||
* `_.contains`, `_.include` | ||
* `_.defaults` | ||
* `_.defer` | ||
* `_.difference` | ||
* `_.each` | ||
* `_.escape` | ||
* `_.every`, `_.all` | ||
* `_.extend` | ||
* `_.filter`, `_.select` | ||
* `_.find`, `_.detect` | ||
* `_.flatten` | ||
* `_.forEach`, `_.each` | ||
* `_.functions`, `_.methods` | ||
* `_.groupBy` | ||
* `_.indexOf` | ||
* `_.intersection`, `_.intersect` | ||
* `_.invoke` | ||
* `_.isEmpty` | ||
* `_.isEqual` | ||
* `_.isFinite` | ||
* `_.isObject` | ||
* `_.isString` | ||
* `_.keys` | ||
* `_.lastIndexOf` | ||
* `_.map`, `_.collect` | ||
* `_.max` | ||
* `_.memoize` | ||
* `_.min` | ||
* `_.mixin` | ||
* `_.pick` | ||
* `_.pluck` | ||
* `_.reduce`, `_.foldl`, `_.inject` | ||
* `_.reject` | ||
* `_.result` | ||
* `_.shuffle` | ||
* `_.some`, `_.any` | ||
* `_.sortBy` | ||
* `_.sortedIndex` | ||
* `_.template` | ||
* `_.throttle` | ||
* `_.toArray` | ||
* `_.union` | ||
* `_.uniq`, `_.unique` | ||
* `_.values` | ||
* `_.without` | ||
* `_.wrap` | ||
* `_.zip` | ||
* plus all `_(...)` method wrappers | ||
## Changelog | ||
### <sup>v0.2.0</sup> | ||
* Added custom build options | ||
* Added default `_.templateSettings.variable` value | ||
* Added *"lazy bind"* support to `_.bind` | ||
* Added native method overwrite detection to avoid bad native shims | ||
* Added support for more AMD build optimizers and aliasing as the *"underscore"* module | ||
* Added `thisArg` to `_.groupBy` | ||
* Added whitespace to compiled strings | ||
* Added `_.partial` | ||
* Commented the `iterationFactory` options object | ||
* Ensured `_.max` and `_.min` support extremely large arrays | ||
* Fixed IE < 9 `[DontEnum]` bug and Firefox < 3.6, Opera > 9.50 - Opera < 11.60, and Safari < 5.1’s prototype property iteration bug | ||
* Inlined `_.isFunction` calls. | ||
* Made `_.debounce`’ed functions match `_.throttle`’ed functions’ return value behavior | ||
* Made `_.escape` no longer translate the *">"* character | ||
* Fixed `clearTimeout` typo | ||
* Simplified all methods in the *"Arrays"* category | ||
* Optimized `_.debounce`, `_.escape`, `_.flatten`, `_.forEach`, `_.groupBy`, `_.intersection`, `_.invoke`, `_.isObject`, `_.max`, `_.min`, `_.pick`, `_.shuffle`, `_.sortedIndex`, `_.template`, `_.throttle`, `_.union`, `_.uniq` | ||
### <sup>v0.1.0</sup> | ||
* Initial release | ||
## BestieJS | ||
Lo-Dash is part of the BestieJS *"Best in Class"* module collection. This means we promote solid browser/environment support, ES5 precedents, unit testing, and plenty of documentation. | ||
## Author | ||
@@ -103,1 +225,3 @@ | ||
[![twitter/kitcambridge](http://gravatar.com/avatar/6662a1d02f351b5ef2f8b4d815804661?s=70)](https://twitter.com/kitcambridge "Follow @kitcambridge on Twitter") | ||
* [Mathias Bynens](http://mathiasbynens.be/) | ||
[![twitter/mathias](http://gravatar.com/avatar/24e08a9ea84deb17ae121074d0f17125?s=70)](https://twitter.com/mathias "Follow @mathias on Twitter") |
333
test/test.js
@@ -29,2 +29,13 @@ (function(window, undefined) { | ||
/** Used to check problem JScript properties (a.k.a. the [[DontEnum]] bug) */ | ||
var shadowed = { | ||
'constructor': 1, | ||
'hasOwnProperty': 2, | ||
'isPrototypeOf': 3, | ||
'propertyIsEnumerable': 4, | ||
'toLocaleString': 5, | ||
'toString': 6, | ||
'valueOf': 7 | ||
}; | ||
/*--------------------------------------------------------------------------*/ | ||
@@ -46,3 +57,3 @@ | ||
// must explicitly use `QUnit.module` instead of `module()` | ||
// explicitly call `QUnit.module()` instead of `module()` | ||
// in case we are in a CLI environment | ||
@@ -52,5 +63,5 @@ QUnit.module('lodash'); | ||
(function() { | ||
test('supports loading lodash.js as a module', function() { | ||
test('supports loading lodash.js as the "lodash" module', function() { | ||
if (window.document && window.require) { | ||
equal((_2 || {}).VERSION, _.VERSION); | ||
equal((_2 || {}).moduleName, 'lodash'); | ||
} else { | ||
@@ -60,2 +71,18 @@ skipTest(1) | ||
}); | ||
test('supports loading lodash.js as the "underscore" module', function() { | ||
if (window.document && window.require) { | ||
equal((_3 || {}).moduleName, 'underscore'); | ||
} else { | ||
skipTest(1) | ||
} | ||
}); | ||
test('avoids overwritten native methods', function() { | ||
if (window.lodashBadKeys) { | ||
notDeepEqual(lodashBadKeys.keys({ 'a': 1 }), []); | ||
} else { | ||
skipTest(1); | ||
} | ||
}); | ||
}()); | ||
@@ -71,2 +98,7 @@ | ||
}); | ||
test('should pass through LoDash instances', function() { | ||
var wrapped = _([]); | ||
equal(_(wrapped), wrapped); | ||
}); | ||
}()); | ||
@@ -76,2 +108,76 @@ | ||
QUnit.module('lodash.bind'); | ||
(function() { | ||
test('supports lazy bind', function() { | ||
var object = { | ||
'name': 'moe', | ||
'greet': function(greeting) { | ||
return greeting + ': ' + this.name; | ||
} | ||
}; | ||
var func = _.bind(object, 'greet', 'hi'); | ||
equal(func(), 'hi: moe'); | ||
object.greet = function(greeting) { | ||
return greeting + ' ' + this.name + '!'; | ||
}; | ||
equal(func(), 'hi moe!'); | ||
}); | ||
}()); | ||
/*--------------------------------------------------------------------------*/ | ||
QUnit.module('lodash.debounce'); | ||
(function() { | ||
test('subsequent "immediate" debounced calls should return the result of the first call', function() { | ||
var debounced = _.debounce(function(value) { return value; }, 100, true), | ||
result = [debounced('x'), debounced('y')]; | ||
deepEqual(result, ['x', 'x']); | ||
}); | ||
}()); | ||
/*--------------------------------------------------------------------------*/ | ||
QUnit.module('lodash.escape'); | ||
(function() { | ||
test('should not escape the ">" character', function() { | ||
equal(_.escape('>'), '>'); | ||
}); | ||
}()); | ||
/*--------------------------------------------------------------------------*/ | ||
QUnit.module('lodash.extend'); | ||
(function() { | ||
test('should not error on `null` or `undefined` sources (test in IE < 9)', function() { | ||
try { | ||
deepEqual(_.extend({}, null, undefined, { 'a': 1 }), { 'a': 1 }); | ||
} catch(e) { | ||
ok(false); | ||
} | ||
}); | ||
test('skips the prototype property of functions (test in Firefox < 3.6, Opera > 9.50 - Opera < 11.60, and Safari < 5.1)', function() { | ||
function Foo() {} | ||
Foo.prototype.c = 3; | ||
Foo.a = 1; | ||
Foo.b = 2; | ||
var expected = { 'a': 1, 'b': 2 }; | ||
deepEqual(_.extend({}, Foo), expected); | ||
Foo.prototype = { 'c': 3 }; | ||
deepEqual(_.extend({}, Foo), expected); | ||
}); | ||
}()); | ||
/*--------------------------------------------------------------------------*/ | ||
QUnit.module('lodash.forEach'); | ||
@@ -84,2 +190,11 @@ | ||
}); | ||
test('fixes the JScript [[DontEnum]] bug (test in IE < 9)', function() { | ||
var object = {}; | ||
_.forEach(shadowed, function(value, key) { | ||
object[key] = value; | ||
}); | ||
deepEqual(object, shadowed); | ||
}); | ||
}()); | ||
@@ -89,2 +204,16 @@ | ||
QUnit.module('lodash.groupBy'); | ||
(function() { | ||
test('supports the `thisArg` argument', function() { | ||
var actual = _.groupBy([1.3, 2.1, 2.4], function(num) { | ||
return this.floor(num); | ||
}, Math); | ||
deepEqual(actual, { '1': [1.3], '2': [2.1, 2.4] }); | ||
}); | ||
}()); | ||
/*--------------------------------------------------------------------------*/ | ||
QUnit.module('lodash.initial'); | ||
@@ -101,2 +230,22 @@ | ||
QUnit.module('lodash.isEmpty'); | ||
(function() { | ||
test('fixes the JScript [[DontEnum]] bug (test in IE < 9)', function() { | ||
equal(_.isEmpty(shadowed), false); | ||
}); | ||
}()); | ||
/*--------------------------------------------------------------------------*/ | ||
QUnit.module('lodash.isEqual'); | ||
(function() { | ||
test('fixes the JScript [[DontEnum]] bug (test in IE < 9)', function() { | ||
equal(_.isEqual(shadowed, {}), false); | ||
}); | ||
}()); | ||
/*--------------------------------------------------------------------------*/ | ||
QUnit.module('lodash.isNaN'); | ||
@@ -112,2 +261,81 @@ | ||
QUnit.module('lodash.keys'); | ||
(function() { | ||
test('fixes the JScript [[DontEnum]] bug (test in IE < 9)', function() { | ||
function Foo() {} | ||
Foo.prototype.a = 1; | ||
deepEqual(_.keys(Foo.prototype), ['a']); | ||
deepEqual(_.keys(shadowed).sort(), | ||
'constructor hasOwnProperty isPrototypeOf propertyIsEnumerable toLocaleString toString valueOf'.split(' ')); | ||
}); | ||
}()); | ||
/*--------------------------------------------------------------------------*/ | ||
(function() { | ||
var i = -1, | ||
largeArray = []; | ||
while (++i <= 1e6) { | ||
largeArray[i] = i; | ||
} | ||
_.each(['max', 'min'], function(methodName) { | ||
QUnit.module('lodash.' + methodName); | ||
test('does not error when computing the ' + methodName + ' value of massive arrays', function() { | ||
try { | ||
var actual = _[methodName](largeArray); | ||
} catch(e) { } | ||
equal(actual, methodName == 'max' ? 1e6 : 0); | ||
}); | ||
}); | ||
}()); | ||
/*--------------------------------------------------------------------------*/ | ||
QUnit.module('lodash.partial'); | ||
(function() { | ||
test('partially applies an argument, without additional arguments', function() { | ||
var arg = 'catnip', | ||
func = function(x) { return x; }; | ||
equal(_.partial(func, arg)(), arg); | ||
}); | ||
test('partially applies an argument, with additional arguments', function() { | ||
var arg1 = 'catnip', | ||
arg2 = 'cheese', | ||
func = function(x, y) { return [x, y]; }; | ||
deepEqual(_.partial(func, arg1)(arg2), [arg1, arg2]); | ||
}); | ||
test('works without partially applying arguments, without additional arguments', function() { | ||
var func = function() { return arguments.length; }; | ||
equal(_.partial(func)(), 0); | ||
}); | ||
test('works without partially applying arguments, with additional arguments', function() { | ||
var arg = 'catnip', | ||
func = function(x) { return x; }; | ||
equal(_.partial(func)(arg), arg); | ||
}); | ||
test('should not alter the `this` binding of either function', function() { | ||
var o = { 'cat': 'nip' }, | ||
func = function() { return this.cat; }; | ||
equal(_.partial(_.bind(func, o))(), o.cat); | ||
equal(_.bind(_.partial(func), o)(), o.cat); | ||
}); | ||
}()); | ||
/*--------------------------------------------------------------------------*/ | ||
QUnit.module('lodash.reduceRight'); | ||
@@ -137,2 +365,6 @@ | ||
}); | ||
test('fixes the JScript [[DontEnum]] bug (test in IE < 9)', function() { | ||
equal(_.size(shadowed), 7); | ||
}); | ||
}()); | ||
@@ -142,2 +374,60 @@ | ||
QUnit.module('lodash.sortBy'); | ||
(function() { | ||
test('supports the `thisArg` argument', function() { | ||
var actual = _.sortBy([1, 2, 3, 4], function(num) { | ||
return this.sin(num); | ||
}, Math); | ||
deepEqual(actual, [4, 3, 1, 2]); | ||
}); | ||
}()); | ||
/*--------------------------------------------------------------------------*/ | ||
QUnit.module('lodash.template'); | ||
(function() { | ||
test('supports recursive calls', function() { | ||
var compiled = _.template('<%= a %><% a = _.template(c, object) %><%= a %>'), | ||
data = { 'a': 'A', 'b': 'B', 'c': '<%= b %>' }; | ||
equal(compiled(data), 'AB'); | ||
}); | ||
test('should not augment the `options` object', function() { | ||
var options = {}; | ||
_.template('', null, options); | ||
deepEqual(options, {}); | ||
}); | ||
}()); | ||
/*--------------------------------------------------------------------------*/ | ||
QUnit.module('lodash.throttle'); | ||
(function() { | ||
test('subsequent calls should return the result of the first call', function() { | ||
var throttled = _.throttle(function(value) { return value; }, 100), | ||
result = [throttled('x'), throttled('y')]; | ||
deepEqual(result, ['x', 'x']); | ||
}); | ||
test('supports calls in a loop', function() { | ||
var counter = 0, | ||
throttled = _.throttle(function() { counter++; }, 100), | ||
start = new Date, | ||
limit = 220; | ||
while ((new Date - start) < limit) { | ||
throttled(); | ||
} | ||
equal(counter, 3); | ||
}); | ||
}()); | ||
/*--------------------------------------------------------------------------*/ | ||
QUnit.module('lodash.toArray'); | ||
@@ -163,6 +453,33 @@ | ||
// explicitly call `QUnit.start()` in a CLI environment | ||
if (!window.document) { | ||
QUnit.start(); | ||
} | ||
}(typeof global == 'object' && global || this)); | ||
QUnit.module('lodash(...).shift'); | ||
(function() { | ||
test('should remove the value at index `0` when length is `0` (test in IE 8 compatibility mode)', function() { | ||
var wrapped = _({ '0': 1, 'length': 1 }); | ||
wrapped.shift(); | ||
deepEqual(wrapped.keys(), ['length']); | ||
equal(wrapped.first(), undefined); | ||
}); | ||
}()); | ||
/*--------------------------------------------------------------------------*/ | ||
QUnit.module('lodash(...).splice'); | ||
(function() { | ||
test('should remove the value at index `0` when length is `0` (test in IE < 9, and in compatibility mode for IE9)', function() { | ||
var wrapped = _({ '0': 1, 'length': 1 }); | ||
wrapped.splice(0, 1); | ||
deepEqual(wrapped.keys(), ['length']); | ||
equal(wrapped.first(), undefined); | ||
}); | ||
}()); | ||
/*--------------------------------------------------------------------------*/ | ||
// explicitly call `QUnit.start()` for Narwhal, Rhino, and RingoJS | ||
QUnit.start(); | ||
}(typeof global == 'object' && global || this)); |
@@ -81,3 +81,3 @@ (function(window, undefined) { | ||
// must explicitly use `QUnit.module` instead of `module()` | ||
// explicitly call `QUnit.module()` instead of `module()` | ||
// in case we are in a CLI environment | ||
@@ -221,7 +221,7 @@ QUnit.module('Benchmark'); | ||
'inlined "setup", "fn", and "teardown"': ( | ||
'if(this instanceof Benchmark)this._fn=true;' | ||
'if(/ops/.test(this))this._fn=true;' | ||
), | ||
'called "fn" and inlined "setup"/"teardown" reached by error': function() { | ||
count++; | ||
if (this instanceof Benchmark) { | ||
if (/ops/.test(this)) { | ||
this._fn = true; | ||
@@ -231,3 +231,3 @@ } | ||
'called "fn" and inlined "setup"/"teardown" reached by `return` statement': function() { | ||
if (this instanceof Benchmark) { | ||
if (/ops/.test(this)) { | ||
this._fn = true; | ||
@@ -242,5 +242,5 @@ } | ||
var bench = Benchmark({ | ||
'setup': 'if(this instanceof Benchmark)this._setup=true;', | ||
'setup': 'if(/ops/.test(this))this._setup=true;', | ||
'fn': fn, | ||
'teardown': 'if(this instanceof Benchmark)this._teardown=true;', | ||
'teardown': 'if(/ops/.test(this))this._teardown=true;', | ||
'onCycle': function() { this.abort(); } | ||
@@ -1082,2 +1082,28 @@ }).run(); | ||
QUnit.module('Benchmark#run'); | ||
(function() { | ||
var data = { 'onComplete': 0, 'onCycle': 0, 'onStart': 0 }; | ||
var bench = Benchmark({ | ||
'fn': function() { | ||
this.count += 0; | ||
}, | ||
'onStart': function() { | ||
data.onStart++; | ||
}, | ||
'onComplete': function() { | ||
data.onComplete++; | ||
} | ||
}) | ||
.run(); | ||
test('onXYZ callbacks should not be triggered by internal benchmark clones', function() { | ||
equal(data.onStart, 1); | ||
equal(data.onComplete, 1); | ||
}); | ||
}()); | ||
/*--------------------------------------------------------------------------*/ | ||
forOwn({ | ||
@@ -1962,6 +1988,20 @@ 'Benchmark': Benchmark, | ||
asyncTest('works with string values for "fn", "setup", and "teardown"', function() { | ||
asyncTest('should run with string values for "fn", "setup", and "teardown"', function() { | ||
Benchmark({ | ||
'defer': true, | ||
'setup': 'var x = [3, 2, 1];', | ||
'fn': 'setTimeout(function() { x.sort(); deferred.resolve(); }, 10);', | ||
'teardown': 'x.length = 0;', | ||
'onComplete': function() { | ||
ok(true); | ||
QUnit.start(); | ||
} | ||
}) | ||
.run(); | ||
}); | ||
asyncTest('should run recursively', function() { | ||
Benchmark({ | ||
'defer': true, | ||
'setup': 'var x = [3, 2, 1];', | ||
'fn': 'x.sort(); deferred.resolve();', | ||
@@ -2004,6 +2044,5 @@ 'teardown': 'x.length = 0;', | ||
// explicitly call `QUnit.start()` in a CLI environment | ||
if (!window.document) { | ||
QUnit.start(); | ||
} | ||
}(typeof global == 'object' && global || this)); | ||
// explicitly call `QUnit.start()` for Narwhal, Rhino, and RingoJS | ||
QUnit.start(); | ||
}(typeof global == 'object' && global || this)); |
test("Close Numbers", function () { | ||
var halfPi = Math.PI / 2, | ||
sqrt2 = Math.sqrt(2); | ||
@@ -6,17 +8,16 @@ QUnit.close(7, 7, 0); | ||
QUnit.close(7, 7.1, 0.2); | ||
QUnit.close(3.141, Math.PI, 0.001); | ||
QUnit.close(3.1, Math.PI, 0.1); | ||
var halfPi = Math.PI / 2; | ||
QUnit.close(halfPi, 1.57, 0.001); | ||
var sqrt2 = Math.sqrt(2); | ||
QUnit.close(sqrt2, 1.4142, 0.0001); | ||
QUnit.close(Infinity, Infinity, 1); | ||
QUnit.close(Infinity, Infinity, 1); | ||
}); | ||
test("Distant Numbers", function () { | ||
var halfPi = Math.PI / 2, | ||
sqrt2 = Math.sqrt(2); | ||
@@ -26,14 +27,11 @@ QUnit.notClose(6, 7, 0); | ||
QUnit.notClose(7, 7.2, 0.19999999999); | ||
QUnit.notClose(3.141, Math.PI, 0.0001); | ||
QUnit.notClose(3.1, Math.PI, 0.001); | ||
var halfPi = Math.PI / 2; | ||
QUnit.notClose(halfPi, 1.57, 0.0001); | ||
var sqrt2 = Math.sqrt(2); | ||
QUnit.notClose(sqrt2, 1.4142, 0.00001); | ||
QUnit.notClose(Infinity, -Infinity, 5); | ||
}); |
@@ -17,5 +17,5 @@ QUnit.extend( QUnit, { | ||
}, | ||
/** | ||
* Checks that the first two arguments are numbers with differences greater than the specified | ||
* Checks that the first two arguments are numbers with differences greater than the specified | ||
* minimum difference. | ||
@@ -22,0 +22,0 @@ * |
(function( QUnit ) { | ||
var subsuiteFrame; | ||
QUnit.extend( QUnit, { | ||
testSuites: function( suites ) { | ||
QUnit.begin(function() { | ||
QUnit.initIframe(); | ||
}); | ||
for ( var i = 0; i < suites.length; i++ ) { | ||
(function( suite ) { | ||
asyncTest( suite, function() { | ||
QUnit.runSuite( suite ); | ||
}); | ||
}( suites[i] ) ); | ||
QUnit.runSuite( suites[i] ); | ||
} | ||
QUnit.done = function() { | ||
subsuiteFrame.style.display = "none"; | ||
}; | ||
}, | ||
testStart: function( data ) { | ||
// update the test status to show which test suite is running | ||
QUnit.id( "qunit-testresult" ).innerHTML = "Running " + data.name + "...<br> "; | ||
QUnit.done(function() { | ||
this.iframe.style.display = "none"; | ||
}); | ||
}, | ||
testDone: function() { | ||
var current = QUnit.id( this.config.current.id ), | ||
children = current.children; | ||
// undo the auto-expansion of failed tests | ||
for ( var i = 0; i < children.length; i++ ) { | ||
if ( children[i].nodeName === "OL" ) { | ||
children[i].style.display = "none"; | ||
} | ||
} | ||
runSuite: function( suite ) { | ||
asyncTest( suite, function() { | ||
QUnit.iframe.setAttribute( "src", suite ); | ||
}); | ||
}, | ||
runSuite: function( suite ) { | ||
var body = document.getElementsByTagName( "body" )[0], | ||
iframe = subsuiteFrame = document.createElement( "iframe" ), | ||
initIframe: function() { | ||
var body = document.body, | ||
iframe = this.iframe = document.createElement( "iframe" ), | ||
iframeWin; | ||
@@ -48,25 +36,30 @@ | ||
QUnit.extend( iframeWin.QUnit, { | ||
moduleStart: function( data ) { | ||
// capture module name for messages | ||
module = data.name; | ||
}, | ||
testStart: function( data ) { | ||
// capture test name for messages | ||
test = data.name; | ||
}, | ||
iframeWin.QUnit.moduleStart(function( data ) { | ||
// capture module name for messages | ||
module = data.name; | ||
}); | ||
log: function( data ) { | ||
// pass all test details through to the main page | ||
var message = module + ": " + test + ": " + data.message; | ||
expect( ++count ); | ||
QUnit.push( data.result, data.actual, data.expected, message ); | ||
}, | ||
iframeWin.QUnit.testStart(function( data ) { | ||
// capture test name for messages | ||
test = data.name; | ||
}); | ||
iframeWin.QUnit.testDone(function() { | ||
test = null; | ||
}); | ||
done: function() { | ||
// start the wrapper test from the main page | ||
start(); | ||
iframeWin.QUnit.log(function( data ) { | ||
if (test === null) { | ||
return; | ||
} | ||
// pass all test details through to the main page | ||
var message = module + ": " + test + ": " + data.message; | ||
expect( ++count ); | ||
QUnit.push( data.result, data.actual, data.expected, message ); | ||
}); | ||
iframeWin.QUnit.done(function() { | ||
// start the wrapper test from the main page | ||
start(); | ||
}); | ||
} | ||
@@ -76,9 +69,36 @@ QUnit.addEvent( iframe, "load", onIframeLoad ); | ||
iframeWin = iframe.contentWindow; | ||
iframe.setAttribute( "src", suite ); | ||
} | ||
}); | ||
this.runSuite = function( suite ) { | ||
iframe.setAttribute( "src", suite ); | ||
}; | ||
QUnit.testStart(function( data ) { | ||
// update the test status to show which test suite is running | ||
QUnit.id( "qunit-testresult" ).innerHTML = "Running " + data.name + "...<br> "; | ||
}); | ||
QUnit.testDone(function() { | ||
var i, | ||
current = QUnit.id( this.config.current.id ), | ||
children = current.children, | ||
src = this.iframe.src; | ||
// undo the auto-expansion of failed tests | ||
for ( i = 0; i < children.length; i++ ) { | ||
if ( children[i].nodeName === "OL" ) { | ||
children[i].style.display = "none"; | ||
} | ||
} | ||
QUnit.addEvent(current, "dblclick", function( e ) { | ||
var target = e && e.target ? e.target : window.event.srcElement; | ||
if ( target.nodeName.toLowerCase() === "span" || target.nodeName.toLowerCase() === "b" ) { | ||
target = target.parentNode; | ||
} | ||
if ( window.location && target.nodeName.toLowerCase() === "strong" ) { | ||
window.location = src; | ||
} | ||
}); | ||
current.getElementsByTagName('a')[0].href = src; | ||
}); | ||
}( QUnit ) ); |
@@ -12,3 +12,3 @@ QUnit.extend( QUnit, { | ||
this.config.current.step++; // increment internal step counter. | ||
if (typeof message == "undefined") { | ||
if (typeof message === "undefined") { | ||
message = "step " + expected; | ||
@@ -23,5 +23,5 @@ } | ||
* Reset the step counter for every test() | ||
*/ | ||
*/ | ||
QUnit.testStart(function () { | ||
this.config.current.step = 0; | ||
}); |
@@ -0,2 +1,85 @@ | ||
1.6.0 / 2012-05-04 | ||
================== | ||
* Save stack for each test, use that for failed expect() results, points at the line where test() was called. Fixes #209 | ||
* Prefix test-output id and ignore that in noglobals check. Fixes #212 | ||
* Only check for an exports object to detect a CommonJS enviroment. Fixes #237 - Incompatibility with require.js | ||
* Add testswarm integration as grunt task | ||
* Added padding on URL config checkboxes. | ||
* Cleanup composite addon: Use callback registration instead of overwriting them. Set the correct src on rerun link (and dblclick). Remove the composite test itself, as that was a crazy hack not worth maintaining | ||
* Cleanup reset() test and usage - run testDone callback first, to allow listeneres ignoring reset assertions | ||
* Double clicking on composite test rows opens individual test page | ||
* test-message for all message-bearing API reporting details | ||
1.5.0 / 2012-04-04 | ||
================== | ||
* Modify "Running..." to display test name. Fixes #220 | ||
* Fixed clearing of sessionStorage in Firefox 3.6. | ||
* Fixes #217 by calling "block" with config.current.testEnvironment | ||
* Add stats results to data. QUnit.jUnitReport function take one argument { xml:'<?xml ...', results:{failed:0, passed:0, total:0, time:0} } | ||
* Add link to MDN about stack property | ||
1.4.0 / 2012-03-10 | ||
================== | ||
* Prefix test-related session-storage items to make removal more specific. Fixes #213 - Keep hide-passed state when clearing session storage | ||
* Update grunt.js with seperate configs for qunit.js and grunt.js, also add tests but disable for now, not passing yet. Add grunt to devDependencies | ||
* typo | ||
* Cleanup grunt.js, no need for the banner | ||
* Fix lint errors and some formatting issues. Use QUnit.pushFailure for noglobals and global error handler. | ||
* Fix a missing expect in logs test | ||
* Add grunt.js configuration and include some usage instructions in the readme | ||
* Update package.json | ||
* Partially revert af27eae841c3e1c01c46de72d676f1047e1ee375 - can't move reset around, so also don't wrap in try-catch, as the result of that is effectively swallowed. Can't output the result as the outputting is already done. | ||
* Add QUnit.pushFailure to log error conditions like exceptions. Accepts stacktrace as second argument, allowing extraction with catched exceptions (useful even in Safari). Remove old fail() function that would just log to console, not useful anymore as regular test output is much more useful by now. Move up QUnit.reset() call to just make that another failed assertion. Used to not make a test fail. Fixes #210 | ||
* Update equals and same deprecations to use QUnit.push to provide correct source lines. Fixes #211 | ||
* Add a test file for narwhal integration. Has to use print instead of console.log. Fails when an assertion fails, something about setInterval... | ||
* Apply notrycatch option to setup and teardown as well. Fixes #203. Reorder noglobals check to allow teardown to remove globals that were introduced intentionally. Fixes #204 | ||
* Extend exports object with QUnit properties at the end of the file to export everything. | ||
* Output source line for ok() assertions. Fixes #202 | ||
* Make test fail if no assertions run. Fixes #178 | ||
* Sort object output alphabetically in order to improve diffs of objects where properties were set in a different order. Fixes #206 | ||
* Revert "Change fixture reset behavior", changing #194 and #195 to wontfix. | ||
1.3.0 / 2012-02-26 | ||
================== | ||
* Cleanup test markup | ||
* Fix the jQuery branch of fixture reset. Would break when no fixture exists. | ||
* Added initial version of a junitlogger addon. | ||
* Escape document.title before inserting into markup. Extends fix for #127 | ||
* Catch assertions running outside of test() context, make sure source is provided even for ok(). Fixes #98 | ||
* Improve global object access, based on comments for 1a9120651d5464773256d8a1f2cf2eabe38ea5b3 | ||
* Clear all sessionStorage entries once all tests passed. Helps getting rid of items from renamed tests. Fixes #101 | ||
* Set fixed dimensions for #qunit-fixture. Fixes #114 | ||
* Extend nodejs test runner to check for stacktrace output, twice | ||
* Extend nodejs test runner to check for stacktrace output | ||
* Generate more base markup, but allow the user to exclude that completelty or choose their own. Fixes #127 | ||
* Add a simple test file to check basic nodejs integration works | ||
* Check for global object to find setTimeout in node | ||
* Fix CommonJS export by assigning QUnit to module.exports. | ||
* Remove the testEnviromentArg to test(). Most obscure, never used anywhere. test() is still heavily overloaded with argument shifting, this makes it a little more sane. Fixes #172 | ||
* Serialize expected and actual values only when test fails. Speeds up output of valid tests, especially for lots of large objects. Fixes #183 | ||
* Fix sourceFromsTacktrace to get the right line in Firefox. Shift the 'error' line away in Chrome to get a match. | ||
* Fix references to test/deepEqual.js | ||
* In autorun mode, moduleDone is called without matching moduleStart. Fix issue #184 | ||
* Fixture test: allow anything falsy in test as getAttribute in oldIE will return empty string instead of null. We don't really care. | ||
* Keep label and checkbox together ( http://i.imgur.com/5Wk3A.png ) | ||
* Add readme for themes | ||
* Fix bad global in reset() | ||
* Some cleanup in theme addons | ||
* Update headers | ||
* Update nv.html, add gabe theme based on https://github.com/jquery/qunit/pull/188 | ||
* Experiemental custom theme based on https://github.com/jquery/qunit/pull/62 by NV | ||
* Replace deprecated same and equals aliases with placeholders that just throw errors, providing a hint at what to use instead. Rename test file to match that. | ||
* Can't rely on outerHTML for Firefox < 11. Use cloneNode instead. | ||
* Merge remote branch 'conzett/master' | ||
* Cleanup whitespace | ||
* Update sessionStorage support test to latest version from Modernizr, trying to setItem to avoid QUOTA_EXCEEDED_EXCEPTION | ||
* Change fixture reset behavior | ||
* Merge pull request #181 from simonz/development | ||
* Escaping test names | ||
* Show exception stack when test failed | ||
1.2.0 / 2011-11-24 | ||
@@ -35,3 +118,2 @@ ================== | ||
* Added more strict array type detection for dump output, and allowed NodeList objects to be output as arrays | ||
* Bump post-release version | ||
* Fixes a bug where after an async test, assertions could move between test cases because of internal state (config.current) being incorrectly set | ||
@@ -38,0 +120,0 @@ * Simplified check for assertion count and adjusted whitespace |
{ | ||
"name": "qunit", | ||
"author": "The jQuery Project", | ||
"version": "1.1.0pre", | ||
"contributors": [ | ||
{ | ||
"name": "John Resig", | ||
"email": "jeresig@gmail.com", | ||
"url": "http://ejohn.org/" | ||
}, | ||
{ | ||
"name": "Jörn Zaefferer", | ||
"email": "joern.zaefferer@googlemail.com", | ||
"url": "http://bassistance.de/" | ||
}], | ||
"url": "http://docs.jquery.com/QUnit", | ||
"repositories" : [{ | ||
"type": "git", | ||
"url": "https://github.com/jquery/qunit.git" | ||
}], | ||
"license": { | ||
"name": "MIT", | ||
"url": "http://www.opensource.org/licenses/mit-license.php" | ||
}, | ||
"description": "An easy-to-use JavaScript Unit Testing framework.", | ||
"keywords": [ "testing", "unit", "jquery" ], | ||
"main": "qunit/qunit.js" | ||
"name": "qunit", | ||
"title": "QUnit", | ||
"description": "An easy-to-use JavaScript Unit Testing framework.", | ||
"version": "1.6.0", | ||
"author": "The jQuery Project", | ||
"contributors": [ | ||
"John Resig <jeresig@gmail.com> (http://ejohn.org/)", | ||
"Jörn Zaefferer <joern.zaefferer@googlemail.com> (http://bassistance.de/)" | ||
], | ||
"homepage": "http://docs.jquery.com/QUnit", | ||
"repository": { | ||
"type": "git", | ||
"url": "git://github.com/jquery/qunit.git" | ||
}, | ||
"bugs": { | ||
"url": "https://github.com/jquery/qunit/issues" | ||
}, | ||
"license": { | ||
"name": "MIT", | ||
"url": "http://www.opensource.org/licenses/mit-license.php" | ||
}, | ||
"keywords": [ | ||
"testing", | ||
"unit", | ||
"jquery" | ||
], | ||
"main": "qunit/qunit.js", | ||
"devDependencies": { | ||
"grunt": "0.3.x", | ||
"testswarm": "0.2.1" | ||
} | ||
} |
/** | ||
* QUnit v1.2.0 - A JavaScript Unit Testing Framework | ||
* QUnit v1.6.0 - A JavaScript Unit Testing Framework | ||
* | ||
* http://docs.jquery.com/QUnit | ||
* | ||
* Copyright (c) 2011 John Resig, Jörn Zaefferer | ||
* Copyright (c) 2012 John Resig, Jörn Zaefferer | ||
* Dual licensed under the MIT (MIT-LICENSE.txt) | ||
@@ -11,38 +11,46 @@ * or GPL (GPL-LICENSE.txt) licenses. | ||
(function(window) { | ||
(function( window ) { | ||
var defined = { | ||
var QUnit, | ||
config, | ||
testId = 0, | ||
toString = Object.prototype.toString, | ||
hasOwn = Object.prototype.hasOwnProperty, | ||
defined = { | ||
setTimeout: typeof window.setTimeout !== "undefined", | ||
sessionStorage: (function() { | ||
var x = "qunit-test-string"; | ||
try { | ||
return !!sessionStorage.getItem; | ||
} catch(e) { | ||
sessionStorage.setItem( x, x ); | ||
sessionStorage.removeItem( x ); | ||
return true; | ||
} catch( e ) { | ||
return false; | ||
} | ||
})() | ||
}()) | ||
}; | ||
var testId = 0, | ||
toString = Object.prototype.toString, | ||
hasOwn = Object.prototype.hasOwnProperty; | ||
var Test = function(name, testName, expected, testEnvironmentArg, async, callback) { | ||
function Test( name, testName, expected, async, callback ) { | ||
this.name = name; | ||
this.testName = testName; | ||
this.expected = expected; | ||
this.testEnvironmentArg = testEnvironmentArg; | ||
this.async = async; | ||
this.callback = callback; | ||
this.assertions = []; | ||
}; | ||
} | ||
Test.prototype = { | ||
init: function() { | ||
var tests = id("qunit-tests"); | ||
if (tests) { | ||
var b = document.createElement("strong"); | ||
b.innerHTML = "Running " + this.name; | ||
var li = document.createElement("li"); | ||
li.appendChild( b ); | ||
li.className = "running"; | ||
li.id = this.id = "test-output" + testId++; | ||
var b, li, | ||
tests = id( "qunit-tests" ); | ||
if ( tests ) { | ||
b = document.createElement( "strong" ); | ||
b.innerHTML = "Running " + this.name; | ||
li = document.createElement( "li" ); | ||
li.appendChild( b ); | ||
li.className = "running"; | ||
li.id = this.id = "qunit-test-output" + testId++; | ||
tests.appendChild( li ); | ||
@@ -52,5 +60,5 @@ } | ||
setup: function() { | ||
if (this.module != config.previousModule) { | ||
if ( this.module !== config.previousModule ) { | ||
if ( config.previousModule ) { | ||
runLoggingCallbacks('moduleDone', QUnit, { | ||
runLoggingCallbacks( "moduleDone", QUnit, { | ||
name: config.previousModule, | ||
@@ -60,21 +68,23 @@ failed: config.moduleStats.bad, | ||
total: config.moduleStats.all | ||
} ); | ||
}); | ||
} | ||
config.previousModule = this.module; | ||
config.moduleStats = { all: 0, bad: 0 }; | ||
runLoggingCallbacks( 'moduleStart', QUnit, { | ||
runLoggingCallbacks( "moduleStart", QUnit, { | ||
name: this.module | ||
} ); | ||
}); | ||
} else if ( config.autorun ) { | ||
runLoggingCallbacks( "moduleStart", QUnit, { | ||
name: this.module | ||
}); | ||
} | ||
config.current = this; | ||
this.testEnvironment = extend({ | ||
setup: function() {}, | ||
teardown: function() {} | ||
}, this.moduleTestEnvironment); | ||
if (this.testEnvironmentArg) { | ||
extend(this.testEnvironment, this.testEnvironmentArg); | ||
} | ||
}, this.moduleTestEnvironment ); | ||
runLoggingCallbacks( 'testStart', QUnit, { | ||
runLoggingCallbacks( "testStart", QUnit, { | ||
name: this.testName, | ||
@@ -88,10 +98,13 @@ module: this.module | ||
if ( !config.pollution ) { | ||
saveGlobal(); | ||
} | ||
if ( config.notrycatch ) { | ||
this.testEnvironment.setup.call( this.testEnvironment ); | ||
return; | ||
} | ||
try { | ||
if ( !config.pollution ) { | ||
saveGlobal(); | ||
} | ||
this.testEnvironment.setup.call(this.testEnvironment); | ||
} catch(e) { | ||
QUnit.ok( false, "Setup failed on " + this.testName + ": " + e.message ); | ||
this.testEnvironment.setup.call( this.testEnvironment ); | ||
} catch( e ) { | ||
QUnit.pushFailure( "Setup failed on " + this.testName + ": " + e.message, extractStacktrace( e, 1 ) ); | ||
} | ||
@@ -101,2 +114,9 @@ }, | ||
config.current = this; | ||
var running = id( "qunit-testresult" ); | ||
if ( running ) { | ||
running.innerHTML = "Running: <br/>" + this.name; | ||
} | ||
if ( this.async ) { | ||
@@ -107,10 +127,10 @@ QUnit.stop(); | ||
if ( config.notrycatch ) { | ||
this.callback.call(this.testEnvironment); | ||
this.callback.call( this.testEnvironment ); | ||
return; | ||
} | ||
try { | ||
this.callback.call(this.testEnvironment); | ||
} catch(e) { | ||
fail("Test " + this.testName + " died, exception and test follows", e, this.callback); | ||
QUnit.ok( false, "Died on test #" + (this.assertions.length + 1) + ": " + e.message + " - " + QUnit.jsDump.parse(e) ); | ||
this.callback.call( this.testEnvironment ); | ||
} catch( e ) { | ||
QUnit.pushFailure( "Died on test #" + (this.assertions.length + 1) + ": " + e.message, extractStacktrace( e, 1 ) ); | ||
// else next test will carry the responsibility | ||
@@ -127,8 +147,13 @@ saveGlobal(); | ||
config.current = this; | ||
try { | ||
this.testEnvironment.teardown.call(this.testEnvironment); | ||
checkPollution(); | ||
} catch(e) { | ||
QUnit.ok( false, "Teardown failed on " + this.testName + ": " + e.message ); | ||
if ( config.notrycatch ) { | ||
this.testEnvironment.teardown.call( this.testEnvironment ); | ||
return; | ||
} else { | ||
try { | ||
this.testEnvironment.teardown.call( this.testEnvironment ); | ||
} catch( e ) { | ||
QUnit.pushFailure( "Teardown failed on " + this.testName + ": " + e.message, extractStacktrace( e, 1 ) ); | ||
} | ||
} | ||
checkPollution(); | ||
}, | ||
@@ -138,7 +163,11 @@ finish: function() { | ||
if ( this.expected != null && this.expected != this.assertions.length ) { | ||
QUnit.ok( false, "Expected " + this.expected + " assertions, but " + this.assertions.length + " were run" ); | ||
QUnit.pushFailure( "Expected " + this.expected + " assertions, but " + this.assertions.length + " were run", this.stack ); | ||
} else if ( this.expected == null && !this.assertions.length ) { | ||
QUnit.pushFailure( "Expected at least one assertion, but none were run - call expect(0) to accept zero assertions.", this.stack ); | ||
} | ||
var good = 0, bad = 0, | ||
tests = id("qunit-tests"); | ||
var assertion, a, b, i, li, ol, | ||
good = 0, | ||
bad = 0, | ||
tests = id( "qunit-tests" ); | ||
@@ -149,10 +178,10 @@ config.stats.all += this.assertions.length; | ||
if ( tests ) { | ||
var ol = document.createElement("ol"); | ||
ol = document.createElement( "ol" ); | ||
for ( var i = 0; i < this.assertions.length; i++ ) { | ||
var assertion = this.assertions[i]; | ||
for ( i = 0; i < this.assertions.length; i++ ) { | ||
assertion = this.assertions[i]; | ||
var li = document.createElement("li"); | ||
li = document.createElement( "li" ); | ||
li.className = assertion.result ? "pass" : "fail"; | ||
li.innerHTML = assertion.message || (assertion.result ? "okay" : "failed"); | ||
li.innerHTML = assertion.message || ( assertion.result ? "okay" : "failed" ); | ||
ol.appendChild( li ); | ||
@@ -171,19 +200,21 @@ | ||
if ( QUnit.config.reorder && defined.sessionStorage ) { | ||
if (bad) { | ||
sessionStorage.setItem("qunit-" + this.module + "-" + this.testName, bad); | ||
if ( bad ) { | ||
sessionStorage.setItem( "qunit-test-" + this.module + "-" + this.testName, bad ); | ||
} else { | ||
sessionStorage.removeItem("qunit-" + this.module + "-" + this.testName); | ||
sessionStorage.removeItem( "qunit-test-" + this.module + "-" + this.testName ); | ||
} | ||
} | ||
if (bad == 0) { | ||
if ( bad === 0 ) { | ||
ol.style.display = "none"; | ||
} | ||
var b = document.createElement("strong"); | ||
// `b` initialized at top of scope | ||
b = document.createElement( "strong" ); | ||
b.innerHTML = this.name + " <b class='counts'>(<b class='failed'>" + bad + "</b>, <b class='passed'>" + good + "</b>, " + this.assertions.length + ")</b>"; | ||
var a = document.createElement("a"); | ||
// `a` initialized at top of scope | ||
a = document.createElement( "a" ); | ||
a.innerHTML = "Rerun"; | ||
a.href = QUnit.url({ filter: getText([b]).replace(/\([^)]+\)$/, "").replace(/(^\s*|\s*$)/g, "") }); | ||
a.href = QUnit.url({ filter: getText([b]).replace( /\([^)]+\)$/, "" ).replace( /(^\s*|\s*$)/g, "" ) }); | ||
@@ -196,3 +227,3 @@ addEvent(b, "click", function() { | ||
addEvent(b, "dblclick", function(e) { | ||
addEvent(b, "dblclick", function( e ) { | ||
var target = e && e.target ? e.target : window.event.srcElement; | ||
@@ -203,7 +234,10 @@ if ( target.nodeName.toLowerCase() == "span" || target.nodeName.toLowerCase() == "b" ) { | ||
if ( window.location && target.nodeName.toLowerCase() === "strong" ) { | ||
window.location = QUnit.url({ filter: getText([target]).replace(/\([^)]+\)$/, "").replace(/(^\s*|\s*$)/g, "") }); | ||
window.location = QUnit.url({ | ||
filter: getText([target]).replace( /\([^)]+\)$/, "" ).replace( /(^\s*|\s*$)/g, "" ) | ||
}); | ||
} | ||
}); | ||
var li = id(this.id); | ||
// `li` initialized at top of scope | ||
li = id( this.id ); | ||
li.className = bad ? "fail" : "pass"; | ||
@@ -216,3 +250,3 @@ li.removeChild( li.firstChild ); | ||
} else { | ||
for ( var i = 0; i < this.assertions.length; i++ ) { | ||
for ( i = 0; i < this.assertions.length; i++ ) { | ||
if ( !this.assertions[i].result ) { | ||
@@ -226,9 +260,3 @@ bad++; | ||
try { | ||
QUnit.reset(); | ||
} catch(e) { | ||
fail("reset() failed, following Test " + this.testName + ", exception and reset fn follows", e, QUnit.reset); | ||
} | ||
runLoggingCallbacks( 'testDone', QUnit, { | ||
runLoggingCallbacks( "testDone", QUnit, { | ||
name: this.testName, | ||
@@ -239,7 +267,11 @@ module: this.module, | ||
total: this.assertions.length | ||
} ); | ||
}); | ||
QUnit.reset(); | ||
}, | ||
queue: function() { | ||
var test = this; | ||
var bad, | ||
test = this; | ||
synchronize(function() { | ||
@@ -263,17 +295,21 @@ test.init(); | ||
} | ||
// `bad` initialized at top of scope | ||
// defer when previous test run passed, if storage is available | ||
var bad = QUnit.config.reorder && defined.sessionStorage && +sessionStorage.getItem("qunit-" + this.module + "-" + this.testName); | ||
if (bad) { | ||
bad = QUnit.config.reorder && defined.sessionStorage && | ||
+sessionStorage.getItem( "qunit-test-" + this.module + "-" + this.testName ); | ||
if ( bad ) { | ||
run(); | ||
} else { | ||
synchronize(run, true); | ||
}; | ||
synchronize( run, true ); | ||
} | ||
} | ||
}; | ||
var QUnit = { | ||
// `QUnit` initialized at top of scope | ||
QUnit = { | ||
// call on start of module test to prepend name to all tests | ||
module: function(name, testEnvironment) { | ||
module: function( name, testEnvironment ) { | ||
config.currentModule = name; | ||
@@ -283,3 +319,3 @@ config.currentModuleTestEnviroment = testEnvironment; | ||
asyncTest: function(testName, expected, callback) { | ||
asyncTest: function( testName, expected, callback ) { | ||
if ( arguments.length === 2 ) { | ||
@@ -290,7 +326,8 @@ callback = expected; | ||
QUnit.test(testName, expected, callback, true); | ||
QUnit.test( testName, expected, callback, true ); | ||
}, | ||
test: function(testName, expected, callback, async) { | ||
var name = '<span class="test-name">' + testName + '</span>', testEnvironmentArg; | ||
test: function( testName, expected, callback, async ) { | ||
var test, | ||
name = "<span class='test-name'>" + escapeInnerText( testName ) + "</span>"; | ||
@@ -301,10 +338,5 @@ if ( arguments.length === 2 ) { | ||
} | ||
// is 2nd argument a testEnvironment? | ||
if ( expected && typeof expected === 'object') { | ||
testEnvironmentArg = expected; | ||
expected = null; | ||
} | ||
if ( config.currentModule ) { | ||
name = '<span class="module-name">' + config.currentModule + "</span>: " + name; | ||
name = "<span class='module-name'>" + config.currentModule + "</span>: " + name; | ||
} | ||
@@ -316,29 +348,41 @@ | ||
var test = new Test(name, testName, expected, testEnvironmentArg, async, callback); | ||
test = new Test( name, testName, expected, async, callback ); | ||
test.module = config.currentModule; | ||
test.moduleTestEnvironment = config.currentModuleTestEnviroment; | ||
test.stack = sourceFromStacktrace( 2 ); | ||
test.queue(); | ||
}, | ||
/** | ||
* Specify the number of expected assertions to gurantee that failed test (no assertions are run at all) don't slip through. | ||
*/ | ||
expect: function(asserts) { | ||
// Specify the number of expected assertions to gurantee that failed test (no assertions are run at all) don't slip through. | ||
expect: function( asserts ) { | ||
config.current.expected = asserts; | ||
}, | ||
/** | ||
* Asserts true. | ||
* @example ok( "asdfasdf".length > 5, "There must be at least 5 chars" ); | ||
*/ | ||
ok: function(a, msg) { | ||
a = !!a; | ||
var details = { | ||
result: a, | ||
message: msg | ||
}; | ||
msg = escapeInnerText(msg); | ||
runLoggingCallbacks( 'log', QUnit, details ); | ||
// Asserts true. | ||
// @example ok( "asdfasdf".length > 5, "There must be at least 5 chars" ); | ||
ok: function( result, msg ) { | ||
if ( !config.current ) { | ||
throw new Error( "ok() assertion outside test context, was " + sourceFromStacktrace(2) ); | ||
} | ||
result = !!result; | ||
var source, | ||
details = { | ||
result: result, | ||
message: msg | ||
}; | ||
msg = escapeInnerText( msg || (result ? "okay" : "failed" ) ); | ||
msg = "<span class='test-message'>" + msg + "</span>"; | ||
if ( !result ) { | ||
source = sourceFromStacktrace( 2 ); | ||
if ( source ) { | ||
details.source = source; | ||
msg += "<table><tr class='test-source'><th>Source: </th><td><pre>" + escapeInnerText( source ) + "</pre></td></tr></table>"; | ||
} | ||
} | ||
runLoggingCallbacks( "log", QUnit, details ); | ||
config.current.assertions.push({ | ||
result: a, | ||
result: result, | ||
message: msg | ||
@@ -348,42 +392,33 @@ }); | ||
/** | ||
* Checks that the first two arguments are equal, with an optional message. | ||
* Prints out both actual and expected values. | ||
* | ||
* Prefered to ok( actual == expected, message ) | ||
* | ||
* @example equal( format("Received {0} bytes.", 2), "Received 2 bytes." ); | ||
* | ||
* @param Object actual | ||
* @param Object expected | ||
* @param String message (optional) | ||
*/ | ||
equal: function(actual, expected, message) { | ||
QUnit.push(expected == actual, actual, expected, message); | ||
// Checks that the first two arguments are equal, with an optional message. Prints out both actual and expected values. | ||
// @example equal( format( "Received {0} bytes.", 2), "Received 2 bytes." ); | ||
equal: function( actual, expected, message ) { | ||
QUnit.push( expected == actual, actual, expected, message ); | ||
}, | ||
notEqual: function(actual, expected, message) { | ||
QUnit.push(expected != actual, actual, expected, message); | ||
notEqual: function( actual, expected, message ) { | ||
QUnit.push( expected != actual, actual, expected, message ); | ||
}, | ||
deepEqual: function(actual, expected, message) { | ||
QUnit.push(QUnit.equiv(actual, expected), actual, expected, message); | ||
deepEqual: function( actual, expected, message ) { | ||
QUnit.push( QUnit.equiv(actual, expected), actual, expected, message ); | ||
}, | ||
notDeepEqual: function(actual, expected, message) { | ||
QUnit.push(!QUnit.equiv(actual, expected), actual, expected, message); | ||
notDeepEqual: function( actual, expected, message ) { | ||
QUnit.push( !QUnit.equiv(actual, expected), actual, expected, message ); | ||
}, | ||
strictEqual: function(actual, expected, message) { | ||
QUnit.push(expected === actual, actual, expected, message); | ||
strictEqual: function( actual, expected, message ) { | ||
QUnit.push( expected === actual, actual, expected, message ); | ||
}, | ||
notStrictEqual: function(actual, expected, message) { | ||
QUnit.push(expected !== actual, actual, expected, message); | ||
notStrictEqual: function( actual, expected, message ) { | ||
QUnit.push( expected !== actual, actual, expected, message ); | ||
}, | ||
raises: function(block, expected, message) { | ||
var actual, ok = false; | ||
raises: function( block, expected, message ) { | ||
var actual, | ||
ok = false; | ||
if (typeof expected === 'string') { | ||
if ( typeof expected === "string" ) { | ||
message = expected; | ||
@@ -394,3 +429,3 @@ expected = null; | ||
try { | ||
block(); | ||
block.call( config.current.testEnvironment ); | ||
} catch (e) { | ||
@@ -400,14 +435,14 @@ actual = e; | ||
if (actual) { | ||
if ( actual ) { | ||
// we don't want to validate thrown error | ||
if (!expected) { | ||
if ( !expected ) { | ||
ok = true; | ||
// expected is a regexp | ||
} else if (QUnit.objectType(expected) === "regexp") { | ||
ok = expected.test(actual); | ||
} else if ( QUnit.objectType( expected ) === "regexp" ) { | ||
ok = expected.test( actual ); | ||
// expected is a constructor | ||
} else if (actual instanceof expected) { | ||
} else if ( actual instanceof expected ) { | ||
ok = true; | ||
// expected is a validation function which returns true is validation passed | ||
} else if (expected.call({}, actual) === true) { | ||
} else if ( expected.call( {}, actual ) === true ) { | ||
ok = true; | ||
@@ -417,13 +452,13 @@ } | ||
QUnit.ok(ok, message); | ||
QUnit.ok( ok, message ); | ||
}, | ||
start: function(count) { | ||
start: function( count ) { | ||
config.semaphore -= count || 1; | ||
if (config.semaphore > 0) { | ||
// don't start until equal number of stop-calls | ||
// don't start until equal number of stop-calls | ||
if ( config.semaphore > 0 ) { | ||
return; | ||
} | ||
if (config.semaphore < 0) { | ||
// ignore if start is called more often then stop | ||
// ignore if start is called more often then stop | ||
if ( config.semaphore < 0 ) { | ||
config.semaphore = 0; | ||
@@ -434,19 +469,19 @@ } | ||
window.setTimeout(function() { | ||
if (config.semaphore > 0) { | ||
if ( config.semaphore > 0 ) { | ||
return; | ||
} | ||
if ( config.timeout ) { | ||
clearTimeout(config.timeout); | ||
clearTimeout( config.timeout ); | ||
} | ||
config.blocking = false; | ||
process(true); | ||
process( true ); | ||
}, 13); | ||
} else { | ||
config.blocking = false; | ||
process(true); | ||
process( true ); | ||
} | ||
}, | ||
stop: function(count) { | ||
stop: function( count ) { | ||
config.semaphore += count || 1; | ||
@@ -456,3 +491,3 @@ config.blocking = true; | ||
if ( config.testTimeout && defined.setTimeout ) { | ||
clearTimeout(config.timeout); | ||
clearTimeout( config.timeout ); | ||
config.timeout = window.setTimeout(function() { | ||
@@ -462,3 +497,3 @@ QUnit.ok( false, "Test timed out" ); | ||
QUnit.start(); | ||
}, config.testTimeout); | ||
}, config.testTimeout ); | ||
} | ||
@@ -468,17 +503,23 @@ } | ||
//We want access to the constructor's prototype | ||
// We want access to the constructor's prototype | ||
(function() { | ||
function F(){}; | ||
function F() {} | ||
F.prototype = QUnit; | ||
QUnit = new F(); | ||
//Make F QUnit's constructor so that we can add to the prototype later | ||
// Make F QUnit's constructor so that we can add to the prototype later | ||
QUnit.constructor = F; | ||
})(); | ||
}()); | ||
// Backwards compatibility, deprecated | ||
QUnit.equals = QUnit.equal; | ||
QUnit.same = QUnit.deepEqual; | ||
// deprecated; still export them to window to provide clear error messages | ||
// next step: remove entirely | ||
QUnit.equals = function() { | ||
QUnit.push( false, false, false, "QUnit.equals has been deprecated since 2009 (e88049a0), use QUnit.equal instead" ); | ||
}; | ||
QUnit.same = function() { | ||
QUnit.push( false, false, false, "QUnit.same has been deprecated since 2009 (e88049a0), use QUnit.deepEqual instead" ); | ||
}; | ||
// Maintain internal state | ||
var config = { | ||
// `config` initialized at top of scope | ||
config = { | ||
// The queue of tests to run | ||
@@ -501,5 +542,5 @@ queue: [], | ||
urlConfig: ['noglobals', 'notrycatch'], | ||
urlConfig: [ "noglobals", "notrycatch" ], | ||
//logging callback queues | ||
// logging callback queues | ||
begin: [], | ||
@@ -516,3 +557,4 @@ done: [], | ||
(function() { | ||
var location = window.location || { search: "", protocol: "file:" }, | ||
var i, | ||
location = window.location || { search: "", protocol: "file:" }, | ||
params = location.search.slice( 1 ).split( "&" ), | ||
@@ -524,3 +566,3 @@ length = params.length, | ||
if ( params[ 0 ] ) { | ||
for ( var i = 0; i < length; i++ ) { | ||
for ( i = 0; i < length; i++ ) { | ||
current = params[ i ].split( "=" ); | ||
@@ -538,17 +580,14 @@ current[ 0 ] = decodeURIComponent( current[ 0 ] ); | ||
// Figure out if we're running the tests from a server or not | ||
QUnit.isLocal = !!(location.protocol === 'file:'); | ||
})(); | ||
QUnit.isLocal = location.protocol === "file:"; | ||
}()); | ||
// Expose the API as global variables, unless an 'exports' | ||
// object exists, in that case we assume we're in CommonJS | ||
if ( typeof exports === "undefined" || typeof require === "undefined" ) { | ||
extend(window, QUnit); | ||
// Expose the API as global variables, unless an 'exports' object exists, | ||
// in that case we assume we're in CommonJS - export everything at the end | ||
if ( typeof exports === "undefined" ) { | ||
extend( window, QUnit ); | ||
window.QUnit = QUnit; | ||
} else { | ||
extend(exports, QUnit); | ||
exports.QUnit = QUnit; | ||
} | ||
// define these after exposing globals to keep them in these QUnit namespace only | ||
extend(QUnit, { | ||
extend( QUnit, { | ||
config: config, | ||
@@ -558,6 +597,6 @@ | ||
init: function() { | ||
extend(config, { | ||
extend( config, { | ||
stats: { all: 0, bad: 0 }, | ||
moduleStats: { all: 0, bad: 0 }, | ||
started: +new Date, | ||
started: +new Date(), | ||
updateRate: 1000, | ||
@@ -572,6 +611,18 @@ blocking: false, | ||
var tests = id( "qunit-tests" ), | ||
banner = id( "qunit-banner" ), | ||
result = id( "qunit-testresult" ); | ||
var tests, banner, result, | ||
qunit = id( "qunit" ); | ||
if ( qunit ) { | ||
qunit.innerHTML = | ||
"<h1 id='qunit-header'>" + escapeInnerText( document.title ) + "</h1>" + | ||
"<h2 id='qunit-banner'></h2>" + | ||
"<div id='qunit-testrunner-toolbar'></div>" + | ||
"<h2 id='qunit-userAgent'></h2>" + | ||
"<ol id='qunit-tests'></ol>"; | ||
} | ||
tests = id( "qunit-tests" ); | ||
banner = id( "qunit-banner" ); | ||
result = id( "qunit-testresult" ); | ||
if ( tests ) { | ||
@@ -594,18 +645,17 @@ tests.innerHTML = ""; | ||
tests.parentNode.insertBefore( result, tests ); | ||
result.innerHTML = 'Running...<br/> '; | ||
result.innerHTML = "Running...<br/> "; | ||
} | ||
}, | ||
/** | ||
* Resets the test setup. Useful for tests that modify the DOM. | ||
* | ||
* If jQuery is available, uses jQuery's html(), otherwise just innerHTML. | ||
*/ | ||
// Resets the test setup. Useful for tests that modify the DOM. | ||
// If jQuery is available, uses jQuery's html(), otherwise just innerHTML. | ||
reset: function() { | ||
var fixture; | ||
if ( window.jQuery ) { | ||
jQuery( "#qunit-fixture" ).html( config.fixture ); | ||
} else { | ||
var main = id( 'qunit-fixture' ); | ||
if ( main ) { | ||
main.innerHTML = config.fixture; | ||
fixture = id( "qunit-fixture" ); | ||
if ( fixture ) { | ||
fixture.innerHTML = config.fixture; | ||
} | ||
@@ -615,19 +665,13 @@ } | ||
/** | ||
* Trigger an event on an element. | ||
* | ||
* @example triggerEvent( document.body, "click" ); | ||
* | ||
* @param DOMElement elem | ||
* @param String type | ||
*/ | ||
// Trigger an event on an element. | ||
// @example triggerEvent( document.body, "click" ); | ||
triggerEvent: function( elem, type, event ) { | ||
if ( document.createEvent ) { | ||
event = document.createEvent("MouseEvents"); | ||
event = document.createEvent( "MouseEvents" ); | ||
event.initMouseEvent(type, true, true, elem.ownerDocument.defaultView, | ||
0, 0, 0, 0, 0, false, false, false, false, 0, null); | ||
elem.dispatchEvent( event ); | ||
} else if ( elem.fireEvent ) { | ||
elem.fireEvent("on"+type); | ||
elem.fireEvent( "on" + type ); | ||
} | ||
@@ -642,30 +686,28 @@ }, | ||
objectType: function( obj ) { | ||
if (typeof obj === "undefined") { | ||
if ( typeof obj === "undefined" ) { | ||
return "undefined"; | ||
// consider: typeof null === object | ||
} | ||
if (obj === null) { | ||
if ( obj === null ) { | ||
return "null"; | ||
} | ||
var type = toString.call( obj ).match(/^\[object\s(.*)\]$/)[1] || ''; | ||
var type = toString.call( obj ).match(/^\[object\s(.*)\]$/)[1] || ""; | ||
switch (type) { | ||
case 'Number': | ||
if (isNaN(obj)) { | ||
return "nan"; | ||
} else { | ||
return "number"; | ||
} | ||
case 'String': | ||
case 'Boolean': | ||
case 'Array': | ||
case 'Date': | ||
case 'RegExp': | ||
case 'Function': | ||
return type.toLowerCase(); | ||
switch ( type ) { | ||
case "Number": | ||
if ( isNaN(obj) ) { | ||
return "nan"; | ||
} | ||
return "number"; | ||
case "String": | ||
case "Boolean": | ||
case "Array": | ||
case "Date": | ||
case "RegExp": | ||
case "Function": | ||
return type.toLowerCase(); | ||
} | ||
if (typeof obj === "object") { | ||
return "object"; | ||
if ( typeof obj === "object" ) { | ||
return "object"; | ||
} | ||
@@ -675,29 +717,40 @@ return undefined; | ||
push: function(result, actual, expected, message) { | ||
var details = { | ||
result: result, | ||
message: message, | ||
actual: actual, | ||
expected: expected | ||
}; | ||
push: function( result, actual, expected, message ) { | ||
if ( !config.current ) { | ||
throw new Error( "assertion outside test context, was " + sourceFromStacktrace() ); | ||
} | ||
message = escapeInnerText(message) || (result ? "okay" : "failed"); | ||
message = '<span class="test-message">' + message + "</span>"; | ||
expected = escapeInnerText(QUnit.jsDump.parse(expected)); | ||
actual = escapeInnerText(QUnit.jsDump.parse(actual)); | ||
var output = message + '<table><tr class="test-expected"><th>Expected: </th><td><pre>' + expected + '</pre></td></tr>'; | ||
if (actual != expected) { | ||
output += '<tr class="test-actual"><th>Result: </th><td><pre>' + actual + '</pre></td></tr>'; | ||
output += '<tr class="test-diff"><th>Diff: </th><td><pre>' + QUnit.diff(expected, actual) +'</pre></td></tr>'; | ||
} | ||
if (!result) { | ||
var source = sourceFromStacktrace(); | ||
if (source) { | ||
var output, source, | ||
details = { | ||
result: result, | ||
message: message, | ||
actual: actual, | ||
expected: expected | ||
}; | ||
message = escapeInnerText( message ) || ( result ? "okay" : "failed" ); | ||
message = "<span class='test-message'>" + message + "</span>"; | ||
output = message; | ||
if ( !result ) { | ||
expected = escapeInnerText( QUnit.jsDump.parse(expected) ); | ||
actual = escapeInnerText( QUnit.jsDump.parse(actual) ); | ||
output += "<table><tr class='test-expected'><th>Expected: </th><td><pre>" + expected + "</pre></td></tr>"; | ||
if ( actual != expected ) { | ||
output += "<tr class='test-actual'><th>Result: </th><td><pre>" + actual + "</pre></td></tr>"; | ||
output += "<tr class='test-diff'><th>Diff: </th><td><pre>" + QUnit.diff( expected, actual ) + "</pre></td></tr>"; | ||
} | ||
source = sourceFromStacktrace(); | ||
if ( source ) { | ||
details.source = source; | ||
output += '<tr class="test-source"><th>Source: </th><td><pre>' + escapeInnerText(source) + '</pre></td></tr>'; | ||
output += "<tr class='test-source'><th>Source: </th><td><pre>" + escapeInnerText( source ) + "</pre></td></tr>"; | ||
} | ||
output += "</table>"; | ||
} | ||
output += "</table>"; | ||
runLoggingCallbacks( 'log', QUnit, details ); | ||
runLoggingCallbacks( "log", QUnit, details ); | ||
@@ -710,6 +763,31 @@ config.current.assertions.push({ | ||
pushFailure: function( message, source ) { | ||
var output, | ||
details = { | ||
result: false, | ||
message: message | ||
}; | ||
message = escapeInnerText(message ) || "error"; | ||
message = "<span class='test-message'>" + message + "</span>"; | ||
output = message; | ||
if ( source ) { | ||
details.source = source; | ||
output += "<table><tr class='test-source'><th>Source: </th><td><pre>" + escapeInnerText( source ) + "</pre></td></tr></table>"; | ||
} | ||
runLoggingCallbacks( "log", QUnit, details ); | ||
config.current.assertions.push({ | ||
result: false, | ||
message: output | ||
}); | ||
}, | ||
url: function( params ) { | ||
params = extend( extend( {}, QUnit.urlParams ), params ); | ||
var querystring = "?", | ||
key; | ||
var key, | ||
querystring = "?"; | ||
for ( key in params ) { | ||
@@ -730,21 +808,21 @@ if ( !hasOwn.call( params, key ) ) { | ||
//QUnit.constructor is set to the empty F() above so that we can add to it's prototype later | ||
//Doing this allows us to tell if the following methods have been overwritten on the actual | ||
//QUnit object, which is a deprecated way of using the callbacks. | ||
extend(QUnit.constructor.prototype, { | ||
// QUnit.constructor is set to the empty F() above so that we can add to it's prototype later | ||
// Doing this allows us to tell if the following methods have been overwritten on the actual | ||
// QUnit object, which is a deprecated way of using the callbacks. | ||
extend( QUnit.constructor.prototype, { | ||
// Logging callbacks; all receive a single argument with the listed properties | ||
// run test/logs.html for any related changes | ||
begin: registerLoggingCallback('begin'), | ||
begin: registerLoggingCallback( "begin" ), | ||
// done: { failed, passed, total, runtime } | ||
done: registerLoggingCallback('done'), | ||
done: registerLoggingCallback( "done" ), | ||
// log: { result, actual, expected, message } | ||
log: registerLoggingCallback('log'), | ||
log: registerLoggingCallback( "log" ), | ||
// testStart: { name } | ||
testStart: registerLoggingCallback('testStart'), | ||
testStart: registerLoggingCallback( "testStart" ), | ||
// testDone: { name, failed, passed, total } | ||
testDone: registerLoggingCallback('testDone'), | ||
testDone: registerLoggingCallback( "testDone" ), | ||
// moduleStart: { name } | ||
moduleStart: registerLoggingCallback('moduleStart'), | ||
moduleStart: registerLoggingCallback( "moduleStart" ), | ||
// moduleDone: { name, failed, passed, total } | ||
moduleDone: registerLoggingCallback('moduleDone') | ||
moduleDone: registerLoggingCallback( "moduleDone" ) | ||
}); | ||
@@ -757,6 +835,9 @@ | ||
QUnit.load = function() { | ||
runLoggingCallbacks( 'begin', QUnit, {} ); | ||
runLoggingCallbacks( "begin", QUnit, {} ); | ||
// Initialize the config, saving the execution queue | ||
var oldconfig = extend({}, config); | ||
var banner, filter, i, label, len, main, ol, toolbar, userAgent, val, | ||
urlConfigHtml = "", | ||
oldconfig = extend( {}, config ); | ||
QUnit.init(); | ||
@@ -767,15 +848,20 @@ extend(config, oldconfig); | ||
var urlConfigHtml = '', len = config.urlConfig.length; | ||
for ( var i = 0, val; i < len, val = config.urlConfig[i]; i++ ) { | ||
len = config.urlConfig.length; | ||
for ( i = 0; i < len; i++ ) { | ||
val = config.urlConfig[i]; | ||
config[val] = QUnit.urlParams[val]; | ||
urlConfigHtml += '<label><input name="' + val + '" type="checkbox"' + ( config[val] ? ' checked="checked"' : '' ) + '>' + val + '</label>'; | ||
urlConfigHtml += "<label><input name='" + val + "' type='checkbox'" + ( config[val] ? " checked='checked'" : "" ) + ">" + val + "</label>"; | ||
} | ||
var userAgent = id("qunit-userAgent"); | ||
// `userAgent` initialized at top of scope | ||
userAgent = id( "qunit-userAgent" ); | ||
if ( userAgent ) { | ||
userAgent.innerHTML = navigator.userAgent; | ||
} | ||
var banner = id("qunit-header"); | ||
// `banner` initialized at top of scope | ||
banner = id( "qunit-header" ); | ||
if ( banner ) { | ||
banner.innerHTML = '<a href="' + QUnit.url({ filter: undefined }) + '"> ' + banner.innerHTML + '</a> ' + urlConfigHtml; | ||
banner.innerHTML = "<a href='" + QUnit.url({ filter: undefined }) + "'>" + banner.innerHTML + "</a> " + urlConfigHtml; | ||
addEvent( banner, "change", function( event ) { | ||
@@ -788,26 +874,33 @@ var params = {}; | ||
var toolbar = id("qunit-testrunner-toolbar"); | ||
// `toolbar` initialized at top of scope | ||
toolbar = id( "qunit-testrunner-toolbar" ); | ||
if ( toolbar ) { | ||
var filter = document.createElement("input"); | ||
// `filter` initialized at top of scope | ||
filter = document.createElement( "input" ); | ||
filter.type = "checkbox"; | ||
filter.id = "qunit-filter-pass"; | ||
addEvent( filter, "click", function() { | ||
var ol = document.getElementById("qunit-tests"); | ||
var tmp, | ||
ol = document.getElementById( "qunit-tests" ); | ||
if ( filter.checked ) { | ||
ol.className = ol.className + " hidepass"; | ||
} else { | ||
var tmp = " " + ol.className.replace( /[\n\t\r]/g, " " ) + " "; | ||
ol.className = tmp.replace(/ hidepass /, " "); | ||
tmp = " " + ol.className.replace( /[\n\t\r]/g, " " ) + " "; | ||
ol.className = tmp.replace( / hidepass /, " " ); | ||
} | ||
if ( defined.sessionStorage ) { | ||
if (filter.checked) { | ||
sessionStorage.setItem("qunit-filter-passed-tests", "true"); | ||
sessionStorage.setItem( "qunit-filter-passed-tests", "true" ); | ||
} else { | ||
sessionStorage.removeItem("qunit-filter-passed-tests"); | ||
sessionStorage.removeItem( "qunit-filter-passed-tests" ); | ||
} | ||
} | ||
}); | ||
if ( config.hidepassed || defined.sessionStorage && sessionStorage.getItem("qunit-filter-passed-tests") ) { | ||
if ( config.hidepassed || defined.sessionStorage && sessionStorage.getItem( "qunit-filter-passed-tests" ) ) { | ||
filter.checked = true; | ||
var ol = document.getElementById("qunit-tests"); | ||
// `ol` initialized at top of scope | ||
ol = document.getElementById( "qunit-tests" ); | ||
ol.className = ol.className + " hidepass"; | ||
@@ -817,4 +910,5 @@ } | ||
var label = document.createElement("label"); | ||
label.setAttribute("for", "qunit-filter-pass"); | ||
// `label` initialized at top of scope | ||
label = document.createElement( "label" ); | ||
label.setAttribute( "for", "qunit-filter-pass" ); | ||
label.innerHTML = "Hide passed tests"; | ||
@@ -824,3 +918,4 @@ toolbar.appendChild( label ); | ||
var main = id('qunit-fixture'); | ||
// `main` initialized at top of scope | ||
main = id( "qunit-fixture" ); | ||
if ( main ) { | ||
@@ -830,3 +925,3 @@ config.fixture = main.innerHTML; | ||
if (config.autostart) { | ||
if ( config.autostart ) { | ||
QUnit.start(); | ||
@@ -836,11 +931,11 @@ } | ||
addEvent(window, "load", QUnit.load); | ||
addEvent( window, "load", QUnit.load ); | ||
// addEvent(window, "error") gives us a useless event object | ||
// addEvent(window, "error" ) gives us a useless event object | ||
window.onerror = function( message, file, line ) { | ||
if ( QUnit.config.current ) { | ||
ok( false, message + ", " + file + ":" + line ); | ||
QUnit.pushFailure( message, file + ":" + line ); | ||
} else { | ||
test( "global failure", function() { | ||
ok( false, message + ", " + file + ":" + line ); | ||
QUnit.test( "global failure", function() { | ||
QUnit.pushFailure( message, file + ":" + line ); | ||
}); | ||
@@ -855,3 +950,3 @@ } | ||
if ( config.currentModule ) { | ||
runLoggingCallbacks( 'moduleDone', QUnit, { | ||
runLoggingCallbacks( "moduleDone", QUnit, { | ||
name: config.currentModule, | ||
@@ -861,24 +956,25 @@ failed: config.moduleStats.bad, | ||
total: config.moduleStats.all | ||
} ); | ||
}); | ||
} | ||
var banner = id("qunit-banner"), | ||
tests = id("qunit-tests"), | ||
runtime = +new Date - config.started, | ||
var i, key, | ||
banner = id( "qunit-banner" ), | ||
tests = id( "qunit-tests" ), | ||
runtime = +new Date() - config.started, | ||
passed = config.stats.all - config.stats.bad, | ||
html = [ | ||
'Tests completed in ', | ||
"Tests completed in ", | ||
runtime, | ||
' milliseconds.<br/>', | ||
'<span class="passed">', | ||
" milliseconds.<br/>", | ||
"<span class='passed'>", | ||
passed, | ||
'</span> tests of <span class="total">', | ||
"</span> tests of <span class='total'>", | ||
config.stats.all, | ||
'</span> passed, <span class="failed">', | ||
"</span> passed, <span class='failed'>", | ||
config.stats.bad, | ||
'</span> failed.' | ||
].join(''); | ||
"</span> failed." | ||
].join( "" ); | ||
if ( banner ) { | ||
banner.className = (config.stats.bad ? "qunit-fail" : "qunit-pass"); | ||
banner.className = ( config.stats.bad ? "qunit-fail" : "qunit-pass" ); | ||
} | ||
@@ -894,8 +990,19 @@ | ||
document.title = [ | ||
(config.stats.bad ? "\u2716" : "\u2714"), | ||
document.title.replace(/^[\u2714\u2716] /i, "") | ||
].join(" "); | ||
( config.stats.bad ? "\u2716" : "\u2714" ), | ||
document.title.replace( /^[\u2714\u2716] /i, "" ) | ||
].join( " " ); | ||
} | ||
runLoggingCallbacks( 'done', QUnit, { | ||
// clear own sessionStorage items if all tests passed | ||
if ( config.reorder && defined.sessionStorage && config.stats.bad === 0 ) { | ||
// `key` & `i` initialized at top of scope | ||
for ( i = 0; i < sessionStorage.length; i++ ) { | ||
key = sessionStorage.key( i++ ); | ||
if ( key.indexOf( "qunit-test-" ) === 0 ) { | ||
sessionStorage.removeItem( key ); | ||
} | ||
} | ||
} | ||
runLoggingCallbacks( "done", QUnit, { | ||
failed: config.stats.bad, | ||
@@ -905,7 +1012,8 @@ passed: passed, | ||
runtime: runtime | ||
} ); | ||
}); | ||
} | ||
function validTest( name ) { | ||
var filter = config.filter, | ||
var not, | ||
filter = config.filter, | ||
run = false; | ||
@@ -917,3 +1025,4 @@ | ||
var not = filter.charAt( 0 ) === "!"; | ||
not = filter.charAt( 0 ) === "!"; | ||
if ( not ) { | ||
@@ -934,29 +1043,46 @@ filter = filter.slice( 1 ); | ||
// so far supports only Firefox, Chrome and Opera (buggy) | ||
// could be extended in the future to use something like https://github.com/csnover/TraceKit | ||
function sourceFromStacktrace() { | ||
// so far supports only Firefox, Chrome and Opera (buggy), Safari (for real exceptions) | ||
// Later Safari and IE10 are supposed to support error.stack as well | ||
// See also https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Error/Stack | ||
function extractStacktrace( e, offset ) { | ||
offset = offset || 3; | ||
var stack; | ||
if ( e.stacktrace ) { | ||
// Opera | ||
return e.stacktrace.split( "\n" )[ offset + 3 ]; | ||
} else if ( e.stack ) { | ||
// Firefox, Chrome | ||
stack = e.stack.split( "\n" ); | ||
if (/^error$/i.test( stack[0] ) ) { | ||
stack.shift(); | ||
} | ||
return stack[ offset ]; | ||
} else if ( e.sourceURL ) { | ||
// Safari, PhantomJS | ||
// hopefully one day Safari provides actual stacktraces | ||
// exclude useless self-reference for generated Error objects | ||
if ( /qunit.js$/.test( e.sourceURL ) ) { | ||
return; | ||
} | ||
// for actual exceptions, this is useful | ||
return e.sourceURL + ":" + e.line; | ||
} | ||
} | ||
function sourceFromStacktrace( offset ) { | ||
try { | ||
throw new Error(); | ||
} catch ( e ) { | ||
if (e.stacktrace) { | ||
// Opera | ||
return e.stacktrace.split("\n")[6]; | ||
} else if (e.stack) { | ||
// Firefox, Chrome | ||
return e.stack.split("\n")[4]; | ||
} else if (e.sourceURL) { | ||
// Safari, PhantomJS | ||
// TODO sourceURL points at the 'throw new Error' line above, useless | ||
//return e.sourceURL + ":" + e.line; | ||
} | ||
return extractStacktrace( e, offset ); | ||
} | ||
} | ||
function escapeInnerText(s) { | ||
if (!s) { | ||
function escapeInnerText( s ) { | ||
if ( !s ) { | ||
return ""; | ||
} | ||
s = s + ""; | ||
return s.replace(/[\&<>]/g, function(s) { | ||
switch(s) { | ||
return s.replace( /[\&<>]/g, function( s ) { | ||
switch( s ) { | ||
case "&": return "&"; | ||
@@ -974,3 +1100,3 @@ case "<": return "<"; | ||
if ( config.autorun && !config.blocking ) { | ||
process(last); | ||
process( last ); | ||
} | ||
@@ -980,2 +1106,5 @@ } | ||
function process( last ) { | ||
function next() { | ||
process( last ); | ||
} | ||
var start = new Date().getTime(); | ||
@@ -988,5 +1117,3 @@ config.depth = config.depth ? config.depth + 1 : 1; | ||
} else { | ||
window.setTimeout( function(){ | ||
process( last ); | ||
}, 13 ); | ||
window.setTimeout( next, 13 ); | ||
break; | ||
@@ -1006,3 +1133,4 @@ } | ||
for ( var key in window ) { | ||
if ( !hasOwn.call( window, key ) ) { | ||
// in Opera sometimes DOM element ids show up here, ignore them | ||
if ( !hasOwn.call( window, key ) || /^qunit-test-output/.test( key ) ) { | ||
continue; | ||
@@ -1016,13 +1144,16 @@ } | ||
function checkPollution( name ) { | ||
var old = config.pollution; | ||
var newGlobals, | ||
deletedGlobals, | ||
old = config.pollution; | ||
saveGlobal(); | ||
var newGlobals = diff( config.pollution, old ); | ||
newGlobals = diff( config.pollution, old ); | ||
if ( newGlobals.length > 0 ) { | ||
ok( false, "Introduced global variable(s): " + newGlobals.join(", ") ); | ||
QUnit.pushFailure( "Introduced global variable(s): " + newGlobals.join(", ") ); | ||
} | ||
var deletedGlobals = diff( old, config.pollution ); | ||
deletedGlobals = diff( old, config.pollution ); | ||
if ( deletedGlobals.length > 0 ) { | ||
ok( false, "Deleted global variable(s): " + deletedGlobals.join(", ") ); | ||
QUnit.pushFailure( "Deleted global variable(s): " + deletedGlobals.join(", ") ); | ||
} | ||
@@ -1033,7 +1164,9 @@ } | ||
function diff( a, b ) { | ||
var result = a.slice(); | ||
for ( var i = 0; i < result.length; i++ ) { | ||
for ( var j = 0; j < b.length; j++ ) { | ||
var i, j, | ||
result = a.slice(); | ||
for ( i = 0; i < result.length; i++ ) { | ||
for ( j = 0; j < b.length; j++ ) { | ||
if ( result[i] === b[j] ) { | ||
result.splice(i, 1); | ||
result.splice( i, 1 ); | ||
i--; | ||
@@ -1047,21 +1180,10 @@ break; | ||
function fail(message, exception, callback) { | ||
if ( typeof console !== "undefined" && console.error && console.warn ) { | ||
console.error(message); | ||
console.error(exception); | ||
console.warn(callback.toString()); | ||
} else if ( window.opera && opera.postError ) { | ||
opera.postError(message, exception, callback.toString); | ||
} | ||
} | ||
function extend(a, b) { | ||
function extend( a, b ) { | ||
for ( var prop in b ) { | ||
if ( b[prop] === undefined ) { | ||
delete a[prop]; | ||
if ( b[ prop ] === undefined ) { | ||
delete a[ prop ]; | ||
// Avoid "Member not found" error in IE8 caused by setting window.constructor | ||
} else if ( prop !== "constructor" || a !== window ) { | ||
a[prop] = b[prop]; | ||
a[ prop ] = b[ prop ]; | ||
} | ||
@@ -1073,3 +1195,3 @@ } | ||
function addEvent(elem, type, fn) { | ||
function addEvent( elem, type, fn ) { | ||
if ( elem.addEventListener ) { | ||
@@ -1084,9 +1206,9 @@ elem.addEventListener( type, fn, false ); | ||
function id(name) { | ||
return !!(typeof document !== "undefined" && document && document.getElementById) && | ||
function id( name ) { | ||
return !!( typeof document !== "undefined" && document && document.getElementById ) && | ||
document.getElementById( name ); | ||
} | ||
function registerLoggingCallback(key){ | ||
return function(callback){ | ||
function registerLoggingCallback( key ) { | ||
return function( callback ) { | ||
config[key].push( callback ); | ||
@@ -1097,11 +1219,11 @@ }; | ||
// Supports deprecated method of completely overwriting logging callbacks | ||
function runLoggingCallbacks(key, scope, args) { | ||
function runLoggingCallbacks( key, scope, args ) { | ||
//debugger; | ||
var callbacks; | ||
if ( QUnit.hasOwnProperty(key) ) { | ||
QUnit[key].call(scope, args); | ||
var i, callbacks; | ||
if ( QUnit.hasOwnProperty( key ) ) { | ||
QUnit[ key ].call(scope, args ); | ||
} else { | ||
callbacks = config[key]; | ||
for( var i = 0; i < callbacks.length; i++ ) { | ||
callbacks[i].call( scope, args ); | ||
callbacks = config[ key ]; | ||
for ( i = 0; i < callbacks.length; i++ ) { | ||
callbacks[ i ].call( scope, args ); | ||
} | ||
@@ -1113,16 +1235,12 @@ } | ||
// Author: Philippe Rathé <prathe@gmail.com> | ||
QUnit.equiv = function () { | ||
QUnit.equiv = (function() { | ||
var innerEquiv; // the real equiv function | ||
var callers = []; // stack to decide between skip/abort functions | ||
var parents = []; // stack to avoiding loops from circular referencing | ||
// Call the o related callback with the given arguments. | ||
function bindCallbacks(o, callbacks, args) { | ||
var prop = QUnit.objectType(o); | ||
if (prop) { | ||
if (QUnit.objectType(callbacks[prop]) === "function") { | ||
return callbacks[prop].apply(callbacks, args); | ||
function bindCallbacks( o, callbacks, args ) { | ||
var prop = QUnit.objectType( o ); | ||
if ( prop ) { | ||
if ( QUnit.objectType( callbacks[ prop ] ) === "function" ) { | ||
return callbacks[ prop ].apply( callbacks, args ); | ||
} else { | ||
return callbacks[prop]; // or undefined | ||
return callbacks[ prop ]; // or undefined | ||
} | ||
@@ -1132,152 +1250,158 @@ } | ||
var getProto = Object.getPrototypeOf || function (obj) { | ||
return obj.__proto__; | ||
}; | ||
// the real equiv function | ||
var innerEquiv, | ||
// stack to decide between skip/abort functions | ||
callers = [], | ||
// stack to avoiding loops from circular referencing | ||
parents = [], | ||
var callbacks = function () { | ||
getProto = Object.getPrototypeOf || function ( obj ) { | ||
return obj.__proto__; | ||
}, | ||
callbacks = (function () { | ||
// for string, boolean, number and null | ||
function useStrictEquality(b, a) { | ||
if (b instanceof a.constructor || a instanceof b.constructor) { | ||
// to catch short annotaion VS 'new' annotation of a | ||
// declaration | ||
// e.g. var i = 1; | ||
// var j = new Number(1); | ||
return a == b; | ||
} else { | ||
return a === b; | ||
// for string, boolean, number and null | ||
function useStrictEquality( b, a ) { | ||
if ( b instanceof a.constructor || a instanceof b.constructor ) { | ||
// to catch short annotaion VS 'new' annotation of a | ||
// declaration | ||
// e.g. var i = 1; | ||
// var j = new Number(1); | ||
return a == b; | ||
} else { | ||
return a === b; | ||
} | ||
} | ||
} | ||
return { | ||
"string" : useStrictEquality, | ||
"boolean" : useStrictEquality, | ||
"number" : useStrictEquality, | ||
"null" : useStrictEquality, | ||
"undefined" : useStrictEquality, | ||
return { | ||
"string": useStrictEquality, | ||
"boolean": useStrictEquality, | ||
"number": useStrictEquality, | ||
"null": useStrictEquality, | ||
"undefined": useStrictEquality, | ||
"nan" : function(b) { | ||
return isNaN(b); | ||
}, | ||
"nan": function( b ) { | ||
return isNaN( b ); | ||
}, | ||
"date" : function(b, a) { | ||
return QUnit.objectType(b) === "date" | ||
&& a.valueOf() === b.valueOf(); | ||
}, | ||
"date": function( b, a ) { | ||
return QUnit.objectType( b ) === "date" && a.valueOf() === b.valueOf(); | ||
}, | ||
"regexp" : function(b, a) { | ||
return QUnit.objectType(b) === "regexp" | ||
&& a.source === b.source && // the regex itself | ||
a.global === b.global && // and its modifers | ||
// (gmi) ... | ||
a.ignoreCase === b.ignoreCase | ||
&& a.multiline === b.multiline; | ||
}, | ||
"regexp": function( b, a ) { | ||
return QUnit.objectType( b ) === "regexp" && | ||
// the regex itself | ||
a.source === b.source && | ||
// and its modifers | ||
a.global === b.global && | ||
// (gmi) ... | ||
a.ignoreCase === b.ignoreCase && | ||
a.multiline === b.multiline; | ||
}, | ||
// - skip when the property is a method of an instance (OOP) | ||
// - abort otherwise, | ||
// initial === would have catch identical references anyway | ||
"function" : function() { | ||
var caller = callers[callers.length - 1]; | ||
return caller !== Object && typeof caller !== "undefined"; | ||
}, | ||
// - skip when the property is a method of an instance (OOP) | ||
// - abort otherwise, | ||
// initial === would have catch identical references anyway | ||
"function": function() { | ||
var caller = callers[callers.length - 1]; | ||
return caller !== Object && typeof caller !== "undefined"; | ||
}, | ||
"array" : function(b, a) { | ||
var i, j, loop; | ||
var len; | ||
"array": function( b, a ) { | ||
var i, j, len, loop; | ||
// b could be an object literal here | ||
if (!(QUnit.objectType(b) === "array")) { | ||
return false; | ||
} | ||
// b could be an object literal here | ||
if ( QUnit.objectType( b ) !== "array" ) { | ||
return false; | ||
} | ||
len = a.length; | ||
if (len !== b.length) { // safe and faster | ||
return false; | ||
} | ||
len = a.length; | ||
if ( len !== b.length ) { | ||
// safe and faster | ||
return false; | ||
} | ||
// track reference to avoid circular references | ||
parents.push(a); | ||
for (i = 0; i < len; i++) { | ||
loop = false; | ||
for (j = 0; j < parents.length; j++) { | ||
if (parents[j] === a[i]) { | ||
loop = true;// dont rewalk array | ||
// track reference to avoid circular references | ||
parents.push( a ); | ||
for ( i = 0; i < len; i++ ) { | ||
loop = false; | ||
for ( j = 0; j < parents.length; j++ ) { | ||
if ( parents[j] === a[i] ) { | ||
loop = true;// dont rewalk array | ||
} | ||
} | ||
if ( !loop && !innerEquiv(a[i], b[i]) ) { | ||
parents.pop(); | ||
return false; | ||
} | ||
} | ||
if (!loop && !innerEquiv(a[i], b[i])) { | ||
parents.pop(); | ||
return false; | ||
} | ||
} | ||
parents.pop(); | ||
return true; | ||
}, | ||
parents.pop(); | ||
return true; | ||
}, | ||
"object" : function(b, a) { | ||
var i, j, loop; | ||
var eq = true; // unless we can proove it | ||
var aProperties = [], bProperties = []; // collection of | ||
// strings | ||
"object": function( b, a ) { | ||
var i, j, loop, | ||
// Default to true | ||
eq = true, | ||
aProperties = [], | ||
bProperties = []; | ||
// comparing constructors is more strict than using | ||
// instanceof | ||
if (a.constructor !== b.constructor) { | ||
// Allow objects with no prototype to be equivalent to | ||
// objects with Object as their constructor. | ||
if (!((getProto(a) === null && getProto(b) === Object.prototype) || | ||
(getProto(b) === null && getProto(a) === Object.prototype))) | ||
{ | ||
return false; | ||
// comparing constructors is more strict than using | ||
// instanceof | ||
if ( a.constructor !== b.constructor ) { | ||
// Allow objects with no prototype to be equivalent to | ||
// objects with Object as their constructor. | ||
if ( !(( getProto(a) === null && getProto(b) === Object.prototype ) || | ||
( getProto(b) === null && getProto(a) === Object.prototype ) ) ) { | ||
return false; | ||
} | ||
} | ||
} | ||
// stack constructor before traversing properties | ||
callers.push(a.constructor); | ||
// track reference to avoid circular references | ||
parents.push(a); | ||
// stack constructor before traversing properties | ||
callers.push( a.constructor ); | ||
// track reference to avoid circular references | ||
parents.push( a ); | ||
for (i in a) { // be strict: don't ensures hasOwnProperty | ||
// and go deep | ||
loop = false; | ||
for (j = 0; j < parents.length; j++) { | ||
if (parents[j] === a[i]) | ||
loop = true; // don't go down the same path | ||
// twice | ||
for ( i in a ) { // be strict: don't ensures hasOwnProperty | ||
// and go deep | ||
loop = false; | ||
for ( j = 0; j < parents.length; j++ ) { | ||
if ( parents[j] === a[i] ) { | ||
// don't go down the same path twice | ||
loop = true; | ||
} | ||
} | ||
aProperties.push(i); // collect a's properties | ||
if (!loop && !innerEquiv( a[i], b[i] ) ) { | ||
eq = false; | ||
break; | ||
} | ||
} | ||
aProperties.push(i); // collect a's properties | ||
if (!loop && !innerEquiv(a[i], b[i])) { | ||
eq = false; | ||
break; | ||
callers.pop(); // unstack, we are done | ||
parents.pop(); | ||
for ( i in b ) { | ||
bProperties.push( i ); // collect b's properties | ||
} | ||
} | ||
callers.pop(); // unstack, we are done | ||
parents.pop(); | ||
for (i in b) { | ||
bProperties.push(i); // collect b's properties | ||
// Ensures identical properties name | ||
return eq && innerEquiv( aProperties.sort(), bProperties.sort() ); | ||
} | ||
}; | ||
}()); | ||
// Ensures identical properties name | ||
return eq | ||
&& innerEquiv(aProperties.sort(), bProperties | ||
.sort()); | ||
} | ||
}; | ||
}(); | ||
innerEquiv = function() { // can take multiple arguments | ||
var args = Array.prototype.slice.apply(arguments); | ||
if (args.length < 2) { | ||
var args = [].slice.apply( arguments ); | ||
if ( args.length < 2 ) { | ||
return true; // end transition | ||
} | ||
return (function(a, b) { | ||
if (a === b) { | ||
return (function( a, b ) { | ||
if ( a === b ) { | ||
return true; // catch the most you can | ||
} else if (a === null || b === null || typeof a === "undefined" | ||
|| typeof b === "undefined" | ||
|| QUnit.objectType(a) !== QUnit.objectType(b)) { | ||
} else if ( a === null || b === null || typeof a === "undefined" || | ||
typeof b === "undefined" || | ||
QUnit.objectType(a) !== QUnit.objectType(b) ) { | ||
return false; // don't lose time with error prone cases | ||
@@ -1289,11 +1413,8 @@ } else { | ||
// apply transition with (1..n) arguments | ||
})(args[0], args[1]) | ||
&& arguments.callee.apply(this, args.splice(1, | ||
args.length - 1)); | ||
}( args[0], args[1] ) && arguments.callee.apply( this, args.splice(1, args.length - 1 )) ); | ||
}; | ||
return innerEquiv; | ||
}()); | ||
}(); | ||
/** | ||
@@ -1311,7 +1432,7 @@ * jsDump Copyright (c) 2008 Ariel Flesler - aflesler(at)gmail(dot)com | | ||
function quote( str ) { | ||
return '"' + str.toString().replace(/"/g, '\\"') + '"'; | ||
}; | ||
return '"' + str.toString().replace( /"/g, '\\"' ) + '"'; | ||
} | ||
function literal( o ) { | ||
return o + ''; | ||
}; | ||
return o + ""; | ||
} | ||
function join( pre, arr, post ) { | ||
@@ -1321,175 +1442,202 @@ var s = jsDump.separator(), | ||
inner = jsDump.indent(1); | ||
if ( arr.join ) | ||
arr = arr.join( ',' + s + inner ); | ||
if ( !arr ) | ||
if ( arr.join ) { | ||
arr = arr.join( "," + s + inner ); | ||
} | ||
if ( !arr ) { | ||
return pre + post; | ||
} | ||
return [ pre, inner + arr, base + post ].join(s); | ||
}; | ||
} | ||
function array( arr, stack ) { | ||
var i = arr.length, ret = Array(i); | ||
var i = arr.length, ret = new Array(i); | ||
this.up(); | ||
while ( i-- ) | ||
while ( i-- ) { | ||
ret[i] = this.parse( arr[i] , undefined , stack); | ||
} | ||
this.down(); | ||
return join( '[', ret, ']' ); | ||
}; | ||
return join( "[", ret, "]" ); | ||
} | ||
var reName = /^function (\w+)/; | ||
var reName = /^function (\w+)/, | ||
jsDump = { | ||
parse: function( obj, type, stack ) { //type is used mostly internally, you can fix a (custom)type in advance | ||
stack = stack || [ ]; | ||
var inStack, res, | ||
parser = this.parsers[ type || this.typeOf(obj) ]; | ||
var jsDump = { | ||
parse:function( obj, type, stack ) { //type is used mostly internally, you can fix a (custom)type in advance | ||
stack = stack || [ ]; | ||
var parser = this.parsers[ type || this.typeOf(obj) ]; | ||
type = typeof parser; | ||
var inStack = inArray(obj, stack); | ||
if (inStack != -1) { | ||
return 'recursion('+(inStack - stack.length)+')'; | ||
} | ||
//else | ||
if (type == 'function') { | ||
stack.push(obj); | ||
var res = parser.call( this, obj, stack ); | ||
type = typeof parser; | ||
inStack = inArray( obj, stack ); | ||
if ( inStack != -1 ) { | ||
return "recursion(" + (inStack - stack.length) + ")"; | ||
} | ||
//else | ||
if ( type == "function" ) { | ||
stack.push( obj ); | ||
res = parser.call( this, obj, stack ); | ||
stack.pop(); | ||
return res; | ||
} | ||
// else | ||
return (type == 'string') ? parser : this.parsers.error; | ||
}, | ||
typeOf:function( obj ) { | ||
var type; | ||
if ( obj === null ) { | ||
type = "null"; | ||
} else if (typeof obj === "undefined") { | ||
type = "undefined"; | ||
} else if (QUnit.is("RegExp", obj)) { | ||
type = "regexp"; | ||
} else if (QUnit.is("Date", obj)) { | ||
type = "date"; | ||
} else if (QUnit.is("Function", obj)) { | ||
type = "function"; | ||
} else if (typeof obj.setInterval !== undefined && typeof obj.document !== "undefined" && typeof obj.nodeType === "undefined") { | ||
type = "window"; | ||
} else if (obj.nodeType === 9) { | ||
type = "document"; | ||
} else if (obj.nodeType) { | ||
type = "node"; | ||
} else if ( | ||
// native arrays | ||
toString.call( obj ) === "[object Array]" || | ||
// NodeList objects | ||
( typeof obj.length === "number" && typeof obj.item !== "undefined" && ( obj.length ? obj.item(0) === obj[0] : ( obj.item( 0 ) === null && typeof obj[0] === "undefined" ) ) ) | ||
) { | ||
type = "array"; | ||
} else { | ||
type = typeof obj; | ||
} | ||
return type; | ||
}, | ||
separator:function() { | ||
return this.multiline ? this.HTML ? '<br />' : '\n' : this.HTML ? ' ' : ' '; | ||
}, | ||
indent:function( extra ) {// extra can be a number, shortcut for increasing-calling-decreasing | ||
if ( !this.multiline ) | ||
return ''; | ||
var chr = this.indentChar; | ||
if ( this.HTML ) | ||
chr = chr.replace(/\t/g,' ').replace(/ /g,' '); | ||
return Array( this._depth_ + (extra||0) ).join(chr); | ||
}, | ||
up:function( a ) { | ||
this._depth_ += a || 1; | ||
}, | ||
down:function( a ) { | ||
this._depth_ -= a || 1; | ||
}, | ||
setParser:function( name, parser ) { | ||
this.parsers[name] = parser; | ||
}, | ||
// The next 3 are exposed so you can use them | ||
quote:quote, | ||
literal:literal, | ||
join:join, | ||
// | ||
_depth_: 1, | ||
// This is the list of parsers, to modify them, use jsDump.setParser | ||
parsers:{ | ||
window: '[Window]', | ||
document: '[Document]', | ||
error:'[ERROR]', //when no parser is found, shouldn't happen | ||
unknown: '[Unknown]', | ||
'null':'null', | ||
'undefined':'undefined', | ||
'function':function( fn ) { | ||
var ret = 'function', | ||
name = 'name' in fn ? fn.name : (reName.exec(fn)||[])[1];//functions never have name in IE | ||
if ( name ) | ||
ret += ' ' + name; | ||
ret += '('; | ||
ret = [ ret, QUnit.jsDump.parse( fn, 'functionArgs' ), '){'].join(''); | ||
return join( ret, QUnit.jsDump.parse(fn,'functionCode'), '}' ); | ||
} | ||
// else | ||
return ( type == "string" ) ? parser : this.parsers.error; | ||
}, | ||
array: array, | ||
nodelist: array, | ||
arguments: array, | ||
object:function( map, stack ) { | ||
var ret = [ ]; | ||
QUnit.jsDump.up(); | ||
for ( var key in map ) { | ||
var val = map[key]; | ||
ret.push( QUnit.jsDump.parse(key,'key') + ': ' + QUnit.jsDump.parse(val, undefined, stack)); | ||
} | ||
QUnit.jsDump.down(); | ||
return join( '{', ret, '}' ); | ||
typeOf: function( obj ) { | ||
var type; | ||
if ( obj === null ) { | ||
type = "null"; | ||
} else if ( typeof obj === "undefined" ) { | ||
type = "undefined"; | ||
} else if ( QUnit.is( "RegExp", obj) ) { | ||
type = "regexp"; | ||
} else if ( QUnit.is( "Date", obj) ) { | ||
type = "date"; | ||
} else if ( QUnit.is( "Function", obj) ) { | ||
type = "function"; | ||
} else if ( typeof obj.setInterval !== undefined && typeof obj.document !== "undefined" && typeof obj.nodeType === "undefined" ) { | ||
type = "window"; | ||
} else if ( obj.nodeType === 9 ) { | ||
type = "document"; | ||
} else if ( obj.nodeType ) { | ||
type = "node"; | ||
} else if ( | ||
// native arrays | ||
toString.call( obj ) === "[object Array]" || | ||
// NodeList objects | ||
( typeof obj.length === "number" && typeof obj.item !== "undefined" && ( obj.length ? obj.item(0) === obj[0] : ( obj.item( 0 ) === null && typeof obj[0] === "undefined" ) ) ) | ||
) { | ||
type = "array"; | ||
} else { | ||
type = typeof obj; | ||
} | ||
return type; | ||
}, | ||
node:function( node ) { | ||
var open = QUnit.jsDump.HTML ? '<' : '<', | ||
close = QUnit.jsDump.HTML ? '>' : '>'; | ||
var tag = node.nodeName.toLowerCase(), | ||
ret = open + tag; | ||
for ( var a in QUnit.jsDump.DOMAttrs ) { | ||
var val = node[QUnit.jsDump.DOMAttrs[a]]; | ||
if ( val ) | ||
ret += ' ' + a + '=' + QUnit.jsDump.parse( val, 'attribute' ); | ||
separator: function() { | ||
return this.multiline ? this.HTML ? "<br />" : "\n" : this.HTML ? " " : " "; | ||
}, | ||
indent: function( extra ) {// extra can be a number, shortcut for increasing-calling-decreasing | ||
if ( !this.multiline ) { | ||
return ""; | ||
} | ||
return ret + close + open + '/' + tag + close; | ||
var chr = this.indentChar; | ||
if ( this.HTML ) { | ||
chr = chr.replace( /\t/g, " " ).replace( / /g, " " ); | ||
} | ||
return new Array( this._depth_ + (extra||0) ).join(chr); | ||
}, | ||
functionArgs:function( fn ) {//function calls it internally, it's the arguments part of the function | ||
var l = fn.length; | ||
if ( !l ) return ''; | ||
up: function( a ) { | ||
this._depth_ += a || 1; | ||
}, | ||
down: function( a ) { | ||
this._depth_ -= a || 1; | ||
}, | ||
setParser: function( name, parser ) { | ||
this.parsers[name] = parser; | ||
}, | ||
// The next 3 are exposed so you can use them | ||
quote: quote, | ||
literal: literal, | ||
join: join, | ||
// | ||
_depth_: 1, | ||
// This is the list of parsers, to modify them, use jsDump.setParser | ||
parsers: { | ||
window: "[Window]", | ||
document: "[Document]", | ||
error: "[ERROR]", //when no parser is found, shouldn"t happen | ||
unknown: "[Unknown]", | ||
"null": "null", | ||
"undefined": "undefined", | ||
"function": function( fn ) { | ||
var ret = "function", | ||
name = "name" in fn ? fn.name : (reName.exec(fn) || [])[1];//functions never have name in IE | ||
var args = Array(l); | ||
while ( l-- ) | ||
args[l] = String.fromCharCode(97+l);//97 is 'a' | ||
return ' ' + args.join(', ') + ' '; | ||
if ( name ) { | ||
ret += " " + name; | ||
} | ||
ret += "( "; | ||
ret = [ ret, QUnit.jsDump.parse( fn, "functionArgs" ), "){" ].join( "" ); | ||
return join( ret, QUnit.jsDump.parse(fn,"functionCode" ), "}" ); | ||
}, | ||
array: array, | ||
nodelist: array, | ||
"arguments": array, | ||
object: function( map, stack ) { | ||
var ret = [ ], keys, key, val, i; | ||
QUnit.jsDump.up(); | ||
if ( Object.keys ) { | ||
keys = Object.keys( map ); | ||
} else { | ||
keys = []; | ||
for ( key in map ) { | ||
keys.push( key ); | ||
} | ||
} | ||
keys.sort(); | ||
for ( i = 0; i < keys.length; i++ ) { | ||
key = keys[ i ]; | ||
val = map[ key ]; | ||
ret.push( QUnit.jsDump.parse( key, "key" ) + ": " + QUnit.jsDump.parse( val, undefined, stack ) ); | ||
} | ||
QUnit.jsDump.down(); | ||
return join( "{", ret, "}" ); | ||
}, | ||
node: function( node ) { | ||
var a, val, | ||
open = QUnit.jsDump.HTML ? "<" : "<", | ||
close = QUnit.jsDump.HTML ? ">" : ">", | ||
tag = node.nodeName.toLowerCase(), | ||
ret = open + tag; | ||
for ( a in QUnit.jsDump.DOMAttrs ) { | ||
val = node[ QUnit.jsDump.DOMAttrs[a] ]; | ||
if ( val ) { | ||
ret += " " + a + "=" + QUnit.jsDump.parse( val, "attribute" ); | ||
} | ||
} | ||
return ret + close + open + "/" + tag + close; | ||
}, | ||
functionArgs: function( fn ) {//function calls it internally, it's the arguments part of the function | ||
var args, | ||
l = fn.length; | ||
if ( !l ) { | ||
return ""; | ||
} | ||
args = new Array(l); | ||
while ( l-- ) { | ||
args[l] = String.fromCharCode(97+l);//97 is 'a' | ||
} | ||
return " " + args.join( ", " ) + " "; | ||
}, | ||
key: quote, //object calls it internally, the key part of an item in a map | ||
functionCode: "[code]", //function calls it internally, it's the content of the function | ||
attribute: quote, //node calls it internally, it's an html attribute value | ||
string: quote, | ||
date: quote, | ||
regexp: literal, //regex | ||
number: literal, | ||
"boolean": literal | ||
}, | ||
key:quote, //object calls it internally, the key part of an item in a map | ||
functionCode:'[code]', //function calls it internally, it's the content of the function | ||
attribute:quote, //node calls it internally, it's an html attribute value | ||
string:quote, | ||
date:quote, | ||
regexp:literal, //regex | ||
number:literal, | ||
'boolean':literal | ||
}, | ||
DOMAttrs:{//attributes to dump from nodes, name=>realName | ||
id:'id', | ||
name:'name', | ||
'class':'className' | ||
}, | ||
HTML:false,//if true, entities are escaped ( <, >, \t, space and \n ) | ||
indentChar:' ',//indentation unit | ||
multiline:true //if true, items in a collection, are separated by a \n, else just a space. | ||
}; | ||
DOMAttrs: { | ||
//attributes to dump from nodes, name=>realName | ||
id: "id", | ||
name: "name", | ||
"class": "className" | ||
}, | ||
HTML: false,//if true, entities are escaped ( <, >, \t, space and \n ) | ||
indentChar: " ",//indentation unit | ||
multiline: true //if true, items in a collection, are separated by a \n, else just a space. | ||
}; | ||
return jsDump; | ||
})(); | ||
}()); | ||
// from Sizzle.js | ||
function getText( elems ) { | ||
var ret = "", elem; | ||
var i, elem, | ||
ret = ""; | ||
for ( var i = 0; elems[i]; i++ ) { | ||
for ( i = 0; elems[i]; i++ ) { | ||
elem = elems[i]; | ||
@@ -1508,5 +1656,5 @@ | ||
return ret; | ||
}; | ||
} | ||
//from jquery.js | ||
// from jquery.js | ||
function inArray( elem, array ) { | ||
@@ -1538,38 +1686,41 @@ if ( array.indexOf ) { | ||
* | ||
* 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" | ||
* 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" | ||
*/ | ||
QUnit.diff = (function() { | ||
function diff(o, n) { | ||
var ns = {}; | ||
var os = {}; | ||
function diff( o, n ) { | ||
var i, | ||
ns = {}, | ||
os = {}; | ||
for (var i = 0; i < n.length; i++) { | ||
if (ns[n[i]] == null) | ||
ns[n[i]] = { | ||
for ( i = 0; i < n.length; i++ ) { | ||
if ( ns[ n[i] ] == null ) { | ||
ns[ n[i] ] = { | ||
rows: [], | ||
o: null | ||
}; | ||
ns[n[i]].rows.push(i); | ||
} | ||
ns[ n[i] ].rows.push( i ); | ||
} | ||
for (var i = 0; i < o.length; i++) { | ||
if (os[o[i]] == null) | ||
os[o[i]] = { | ||
for ( i = 0; i < o.length; i++ ) { | ||
if ( os[ o[i] ] == null ) { | ||
os[ o[i] ] = { | ||
rows: [], | ||
n: null | ||
}; | ||
os[o[i]].rows.push(i); | ||
} | ||
os[ o[i] ].rows.push( i ); | ||
} | ||
for (var i in ns) { | ||
for ( i in ns ) { | ||
if ( !hasOwn.call( ns, i ) ) { | ||
continue; | ||
} | ||
if (ns[i].rows.length == 1 && typeof(os[i]) != "undefined" && os[i].rows.length == 1) { | ||
n[ns[i].rows[0]] = { | ||
text: n[ns[i].rows[0]], | ||
if ( ns[i].rows.length == 1 && typeof os[i] != "undefined" && os[i].rows.length == 1 ) { | ||
n[ ns[i].rows[0] ] = { | ||
text: n[ ns[i].rows[0] ], | ||
row: os[i].rows[0] | ||
}; | ||
o[os[i].rows[0]] = { | ||
text: o[os[i].rows[0]], | ||
o[ os[i].rows[0] ] = { | ||
text: o[ os[i].rows[0] ], | ||
row: ns[i].rows[0] | ||
@@ -1580,11 +1731,12 @@ }; | ||
for (var i = 0; i < n.length - 1; i++) { | ||
if (n[i].text != null && n[i + 1].text == null && n[i].row + 1 < o.length && o[n[i].row + 1].text == null && | ||
n[i + 1] == o[n[i].row + 1]) { | ||
n[i + 1] = { | ||
text: n[i + 1], | ||
for ( i = 0; i < n.length - 1; i++ ) { | ||
if ( n[i].text != null && n[ i + 1 ].text == null && n[i].row + 1 < o.length && o[ n[i].row + 1 ].text == null && | ||
n[ i + 1 ] == o[ n[i].row + 1 ] ) { | ||
n[ i + 1 ] = { | ||
text: n[ i + 1 ], | ||
row: n[i].row + 1 | ||
}; | ||
o[n[i].row + 1] = { | ||
text: o[n[i].row + 1], | ||
o[ n[i].row + 1 ] = { | ||
text: o[ n[i].row + 1 ], | ||
row: i + 1 | ||
@@ -1595,11 +1747,12 @@ }; | ||
for (var i = n.length - 1; i > 0; i--) { | ||
if (n[i].text != null && n[i - 1].text == null && n[i].row > 0 && o[n[i].row - 1].text == null && | ||
n[i - 1] == o[n[i].row - 1]) { | ||
n[i - 1] = { | ||
text: n[i - 1], | ||
for ( i = n.length - 1; i > 0; i-- ) { | ||
if ( n[i].text != null && n[ i - 1 ].text == null && n[i].row > 0 && o[ n[i].row - 1 ].text == null && | ||
n[ i - 1 ] == o[ n[i].row - 1 ]) { | ||
n[ i - 1 ] = { | ||
text: n[ i - 1 ], | ||
row: n[i].row - 1 | ||
}; | ||
o[n[i].row - 1] = { | ||
text: o[n[i].row - 1], | ||
o[ n[i].row - 1 ] = { | ||
text: o[ n[i].row - 1 ], | ||
row: i - 1 | ||
@@ -1616,45 +1769,48 @@ }; | ||
return function(o, n) { | ||
o = o.replace(/\s+$/, ''); | ||
n = n.replace(/\s+$/, ''); | ||
var out = diff(o == "" ? [] : o.split(/\s+/), n == "" ? [] : n.split(/\s+/)); | ||
return function( o, n ) { | ||
o = o.replace( /\s+$/, "" ); | ||
n = n.replace( /\s+$/, "" ); | ||
var str = ""; | ||
var i, pre, | ||
str = "", | ||
out = diff( o === "" ? [] : o.split(/\s+/), n === "" ? [] : n.split(/\s+/) ), | ||
oSpace = o.match(/\s+/g), | ||
nSpace = n.match(/\s+/g); | ||
var oSpace = o.match(/\s+/g); | ||
if (oSpace == null) { | ||
oSpace = [" "]; | ||
if ( oSpace == null ) { | ||
oSpace = [ " " ]; | ||
} | ||
else { | ||
oSpace.push(" "); | ||
oSpace.push( " " ); | ||
} | ||
var nSpace = n.match(/\s+/g); | ||
if (nSpace == null) { | ||
nSpace = [" "]; | ||
if ( nSpace == null ) { | ||
nSpace = [ " " ]; | ||
} | ||
else { | ||
nSpace.push(" "); | ||
nSpace.push( " " ); | ||
} | ||
if (out.n.length == 0) { | ||
for (var i = 0; i < out.o.length; i++) { | ||
str += '<del>' + out.o[i] + oSpace[i] + "</del>"; | ||
if ( out.n.length === 0 ) { | ||
for ( i = 0; i < out.o.length; i++ ) { | ||
str += "<del>" + out.o[i] + oSpace[i] + "</del>"; | ||
} | ||
} | ||
else { | ||
if (out.n[0].text == null) { | ||
for (n = 0; n < out.o.length && out.o[n].text == null; n++) { | ||
str += '<del>' + out.o[n] + oSpace[n] + "</del>"; | ||
if ( out.n[0].text == null ) { | ||
for ( n = 0; n < out.o.length && out.o[n].text == null; n++ ) { | ||
str += "<del>" + out.o[n] + oSpace[n] + "</del>"; | ||
} | ||
} | ||
for (var i = 0; i < out.n.length; i++) { | ||
for ( i = 0; i < out.n.length; i++ ) { | ||
if (out.n[i].text == null) { | ||
str += '<ins>' + out.n[i] + nSpace[i] + "</ins>"; | ||
str += "<ins>" + out.n[i] + nSpace[i] + "</ins>"; | ||
} | ||
else { | ||
var pre = ""; | ||
// `pre` initialized at top of scope | ||
pre = ""; | ||
for (n = out.n[i].row + 1; n < out.o.length && out.o[n].text == null; n++) { | ||
pre += '<del>' + out.o[n] + oSpace[n] + "</del>"; | ||
for ( n = out.n[i].row + 1; n < out.o.length && out.o[n].text == null; n++ ) { | ||
pre += "<del>" + out.o[n] + oSpace[n] + "</del>"; | ||
} | ||
@@ -1668,4 +1824,10 @@ str += " " + out.n[i].text + nSpace[i] + pre; | ||
}; | ||
})(); | ||
}()); | ||
})(this); | ||
// for CommonJS enviroments, export everything | ||
if ( typeof exports !== "undefined" ) { | ||
extend(exports, QUnit); | ||
} | ||
// get at whatever the global object is, like window in browsers | ||
}( (function() {return this;}.call()) )); |
@@ -19,4 +19,4 @@ [QUnit](http://docs.jquery.com/QUnit) - A JavaScript Unit Testing framework. | ||
QUnit is similar to other unit testing frameworks like JUnit, but makes use of | ||
the features JavaScript provides and helps with testing code in the browser, eg. | ||
with it's stop/start facilities for testing asynchronous code. | ||
the features JavaScript provides and helps with testing code in the browser, e.g. | ||
with its stop/start facilities for testing asynchronous code. | ||
@@ -30,2 +30,13 @@ If you are interested in helping developing QUnit, you are in the right place. | ||
Development | ||
----------- | ||
To submit patches, fork the repository, create a branch for the change. Then implement | ||
the change, run `grunt` to lint and test it, then commit, push and create a pull request. | ||
Include some background for the change in the commit message and `Fixes #nnn`, referring | ||
to the issue number you're addressing. | ||
To run `grunt`, you need `node` and `npm`, then `npm install grunt -g`. | ||
Releases | ||
@@ -32,0 +43,0 @@ -------- |
@@ -154,1 +154,28 @@ // TODO disable reordering for this suite! | ||
}); | ||
var testAutorun = true; | ||
QUnit.done(function() { | ||
if (!testAutorun) { | ||
return; | ||
} | ||
testAutorun = false; | ||
module("autorun"); | ||
test("reset", 0, function() {}); | ||
moduleStart = moduleDone = 0; | ||
test("first", function(){ | ||
equal(moduleStart, 1, "test started"); | ||
equal(moduleDone, 0, "test in progress"); | ||
}); | ||
test("second", function(){ | ||
equal(moduleStart, 2, "test started"); | ||
equal(moduleDone, 1, "test in progress"); | ||
}); | ||
}); |
@@ -27,3 +27,2 @@ test("module without setup/teardown (default)", function() { | ||
test("module with setup, expect in test call", 2, function() { | ||
@@ -39,5 +38,9 @@ ok(true); | ||
ok(true); | ||
x = 1; | ||
}, | ||
teardown: function() { | ||
ok(true); | ||
// can introduce and delete globals in setup/teardown | ||
// without noglobals sounding the alarm | ||
delete x; | ||
} | ||
@@ -165,12 +168,12 @@ }); | ||
test("test synchronous calls to stop", 2, function() { | ||
stop(); | ||
setTimeout(function(){ | ||
ok(true, 'first'); | ||
start(); | ||
stop(); | ||
setTimeout(function(){ | ||
ok(true, 'second'); | ||
start(); | ||
}, 150); | ||
}, 150); | ||
stop(); | ||
setTimeout(function(){ | ||
ok(true, 'first'); | ||
start(); | ||
stop(); | ||
setTimeout(function(){ | ||
ok(true, 'second'); | ||
start(); | ||
}, 150); | ||
}, 150); | ||
}); | ||
@@ -200,2 +203,3 @@ } | ||
test("modify testEnvironment",function() { | ||
expect(0); | ||
this.foo="hamster"; | ||
@@ -217,2 +221,3 @@ }); | ||
test("modify testEnvironment",function() { | ||
expect(0); | ||
// since we do a shallow copy, the testEnvironment can be modified | ||
@@ -245,9 +250,4 @@ this.options.ingredients.push("carrots"); | ||
test("makeurl working with settings from testEnvironment", function() { | ||
equal( makeurl(), 'http://google.com/?q=another_search_test', 'rather than passing arguments, we use test metadata to form the url'); | ||
equal( makeurl(), 'http://google.com/?q=another_search_test', 'rather than passing arguments, we use test metadata to from the url'); | ||
}); | ||
test("each test can extend the module testEnvironment", { | ||
q:'hamstersoup' | ||
}, function() { | ||
equal( makeurl(), 'http://google.com/?q=hamstersoup', 'url from module, q from test'); | ||
}); | ||
@@ -257,3 +257,3 @@ module("jsDump"); | ||
equal( QUnit.jsDump.parse([1, 2]), "[\n 1,\n 2\n]" ); | ||
equal( QUnit.jsDump.parse({top: 5, left: 0}), "{\n \"top\": 5,\n \"left\": 0\n}" ); | ||
equal( QUnit.jsDump.parse({top: 5, left: 0}), "{\n \"left\": 0,\n \"top\": 5\n}" ); | ||
if (typeof document !== 'undefined' && document.getElementById("qunit-header")) { | ||
@@ -316,2 +316,12 @@ equal( QUnit.jsDump.parse(document.getElementById("qunit-header")), "<h1 id=\"qunit-header\"></h1>" ); | ||
this.CustomError = CustomError; | ||
raises( | ||
function() { | ||
throw new this.CustomError("some error description"); | ||
}, | ||
/description/, | ||
"raised error with 'this' context" | ||
); | ||
}); | ||
@@ -323,2 +333,3 @@ | ||
test("setup", function() { | ||
expect(0); | ||
document.getElementById("qunit-fixture").innerHTML = "foobar"; | ||
@@ -330,2 +341,11 @@ }); | ||
test("running test name displayed", function() { | ||
expect(2); | ||
var displaying = document.getElementById("qunit-testresult"); | ||
ok( /running test name displayed/.test(displaying.innerHTML), "Expect test name to be found in displayed text" ); | ||
ok( /fixture/.test(displaying.innerHTML), "Expect module name to be found in displayed text" ); | ||
}); | ||
} | ||
@@ -342,3 +362,3 @@ | ||
mod2(3, 1, "3 % 2 == 1"); | ||
}) | ||
}); | ||
})(); | ||
@@ -350,53 +370,53 @@ | ||
function Wrap(x) { | ||
this.wrap = x; | ||
if (x == undefined) this.first = true; | ||
this.wrap = x; | ||
if (x == undefined) this.first = true; | ||
} | ||
function chainwrap(depth, first, prev) { | ||
depth = depth || 0; | ||
var last = prev || new Wrap(); | ||
first = first || last; | ||
depth = depth || 0; | ||
var last = prev || new Wrap(); | ||
first = first || last; | ||
if (depth == 1) { | ||
first.wrap = last; | ||
} | ||
if (depth > 1) { | ||
last = chainwrap(depth-1, first, new Wrap(last)); | ||
} | ||
if (depth == 1) { | ||
first.wrap = last; | ||
} | ||
if (depth > 1) { | ||
last = chainwrap(depth-1, first, new Wrap(last)); | ||
} | ||
return last; | ||
return last; | ||
} | ||
test("check jsDump recursion", function() { | ||
expect(4); | ||
expect(4); | ||
var noref = chainwrap(0); | ||
var nodump = QUnit.jsDump.parse(noref); | ||
equal(nodump, '{\n "wrap": undefined,\n "first": true\n}'); | ||
var noref = chainwrap(0); | ||
var nodump = QUnit.jsDump.parse(noref); | ||
equal(nodump, '{\n "first": true,\n "wrap": undefined\n}'); | ||
var selfref = chainwrap(1); | ||
var selfdump = QUnit.jsDump.parse(selfref); | ||
equal(selfdump, '{\n "wrap": recursion(-1),\n "first": true\n}'); | ||
var selfref = chainwrap(1); | ||
var selfdump = QUnit.jsDump.parse(selfref); | ||
equal(selfdump, '{\n "first": true,\n "wrap": recursion(-1)\n}'); | ||
var parentref = chainwrap(2); | ||
var parentdump = QUnit.jsDump.parse(parentref); | ||
equal(parentdump, '{\n "wrap": {\n "wrap": recursion(-2),\n "first": true\n }\n}'); | ||
var parentref = chainwrap(2); | ||
var parentdump = QUnit.jsDump.parse(parentref); | ||
equal(parentdump, '{\n "wrap": {\n "first": true,\n "wrap": recursion(-2)\n }\n}'); | ||
var circref = chainwrap(10); | ||
var circdump = QUnit.jsDump.parse(circref); | ||
ok(new RegExp("recursion\\(-10\\)").test(circdump), "(" +circdump + ") should show -10 recursion level"); | ||
var circref = chainwrap(10); | ||
var circdump = QUnit.jsDump.parse(circref); | ||
ok(new RegExp("recursion\\(-10\\)").test(circdump), "(" +circdump + ") should show -10 recursion level"); | ||
}); | ||
test("check (deep-)equal recursion", function() { | ||
var noRecursion = chainwrap(0); | ||
equal(noRecursion, noRecursion, "I should be equal to me."); | ||
deepEqual(noRecursion, noRecursion, "... and so in depth."); | ||
var noRecursion = chainwrap(0); | ||
equal(noRecursion, noRecursion, "I should be equal to me."); | ||
deepEqual(noRecursion, noRecursion, "... and so in depth."); | ||
var selfref = chainwrap(1); | ||
equal(selfref, selfref, "Even so if I nest myself."); | ||
deepEqual(selfref, selfref, "... into the depth."); | ||
var selfref = chainwrap(1); | ||
equal(selfref, selfref, "Even so if I nest myself."); | ||
deepEqual(selfref, selfref, "... into the depth."); | ||
var circref = chainwrap(10); | ||
equal(circref, circref, "Or hide that through some levels of indirection."); | ||
deepEqual(circref, circref, "... and checked on all levels!"); | ||
var circref = chainwrap(10); | ||
equal(circref, circref, "Or hide that through some levels of indirection."); | ||
deepEqual(circref, circref, "... and checked on all levels!"); | ||
}); | ||
@@ -407,25 +427,25 @@ | ||
// pure array self-ref | ||
var arr = []; | ||
arr.push(arr); | ||
// pure array self-ref | ||
var arr = []; | ||
arr.push(arr); | ||
var arrdump = QUnit.jsDump.parse(arr); | ||
var arrdump = QUnit.jsDump.parse(arr); | ||
equal(arrdump, '[\n recursion(-1)\n]'); | ||
equal(arr, arr[0], 'no endless stack when trying to dump arrays with circular ref'); | ||
equal(arrdump, '[\n recursion(-1)\n]'); | ||
equal(arr, arr[0], 'no endless stack when trying to dump arrays with circular ref'); | ||
// mix obj-arr circular ref | ||
var obj = {}; | ||
var childarr = [obj]; | ||
obj.childarr = childarr; | ||
// mix obj-arr circular ref | ||
var obj = {}; | ||
var childarr = [obj]; | ||
obj.childarr = childarr; | ||
var objdump = QUnit.jsDump.parse(obj); | ||
var childarrdump = QUnit.jsDump.parse(childarr); | ||
var objdump = QUnit.jsDump.parse(obj); | ||
var childarrdump = QUnit.jsDump.parse(childarr); | ||
equal(objdump, '{\n "childarr": [\n recursion(-2)\n ]\n}'); | ||
equal(childarrdump, '[\n {\n "childarr": recursion(-2)\n }\n]'); | ||
equal(objdump, '{\n "childarr": [\n recursion(-2)\n ]\n}'); | ||
equal(childarrdump, '[\n {\n "childarr": recursion(-2)\n }\n]'); | ||
equal(obj.childarr, childarr, 'no endless stack when trying to dump array/object mix with circular ref'); | ||
equal(childarr[0], obj, 'no endless stack when trying to dump array/object mix with circular ref'); | ||
equal(obj.childarr, childarr, 'no endless stack when trying to dump array/object mix with circular ref'); | ||
equal(childarr[0], obj, 'no endless stack when trying to dump array/object mix with circular ref'); | ||
@@ -436,39 +456,35 @@ }); | ||
test('Circular reference - test reported by soniciq in #105', function() { | ||
var MyObject = function() {}; | ||
MyObject.prototype.parent = function(obj) { | ||
if (obj === undefined) { return this._parent; } | ||
this._parent = obj; | ||
}; | ||
MyObject.prototype.children = function(obj) { | ||
if (obj === undefined) { return this._children; } | ||
this._children = obj; | ||
}; | ||
var MyObject = function() {}; | ||
MyObject.prototype.parent = function(obj) { | ||
if (obj === undefined) { return this._parent; } | ||
this._parent = obj; | ||
}; | ||
MyObject.prototype.children = function(obj) { | ||
if (obj === undefined) { return this._children; } | ||
this._children = obj; | ||
}; | ||
var a = new MyObject(), | ||
b = new MyObject(); | ||
var a = new MyObject(), | ||
b = new MyObject(); | ||
var barr = [b]; | ||
a.children(barr); | ||
b.parent(a); | ||
var barr = [b]; | ||
a.children(barr); | ||
b.parent(a); | ||
equal(a.children(), barr); | ||
deepEqual(a.children(), [b]); | ||
equal(a.children(), barr); | ||
deepEqual(a.children(), [b]); | ||
}); | ||
(function() { | ||
var reset = QUnit.reset; | ||
function afterTest() { | ||
ok( false, "reset should not modify test status" ); | ||
} | ||
module("reset"); | ||
test("reset runs assertions", function() { | ||
expect(0); | ||
QUnit.reset = function() { | ||
afterTest(); | ||
ok( false, "reset should not modify test status" ); | ||
reset.apply( this, arguments ); | ||
}; | ||
}); | ||
test("reset runs assertions2", function() { | ||
test("reset runs assertions, cleanup", function() { | ||
expect(0); | ||
QUnit.reset = reset; | ||
@@ -475,0 +491,0 @@ }); |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Uses eval
Supply chain riskPackage uses eval() which is a dangerous function. This prevents the code from running in certain environments and increases the risk that the code may contain exploits or malicious behavior.
Found 1 instance in 1 package
Shell access
Supply chain riskThis module accesses the system shell. Accessing the system shell increases the risk of executing arbitrary code.
Found 1 instance in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
224
4
645292
56
13687