astro-ghostcms/.pnpm-store/v3/files/5e/74242ec703c1cb0b7d8685b84c4...

3851 lines
132 KiB
Plaintext
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*!
* chai
* http://chaijs.com
* Copyright(c) 2011-2014 Jake Luer <jake@alogicalparadox.com>
* MIT Licensed
*/
module.exports = function (chai, _) {
var Assertion = chai.Assertion
, AssertionError = chai.AssertionError
, flag = _.flag;
/**
* ### Language Chains
*
* The following are provided as chainable getters to improve the readability
* of your assertions.
*
* **Chains**
*
* - to
* - be
* - been
* - is
* - that
* - which
* - and
* - has
* - have
* - with
* - at
* - of
* - same
* - but
* - does
* - still
* - also
*
* @name language chains
* @namespace BDD
* @api public
*/
[ 'to', 'be', 'been', 'is'
, 'and', 'has', 'have', 'with'
, 'that', 'which', 'at', 'of'
, 'same', 'but', 'does', 'still', "also" ].forEach(function (chain) {
Assertion.addProperty(chain);
});
/**
* ### .not
*
* Negates all assertions that follow in the chain.
*
* expect(function () {}).to.not.throw();
* expect({a: 1}).to.not.have.property('b');
* expect([1, 2]).to.be.an('array').that.does.not.include(3);
*
* Just because you can negate any assertion with `.not` doesn't mean you
* should. With great power comes great responsibility. It's often best to
* assert that the one expected output was produced, rather than asserting
* that one of countless unexpected outputs wasn't produced. See individual
* assertions for specific guidance.
*
* expect(2).to.equal(2); // Recommended
* expect(2).to.not.equal(1); // Not recommended
*
* @name not
* @namespace BDD
* @api public
*/
Assertion.addProperty('not', function () {
flag(this, 'negate', true);
});
/**
* ### .deep
*
* Causes all `.equal`, `.include`, `.members`, `.keys`, and `.property`
* assertions that follow in the chain to use deep equality instead of strict
* (`===`) equality. See the `deep-eql` project page for info on the deep
* equality algorithm: https://github.com/chaijs/deep-eql.
*
* // Target object deeply (but not strictly) equals `{a: 1}`
* expect({a: 1}).to.deep.equal({a: 1});
* expect({a: 1}).to.not.equal({a: 1});
*
* // Target array deeply (but not strictly) includes `{a: 1}`
* expect([{a: 1}]).to.deep.include({a: 1});
* expect([{a: 1}]).to.not.include({a: 1});
*
* // Target object deeply (but not strictly) includes `x: {a: 1}`
* expect({x: {a: 1}}).to.deep.include({x: {a: 1}});
* expect({x: {a: 1}}).to.not.include({x: {a: 1}});
*
* // Target array deeply (but not strictly) has member `{a: 1}`
* expect([{a: 1}]).to.have.deep.members([{a: 1}]);
* expect([{a: 1}]).to.not.have.members([{a: 1}]);
*
* // Target set deeply (but not strictly) has key `{a: 1}`
* expect(new Set([{a: 1}])).to.have.deep.keys([{a: 1}]);
* expect(new Set([{a: 1}])).to.not.have.keys([{a: 1}]);
*
* // Target object deeply (but not strictly) has property `x: {a: 1}`
* expect({x: {a: 1}}).to.have.deep.property('x', {a: 1});
* expect({x: {a: 1}}).to.not.have.property('x', {a: 1});
*
* @name deep
* @namespace BDD
* @api public
*/
Assertion.addProperty('deep', function () {
flag(this, 'deep', true);
});
/**
* ### .nested
*
* Enables dot- and bracket-notation in all `.property` and `.include`
* assertions that follow in the chain.
*
* expect({a: {b: ['x', 'y']}}).to.have.nested.property('a.b[1]');
* expect({a: {b: ['x', 'y']}}).to.nested.include({'a.b[1]': 'y'});
*
* If `.` or `[]` are part of an actual property name, they can be escaped by
* adding two backslashes before them.
*
* expect({'.a': {'[b]': 'x'}}).to.have.nested.property('\\.a.\\[b\\]');
* expect({'.a': {'[b]': 'x'}}).to.nested.include({'\\.a.\\[b\\]': 'x'});
*
* `.nested` cannot be combined with `.own`.
*
* @name nested
* @namespace BDD
* @api public
*/
Assertion.addProperty('nested', function () {
flag(this, 'nested', true);
});
/**
* ### .own
*
* Causes all `.property` and `.include` assertions that follow in the chain
* to ignore inherited properties.
*
* Object.prototype.b = 2;
*
* expect({a: 1}).to.have.own.property('a');
* expect({a: 1}).to.have.property('b');
* expect({a: 1}).to.not.have.own.property('b');
*
* expect({a: 1}).to.own.include({a: 1});
* expect({a: 1}).to.include({b: 2}).but.not.own.include({b: 2});
*
* `.own` cannot be combined with `.nested`.
*
* @name own
* @namespace BDD
* @api public
*/
Assertion.addProperty('own', function () {
flag(this, 'own', true);
});
/**
* ### .ordered
*
* Causes all `.members` assertions that follow in the chain to require that
* members be in the same order.
*
* expect([1, 2]).to.have.ordered.members([1, 2])
* .but.not.have.ordered.members([2, 1]);
*
* When `.include` and `.ordered` are combined, the ordering begins at the
* start of both arrays.
*
* expect([1, 2, 3]).to.include.ordered.members([1, 2])
* .but.not.include.ordered.members([2, 3]);
*
* @name ordered
* @namespace BDD
* @api public
*/
Assertion.addProperty('ordered', function () {
flag(this, 'ordered', true);
});
/**
* ### .any
*
* Causes all `.keys` assertions that follow in the chain to only require that
* the target have at least one of the given keys. This is the opposite of
* `.all`, which requires that the target have all of the given keys.
*
* expect({a: 1, b: 2}).to.not.have.any.keys('c', 'd');
*
* See the `.keys` doc for guidance on when to use `.any` or `.all`.
*
* @name any
* @namespace BDD
* @api public
*/
Assertion.addProperty('any', function () {
flag(this, 'any', true);
flag(this, 'all', false);
});
/**
* ### .all
*
* Causes all `.keys` assertions that follow in the chain to require that the
* target have all of the given keys. This is the opposite of `.any`, which
* only requires that the target have at least one of the given keys.
*
* expect({a: 1, b: 2}).to.have.all.keys('a', 'b');
*
* Note that `.all` is used by default when neither `.all` nor `.any` are
* added earlier in the chain. However, it's often best to add `.all` anyway
* because it improves readability.
*
* See the `.keys` doc for guidance on when to use `.any` or `.all`.
*
* @name all
* @namespace BDD
* @api public
*/
Assertion.addProperty('all', function () {
flag(this, 'all', true);
flag(this, 'any', false);
});
/**
* ### .a(type[, msg])
*
* Asserts that the target's type is equal to the given string `type`. Types
* are case insensitive. See the `type-detect` project page for info on the
* type detection algorithm: https://github.com/chaijs/type-detect.
*
* expect('foo').to.be.a('string');
* expect({a: 1}).to.be.an('object');
* expect(null).to.be.a('null');
* expect(undefined).to.be.an('undefined');
* expect(new Error).to.be.an('error');
* expect(Promise.resolve()).to.be.a('promise');
* expect(new Float32Array).to.be.a('float32array');
* expect(Symbol()).to.be.a('symbol');
*
* `.a` supports objects that have a custom type set via `Symbol.toStringTag`.
*
* var myObj = {
* [Symbol.toStringTag]: 'myCustomType'
* };
*
* expect(myObj).to.be.a('myCustomType').but.not.an('object');
*
* It's often best to use `.a` to check a target's type before making more
* assertions on the same target. That way, you avoid unexpected behavior from
* any assertion that does different things based on the target's type.
*
* expect([1, 2, 3]).to.be.an('array').that.includes(2);
* expect([]).to.be.an('array').that.is.empty;
*
* Add `.not` earlier in the chain to negate `.a`. However, it's often best to
* assert that the target is the expected type, rather than asserting that it
* isn't one of many unexpected types.
*
* expect('foo').to.be.a('string'); // Recommended
* expect('foo').to.not.be.an('array'); // Not recommended
*
* `.a` accepts an optional `msg` argument which is a custom error message to
* show when the assertion fails. The message can also be given as the second
* argument to `expect`.
*
* expect(1).to.be.a('string', 'nooo why fail??');
* expect(1, 'nooo why fail??').to.be.a('string');
*
* `.a` can also be used as a language chain to improve the readability of
* your assertions.
*
* expect({b: 2}).to.have.a.property('b');
*
* The alias `.an` can be used interchangeably with `.a`.
*
* @name a
* @alias an
* @param {String} type
* @param {String} msg _optional_
* @namespace BDD
* @api public
*/
function an (type, msg) {
if (msg) flag(this, 'message', msg);
type = type.toLowerCase();
var obj = flag(this, 'object')
, article = ~[ 'a', 'e', 'i', 'o', 'u' ].indexOf(type.charAt(0)) ? 'an ' : 'a ';
this.assert(
type === _.type(obj).toLowerCase()
, 'expected #{this} to be ' + article + type
, 'expected #{this} not to be ' + article + type
);
}
Assertion.addChainableMethod('an', an);
Assertion.addChainableMethod('a', an);
/**
* ### .include(val[, msg])
*
* When the target is a string, `.include` asserts that the given string `val`
* is a substring of the target.
*
* expect('foobar').to.include('foo');
*
* When the target is an array, `.include` asserts that the given `val` is a
* member of the target.
*
* expect([1, 2, 3]).to.include(2);
*
* When the target is an object, `.include` asserts that the given object
* `val`'s properties are a subset of the target's properties.
*
* expect({a: 1, b: 2, c: 3}).to.include({a: 1, b: 2});
*
* When the target is a Set or WeakSet, `.include` asserts that the given `val` is a
* member of the target. SameValueZero equality algorithm is used.
*
* expect(new Set([1, 2])).to.include(2);
*
* When the target is a Map, `.include` asserts that the given `val` is one of
* the values of the target. SameValueZero equality algorithm is used.
*
* expect(new Map([['a', 1], ['b', 2]])).to.include(2);
*
* Because `.include` does different things based on the target's type, it's
* important to check the target's type before using `.include`. See the `.a`
* doc for info on testing a target's type.
*
* expect([1, 2, 3]).to.be.an('array').that.includes(2);
*
* By default, strict (`===`) equality is used to compare array members and
* object properties. Add `.deep` earlier in the chain to use deep equality
* instead (WeakSet targets are not supported). See the `deep-eql` project
* page for info on the deep equality algorithm: https://github.com/chaijs/deep-eql.
*
* // Target array deeply (but not strictly) includes `{a: 1}`
* expect([{a: 1}]).to.deep.include({a: 1});
* expect([{a: 1}]).to.not.include({a: 1});
*
* // Target object deeply (but not strictly) includes `x: {a: 1}`
* expect({x: {a: 1}}).to.deep.include({x: {a: 1}});
* expect({x: {a: 1}}).to.not.include({x: {a: 1}});
*
* By default, all of the target's properties are searched when working with
* objects. This includes properties that are inherited and/or non-enumerable.
* Add `.own` earlier in the chain to exclude the target's inherited
* properties from the search.
*
* Object.prototype.b = 2;
*
* expect({a: 1}).to.own.include({a: 1});
* expect({a: 1}).to.include({b: 2}).but.not.own.include({b: 2});
*
* Note that a target object is always only searched for `val`'s own
* enumerable properties.
*
* `.deep` and `.own` can be combined.
*
* expect({a: {b: 2}}).to.deep.own.include({a: {b: 2}});
*
* Add `.nested` earlier in the chain to enable dot- and bracket-notation when
* referencing nested properties.
*
* expect({a: {b: ['x', 'y']}}).to.nested.include({'a.b[1]': 'y'});
*
* If `.` or `[]` are part of an actual property name, they can be escaped by
* adding two backslashes before them.
*
* expect({'.a': {'[b]': 2}}).to.nested.include({'\\.a.\\[b\\]': 2});
*
* `.deep` and `.nested` can be combined.
*
* expect({a: {b: [{c: 3}]}}).to.deep.nested.include({'a.b[0]': {c: 3}});
*
* `.own` and `.nested` cannot be combined.
*
* Add `.not` earlier in the chain to negate `.include`.
*
* expect('foobar').to.not.include('taco');
* expect([1, 2, 3]).to.not.include(4);
*
* However, it's dangerous to negate `.include` when the target is an object.
* The problem is that it creates uncertain expectations by asserting that the
* target object doesn't have all of `val`'s key/value pairs but may or may
* not have some of them. It's often best to identify the exact output that's
* expected, and then write an assertion that only accepts that exact output.
*
* When the target object isn't even expected to have `val`'s keys, it's
* often best to assert exactly that.
*
* expect({c: 3}).to.not.have.any.keys('a', 'b'); // Recommended
* expect({c: 3}).to.not.include({a: 1, b: 2}); // Not recommended
*
* When the target object is expected to have `val`'s keys, it's often best to
* assert that each of the properties has its expected value, rather than
* asserting that each property doesn't have one of many unexpected values.
*
* expect({a: 3, b: 4}).to.include({a: 3, b: 4}); // Recommended
* expect({a: 3, b: 4}).to.not.include({a: 1, b: 2}); // Not recommended
*
* `.include` accepts an optional `msg` argument which is a custom error
* message to show when the assertion fails. The message can also be given as
* the second argument to `expect`.
*
* expect([1, 2, 3]).to.include(4, 'nooo why fail??');
* expect([1, 2, 3], 'nooo why fail??').to.include(4);
*
* `.include` can also be used as a language chain, causing all `.members` and
* `.keys` assertions that follow in the chain to require the target to be a
* superset of the expected set, rather than an identical set. Note that
* `.members` ignores duplicates in the subset when `.include` is added.
*
* // Target object's keys are a superset of ['a', 'b'] but not identical
* expect({a: 1, b: 2, c: 3}).to.include.all.keys('a', 'b');
* expect({a: 1, b: 2, c: 3}).to.not.have.all.keys('a', 'b');
*
* // Target array is a superset of [1, 2] but not identical
* expect([1, 2, 3]).to.include.members([1, 2]);
* expect([1, 2, 3]).to.not.have.members([1, 2]);
*
* // Duplicates in the subset are ignored
* expect([1, 2, 3]).to.include.members([1, 2, 2, 2]);
*
* Note that adding `.any` earlier in the chain causes the `.keys` assertion
* to ignore `.include`.
*
* // Both assertions are identical
* expect({a: 1}).to.include.any.keys('a', 'b');
* expect({a: 1}).to.have.any.keys('a', 'b');
*
* The aliases `.includes`, `.contain`, and `.contains` can be used
* interchangeably with `.include`.
*
* @name include
* @alias contain
* @alias includes
* @alias contains
* @param {Mixed} val
* @param {String} msg _optional_
* @namespace BDD
* @api public
*/
function SameValueZero(a, b) {
return (_.isNaN(a) && _.isNaN(b)) || a === b;
}
function includeChainingBehavior () {
flag(this, 'contains', true);
}
function include (val, msg) {
if (msg) flag(this, 'message', msg);
var obj = flag(this, 'object')
, objType = _.type(obj).toLowerCase()
, flagMsg = flag(this, 'message')
, negate = flag(this, 'negate')
, ssfi = flag(this, 'ssfi')
, isDeep = flag(this, 'deep')
, descriptor = isDeep ? 'deep ' : ''
, isEql = isDeep ? flag(this, 'eql') : SameValueZero;
flagMsg = flagMsg ? flagMsg + ': ' : '';
var included = false;
switch (objType) {
case 'string':
included = obj.indexOf(val) !== -1;
break;
case 'weakset':
if (isDeep) {
throw new AssertionError(
flagMsg + 'unable to use .deep.include with WeakSet',
undefined,
ssfi
);
}
included = obj.has(val);
break;
case 'map':
obj.forEach(function (item) {
included = included || isEql(item, val);
});
break;
case 'set':
if (isDeep) {
obj.forEach(function (item) {
included = included || isEql(item, val);
});
} else {
included = obj.has(val);
}
break;
case 'array':
if (isDeep) {
included = obj.some(function (item) {
return isEql(item, val);
})
} else {
included = obj.indexOf(val) !== -1;
}
break;
default:
// This block is for asserting a subset of properties in an object.
// `_.expectTypes` isn't used here because `.include` should work with
// objects with a custom `@@toStringTag`.
if (val !== Object(val)) {
throw new AssertionError(
flagMsg + 'the given combination of arguments ('
+ objType + ' and '
+ _.type(val).toLowerCase() + ')'
+ ' is invalid for this assertion. '
+ 'You can use an array, a map, an object, a set, a string, '
+ 'or a weakset instead of a '
+ _.type(val).toLowerCase(),
undefined,
ssfi
);
}
var props = Object.keys(val)
, firstErr = null
, numErrs = 0;
props.forEach(function (prop) {
var propAssertion = new Assertion(obj);
_.transferFlags(this, propAssertion, true);
flag(propAssertion, 'lockSsfi', true);
if (!negate || props.length === 1) {
propAssertion.property(prop, val[prop]);
return;
}
try {
propAssertion.property(prop, val[prop]);
} catch (err) {
if (!_.checkError.compatibleConstructor(err, AssertionError)) {
throw err;
}
if (firstErr === null) firstErr = err;
numErrs++;
}
}, this);
// When validating .not.include with multiple properties, we only want
// to throw an assertion error if all of the properties are included,
// in which case we throw the first property assertion error that we
// encountered.
if (negate && props.length > 1 && numErrs === props.length) {
throw firstErr;
}
return;
}
// Assert inclusion in collection or substring in a string.
this.assert(
included
, 'expected #{this} to ' + descriptor + 'include ' + _.inspect(val)
, 'expected #{this} to not ' + descriptor + 'include ' + _.inspect(val));
}
Assertion.addChainableMethod('include', include, includeChainingBehavior);
Assertion.addChainableMethod('contain', include, includeChainingBehavior);
Assertion.addChainableMethod('contains', include, includeChainingBehavior);
Assertion.addChainableMethod('includes', include, includeChainingBehavior);
/**
* ### .ok
*
* Asserts that the target is a truthy value (considered `true` in boolean context).
* However, it's often best to assert that the target is strictly (`===`) or
* deeply equal to its expected value.
*
* expect(1).to.equal(1); // Recommended
* expect(1).to.be.ok; // Not recommended
*
* expect(true).to.be.true; // Recommended
* expect(true).to.be.ok; // Not recommended
*
* Add `.not` earlier in the chain to negate `.ok`.
*
* expect(0).to.equal(0); // Recommended
* expect(0).to.not.be.ok; // Not recommended
*
* expect(false).to.be.false; // Recommended
* expect(false).to.not.be.ok; // Not recommended
*
* expect(null).to.be.null; // Recommended
* expect(null).to.not.be.ok; // Not recommended
*
* expect(undefined).to.be.undefined; // Recommended
* expect(undefined).to.not.be.ok; // Not recommended
*
* A custom error message can be given as the second argument to `expect`.
*
* expect(false, 'nooo why fail??').to.be.ok;
*
* @name ok
* @namespace BDD
* @api public
*/
Assertion.addProperty('ok', function () {
this.assert(
flag(this, 'object')
, 'expected #{this} to be truthy'
, 'expected #{this} to be falsy');
});
/**
* ### .true
*
* Asserts that the target is strictly (`===`) equal to `true`.
*
* expect(true).to.be.true;
*
* Add `.not` earlier in the chain to negate `.true`. However, it's often best
* to assert that the target is equal to its expected value, rather than not
* equal to `true`.
*
* expect(false).to.be.false; // Recommended
* expect(false).to.not.be.true; // Not recommended
*
* expect(1).to.equal(1); // Recommended
* expect(1).to.not.be.true; // Not recommended
*
* A custom error message can be given as the second argument to `expect`.
*
* expect(false, 'nooo why fail??').to.be.true;
*
* @name true
* @namespace BDD
* @api public
*/
Assertion.addProperty('true', function () {
this.assert(
true === flag(this, 'object')
, 'expected #{this} to be true'
, 'expected #{this} to be false'
, flag(this, 'negate') ? false : true
);
});
/**
* ### .false
*
* Asserts that the target is strictly (`===`) equal to `false`.
*
* expect(false).to.be.false;
*
* Add `.not` earlier in the chain to negate `.false`. However, it's often
* best to assert that the target is equal to its expected value, rather than
* not equal to `false`.
*
* expect(true).to.be.true; // Recommended
* expect(true).to.not.be.false; // Not recommended
*
* expect(1).to.equal(1); // Recommended
* expect(1).to.not.be.false; // Not recommended
*
* A custom error message can be given as the second argument to `expect`.
*
* expect(true, 'nooo why fail??').to.be.false;
*
* @name false
* @namespace BDD
* @api public
*/
Assertion.addProperty('false', function () {
this.assert(
false === flag(this, 'object')
, 'expected #{this} to be false'
, 'expected #{this} to be true'
, flag(this, 'negate') ? true : false
);
});
/**
* ### .null
*
* Asserts that the target is strictly (`===`) equal to `null`.
*
* expect(null).to.be.null;
*
* Add `.not` earlier in the chain to negate `.null`. However, it's often best
* to assert that the target is equal to its expected value, rather than not
* equal to `null`.
*
* expect(1).to.equal(1); // Recommended
* expect(1).to.not.be.null; // Not recommended
*
* A custom error message can be given as the second argument to `expect`.
*
* expect(42, 'nooo why fail??').to.be.null;
*
* @name null
* @namespace BDD
* @api public
*/
Assertion.addProperty('null', function () {
this.assert(
null === flag(this, 'object')
, 'expected #{this} to be null'
, 'expected #{this} not to be null'
);
});
/**
* ### .undefined
*
* Asserts that the target is strictly (`===`) equal to `undefined`.
*
* expect(undefined).to.be.undefined;
*
* Add `.not` earlier in the chain to negate `.undefined`. However, it's often
* best to assert that the target is equal to its expected value, rather than
* not equal to `undefined`.
*
* expect(1).to.equal(1); // Recommended
* expect(1).to.not.be.undefined; // Not recommended
*
* A custom error message can be given as the second argument to `expect`.
*
* expect(42, 'nooo why fail??').to.be.undefined;
*
* @name undefined
* @namespace BDD
* @api public
*/
Assertion.addProperty('undefined', function () {
this.assert(
undefined === flag(this, 'object')
, 'expected #{this} to be undefined'
, 'expected #{this} not to be undefined'
);
});
/**
* ### .NaN
*
* Asserts that the target is exactly `NaN`.
*
* expect(NaN).to.be.NaN;
*
* Add `.not` earlier in the chain to negate `.NaN`. However, it's often best
* to assert that the target is equal to its expected value, rather than not
* equal to `NaN`.
*
* expect('foo').to.equal('foo'); // Recommended
* expect('foo').to.not.be.NaN; // Not recommended
*
* A custom error message can be given as the second argument to `expect`.
*
* expect(42, 'nooo why fail??').to.be.NaN;
*
* @name NaN
* @namespace BDD
* @api public
*/
Assertion.addProperty('NaN', function () {
this.assert(
_.isNaN(flag(this, 'object'))
, 'expected #{this} to be NaN'
, 'expected #{this} not to be NaN'
);
});
/**
* ### .exist
*
* Asserts that the target is not strictly (`===`) equal to either `null` or
* `undefined`. However, it's often best to assert that the target is equal to
* its expected value.
*
* expect(1).to.equal(1); // Recommended
* expect(1).to.exist; // Not recommended
*
* expect(0).to.equal(0); // Recommended
* expect(0).to.exist; // Not recommended
*
* Add `.not` earlier in the chain to negate `.exist`.
*
* expect(null).to.be.null; // Recommended
* expect(null).to.not.exist; // Not recommended
*
* expect(undefined).to.be.undefined; // Recommended
* expect(undefined).to.not.exist; // Not recommended
*
* A custom error message can be given as the second argument to `expect`.
*
* expect(null, 'nooo why fail??').to.exist;
*
* The alias `.exists` can be used interchangeably with `.exist`.
*
* @name exist
* @alias exists
* @namespace BDD
* @api public
*/
function assertExist () {
var val = flag(this, 'object');
this.assert(
val !== null && val !== undefined
, 'expected #{this} to exist'
, 'expected #{this} to not exist'
);
}
Assertion.addProperty('exist', assertExist);
Assertion.addProperty('exists', assertExist);
/**
* ### .empty
*
* When the target is a string or array, `.empty` asserts that the target's
* `length` property is strictly (`===`) equal to `0`.
*
* expect([]).to.be.empty;
* expect('').to.be.empty;
*
* When the target is a map or set, `.empty` asserts that the target's `size`
* property is strictly equal to `0`.
*
* expect(new Set()).to.be.empty;
* expect(new Map()).to.be.empty;
*
* When the target is a non-function object, `.empty` asserts that the target
* doesn't have any own enumerable properties. Properties with Symbol-based
* keys are excluded from the count.
*
* expect({}).to.be.empty;
*
* Because `.empty` does different things based on the target's type, it's
* important to check the target's type before using `.empty`. See the `.a`
* doc for info on testing a target's type.
*
* expect([]).to.be.an('array').that.is.empty;
*
* Add `.not` earlier in the chain to negate `.empty`. However, it's often
* best to assert that the target contains its expected number of values,
* rather than asserting that it's not empty.
*
* expect([1, 2, 3]).to.have.lengthOf(3); // Recommended
* expect([1, 2, 3]).to.not.be.empty; // Not recommended
*
* expect(new Set([1, 2, 3])).to.have.property('size', 3); // Recommended
* expect(new Set([1, 2, 3])).to.not.be.empty; // Not recommended
*
* expect(Object.keys({a: 1})).to.have.lengthOf(1); // Recommended
* expect({a: 1}).to.not.be.empty; // Not recommended
*
* A custom error message can be given as the second argument to `expect`.
*
* expect([1, 2, 3], 'nooo why fail??').to.be.empty;
*
* @name empty
* @namespace BDD
* @api public
*/
Assertion.addProperty('empty', function () {
var val = flag(this, 'object')
, ssfi = flag(this, 'ssfi')
, flagMsg = flag(this, 'message')
, itemsCount;
flagMsg = flagMsg ? flagMsg + ': ' : '';
switch (_.type(val).toLowerCase()) {
case 'array':
case 'string':
itemsCount = val.length;
break;
case 'map':
case 'set':
itemsCount = val.size;
break;
case 'weakmap':
case 'weakset':
throw new AssertionError(
flagMsg + '.empty was passed a weak collection',
undefined,
ssfi
);
case 'function':
var msg = flagMsg + '.empty was passed a function ' + _.getName(val);
throw new AssertionError(msg.trim(), undefined, ssfi);
default:
if (val !== Object(val)) {
throw new AssertionError(
flagMsg + '.empty was passed non-string primitive ' + _.inspect(val),
undefined,
ssfi
);
}
itemsCount = Object.keys(val).length;
}
this.assert(
0 === itemsCount
, 'expected #{this} to be empty'
, 'expected #{this} not to be empty'
);
});
/**
* ### .arguments
*
* Asserts that the target is an `arguments` object.
*
* function test () {
* expect(arguments).to.be.arguments;
* }
*
* test();
*
* Add `.not` earlier in the chain to negate `.arguments`. However, it's often
* best to assert which type the target is expected to be, rather than
* asserting that its not an `arguments` object.
*
* expect('foo').to.be.a('string'); // Recommended
* expect('foo').to.not.be.arguments; // Not recommended
*
* A custom error message can be given as the second argument to `expect`.
*
* expect({}, 'nooo why fail??').to.be.arguments;
*
* The alias `.Arguments` can be used interchangeably with `.arguments`.
*
* @name arguments
* @alias Arguments
* @namespace BDD
* @api public
*/
function checkArguments () {
var obj = flag(this, 'object')
, type = _.type(obj);
this.assert(
'Arguments' === type
, 'expected #{this} to be arguments but got ' + type
, 'expected #{this} to not be arguments'
);
}
Assertion.addProperty('arguments', checkArguments);
Assertion.addProperty('Arguments', checkArguments);
/**
* ### .equal(val[, msg])
*
* Asserts that the target is strictly (`===`) equal to the given `val`.
*
* expect(1).to.equal(1);
* expect('foo').to.equal('foo');
*
* Add `.deep` earlier in the chain to use deep equality instead. See the
* `deep-eql` project page for info on the deep equality algorithm:
* https://github.com/chaijs/deep-eql.
*
* // Target object deeply (but not strictly) equals `{a: 1}`
* expect({a: 1}).to.deep.equal({a: 1});
* expect({a: 1}).to.not.equal({a: 1});
*
* // Target array deeply (but not strictly) equals `[1, 2]`
* expect([1, 2]).to.deep.equal([1, 2]);
* expect([1, 2]).to.not.equal([1, 2]);
*
* Add `.not` earlier in the chain to negate `.equal`. However, it's often
* best to assert that the target is equal to its expected value, rather than
* not equal to one of countless unexpected values.
*
* expect(1).to.equal(1); // Recommended
* expect(1).to.not.equal(2); // Not recommended
*
* `.equal` accepts an optional `msg` argument which is a custom error message
* to show when the assertion fails. The message can also be given as the
* second argument to `expect`.
*
* expect(1).to.equal(2, 'nooo why fail??');
* expect(1, 'nooo why fail??').to.equal(2);
*
* The aliases `.equals` and `eq` can be used interchangeably with `.equal`.
*
* @name equal
* @alias equals
* @alias eq
* @param {Mixed} val
* @param {String} msg _optional_
* @namespace BDD
* @api public
*/
function assertEqual (val, msg) {
if (msg) flag(this, 'message', msg);
var obj = flag(this, 'object');
if (flag(this, 'deep')) {
var prevLockSsfi = flag(this, 'lockSsfi');
flag(this, 'lockSsfi', true);
this.eql(val);
flag(this, 'lockSsfi', prevLockSsfi);
} else {
this.assert(
val === obj
, 'expected #{this} to equal #{exp}'
, 'expected #{this} to not equal #{exp}'
, val
, this._obj
, true
);
}
}
Assertion.addMethod('equal', assertEqual);
Assertion.addMethod('equals', assertEqual);
Assertion.addMethod('eq', assertEqual);
/**
* ### .eql(obj[, msg])
*
* Asserts that the target is deeply equal to the given `obj`. See the
* `deep-eql` project page for info on the deep equality algorithm:
* https://github.com/chaijs/deep-eql.
*
* // Target object is deeply (but not strictly) equal to {a: 1}
* expect({a: 1}).to.eql({a: 1}).but.not.equal({a: 1});
*
* // Target array is deeply (but not strictly) equal to [1, 2]
* expect([1, 2]).to.eql([1, 2]).but.not.equal([1, 2]);
*
* Add `.not` earlier in the chain to negate `.eql`. However, it's often best
* to assert that the target is deeply equal to its expected value, rather
* than not deeply equal to one of countless unexpected values.
*
* expect({a: 1}).to.eql({a: 1}); // Recommended
* expect({a: 1}).to.not.eql({b: 2}); // Not recommended
*
* `.eql` accepts an optional `msg` argument which is a custom error message
* to show when the assertion fails. The message can also be given as the
* second argument to `expect`.
*
* expect({a: 1}).to.eql({b: 2}, 'nooo why fail??');
* expect({a: 1}, 'nooo why fail??').to.eql({b: 2});
*
* The alias `.eqls` can be used interchangeably with `.eql`.
*
* The `.deep.equal` assertion is almost identical to `.eql` but with one
* difference: `.deep.equal` causes deep equality comparisons to also be used
* for any other assertions that follow in the chain.
*
* @name eql
* @alias eqls
* @param {Mixed} obj
* @param {String} msg _optional_
* @namespace BDD
* @api public
*/
function assertEql(obj, msg) {
if (msg) flag(this, 'message', msg);
var eql = flag(this, 'eql');
this.assert(
eql(obj, flag(this, 'object'))
, 'expected #{this} to deeply equal #{exp}'
, 'expected #{this} to not deeply equal #{exp}'
, obj
, this._obj
, true
);
}
Assertion.addMethod('eql', assertEql);
Assertion.addMethod('eqls', assertEql);
/**
* ### .above(n[, msg])
*
* Asserts that the target is a number or a date greater than the given number or date `n` respectively.
* However, it's often best to assert that the target is equal to its expected
* value.
*
* expect(2).to.equal(2); // Recommended
* expect(2).to.be.above(1); // Not recommended
*
* Add `.lengthOf` earlier in the chain to assert that the target's `length`
* or `size` is greater than the given number `n`.
*
* expect('foo').to.have.lengthOf(3); // Recommended
* expect('foo').to.have.lengthOf.above(2); // Not recommended
*
* expect([1, 2, 3]).to.have.lengthOf(3); // Recommended
* expect([1, 2, 3]).to.have.lengthOf.above(2); // Not recommended
*
* Add `.not` earlier in the chain to negate `.above`.
*
* expect(2).to.equal(2); // Recommended
* expect(1).to.not.be.above(2); // Not recommended
*
* `.above` accepts an optional `msg` argument which is a custom error message
* to show when the assertion fails. The message can also be given as the
* second argument to `expect`.
*
* expect(1).to.be.above(2, 'nooo why fail??');
* expect(1, 'nooo why fail??').to.be.above(2);
*
* The aliases `.gt` and `.greaterThan` can be used interchangeably with
* `.above`.
*
* @name above
* @alias gt
* @alias greaterThan
* @param {Number} n
* @param {String} msg _optional_
* @namespace BDD
* @api public
*/
function assertAbove (n, msg) {
if (msg) flag(this, 'message', msg);
var obj = flag(this, 'object')
, doLength = flag(this, 'doLength')
, flagMsg = flag(this, 'message')
, msgPrefix = ((flagMsg) ? flagMsg + ': ' : '')
, ssfi = flag(this, 'ssfi')
, objType = _.type(obj).toLowerCase()
, nType = _.type(n).toLowerCase()
, errorMessage
, shouldThrow = true;
if (doLength && objType !== 'map' && objType !== 'set') {
new Assertion(obj, flagMsg, ssfi, true).to.have.property('length');
}
if (!doLength && (objType === 'date' && nType !== 'date')) {
errorMessage = msgPrefix + 'the argument to above must be a date';
} else if (nType !== 'number' && (doLength || objType === 'number')) {
errorMessage = msgPrefix + 'the argument to above must be a number';
} else if (!doLength && (objType !== 'date' && objType !== 'number')) {
var printObj = (objType === 'string') ? "'" + obj + "'" : obj;
errorMessage = msgPrefix + 'expected ' + printObj + ' to be a number or a date';
} else {
shouldThrow = false;
}
if (shouldThrow) {
throw new AssertionError(errorMessage, undefined, ssfi);
}
if (doLength) {
var descriptor = 'length'
, itemsCount;
if (objType === 'map' || objType === 'set') {
descriptor = 'size';
itemsCount = obj.size;
} else {
itemsCount = obj.length;
}
this.assert(
itemsCount > n
, 'expected #{this} to have a ' + descriptor + ' above #{exp} but got #{act}'
, 'expected #{this} to not have a ' + descriptor + ' above #{exp}'
, n
, itemsCount
);
} else {
this.assert(
obj > n
, 'expected #{this} to be above #{exp}'
, 'expected #{this} to be at most #{exp}'
, n
);
}
}
Assertion.addMethod('above', assertAbove);
Assertion.addMethod('gt', assertAbove);
Assertion.addMethod('greaterThan', assertAbove);
/**
* ### .least(n[, msg])
*
* Asserts that the target is a number or a date greater than or equal to the given
* number or date `n` respectively. However, it's often best to assert that the target is equal to
* its expected value.
*
* expect(2).to.equal(2); // Recommended
* expect(2).to.be.at.least(1); // Not recommended
* expect(2).to.be.at.least(2); // Not recommended
*
* Add `.lengthOf` earlier in the chain to assert that the target's `length`
* or `size` is greater than or equal to the given number `n`.
*
* expect('foo').to.have.lengthOf(3); // Recommended
* expect('foo').to.have.lengthOf.at.least(2); // Not recommended
*
* expect([1, 2, 3]).to.have.lengthOf(3); // Recommended
* expect([1, 2, 3]).to.have.lengthOf.at.least(2); // Not recommended
*
* Add `.not` earlier in the chain to negate `.least`.
*
* expect(1).to.equal(1); // Recommended
* expect(1).to.not.be.at.least(2); // Not recommended
*
* `.least` accepts an optional `msg` argument which is a custom error message
* to show when the assertion fails. The message can also be given as the
* second argument to `expect`.
*
* expect(1).to.be.at.least(2, 'nooo why fail??');
* expect(1, 'nooo why fail??').to.be.at.least(2);
*
* The aliases `.gte` and `.greaterThanOrEqual` can be used interchangeably with
* `.least`.
*
* @name least
* @alias gte
* @alias greaterThanOrEqual
* @param {Number} n
* @param {String} msg _optional_
* @namespace BDD
* @api public
*/
function assertLeast (n, msg) {
if (msg) flag(this, 'message', msg);
var obj = flag(this, 'object')
, doLength = flag(this, 'doLength')
, flagMsg = flag(this, 'message')
, msgPrefix = ((flagMsg) ? flagMsg + ': ' : '')
, ssfi = flag(this, 'ssfi')
, objType = _.type(obj).toLowerCase()
, nType = _.type(n).toLowerCase()
, errorMessage
, shouldThrow = true;
if (doLength && objType !== 'map' && objType !== 'set') {
new Assertion(obj, flagMsg, ssfi, true).to.have.property('length');
}
if (!doLength && (objType === 'date' && nType !== 'date')) {
errorMessage = msgPrefix + 'the argument to least must be a date';
} else if (nType !== 'number' && (doLength || objType === 'number')) {
errorMessage = msgPrefix + 'the argument to least must be a number';
} else if (!doLength && (objType !== 'date' && objType !== 'number')) {
var printObj = (objType === 'string') ? "'" + obj + "'" : obj;
errorMessage = msgPrefix + 'expected ' + printObj + ' to be a number or a date';
} else {
shouldThrow = false;
}
if (shouldThrow) {
throw new AssertionError(errorMessage, undefined, ssfi);
}
if (doLength) {
var descriptor = 'length'
, itemsCount;
if (objType === 'map' || objType === 'set') {
descriptor = 'size';
itemsCount = obj.size;
} else {
itemsCount = obj.length;
}
this.assert(
itemsCount >= n
, 'expected #{this} to have a ' + descriptor + ' at least #{exp} but got #{act}'
, 'expected #{this} to have a ' + descriptor + ' below #{exp}'
, n
, itemsCount
);
} else {
this.assert(
obj >= n
, 'expected #{this} to be at least #{exp}'
, 'expected #{this} to be below #{exp}'
, n
);
}
}
Assertion.addMethod('least', assertLeast);
Assertion.addMethod('gte', assertLeast);
Assertion.addMethod('greaterThanOrEqual', assertLeast);
/**
* ### .below(n[, msg])
*
* Asserts that the target is a number or a date less than the given number or date `n` respectively.
* However, it's often best to assert that the target is equal to its expected
* value.
*
* expect(1).to.equal(1); // Recommended
* expect(1).to.be.below(2); // Not recommended
*
* Add `.lengthOf` earlier in the chain to assert that the target's `length`
* or `size` is less than the given number `n`.
*
* expect('foo').to.have.lengthOf(3); // Recommended
* expect('foo').to.have.lengthOf.below(4); // Not recommended
*
* expect([1, 2, 3]).to.have.length(3); // Recommended
* expect([1, 2, 3]).to.have.lengthOf.below(4); // Not recommended
*
* Add `.not` earlier in the chain to negate `.below`.
*
* expect(2).to.equal(2); // Recommended
* expect(2).to.not.be.below(1); // Not recommended
*
* `.below` accepts an optional `msg` argument which is a custom error message
* to show when the assertion fails. The message can also be given as the
* second argument to `expect`.
*
* expect(2).to.be.below(1, 'nooo why fail??');
* expect(2, 'nooo why fail??').to.be.below(1);
*
* The aliases `.lt` and `.lessThan` can be used interchangeably with
* `.below`.
*
* @name below
* @alias lt
* @alias lessThan
* @param {Number} n
* @param {String} msg _optional_
* @namespace BDD
* @api public
*/
function assertBelow (n, msg) {
if (msg) flag(this, 'message', msg);
var obj = flag(this, 'object')
, doLength = flag(this, 'doLength')
, flagMsg = flag(this, 'message')
, msgPrefix = ((flagMsg) ? flagMsg + ': ' : '')
, ssfi = flag(this, 'ssfi')
, objType = _.type(obj).toLowerCase()
, nType = _.type(n).toLowerCase()
, errorMessage
, shouldThrow = true;
if (doLength && objType !== 'map' && objType !== 'set') {
new Assertion(obj, flagMsg, ssfi, true).to.have.property('length');
}
if (!doLength && (objType === 'date' && nType !== 'date')) {
errorMessage = msgPrefix + 'the argument to below must be a date';
} else if (nType !== 'number' && (doLength || objType === 'number')) {
errorMessage = msgPrefix + 'the argument to below must be a number';
} else if (!doLength && (objType !== 'date' && objType !== 'number')) {
var printObj = (objType === 'string') ? "'" + obj + "'" : obj;
errorMessage = msgPrefix + 'expected ' + printObj + ' to be a number or a date';
} else {
shouldThrow = false;
}
if (shouldThrow) {
throw new AssertionError(errorMessage, undefined, ssfi);
}
if (doLength) {
var descriptor = 'length'
, itemsCount;
if (objType === 'map' || objType === 'set') {
descriptor = 'size';
itemsCount = obj.size;
} else {
itemsCount = obj.length;
}
this.assert(
itemsCount < n
, 'expected #{this} to have a ' + descriptor + ' below #{exp} but got #{act}'
, 'expected #{this} to not have a ' + descriptor + ' below #{exp}'
, n
, itemsCount
);
} else {
this.assert(
obj < n
, 'expected #{this} to be below #{exp}'
, 'expected #{this} to be at least #{exp}'
, n
);
}
}
Assertion.addMethod('below', assertBelow);
Assertion.addMethod('lt', assertBelow);
Assertion.addMethod('lessThan', assertBelow);
/**
* ### .most(n[, msg])
*
* Asserts that the target is a number or a date less than or equal to the given number
* or date `n` respectively. However, it's often best to assert that the target is equal to its
* expected value.
*
* expect(1).to.equal(1); // Recommended
* expect(1).to.be.at.most(2); // Not recommended
* expect(1).to.be.at.most(1); // Not recommended
*
* Add `.lengthOf` earlier in the chain to assert that the target's `length`
* or `size` is less than or equal to the given number `n`.
*
* expect('foo').to.have.lengthOf(3); // Recommended
* expect('foo').to.have.lengthOf.at.most(4); // Not recommended
*
* expect([1, 2, 3]).to.have.lengthOf(3); // Recommended
* expect([1, 2, 3]).to.have.lengthOf.at.most(4); // Not recommended
*
* Add `.not` earlier in the chain to negate `.most`.
*
* expect(2).to.equal(2); // Recommended
* expect(2).to.not.be.at.most(1); // Not recommended
*
* `.most` accepts an optional `msg` argument which is a custom error message
* to show when the assertion fails. The message can also be given as the
* second argument to `expect`.
*
* expect(2).to.be.at.most(1, 'nooo why fail??');
* expect(2, 'nooo why fail??').to.be.at.most(1);
*
* The aliases `.lte` and `.lessThanOrEqual` can be used interchangeably with
* `.most`.
*
* @name most
* @alias lte
* @alias lessThanOrEqual
* @param {Number} n
* @param {String} msg _optional_
* @namespace BDD
* @api public
*/
function assertMost (n, msg) {
if (msg) flag(this, 'message', msg);
var obj = flag(this, 'object')
, doLength = flag(this, 'doLength')
, flagMsg = flag(this, 'message')
, msgPrefix = ((flagMsg) ? flagMsg + ': ' : '')
, ssfi = flag(this, 'ssfi')
, objType = _.type(obj).toLowerCase()
, nType = _.type(n).toLowerCase()
, errorMessage
, shouldThrow = true;
if (doLength && objType !== 'map' && objType !== 'set') {
new Assertion(obj, flagMsg, ssfi, true).to.have.property('length');
}
if (!doLength && (objType === 'date' && nType !== 'date')) {
errorMessage = msgPrefix + 'the argument to most must be a date';
} else if (nType !== 'number' && (doLength || objType === 'number')) {
errorMessage = msgPrefix + 'the argument to most must be a number';
} else if (!doLength && (objType !== 'date' && objType !== 'number')) {
var printObj = (objType === 'string') ? "'" + obj + "'" : obj;
errorMessage = msgPrefix + 'expected ' + printObj + ' to be a number or a date';
} else {
shouldThrow = false;
}
if (shouldThrow) {
throw new AssertionError(errorMessage, undefined, ssfi);
}
if (doLength) {
var descriptor = 'length'
, itemsCount;
if (objType === 'map' || objType === 'set') {
descriptor = 'size';
itemsCount = obj.size;
} else {
itemsCount = obj.length;
}
this.assert(
itemsCount <= n
, 'expected #{this} to have a ' + descriptor + ' at most #{exp} but got #{act}'
, 'expected #{this} to have a ' + descriptor + ' above #{exp}'
, n
, itemsCount
);
} else {
this.assert(
obj <= n
, 'expected #{this} to be at most #{exp}'
, 'expected #{this} to be above #{exp}'
, n
);
}
}
Assertion.addMethod('most', assertMost);
Assertion.addMethod('lte', assertMost);
Assertion.addMethod('lessThanOrEqual', assertMost);
/**
* ### .within(start, finish[, msg])
*
* Asserts that the target is a number or a date greater than or equal to the given
* number or date `start`, and less than or equal to the given number or date `finish` respectively.
* However, it's often best to assert that the target is equal to its expected
* value.
*
* expect(2).to.equal(2); // Recommended
* expect(2).to.be.within(1, 3); // Not recommended
* expect(2).to.be.within(2, 3); // Not recommended
* expect(2).to.be.within(1, 2); // Not recommended
*
* Add `.lengthOf` earlier in the chain to assert that the target's `length`
* or `size` is greater than or equal to the given number `start`, and less
* than or equal to the given number `finish`.
*
* expect('foo').to.have.lengthOf(3); // Recommended
* expect('foo').to.have.lengthOf.within(2, 4); // Not recommended
*
* expect([1, 2, 3]).to.have.lengthOf(3); // Recommended
* expect([1, 2, 3]).to.have.lengthOf.within(2, 4); // Not recommended
*
* Add `.not` earlier in the chain to negate `.within`.
*
* expect(1).to.equal(1); // Recommended
* expect(1).to.not.be.within(2, 4); // Not recommended
*
* `.within` accepts an optional `msg` argument which is a custom error
* message to show when the assertion fails. The message can also be given as
* the second argument to `expect`.
*
* expect(4).to.be.within(1, 3, 'nooo why fail??');
* expect(4, 'nooo why fail??').to.be.within(1, 3);
*
* @name within
* @param {Number} start lower bound inclusive
* @param {Number} finish upper bound inclusive
* @param {String} msg _optional_
* @namespace BDD
* @api public
*/
Assertion.addMethod('within', function (start, finish, msg) {
if (msg) flag(this, 'message', msg);
var obj = flag(this, 'object')
, doLength = flag(this, 'doLength')
, flagMsg = flag(this, 'message')
, msgPrefix = ((flagMsg) ? flagMsg + ': ' : '')
, ssfi = flag(this, 'ssfi')
, objType = _.type(obj).toLowerCase()
, startType = _.type(start).toLowerCase()
, finishType = _.type(finish).toLowerCase()
, errorMessage
, shouldThrow = true
, range = (startType === 'date' && finishType === 'date')
? start.toISOString() + '..' + finish.toISOString()
: start + '..' + finish;
if (doLength && objType !== 'map' && objType !== 'set') {
new Assertion(obj, flagMsg, ssfi, true).to.have.property('length');
}
if (!doLength && (objType === 'date' && (startType !== 'date' || finishType !== 'date'))) {
errorMessage = msgPrefix + 'the arguments to within must be dates';
} else if ((startType !== 'number' || finishType !== 'number') && (doLength || objType === 'number')) {
errorMessage = msgPrefix + 'the arguments to within must be numbers';
} else if (!doLength && (objType !== 'date' && objType !== 'number')) {
var printObj = (objType === 'string') ? "'" + obj + "'" : obj;
errorMessage = msgPrefix + 'expected ' + printObj + ' to be a number or a date';
} else {
shouldThrow = false;
}
if (shouldThrow) {
throw new AssertionError(errorMessage, undefined, ssfi);
}
if (doLength) {
var descriptor = 'length'
, itemsCount;
if (objType === 'map' || objType === 'set') {
descriptor = 'size';
itemsCount = obj.size;
} else {
itemsCount = obj.length;
}
this.assert(
itemsCount >= start && itemsCount <= finish
, 'expected #{this} to have a ' + descriptor + ' within ' + range
, 'expected #{this} to not have a ' + descriptor + ' within ' + range
);
} else {
this.assert(
obj >= start && obj <= finish
, 'expected #{this} to be within ' + range
, 'expected #{this} to not be within ' + range
);
}
});
/**
* ### .instanceof(constructor[, msg])
*
* Asserts that the target is an instance of the given `constructor`.
*
* function Cat () { }
*
* expect(new Cat()).to.be.an.instanceof(Cat);
* expect([1, 2]).to.be.an.instanceof(Array);
*
* Add `.not` earlier in the chain to negate `.instanceof`.
*
* expect({a: 1}).to.not.be.an.instanceof(Array);
*
* `.instanceof` accepts an optional `msg` argument which is a custom error
* message to show when the assertion fails. The message can also be given as
* the second argument to `expect`.
*
* expect(1).to.be.an.instanceof(Array, 'nooo why fail??');
* expect(1, 'nooo why fail??').to.be.an.instanceof(Array);
*
* Due to limitations in ES5, `.instanceof` may not always work as expected
* when using a transpiler such as Babel or TypeScript. In particular, it may
* produce unexpected results when subclassing built-in object such as
* `Array`, `Error`, and `Map`. See your transpiler's docs for details:
*
* - ([Babel](https://babeljs.io/docs/usage/caveats/#classes))
* - ([TypeScript](https://github.com/Microsoft/TypeScript/wiki/Breaking-Changes#extending-built-ins-like-error-array-and-map-may-no-longer-work))
*
* The alias `.instanceOf` can be used interchangeably with `.instanceof`.
*
* @name instanceof
* @param {Constructor} constructor
* @param {String} msg _optional_
* @alias instanceOf
* @namespace BDD
* @api public
*/
function assertInstanceOf (constructor, msg) {
if (msg) flag(this, 'message', msg);
var target = flag(this, 'object')
var ssfi = flag(this, 'ssfi');
var flagMsg = flag(this, 'message');
try {
var isInstanceOf = target instanceof constructor;
} catch (err) {
if (err instanceof TypeError) {
flagMsg = flagMsg ? flagMsg + ': ' : '';
throw new AssertionError(
flagMsg + 'The instanceof assertion needs a constructor but '
+ _.type(constructor) + ' was given.',
undefined,
ssfi
);
}
throw err;
}
var name = _.getName(constructor);
if (name === null) {
name = 'an unnamed constructor';
}
this.assert(
isInstanceOf
, 'expected #{this} to be an instance of ' + name
, 'expected #{this} to not be an instance of ' + name
);
};
Assertion.addMethod('instanceof', assertInstanceOf);
Assertion.addMethod('instanceOf', assertInstanceOf);
/**
* ### .property(name[, val[, msg]])
*
* Asserts that the target has a property with the given key `name`.
*
* expect({a: 1}).to.have.property('a');
*
* When `val` is provided, `.property` also asserts that the property's value
* is equal to the given `val`.
*
* expect({a: 1}).to.have.property('a', 1);
*
* By default, strict (`===`) equality is used. Add `.deep` earlier in the
* chain to use deep equality instead. See the `deep-eql` project page for
* info on the deep equality algorithm: https://github.com/chaijs/deep-eql.
*
* // Target object deeply (but not strictly) has property `x: {a: 1}`
* expect({x: {a: 1}}).to.have.deep.property('x', {a: 1});
* expect({x: {a: 1}}).to.not.have.property('x', {a: 1});
*
* The target's enumerable and non-enumerable properties are always included
* in the search. By default, both own and inherited properties are included.
* Add `.own` earlier in the chain to exclude inherited properties from the
* search.
*
* Object.prototype.b = 2;
*
* expect({a: 1}).to.have.own.property('a');
* expect({a: 1}).to.have.own.property('a', 1);
* expect({a: 1}).to.have.property('b');
* expect({a: 1}).to.not.have.own.property('b');
*
* `.deep` and `.own` can be combined.
*
* expect({x: {a: 1}}).to.have.deep.own.property('x', {a: 1});
*
* Add `.nested` earlier in the chain to enable dot- and bracket-notation when
* referencing nested properties.
*
* expect({a: {b: ['x', 'y']}}).to.have.nested.property('a.b[1]');
* expect({a: {b: ['x', 'y']}}).to.have.nested.property('a.b[1]', 'y');
*
* If `.` or `[]` are part of an actual property name, they can be escaped by
* adding two backslashes before them.
*
* expect({'.a': {'[b]': 'x'}}).to.have.nested.property('\\.a.\\[b\\]');
*
* `.deep` and `.nested` can be combined.
*
* expect({a: {b: [{c: 3}]}})
* .to.have.deep.nested.property('a.b[0]', {c: 3});
*
* `.own` and `.nested` cannot be combined.
*
* Add `.not` earlier in the chain to negate `.property`.
*
* expect({a: 1}).to.not.have.property('b');
*
* However, it's dangerous to negate `.property` when providing `val`. The
* problem is that it creates uncertain expectations by asserting that the
* target either doesn't have a property with the given key `name`, or that it
* does have a property with the given key `name` but its value isn't equal to
* the given `val`. It's often best to identify the exact output that's
* expected, and then write an assertion that only accepts that exact output.
*
* When the target isn't expected to have a property with the given key
* `name`, it's often best to assert exactly that.
*
* expect({b: 2}).to.not.have.property('a'); // Recommended
* expect({b: 2}).to.not.have.property('a', 1); // Not recommended
*
* When the target is expected to have a property with the given key `name`,
* it's often best to assert that the property has its expected value, rather
* than asserting that it doesn't have one of many unexpected values.
*
* expect({a: 3}).to.have.property('a', 3); // Recommended
* expect({a: 3}).to.not.have.property('a', 1); // Not recommended
*
* `.property` changes the target of any assertions that follow in the chain
* to be the value of the property from the original target object.
*
* expect({a: 1}).to.have.property('a').that.is.a('number');
*
* `.property` accepts an optional `msg` argument which is a custom error
* message to show when the assertion fails. The message can also be given as
* the second argument to `expect`. When not providing `val`, only use the
* second form.
*
* // Recommended
* expect({a: 1}).to.have.property('a', 2, 'nooo why fail??');
* expect({a: 1}, 'nooo why fail??').to.have.property('a', 2);
* expect({a: 1}, 'nooo why fail??').to.have.property('b');
*
* // Not recommended
* expect({a: 1}).to.have.property('b', undefined, 'nooo why fail??');
*
* The above assertion isn't the same thing as not providing `val`. Instead,
* it's asserting that the target object has a `b` property that's equal to
* `undefined`.
*
* The assertions `.ownProperty` and `.haveOwnProperty` can be used
* interchangeably with `.own.property`.
*
* @name property
* @param {String} name
* @param {Mixed} val (optional)
* @param {String} msg _optional_
* @returns value of property for chaining
* @namespace BDD
* @api public
*/
function assertProperty (name, val, msg) {
if (msg) flag(this, 'message', msg);
var isNested = flag(this, 'nested')
, isOwn = flag(this, 'own')
, flagMsg = flag(this, 'message')
, obj = flag(this, 'object')
, ssfi = flag(this, 'ssfi')
, nameType = typeof name;
flagMsg = flagMsg ? flagMsg + ': ' : '';
if (isNested) {
if (nameType !== 'string') {
throw new AssertionError(
flagMsg + 'the argument to property must be a string when using nested syntax',
undefined,
ssfi
);
}
} else {
if (nameType !== 'string' && nameType !== 'number' && nameType !== 'symbol') {
throw new AssertionError(
flagMsg + 'the argument to property must be a string, number, or symbol',
undefined,
ssfi
);
}
}
if (isNested && isOwn) {
throw new AssertionError(
flagMsg + 'The "nested" and "own" flags cannot be combined.',
undefined,
ssfi
);
}
if (obj === null || obj === undefined) {
throw new AssertionError(
flagMsg + 'Target cannot be null or undefined.',
undefined,
ssfi
);
}
var isDeep = flag(this, 'deep')
, negate = flag(this, 'negate')
, pathInfo = isNested ? _.getPathInfo(obj, name) : null
, value = isNested ? pathInfo.value : obj[name]
, isEql = isDeep ? flag(this, 'eql') : (val1, val2) => val1 === val2;;
var descriptor = '';
if (isDeep) descriptor += 'deep ';
if (isOwn) descriptor += 'own ';
if (isNested) descriptor += 'nested ';
descriptor += 'property ';
var hasProperty;
if (isOwn) hasProperty = Object.prototype.hasOwnProperty.call(obj, name);
else if (isNested) hasProperty = pathInfo.exists;
else hasProperty = _.hasProperty(obj, name);
// When performing a negated assertion for both name and val, merely having
// a property with the given name isn't enough to cause the assertion to
// fail. It must both have a property with the given name, and the value of
// that property must equal the given val. Therefore, skip this assertion in
// favor of the next.
if (!negate || arguments.length === 1) {
this.assert(
hasProperty
, 'expected #{this} to have ' + descriptor + _.inspect(name)
, 'expected #{this} to not have ' + descriptor + _.inspect(name));
}
if (arguments.length > 1) {
this.assert(
hasProperty && isEql(val, value)
, 'expected #{this} to have ' + descriptor + _.inspect(name) + ' of #{exp}, but got #{act}'
, 'expected #{this} to not have ' + descriptor + _.inspect(name) + ' of #{act}'
, val
, value
);
}
flag(this, 'object', value);
}
Assertion.addMethod('property', assertProperty);
function assertOwnProperty (name, value, msg) {
flag(this, 'own', true);
assertProperty.apply(this, arguments);
}
Assertion.addMethod('ownProperty', assertOwnProperty);
Assertion.addMethod('haveOwnProperty', assertOwnProperty);
/**
* ### .ownPropertyDescriptor(name[, descriptor[, msg]])
*
* Asserts that the target has its own property descriptor with the given key
* `name`. Enumerable and non-enumerable properties are included in the
* search.
*
* expect({a: 1}).to.have.ownPropertyDescriptor('a');
*
* When `descriptor` is provided, `.ownPropertyDescriptor` also asserts that
* the property's descriptor is deeply equal to the given `descriptor`. See
* the `deep-eql` project page for info on the deep equality algorithm:
* https://github.com/chaijs/deep-eql.
*
* expect({a: 1}).to.have.ownPropertyDescriptor('a', {
* configurable: true,
* enumerable: true,
* writable: true,
* value: 1,
* });
*
* Add `.not` earlier in the chain to negate `.ownPropertyDescriptor`.
*
* expect({a: 1}).to.not.have.ownPropertyDescriptor('b');
*
* However, it's dangerous to negate `.ownPropertyDescriptor` when providing
* a `descriptor`. The problem is that it creates uncertain expectations by
* asserting that the target either doesn't have a property descriptor with
* the given key `name`, or that it does have a property descriptor with the
* given key `name` but its not deeply equal to the given `descriptor`. It's
* often best to identify the exact output that's expected, and then write an
* assertion that only accepts that exact output.
*
* When the target isn't expected to have a property descriptor with the given
* key `name`, it's often best to assert exactly that.
*
* // Recommended
* expect({b: 2}).to.not.have.ownPropertyDescriptor('a');
*
* // Not recommended
* expect({b: 2}).to.not.have.ownPropertyDescriptor('a', {
* configurable: true,
* enumerable: true,
* writable: true,
* value: 1,
* });
*
* When the target is expected to have a property descriptor with the given
* key `name`, it's often best to assert that the property has its expected
* descriptor, rather than asserting that it doesn't have one of many
* unexpected descriptors.
*
* // Recommended
* expect({a: 3}).to.have.ownPropertyDescriptor('a', {
* configurable: true,
* enumerable: true,
* writable: true,
* value: 3,
* });
*
* // Not recommended
* expect({a: 3}).to.not.have.ownPropertyDescriptor('a', {
* configurable: true,
* enumerable: true,
* writable: true,
* value: 1,
* });
*
* `.ownPropertyDescriptor` changes the target of any assertions that follow
* in the chain to be the value of the property descriptor from the original
* target object.
*
* expect({a: 1}).to.have.ownPropertyDescriptor('a')
* .that.has.property('enumerable', true);
*
* `.ownPropertyDescriptor` accepts an optional `msg` argument which is a
* custom error message to show when the assertion fails. The message can also
* be given as the second argument to `expect`. When not providing
* `descriptor`, only use the second form.
*
* // Recommended
* expect({a: 1}).to.have.ownPropertyDescriptor('a', {
* configurable: true,
* enumerable: true,
* writable: true,
* value: 2,
* }, 'nooo why fail??');
*
* // Recommended
* expect({a: 1}, 'nooo why fail??').to.have.ownPropertyDescriptor('a', {
* configurable: true,
* enumerable: true,
* writable: true,
* value: 2,
* });
*
* // Recommended
* expect({a: 1}, 'nooo why fail??').to.have.ownPropertyDescriptor('b');
*
* // Not recommended
* expect({a: 1})
* .to.have.ownPropertyDescriptor('b', undefined, 'nooo why fail??');
*
* The above assertion isn't the same thing as not providing `descriptor`.
* Instead, it's asserting that the target object has a `b` property
* descriptor that's deeply equal to `undefined`.
*
* The alias `.haveOwnPropertyDescriptor` can be used interchangeably with
* `.ownPropertyDescriptor`.
*
* @name ownPropertyDescriptor
* @alias haveOwnPropertyDescriptor
* @param {String} name
* @param {Object} descriptor _optional_
* @param {String} msg _optional_
* @namespace BDD
* @api public
*/
function assertOwnPropertyDescriptor (name, descriptor, msg) {
if (typeof descriptor === 'string') {
msg = descriptor;
descriptor = null;
}
if (msg) flag(this, 'message', msg);
var obj = flag(this, 'object');
var actualDescriptor = Object.getOwnPropertyDescriptor(Object(obj), name);
var eql = flag(this, 'eql');
if (actualDescriptor && descriptor) {
this.assert(
eql(descriptor, actualDescriptor)
, 'expected the own property descriptor for ' + _.inspect(name) + ' on #{this} to match ' + _.inspect(descriptor) + ', got ' + _.inspect(actualDescriptor)
, 'expected the own property descriptor for ' + _.inspect(name) + ' on #{this} to not match ' + _.inspect(descriptor)
, descriptor
, actualDescriptor
, true
);
} else {
this.assert(
actualDescriptor
, 'expected #{this} to have an own property descriptor for ' + _.inspect(name)
, 'expected #{this} to not have an own property descriptor for ' + _.inspect(name)
);
}
flag(this, 'object', actualDescriptor);
}
Assertion.addMethod('ownPropertyDescriptor', assertOwnPropertyDescriptor);
Assertion.addMethod('haveOwnPropertyDescriptor', assertOwnPropertyDescriptor);
/**
* ### .lengthOf(n[, msg])
*
* Asserts that the target's `length` or `size` is equal to the given number
* `n`.
*
* expect([1, 2, 3]).to.have.lengthOf(3);
* expect('foo').to.have.lengthOf(3);
* expect(new Set([1, 2, 3])).to.have.lengthOf(3);
* expect(new Map([['a', 1], ['b', 2], ['c', 3]])).to.have.lengthOf(3);
*
* Add `.not` earlier in the chain to negate `.lengthOf`. However, it's often
* best to assert that the target's `length` property is equal to its expected
* value, rather than not equal to one of many unexpected values.
*
* expect('foo').to.have.lengthOf(3); // Recommended
* expect('foo').to.not.have.lengthOf(4); // Not recommended
*
* `.lengthOf` accepts an optional `msg` argument which is a custom error
* message to show when the assertion fails. The message can also be given as
* the second argument to `expect`.
*
* expect([1, 2, 3]).to.have.lengthOf(2, 'nooo why fail??');
* expect([1, 2, 3], 'nooo why fail??').to.have.lengthOf(2);
*
* `.lengthOf` can also be used as a language chain, causing all `.above`,
* `.below`, `.least`, `.most`, and `.within` assertions that follow in the
* chain to use the target's `length` property as the target. However, it's
* often best to assert that the target's `length` property is equal to its
* expected length, rather than asserting that its `length` property falls
* within some range of values.
*
* // Recommended
* expect([1, 2, 3]).to.have.lengthOf(3);
*
* // Not recommended
* expect([1, 2, 3]).to.have.lengthOf.above(2);
* expect([1, 2, 3]).to.have.lengthOf.below(4);
* expect([1, 2, 3]).to.have.lengthOf.at.least(3);
* expect([1, 2, 3]).to.have.lengthOf.at.most(3);
* expect([1, 2, 3]).to.have.lengthOf.within(2,4);
*
* Due to a compatibility issue, the alias `.length` can't be chained directly
* off of an uninvoked method such as `.a`. Therefore, `.length` can't be used
* interchangeably with `.lengthOf` in every situation. It's recommended to
* always use `.lengthOf` instead of `.length`.
*
* expect([1, 2, 3]).to.have.a.length(3); // incompatible; throws error
* expect([1, 2, 3]).to.have.a.lengthOf(3); // passes as expected
*
* @name lengthOf
* @alias length
* @param {Number} n
* @param {String} msg _optional_
* @namespace BDD
* @api public
*/
function assertLengthChain () {
flag(this, 'doLength', true);
}
function assertLength (n, msg) {
if (msg) flag(this, 'message', msg);
var obj = flag(this, 'object')
, objType = _.type(obj).toLowerCase()
, flagMsg = flag(this, 'message')
, ssfi = flag(this, 'ssfi')
, descriptor = 'length'
, itemsCount;
switch (objType) {
case 'map':
case 'set':
descriptor = 'size';
itemsCount = obj.size;
break;
default:
new Assertion(obj, flagMsg, ssfi, true).to.have.property('length');
itemsCount = obj.length;
}
this.assert(
itemsCount == n
, 'expected #{this} to have a ' + descriptor + ' of #{exp} but got #{act}'
, 'expected #{this} to not have a ' + descriptor + ' of #{act}'
, n
, itemsCount
);
}
Assertion.addChainableMethod('length', assertLength, assertLengthChain);
Assertion.addChainableMethod('lengthOf', assertLength, assertLengthChain);
/**
* ### .match(re[, msg])
*
* Asserts that the target matches the given regular expression `re`.
*
* expect('foobar').to.match(/^foo/);
*
* Add `.not` earlier in the chain to negate `.match`.
*
* expect('foobar').to.not.match(/taco/);
*
* `.match` accepts an optional `msg` argument which is a custom error message
* to show when the assertion fails. The message can also be given as the
* second argument to `expect`.
*
* expect('foobar').to.match(/taco/, 'nooo why fail??');
* expect('foobar', 'nooo why fail??').to.match(/taco/);
*
* The alias `.matches` can be used interchangeably with `.match`.
*
* @name match
* @alias matches
* @param {RegExp} re
* @param {String} msg _optional_
* @namespace BDD
* @api public
*/
function assertMatch(re, msg) {
if (msg) flag(this, 'message', msg);
var obj = flag(this, 'object');
this.assert(
re.exec(obj)
, 'expected #{this} to match ' + re
, 'expected #{this} not to match ' + re
);
}
Assertion.addMethod('match', assertMatch);
Assertion.addMethod('matches', assertMatch);
/**
* ### .string(str[, msg])
*
* Asserts that the target string contains the given substring `str`.
*
* expect('foobar').to.have.string('bar');
*
* Add `.not` earlier in the chain to negate `.string`.
*
* expect('foobar').to.not.have.string('taco');
*
* `.string` accepts an optional `msg` argument which is a custom error
* message to show when the assertion fails. The message can also be given as
* the second argument to `expect`.
*
* expect('foobar').to.have.string('taco', 'nooo why fail??');
* expect('foobar', 'nooo why fail??').to.have.string('taco');
*
* @name string
* @param {String} str
* @param {String} msg _optional_
* @namespace BDD
* @api public
*/
Assertion.addMethod('string', function (str, msg) {
if (msg) flag(this, 'message', msg);
var obj = flag(this, 'object')
, flagMsg = flag(this, 'message')
, ssfi = flag(this, 'ssfi');
new Assertion(obj, flagMsg, ssfi, true).is.a('string');
this.assert(
~obj.indexOf(str)
, 'expected #{this} to contain ' + _.inspect(str)
, 'expected #{this} to not contain ' + _.inspect(str)
);
});
/**
* ### .keys(key1[, key2[, ...]])
*
* Asserts that the target object, array, map, or set has the given keys. Only
* the target's own inherited properties are included in the search.
*
* When the target is an object or array, keys can be provided as one or more
* string arguments, a single array argument, or a single object argument. In
* the latter case, only the keys in the given object matter; the values are
* ignored.
*
* expect({a: 1, b: 2}).to.have.all.keys('a', 'b');
* expect(['x', 'y']).to.have.all.keys(0, 1);
*
* expect({a: 1, b: 2}).to.have.all.keys(['a', 'b']);
* expect(['x', 'y']).to.have.all.keys([0, 1]);
*
* expect({a: 1, b: 2}).to.have.all.keys({a: 4, b: 5}); // ignore 4 and 5
* expect(['x', 'y']).to.have.all.keys({0: 4, 1: 5}); // ignore 4 and 5
*
* When the target is a map or set, each key must be provided as a separate
* argument.
*
* expect(new Map([['a', 1], ['b', 2]])).to.have.all.keys('a', 'b');
* expect(new Set(['a', 'b'])).to.have.all.keys('a', 'b');
*
* Because `.keys` does different things based on the target's type, it's
* important to check the target's type before using `.keys`. See the `.a` doc
* for info on testing a target's type.
*
* expect({a: 1, b: 2}).to.be.an('object').that.has.all.keys('a', 'b');
*
* By default, strict (`===`) equality is used to compare keys of maps and
* sets. Add `.deep` earlier in the chain to use deep equality instead. See
* the `deep-eql` project page for info on the deep equality algorithm:
* https://github.com/chaijs/deep-eql.
*
* // Target set deeply (but not strictly) has key `{a: 1}`
* expect(new Set([{a: 1}])).to.have.all.deep.keys([{a: 1}]);
* expect(new Set([{a: 1}])).to.not.have.all.keys([{a: 1}]);
*
* By default, the target must have all of the given keys and no more. Add
* `.any` earlier in the chain to only require that the target have at least
* one of the given keys. Also, add `.not` earlier in the chain to negate
* `.keys`. It's often best to add `.any` when negating `.keys`, and to use
* `.all` when asserting `.keys` without negation.
*
* When negating `.keys`, `.any` is preferred because `.not.any.keys` asserts
* exactly what's expected of the output, whereas `.not.all.keys` creates
* uncertain expectations.
*
* // Recommended; asserts that target doesn't have any of the given keys
* expect({a: 1, b: 2}).to.not.have.any.keys('c', 'd');
*
* // Not recommended; asserts that target doesn't have all of the given
* // keys but may or may not have some of them
* expect({a: 1, b: 2}).to.not.have.all.keys('c', 'd');
*
* When asserting `.keys` without negation, `.all` is preferred because
* `.all.keys` asserts exactly what's expected of the output, whereas
* `.any.keys` creates uncertain expectations.
*
* // Recommended; asserts that target has all the given keys
* expect({a: 1, b: 2}).to.have.all.keys('a', 'b');
*
* // Not recommended; asserts that target has at least one of the given
* // keys but may or may not have more of them
* expect({a: 1, b: 2}).to.have.any.keys('a', 'b');
*
* Note that `.all` is used by default when neither `.all` nor `.any` appear
* earlier in the chain. However, it's often best to add `.all` anyway because
* it improves readability.
*
* // Both assertions are identical
* expect({a: 1, b: 2}).to.have.all.keys('a', 'b'); // Recommended
* expect({a: 1, b: 2}).to.have.keys('a', 'b'); // Not recommended
*
* Add `.include` earlier in the chain to require that the target's keys be a
* superset of the expected keys, rather than identical sets.
*
* // Target object's keys are a superset of ['a', 'b'] but not identical
* expect({a: 1, b: 2, c: 3}).to.include.all.keys('a', 'b');
* expect({a: 1, b: 2, c: 3}).to.not.have.all.keys('a', 'b');
*
* However, if `.any` and `.include` are combined, only the `.any` takes
* effect. The `.include` is ignored in this case.
*
* // Both assertions are identical
* expect({a: 1}).to.have.any.keys('a', 'b');
* expect({a: 1}).to.include.any.keys('a', 'b');
*
* A custom error message can be given as the second argument to `expect`.
*
* expect({a: 1}, 'nooo why fail??').to.have.key('b');
*
* The alias `.key` can be used interchangeably with `.keys`.
*
* @name keys
* @alias key
* @param {...String|Array|Object} keys
* @namespace BDD
* @api public
*/
function assertKeys (keys) {
var obj = flag(this, 'object')
, objType = _.type(obj)
, keysType = _.type(keys)
, ssfi = flag(this, 'ssfi')
, isDeep = flag(this, 'deep')
, str
, deepStr = ''
, actual
, ok = true
, flagMsg = flag(this, 'message');
flagMsg = flagMsg ? flagMsg + ': ' : '';
var mixedArgsMsg = flagMsg + 'when testing keys against an object or an array you must give a single Array|Object|String argument or multiple String arguments';
if (objType === 'Map' || objType === 'Set') {
deepStr = isDeep ? 'deeply ' : '';
actual = [];
// Map and Set '.keys' aren't supported in IE 11. Therefore, use .forEach.
obj.forEach(function (val, key) { actual.push(key) });
if (keysType !== 'Array') {
keys = Array.prototype.slice.call(arguments);
}
} else {
actual = _.getOwnEnumerableProperties(obj);
switch (keysType) {
case 'Array':
if (arguments.length > 1) {
throw new AssertionError(mixedArgsMsg, undefined, ssfi);
}
break;
case 'Object':
if (arguments.length > 1) {
throw new AssertionError(mixedArgsMsg, undefined, ssfi);
}
keys = Object.keys(keys);
break;
default:
keys = Array.prototype.slice.call(arguments);
}
// Only stringify non-Symbols because Symbols would become "Symbol()"
keys = keys.map(function (val) {
return typeof val === 'symbol' ? val : String(val);
});
}
if (!keys.length) {
throw new AssertionError(flagMsg + 'keys required', undefined, ssfi);
}
var len = keys.length
, any = flag(this, 'any')
, all = flag(this, 'all')
, expected = keys
, isEql = isDeep ? flag(this, 'eql') : (val1, val2) => val1 === val2;
if (!any && !all) {
all = true;
}
// Has any
if (any) {
ok = expected.some(function(expectedKey) {
return actual.some(function(actualKey) {
return isEql(expectedKey, actualKey);
});
});
}
// Has all
if (all) {
ok = expected.every(function(expectedKey) {
return actual.some(function(actualKey) {
return isEql(expectedKey, actualKey);
});
});
if (!flag(this, 'contains')) {
ok = ok && keys.length == actual.length;
}
}
// Key string
if (len > 1) {
keys = keys.map(function(key) {
return _.inspect(key);
});
var last = keys.pop();
if (all) {
str = keys.join(', ') + ', and ' + last;
}
if (any) {
str = keys.join(', ') + ', or ' + last;
}
} else {
str = _.inspect(keys[0]);
}
// Form
str = (len > 1 ? 'keys ' : 'key ') + str;
// Have / include
str = (flag(this, 'contains') ? 'contain ' : 'have ') + str;
// Assertion
this.assert(
ok
, 'expected #{this} to ' + deepStr + str
, 'expected #{this} to not ' + deepStr + str
, expected.slice(0).sort(_.compareByInspect)
, actual.sort(_.compareByInspect)
, true
);
}
Assertion.addMethod('keys', assertKeys);
Assertion.addMethod('key', assertKeys);
/**
* ### .throw([errorLike], [errMsgMatcher], [msg])
*
* When no arguments are provided, `.throw` invokes the target function and
* asserts that an error is thrown.
*
* var badFn = function () { throw new TypeError('Illegal salmon!'); };
*
* expect(badFn).to.throw();
*
* When one argument is provided, and it's an error constructor, `.throw`
* invokes the target function and asserts that an error is thrown that's an
* instance of that error constructor.
*
* var badFn = function () { throw new TypeError('Illegal salmon!'); };
*
* expect(badFn).to.throw(TypeError);
*
* When one argument is provided, and it's an error instance, `.throw` invokes
* the target function and asserts that an error is thrown that's strictly
* (`===`) equal to that error instance.
*
* var err = new TypeError('Illegal salmon!');
* var badFn = function () { throw err; };
*
* expect(badFn).to.throw(err);
*
* When one argument is provided, and it's a string, `.throw` invokes the
* target function and asserts that an error is thrown with a message that
* contains that string.
*
* var badFn = function () { throw new TypeError('Illegal salmon!'); };
*
* expect(badFn).to.throw('salmon');
*
* When one argument is provided, and it's a regular expression, `.throw`
* invokes the target function and asserts that an error is thrown with a
* message that matches that regular expression.
*
* var badFn = function () { throw new TypeError('Illegal salmon!'); };
*
* expect(badFn).to.throw(/salmon/);
*
* When two arguments are provided, and the first is an error instance or
* constructor, and the second is a string or regular expression, `.throw`
* invokes the function and asserts that an error is thrown that fulfills both
* conditions as described above.
*
* var err = new TypeError('Illegal salmon!');
* var badFn = function () { throw err; };
*
* expect(badFn).to.throw(TypeError, 'salmon');
* expect(badFn).to.throw(TypeError, /salmon/);
* expect(badFn).to.throw(err, 'salmon');
* expect(badFn).to.throw(err, /salmon/);
*
* Add `.not` earlier in the chain to negate `.throw`.
*
* var goodFn = function () {};
*
* expect(goodFn).to.not.throw();
*
* However, it's dangerous to negate `.throw` when providing any arguments.
* The problem is that it creates uncertain expectations by asserting that the
* target either doesn't throw an error, or that it throws an error but of a
* different type than the given type, or that it throws an error of the given
* type but with a message that doesn't include the given string. It's often
* best to identify the exact output that's expected, and then write an
* assertion that only accepts that exact output.
*
* When the target isn't expected to throw an error, it's often best to assert
* exactly that.
*
* var goodFn = function () {};
*
* expect(goodFn).to.not.throw(); // Recommended
* expect(goodFn).to.not.throw(ReferenceError, 'x'); // Not recommended
*
* When the target is expected to throw an error, it's often best to assert
* that the error is of its expected type, and has a message that includes an
* expected string, rather than asserting that it doesn't have one of many
* unexpected types, and doesn't have a message that includes some string.
*
* var badFn = function () { throw new TypeError('Illegal salmon!'); };
*
* expect(badFn).to.throw(TypeError, 'salmon'); // Recommended
* expect(badFn).to.not.throw(ReferenceError, 'x'); // Not recommended
*
* `.throw` changes the target of any assertions that follow in the chain to
* be the error object that's thrown.
*
* var err = new TypeError('Illegal salmon!');
* err.code = 42;
* var badFn = function () { throw err; };
*
* expect(badFn).to.throw(TypeError).with.property('code', 42);
*
* `.throw` accepts an optional `msg` argument which is a custom error message
* to show when the assertion fails. The message can also be given as the
* second argument to `expect`. When not providing two arguments, always use
* the second form.
*
* var goodFn = function () {};
*
* expect(goodFn).to.throw(TypeError, 'x', 'nooo why fail??');
* expect(goodFn, 'nooo why fail??').to.throw();
*
* Due to limitations in ES5, `.throw` may not always work as expected when
* using a transpiler such as Babel or TypeScript. In particular, it may
* produce unexpected results when subclassing the built-in `Error` object and
* then passing the subclassed constructor to `.throw`. See your transpiler's
* docs for details:
*
* - ([Babel](https://babeljs.io/docs/usage/caveats/#classes))
* - ([TypeScript](https://github.com/Microsoft/TypeScript/wiki/Breaking-Changes#extending-built-ins-like-error-array-and-map-may-no-longer-work))
*
* Beware of some common mistakes when using the `throw` assertion. One common
* mistake is to accidentally invoke the function yourself instead of letting
* the `throw` assertion invoke the function for you. For example, when
* testing if a function named `fn` throws, provide `fn` instead of `fn()` as
* the target for the assertion.
*
* expect(fn).to.throw(); // Good! Tests `fn` as desired
* expect(fn()).to.throw(); // Bad! Tests result of `fn()`, not `fn`
*
* If you need to assert that your function `fn` throws when passed certain
* arguments, then wrap a call to `fn` inside of another function.
*
* expect(function () { fn(42); }).to.throw(); // Function expression
* expect(() => fn(42)).to.throw(); // ES6 arrow function
*
* Another common mistake is to provide an object method (or any stand-alone
* function that relies on `this`) as the target of the assertion. Doing so is
* problematic because the `this` context will be lost when the function is
* invoked by `.throw`; there's no way for it to know what `this` is supposed
* to be. There are two ways around this problem. One solution is to wrap the
* method or function call inside of another function. Another solution is to
* use `bind`.
*
* expect(function () { cat.meow(); }).to.throw(); // Function expression
* expect(() => cat.meow()).to.throw(); // ES6 arrow function
* expect(cat.meow.bind(cat)).to.throw(); // Bind
*
* Finally, it's worth mentioning that it's a best practice in JavaScript to
* only throw `Error` and derivatives of `Error` such as `ReferenceError`,
* `TypeError`, and user-defined objects that extend `Error`. No other type of
* value will generate a stack trace when initialized. With that said, the
* `throw` assertion does technically support any type of value being thrown,
* not just `Error` and its derivatives.
*
* The aliases `.throws` and `.Throw` can be used interchangeably with
* `.throw`.
*
* @name throw
* @alias throws
* @alias Throw
* @param {Error|ErrorConstructor} errorLike
* @param {String|RegExp} errMsgMatcher error message
* @param {String} msg _optional_
* @see https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Error#Error_types
* @returns error for chaining (null if no error)
* @namespace BDD
* @api public
*/
function assertThrows (errorLike, errMsgMatcher, msg) {
if (msg) flag(this, 'message', msg);
var obj = flag(this, 'object')
, ssfi = flag(this, 'ssfi')
, flagMsg = flag(this, 'message')
, negate = flag(this, 'negate') || false;
new Assertion(obj, flagMsg, ssfi, true).is.a('function');
if (errorLike instanceof RegExp || typeof errorLike === 'string') {
errMsgMatcher = errorLike;
errorLike = null;
}
var caughtErr;
try {
obj();
} catch (err) {
caughtErr = err;
}
// If we have the negate flag enabled and at least one valid argument it means we do expect an error
// but we want it to match a given set of criteria
var everyArgIsUndefined = errorLike === undefined && errMsgMatcher === undefined;
// If we've got the negate flag enabled and both args, we should only fail if both aren't compatible
// See Issue #551 and PR #683@GitHub
var everyArgIsDefined = Boolean(errorLike && errMsgMatcher);
var errorLikeFail = false;
var errMsgMatcherFail = false;
// Checking if error was thrown
if (everyArgIsUndefined || !everyArgIsUndefined && !negate) {
// We need this to display results correctly according to their types
var errorLikeString = 'an error';
if (errorLike instanceof Error) {
errorLikeString = '#{exp}';
} else if (errorLike) {
errorLikeString = _.checkError.getConstructorName(errorLike);
}
this.assert(
caughtErr
, 'expected #{this} to throw ' + errorLikeString
, 'expected #{this} to not throw an error but #{act} was thrown'
, errorLike && errorLike.toString()
, (caughtErr instanceof Error ?
caughtErr.toString() : (typeof caughtErr === 'string' ? caughtErr : caughtErr &&
_.checkError.getConstructorName(caughtErr)))
);
}
if (errorLike && caughtErr) {
// We should compare instances only if `errorLike` is an instance of `Error`
if (errorLike instanceof Error) {
var isCompatibleInstance = _.checkError.compatibleInstance(caughtErr, errorLike);
if (isCompatibleInstance === negate) {
// These checks were created to ensure we won't fail too soon when we've got both args and a negate
// See Issue #551 and PR #683@GitHub
if (everyArgIsDefined && negate) {
errorLikeFail = true;
} else {
this.assert(
negate
, 'expected #{this} to throw #{exp} but #{act} was thrown'
, 'expected #{this} to not throw #{exp}' + (caughtErr && !negate ? ' but #{act} was thrown' : '')
, errorLike.toString()
, caughtErr.toString()
);
}
}
}
var isCompatibleConstructor = _.checkError.compatibleConstructor(caughtErr, errorLike);
if (isCompatibleConstructor === negate) {
if (everyArgIsDefined && negate) {
errorLikeFail = true;
} else {
this.assert(
negate
, 'expected #{this} to throw #{exp} but #{act} was thrown'
, 'expected #{this} to not throw #{exp}' + (caughtErr ? ' but #{act} was thrown' : '')
, (errorLike instanceof Error ? errorLike.toString() : errorLike && _.checkError.getConstructorName(errorLike))
, (caughtErr instanceof Error ? caughtErr.toString() : caughtErr && _.checkError.getConstructorName(caughtErr))
);
}
}
}
if (caughtErr && errMsgMatcher !== undefined && errMsgMatcher !== null) {
// Here we check compatible messages
var placeholder = 'including';
if (errMsgMatcher instanceof RegExp) {
placeholder = 'matching'
}
var isCompatibleMessage = _.checkError.compatibleMessage(caughtErr, errMsgMatcher);
if (isCompatibleMessage === negate) {
if (everyArgIsDefined && negate) {
errMsgMatcherFail = true;
} else {
this.assert(
negate
, 'expected #{this} to throw error ' + placeholder + ' #{exp} but got #{act}'
, 'expected #{this} to throw error not ' + placeholder + ' #{exp}'
, errMsgMatcher
, _.checkError.getMessage(caughtErr)
);
}
}
}
// If both assertions failed and both should've matched we throw an error
if (errorLikeFail && errMsgMatcherFail) {
this.assert(
negate
, 'expected #{this} to throw #{exp} but #{act} was thrown'
, 'expected #{this} to not throw #{exp}' + (caughtErr ? ' but #{act} was thrown' : '')
, (errorLike instanceof Error ? errorLike.toString() : errorLike && _.checkError.getConstructorName(errorLike))
, (caughtErr instanceof Error ? caughtErr.toString() : caughtErr && _.checkError.getConstructorName(caughtErr))
);
}
flag(this, 'object', caughtErr);
};
Assertion.addMethod('throw', assertThrows);
Assertion.addMethod('throws', assertThrows);
Assertion.addMethod('Throw', assertThrows);
/**
* ### .respondTo(method[, msg])
*
* When the target is a non-function object, `.respondTo` asserts that the
* target has a method with the given name `method`. The method can be own or
* inherited, and it can be enumerable or non-enumerable.
*
* function Cat () {}
* Cat.prototype.meow = function () {};
*
* expect(new Cat()).to.respondTo('meow');
*
* When the target is a function, `.respondTo` asserts that the target's
* `prototype` property has a method with the given name `method`. Again, the
* method can be own or inherited, and it can be enumerable or non-enumerable.
*
* function Cat () {}
* Cat.prototype.meow = function () {};
*
* expect(Cat).to.respondTo('meow');
*
* Add `.itself` earlier in the chain to force `.respondTo` to treat the
* target as a non-function object, even if it's a function. Thus, it asserts
* that the target has a method with the given name `method`, rather than
* asserting that the target's `prototype` property has a method with the
* given name `method`.
*
* function Cat () {}
* Cat.prototype.meow = function () {};
* Cat.hiss = function () {};
*
* expect(Cat).itself.to.respondTo('hiss').but.not.respondTo('meow');
*
* When not adding `.itself`, it's important to check the target's type before
* using `.respondTo`. See the `.a` doc for info on checking a target's type.
*
* function Cat () {}
* Cat.prototype.meow = function () {};
*
* expect(new Cat()).to.be.an('object').that.respondsTo('meow');
*
* Add `.not` earlier in the chain to negate `.respondTo`.
*
* function Dog () {}
* Dog.prototype.bark = function () {};
*
* expect(new Dog()).to.not.respondTo('meow');
*
* `.respondTo` accepts an optional `msg` argument which is a custom error
* message to show when the assertion fails. The message can also be given as
* the second argument to `expect`.
*
* expect({}).to.respondTo('meow', 'nooo why fail??');
* expect({}, 'nooo why fail??').to.respondTo('meow');
*
* The alias `.respondsTo` can be used interchangeably with `.respondTo`.
*
* @name respondTo
* @alias respondsTo
* @param {String} method
* @param {String} msg _optional_
* @namespace BDD
* @api public
*/
function respondTo (method, msg) {
if (msg) flag(this, 'message', msg);
var obj = flag(this, 'object')
, itself = flag(this, 'itself')
, context = ('function' === typeof obj && !itself)
? obj.prototype[method]
: obj[method];
this.assert(
'function' === typeof context
, 'expected #{this} to respond to ' + _.inspect(method)
, 'expected #{this} to not respond to ' + _.inspect(method)
);
}
Assertion.addMethod('respondTo', respondTo);
Assertion.addMethod('respondsTo', respondTo);
/**
* ### .itself
*
* Forces all `.respondTo` assertions that follow in the chain to behave as if
* the target is a non-function object, even if it's a function. Thus, it
* causes `.respondTo` to assert that the target has a method with the given
* name, rather than asserting that the target's `prototype` property has a
* method with the given name.
*
* function Cat () {}
* Cat.prototype.meow = function () {};
* Cat.hiss = function () {};
*
* expect(Cat).itself.to.respondTo('hiss').but.not.respondTo('meow');
*
* @name itself
* @namespace BDD
* @api public
*/
Assertion.addProperty('itself', function () {
flag(this, 'itself', true);
});
/**
* ### .satisfy(matcher[, msg])
*
* Invokes the given `matcher` function with the target being passed as the
* first argument, and asserts that the value returned is truthy.
*
* expect(1).to.satisfy(function(num) {
* return num > 0;
* });
*
* Add `.not` earlier in the chain to negate `.satisfy`.
*
* expect(1).to.not.satisfy(function(num) {
* return num > 2;
* });
*
* `.satisfy` accepts an optional `msg` argument which is a custom error
* message to show when the assertion fails. The message can also be given as
* the second argument to `expect`.
*
* expect(1).to.satisfy(function(num) {
* return num > 2;
* }, 'nooo why fail??');
*
* expect(1, 'nooo why fail??').to.satisfy(function(num) {
* return num > 2;
* });
*
* The alias `.satisfies` can be used interchangeably with `.satisfy`.
*
* @name satisfy
* @alias satisfies
* @param {Function} matcher
* @param {String} msg _optional_
* @namespace BDD
* @api public
*/
function satisfy (matcher, msg) {
if (msg) flag(this, 'message', msg);
var obj = flag(this, 'object');
var result = matcher(obj);
this.assert(
result
, 'expected #{this} to satisfy ' + _.objDisplay(matcher)
, 'expected #{this} to not satisfy' + _.objDisplay(matcher)
, flag(this, 'negate') ? false : true
, result
);
}
Assertion.addMethod('satisfy', satisfy);
Assertion.addMethod('satisfies', satisfy);
/**
* ### .closeTo(expected, delta[, msg])
*
* Asserts that the target is a number that's within a given +/- `delta` range
* of the given number `expected`. However, it's often best to assert that the
* target is equal to its expected value.
*
* // Recommended
* expect(1.5).to.equal(1.5);
*
* // Not recommended
* expect(1.5).to.be.closeTo(1, 0.5);
* expect(1.5).to.be.closeTo(2, 0.5);
* expect(1.5).to.be.closeTo(1, 1);
*
* Add `.not` earlier in the chain to negate `.closeTo`.
*
* expect(1.5).to.equal(1.5); // Recommended
* expect(1.5).to.not.be.closeTo(3, 1); // Not recommended
*
* `.closeTo` accepts an optional `msg` argument which is a custom error
* message to show when the assertion fails. The message can also be given as
* the second argument to `expect`.
*
* expect(1.5).to.be.closeTo(3, 1, 'nooo why fail??');
* expect(1.5, 'nooo why fail??').to.be.closeTo(3, 1);
*
* The alias `.approximately` can be used interchangeably with `.closeTo`.
*
* @name closeTo
* @alias approximately
* @param {Number} expected
* @param {Number} delta
* @param {String} msg _optional_
* @namespace BDD
* @api public
*/
function closeTo(expected, delta, msg) {
if (msg) flag(this, 'message', msg);
var obj = flag(this, 'object')
, flagMsg = flag(this, 'message')
, ssfi = flag(this, 'ssfi');
new Assertion(obj, flagMsg, ssfi, true).is.a('number');
if (typeof expected !== 'number' || typeof delta !== 'number') {
flagMsg = flagMsg ? flagMsg + ': ' : '';
var deltaMessage = delta === undefined ? ", and a delta is required" : "";
throw new AssertionError(
flagMsg + 'the arguments to closeTo or approximately must be numbers' + deltaMessage,
undefined,
ssfi
);
}
this.assert(
Math.abs(obj - expected) <= delta
, 'expected #{this} to be close to ' + expected + ' +/- ' + delta
, 'expected #{this} not to be close to ' + expected + ' +/- ' + delta
);
}
Assertion.addMethod('closeTo', closeTo);
Assertion.addMethod('approximately', closeTo);
// Note: Duplicates are ignored if testing for inclusion instead of sameness.
function isSubsetOf(subset, superset, cmp, contains, ordered) {
if (!contains) {
if (subset.length !== superset.length) return false;
superset = superset.slice();
}
return subset.every(function(elem, idx) {
if (ordered) return cmp ? cmp(elem, superset[idx]) : elem === superset[idx];
if (!cmp) {
var matchIdx = superset.indexOf(elem);
if (matchIdx === -1) return false;
// Remove match from superset so not counted twice if duplicate in subset.
if (!contains) superset.splice(matchIdx, 1);
return true;
}
return superset.some(function(elem2, matchIdx) {
if (!cmp(elem, elem2)) return false;
// Remove match from superset so not counted twice if duplicate in subset.
if (!contains) superset.splice(matchIdx, 1);
return true;
});
});
}
/**
* ### .members(set[, msg])
*
* Asserts that the target array has the same members as the given array
* `set`.
*
* expect([1, 2, 3]).to.have.members([2, 1, 3]);
* expect([1, 2, 2]).to.have.members([2, 1, 2]);
*
* By default, members are compared using strict (`===`) equality. Add `.deep`
* earlier in the chain to use deep equality instead. See the `deep-eql`
* project page for info on the deep equality algorithm:
* https://github.com/chaijs/deep-eql.
*
* // Target array deeply (but not strictly) has member `{a: 1}`
* expect([{a: 1}]).to.have.deep.members([{a: 1}]);
* expect([{a: 1}]).to.not.have.members([{a: 1}]);
*
* By default, order doesn't matter. Add `.ordered` earlier in the chain to
* require that members appear in the same order.
*
* expect([1, 2, 3]).to.have.ordered.members([1, 2, 3]);
* expect([1, 2, 3]).to.have.members([2, 1, 3])
* .but.not.ordered.members([2, 1, 3]);
*
* By default, both arrays must be the same size. Add `.include` earlier in
* the chain to require that the target's members be a superset of the
* expected members. Note that duplicates are ignored in the subset when
* `.include` is added.
*
* // Target array is a superset of [1, 2] but not identical
* expect([1, 2, 3]).to.include.members([1, 2]);
* expect([1, 2, 3]).to.not.have.members([1, 2]);
*
* // Duplicates in the subset are ignored
* expect([1, 2, 3]).to.include.members([1, 2, 2, 2]);
*
* `.deep`, `.ordered`, and `.include` can all be combined. However, if
* `.include` and `.ordered` are combined, the ordering begins at the start of
* both arrays.
*
* expect([{a: 1}, {b: 2}, {c: 3}])
* .to.include.deep.ordered.members([{a: 1}, {b: 2}])
* .but.not.include.deep.ordered.members([{b: 2}, {c: 3}]);
*
* Add `.not` earlier in the chain to negate `.members`. However, it's
* dangerous to do so. The problem is that it creates uncertain expectations
* by asserting that the target array doesn't have all of the same members as
* the given array `set` but may or may not have some of them. It's often best
* to identify the exact output that's expected, and then write an assertion
* that only accepts that exact output.
*
* expect([1, 2]).to.not.include(3).and.not.include(4); // Recommended
* expect([1, 2]).to.not.have.members([3, 4]); // Not recommended
*
* `.members` accepts an optional `msg` argument which is a custom error
* message to show when the assertion fails. The message can also be given as
* the second argument to `expect`.
*
* expect([1, 2]).to.have.members([1, 2, 3], 'nooo why fail??');
* expect([1, 2], 'nooo why fail??').to.have.members([1, 2, 3]);
*
* @name members
* @param {Array} set
* @param {String} msg _optional_
* @namespace BDD
* @api public
*/
Assertion.addMethod('members', function (subset, msg) {
if (msg) flag(this, 'message', msg);
var obj = flag(this, 'object')
, flagMsg = flag(this, 'message')
, ssfi = flag(this, 'ssfi');
new Assertion(obj, flagMsg, ssfi, true).to.be.an('array');
new Assertion(subset, flagMsg, ssfi, true).to.be.an('array');
var contains = flag(this, 'contains');
var ordered = flag(this, 'ordered');
var subject, failMsg, failNegateMsg;
if (contains) {
subject = ordered ? 'an ordered superset' : 'a superset';
failMsg = 'expected #{this} to be ' + subject + ' of #{exp}';
failNegateMsg = 'expected #{this} to not be ' + subject + ' of #{exp}';
} else {
subject = ordered ? 'ordered members' : 'members';
failMsg = 'expected #{this} to have the same ' + subject + ' as #{exp}';
failNegateMsg = 'expected #{this} to not have the same ' + subject + ' as #{exp}';
}
var cmp = flag(this, 'deep') ? flag(this, 'eql') : undefined;
this.assert(
isSubsetOf(subset, obj, cmp, contains, ordered)
, failMsg
, failNegateMsg
, subset
, obj
, true
);
});
/**
* ### .oneOf(list[, msg])
*
* Asserts that the target is a member of the given array `list`. However,
* it's often best to assert that the target is equal to its expected value.
*
* expect(1).to.equal(1); // Recommended
* expect(1).to.be.oneOf([1, 2, 3]); // Not recommended
*
* Comparisons are performed using strict (`===`) equality.
*
* Add `.not` earlier in the chain to negate `.oneOf`.
*
* expect(1).to.equal(1); // Recommended
* expect(1).to.not.be.oneOf([2, 3, 4]); // Not recommended
*
* It can also be chained with `.contain` or `.include`, which will work with
* both arrays and strings:
*
* expect('Today is sunny').to.contain.oneOf(['sunny', 'cloudy'])
* expect('Today is rainy').to.not.contain.oneOf(['sunny', 'cloudy'])
* expect([1,2,3]).to.contain.oneOf([3,4,5])
* expect([1,2,3]).to.not.contain.oneOf([4,5,6])
*
* `.oneOf` accepts an optional `msg` argument which is a custom error message
* to show when the assertion fails. The message can also be given as the
* second argument to `expect`.
*
* expect(1).to.be.oneOf([2, 3, 4], 'nooo why fail??');
* expect(1, 'nooo why fail??').to.be.oneOf([2, 3, 4]);
*
* @name oneOf
* @param {Array<*>} list
* @param {String} msg _optional_
* @namespace BDD
* @api public
*/
function oneOf (list, msg) {
if (msg) flag(this, 'message', msg);
var expected = flag(this, 'object')
, flagMsg = flag(this, 'message')
, ssfi = flag(this, 'ssfi')
, contains = flag(this, 'contains')
, isDeep = flag(this, 'deep')
, eql = flag(this, 'eql');
new Assertion(list, flagMsg, ssfi, true).to.be.an('array');
if (contains) {
this.assert(
list.some(function(possibility) { return expected.indexOf(possibility) > -1 })
, 'expected #{this} to contain one of #{exp}'
, 'expected #{this} to not contain one of #{exp}'
, list
, expected
);
} else {
if (isDeep) {
this.assert(
list.some(function(possibility) { return eql(expected, possibility) })
, 'expected #{this} to deeply equal one of #{exp}'
, 'expected #{this} to deeply equal one of #{exp}'
, list
, expected
);
} else {
this.assert(
list.indexOf(expected) > -1
, 'expected #{this} to be one of #{exp}'
, 'expected #{this} to not be one of #{exp}'
, list
, expected
);
}
}
}
Assertion.addMethod('oneOf', oneOf);
/**
* ### .change(subject[, prop[, msg]])
*
* When one argument is provided, `.change` asserts that the given function
* `subject` returns a different value when it's invoked before the target
* function compared to when it's invoked afterward. However, it's often best
* to assert that `subject` is equal to its expected value.
*
* var dots = ''
* , addDot = function () { dots += '.'; }
* , getDots = function () { return dots; };
*
* // Recommended
* expect(getDots()).to.equal('');
* addDot();
* expect(getDots()).to.equal('.');
*
* // Not recommended
* expect(addDot).to.change(getDots);
*
* When two arguments are provided, `.change` asserts that the value of the
* given object `subject`'s `prop` property is different before invoking the
* target function compared to afterward.
*
* var myObj = {dots: ''}
* , addDot = function () { myObj.dots += '.'; };
*
* // Recommended
* expect(myObj).to.have.property('dots', '');
* addDot();
* expect(myObj).to.have.property('dots', '.');
*
* // Not recommended
* expect(addDot).to.change(myObj, 'dots');
*
* Strict (`===`) equality is used to compare before and after values.
*
* Add `.not` earlier in the chain to negate `.change`.
*
* var dots = ''
* , noop = function () {}
* , getDots = function () { return dots; };
*
* expect(noop).to.not.change(getDots);
*
* var myObj = {dots: ''}
* , noop = function () {};
*
* expect(noop).to.not.change(myObj, 'dots');
*
* `.change` accepts an optional `msg` argument which is a custom error
* message to show when the assertion fails. The message can also be given as
* the second argument to `expect`. When not providing two arguments, always
* use the second form.
*
* var myObj = {dots: ''}
* , addDot = function () { myObj.dots += '.'; };
*
* expect(addDot).to.not.change(myObj, 'dots', 'nooo why fail??');
*
* var dots = ''
* , addDot = function () { dots += '.'; }
* , getDots = function () { return dots; };
*
* expect(addDot, 'nooo why fail??').to.not.change(getDots);
*
* `.change` also causes all `.by` assertions that follow in the chain to
* assert how much a numeric subject was increased or decreased by. However,
* it's dangerous to use `.change.by`. The problem is that it creates
* uncertain expectations by asserting that the subject either increases by
* the given delta, or that it decreases by the given delta. It's often best
* to identify the exact output that's expected, and then write an assertion
* that only accepts that exact output.
*
* var myObj = {val: 1}
* , addTwo = function () { myObj.val += 2; }
* , subtractTwo = function () { myObj.val -= 2; };
*
* expect(addTwo).to.increase(myObj, 'val').by(2); // Recommended
* expect(addTwo).to.change(myObj, 'val').by(2); // Not recommended
*
* expect(subtractTwo).to.decrease(myObj, 'val').by(2); // Recommended
* expect(subtractTwo).to.change(myObj, 'val').by(2); // Not recommended
*
* The alias `.changes` can be used interchangeably with `.change`.
*
* @name change
* @alias changes
* @param {String} subject
* @param {String} prop name _optional_
* @param {String} msg _optional_
* @namespace BDD
* @api public
*/
function assertChanges (subject, prop, msg) {
if (msg) flag(this, 'message', msg);
var fn = flag(this, 'object')
, flagMsg = flag(this, 'message')
, ssfi = flag(this, 'ssfi');
new Assertion(fn, flagMsg, ssfi, true).is.a('function');
var initial;
if (!prop) {
new Assertion(subject, flagMsg, ssfi, true).is.a('function');
initial = subject();
} else {
new Assertion(subject, flagMsg, ssfi, true).to.have.property(prop);
initial = subject[prop];
}
fn();
var final = prop === undefined || prop === null ? subject() : subject[prop];
var msgObj = prop === undefined || prop === null ? initial : '.' + prop;
// This gets flagged because of the .by(delta) assertion
flag(this, 'deltaMsgObj', msgObj);
flag(this, 'initialDeltaValue', initial);
flag(this, 'finalDeltaValue', final);
flag(this, 'deltaBehavior', 'change');
flag(this, 'realDelta', final !== initial);
this.assert(
initial !== final
, 'expected ' + msgObj + ' to change'
, 'expected ' + msgObj + ' to not change'
);
}
Assertion.addMethod('change', assertChanges);
Assertion.addMethod('changes', assertChanges);
/**
* ### .increase(subject[, prop[, msg]])
*
* When one argument is provided, `.increase` asserts that the given function
* `subject` returns a greater number when it's invoked after invoking the
* target function compared to when it's invoked beforehand. `.increase` also
* causes all `.by` assertions that follow in the chain to assert how much
* greater of a number is returned. It's often best to assert that the return
* value increased by the expected amount, rather than asserting it increased
* by any amount.
*
* var val = 1
* , addTwo = function () { val += 2; }
* , getVal = function () { return val; };
*
* expect(addTwo).to.increase(getVal).by(2); // Recommended
* expect(addTwo).to.increase(getVal); // Not recommended
*
* When two arguments are provided, `.increase` asserts that the value of the
* given object `subject`'s `prop` property is greater after invoking the
* target function compared to beforehand.
*
* var myObj = {val: 1}
* , addTwo = function () { myObj.val += 2; };
*
* expect(addTwo).to.increase(myObj, 'val').by(2); // Recommended
* expect(addTwo).to.increase(myObj, 'val'); // Not recommended
*
* Add `.not` earlier in the chain to negate `.increase`. However, it's
* dangerous to do so. The problem is that it creates uncertain expectations
* by asserting that the subject either decreases, or that it stays the same.
* It's often best to identify the exact output that's expected, and then
* write an assertion that only accepts that exact output.
*
* When the subject is expected to decrease, it's often best to assert that it
* decreased by the expected amount.
*
* var myObj = {val: 1}
* , subtractTwo = function () { myObj.val -= 2; };
*
* expect(subtractTwo).to.decrease(myObj, 'val').by(2); // Recommended
* expect(subtractTwo).to.not.increase(myObj, 'val'); // Not recommended
*
* When the subject is expected to stay the same, it's often best to assert
* exactly that.
*
* var myObj = {val: 1}
* , noop = function () {};
*
* expect(noop).to.not.change(myObj, 'val'); // Recommended
* expect(noop).to.not.increase(myObj, 'val'); // Not recommended
*
* `.increase` accepts an optional `msg` argument which is a custom error
* message to show when the assertion fails. The message can also be given as
* the second argument to `expect`. When not providing two arguments, always
* use the second form.
*
* var myObj = {val: 1}
* , noop = function () {};
*
* expect(noop).to.increase(myObj, 'val', 'nooo why fail??');
*
* var val = 1
* , noop = function () {}
* , getVal = function () { return val; };
*
* expect(noop, 'nooo why fail??').to.increase(getVal);
*
* The alias `.increases` can be used interchangeably with `.increase`.
*
* @name increase
* @alias increases
* @param {String|Function} subject
* @param {String} prop name _optional_
* @param {String} msg _optional_
* @namespace BDD
* @api public
*/
function assertIncreases (subject, prop, msg) {
if (msg) flag(this, 'message', msg);
var fn = flag(this, 'object')
, flagMsg = flag(this, 'message')
, ssfi = flag(this, 'ssfi');
new Assertion(fn, flagMsg, ssfi, true).is.a('function');
var initial;
if (!prop) {
new Assertion(subject, flagMsg, ssfi, true).is.a('function');
initial = subject();
} else {
new Assertion(subject, flagMsg, ssfi, true).to.have.property(prop);
initial = subject[prop];
}
// Make sure that the target is a number
new Assertion(initial, flagMsg, ssfi, true).is.a('number');
fn();
var final = prop === undefined || prop === null ? subject() : subject[prop];
var msgObj = prop === undefined || prop === null ? initial : '.' + prop;
flag(this, 'deltaMsgObj', msgObj);
flag(this, 'initialDeltaValue', initial);
flag(this, 'finalDeltaValue', final);
flag(this, 'deltaBehavior', 'increase');
flag(this, 'realDelta', final - initial);
this.assert(
final - initial > 0
, 'expected ' + msgObj + ' to increase'
, 'expected ' + msgObj + ' to not increase'
);
}
Assertion.addMethod('increase', assertIncreases);
Assertion.addMethod('increases', assertIncreases);
/**
* ### .decrease(subject[, prop[, msg]])
*
* When one argument is provided, `.decrease` asserts that the given function
* `subject` returns a lesser number when it's invoked after invoking the
* target function compared to when it's invoked beforehand. `.decrease` also
* causes all `.by` assertions that follow in the chain to assert how much
* lesser of a number is returned. It's often best to assert that the return
* value decreased by the expected amount, rather than asserting it decreased
* by any amount.
*
* var val = 1
* , subtractTwo = function () { val -= 2; }
* , getVal = function () { return val; };
*
* expect(subtractTwo).to.decrease(getVal).by(2); // Recommended
* expect(subtractTwo).to.decrease(getVal); // Not recommended
*
* When two arguments are provided, `.decrease` asserts that the value of the
* given object `subject`'s `prop` property is lesser after invoking the
* target function compared to beforehand.
*
* var myObj = {val: 1}
* , subtractTwo = function () { myObj.val -= 2; };
*
* expect(subtractTwo).to.decrease(myObj, 'val').by(2); // Recommended
* expect(subtractTwo).to.decrease(myObj, 'val'); // Not recommended
*
* Add `.not` earlier in the chain to negate `.decrease`. However, it's
* dangerous to do so. The problem is that it creates uncertain expectations
* by asserting that the subject either increases, or that it stays the same.
* It's often best to identify the exact output that's expected, and then
* write an assertion that only accepts that exact output.
*
* When the subject is expected to increase, it's often best to assert that it
* increased by the expected amount.
*
* var myObj = {val: 1}
* , addTwo = function () { myObj.val += 2; };
*
* expect(addTwo).to.increase(myObj, 'val').by(2); // Recommended
* expect(addTwo).to.not.decrease(myObj, 'val'); // Not recommended
*
* When the subject is expected to stay the same, it's often best to assert
* exactly that.
*
* var myObj = {val: 1}
* , noop = function () {};
*
* expect(noop).to.not.change(myObj, 'val'); // Recommended
* expect(noop).to.not.decrease(myObj, 'val'); // Not recommended
*
* `.decrease` accepts an optional `msg` argument which is a custom error
* message to show when the assertion fails. The message can also be given as
* the second argument to `expect`. When not providing two arguments, always
* use the second form.
*
* var myObj = {val: 1}
* , noop = function () {};
*
* expect(noop).to.decrease(myObj, 'val', 'nooo why fail??');
*
* var val = 1
* , noop = function () {}
* , getVal = function () { return val; };
*
* expect(noop, 'nooo why fail??').to.decrease(getVal);
*
* The alias `.decreases` can be used interchangeably with `.decrease`.
*
* @name decrease
* @alias decreases
* @param {String|Function} subject
* @param {String} prop name _optional_
* @param {String} msg _optional_
* @namespace BDD
* @api public
*/
function assertDecreases (subject, prop, msg) {
if (msg) flag(this, 'message', msg);
var fn = flag(this, 'object')
, flagMsg = flag(this, 'message')
, ssfi = flag(this, 'ssfi');
new Assertion(fn, flagMsg, ssfi, true).is.a('function');
var initial;
if (!prop) {
new Assertion(subject, flagMsg, ssfi, true).is.a('function');
initial = subject();
} else {
new Assertion(subject, flagMsg, ssfi, true).to.have.property(prop);
initial = subject[prop];
}
// Make sure that the target is a number
new Assertion(initial, flagMsg, ssfi, true).is.a('number');
fn();
var final = prop === undefined || prop === null ? subject() : subject[prop];
var msgObj = prop === undefined || prop === null ? initial : '.' + prop;
flag(this, 'deltaMsgObj', msgObj);
flag(this, 'initialDeltaValue', initial);
flag(this, 'finalDeltaValue', final);
flag(this, 'deltaBehavior', 'decrease');
flag(this, 'realDelta', initial - final);
this.assert(
final - initial < 0
, 'expected ' + msgObj + ' to decrease'
, 'expected ' + msgObj + ' to not decrease'
);
}
Assertion.addMethod('decrease', assertDecreases);
Assertion.addMethod('decreases', assertDecreases);
/**
* ### .by(delta[, msg])
*
* When following an `.increase` assertion in the chain, `.by` asserts that
* the subject of the `.increase` assertion increased by the given `delta`.
*
* var myObj = {val: 1}
* , addTwo = function () { myObj.val += 2; };
*
* expect(addTwo).to.increase(myObj, 'val').by(2);
*
* When following a `.decrease` assertion in the chain, `.by` asserts that the
* subject of the `.decrease` assertion decreased by the given `delta`.
*
* var myObj = {val: 1}
* , subtractTwo = function () { myObj.val -= 2; };
*
* expect(subtractTwo).to.decrease(myObj, 'val').by(2);
*
* When following a `.change` assertion in the chain, `.by` asserts that the
* subject of the `.change` assertion either increased or decreased by the
* given `delta`. However, it's dangerous to use `.change.by`. The problem is
* that it creates uncertain expectations. It's often best to identify the
* exact output that's expected, and then write an assertion that only accepts
* that exact output.
*
* var myObj = {val: 1}
* , addTwo = function () { myObj.val += 2; }
* , subtractTwo = function () { myObj.val -= 2; };
*
* expect(addTwo).to.increase(myObj, 'val').by(2); // Recommended
* expect(addTwo).to.change(myObj, 'val').by(2); // Not recommended
*
* expect(subtractTwo).to.decrease(myObj, 'val').by(2); // Recommended
* expect(subtractTwo).to.change(myObj, 'val').by(2); // Not recommended
*
* Add `.not` earlier in the chain to negate `.by`. However, it's often best
* to assert that the subject changed by its expected delta, rather than
* asserting that it didn't change by one of countless unexpected deltas.
*
* var myObj = {val: 1}
* , addTwo = function () { myObj.val += 2; };
*
* // Recommended
* expect(addTwo).to.increase(myObj, 'val').by(2);
*
* // Not recommended
* expect(addTwo).to.increase(myObj, 'val').but.not.by(3);
*
* `.by` accepts an optional `msg` argument which is a custom error message to
* show when the assertion fails. The message can also be given as the second
* argument to `expect`.
*
* var myObj = {val: 1}
* , addTwo = function () { myObj.val += 2; };
*
* expect(addTwo).to.increase(myObj, 'val').by(3, 'nooo why fail??');
* expect(addTwo, 'nooo why fail??').to.increase(myObj, 'val').by(3);
*
* @name by
* @param {Number} delta
* @param {String} msg _optional_
* @namespace BDD
* @api public
*/
function assertDelta(delta, msg) {
if (msg) flag(this, 'message', msg);
var msgObj = flag(this, 'deltaMsgObj');
var initial = flag(this, 'initialDeltaValue');
var final = flag(this, 'finalDeltaValue');
var behavior = flag(this, 'deltaBehavior');
var realDelta = flag(this, 'realDelta');
var expression;
if (behavior === 'change') {
expression = Math.abs(final - initial) === Math.abs(delta);
} else {
expression = realDelta === Math.abs(delta);
}
this.assert(
expression
, 'expected ' + msgObj + ' to ' + behavior + ' by ' + delta
, 'expected ' + msgObj + ' to not ' + behavior + ' by ' + delta
);
}
Assertion.addMethod('by', assertDelta);
/**
* ### .extensible
*
* Asserts that the target is extensible, which means that new properties can
* be added to it. Primitives are never extensible.
*
* expect({a: 1}).to.be.extensible;
*
* Add `.not` earlier in the chain to negate `.extensible`.
*
* var nonExtensibleObject = Object.preventExtensions({})
* , sealedObject = Object.seal({})
* , frozenObject = Object.freeze({});
*
* expect(nonExtensibleObject).to.not.be.extensible;
* expect(sealedObject).to.not.be.extensible;
* expect(frozenObject).to.not.be.extensible;
* expect(1).to.not.be.extensible;
*
* A custom error message can be given as the second argument to `expect`.
*
* expect(1, 'nooo why fail??').to.be.extensible;
*
* @name extensible
* @namespace BDD
* @api public
*/
Assertion.addProperty('extensible', function() {
var obj = flag(this, 'object');
// In ES5, if the argument to this method is a primitive, then it will cause a TypeError.
// In ES6, a non-object argument will be treated as if it was a non-extensible ordinary object, simply return false.
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/isExtensible
// The following provides ES6 behavior for ES5 environments.
var isExtensible = obj === Object(obj) && Object.isExtensible(obj);
this.assert(
isExtensible
, 'expected #{this} to be extensible'
, 'expected #{this} to not be extensible'
);
});
/**
* ### .sealed
*
* Asserts that the target is sealed, which means that new properties can't be
* added to it, and its existing properties can't be reconfigured or deleted.
* However, it's possible that its existing properties can still be reassigned
* to different values. Primitives are always sealed.
*
* var sealedObject = Object.seal({});
* var frozenObject = Object.freeze({});
*
* expect(sealedObject).to.be.sealed;
* expect(frozenObject).to.be.sealed;
* expect(1).to.be.sealed;
*
* Add `.not` earlier in the chain to negate `.sealed`.
*
* expect({a: 1}).to.not.be.sealed;
*
* A custom error message can be given as the second argument to `expect`.
*
* expect({a: 1}, 'nooo why fail??').to.be.sealed;
*
* @name sealed
* @namespace BDD
* @api public
*/
Assertion.addProperty('sealed', function() {
var obj = flag(this, 'object');
// In ES5, if the argument to this method is a primitive, then it will cause a TypeError.
// In ES6, a non-object argument will be treated as if it was a sealed ordinary object, simply return true.
// See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/isSealed
// The following provides ES6 behavior for ES5 environments.
var isSealed = obj === Object(obj) ? Object.isSealed(obj) : true;
this.assert(
isSealed
, 'expected #{this} to be sealed'
, 'expected #{this} to not be sealed'
);
});
/**
* ### .frozen
*
* Asserts that the target is frozen, which means that new properties can't be
* added to it, and its existing properties can't be reassigned to different
* values, reconfigured, or deleted. Primitives are always frozen.
*
* var frozenObject = Object.freeze({});
*
* expect(frozenObject).to.be.frozen;
* expect(1).to.be.frozen;
*
* Add `.not` earlier in the chain to negate `.frozen`.
*
* expect({a: 1}).to.not.be.frozen;
*
* A custom error message can be given as the second argument to `expect`.
*
* expect({a: 1}, 'nooo why fail??').to.be.frozen;
*
* @name frozen
* @namespace BDD
* @api public
*/
Assertion.addProperty('frozen', function() {
var obj = flag(this, 'object');
// In ES5, if the argument to this method is a primitive, then it will cause a TypeError.
// In ES6, a non-object argument will be treated as if it was a frozen ordinary object, simply return true.
// See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/isFrozen
// The following provides ES6 behavior for ES5 environments.
var isFrozen = obj === Object(obj) ? Object.isFrozen(obj) : true;
this.assert(
isFrozen
, 'expected #{this} to be frozen'
, 'expected #{this} to not be frozen'
);
});
/**
* ### .finite
*
* Asserts that the target is a number, and isn't `NaN` or positive/negative
* `Infinity`.
*
* expect(1).to.be.finite;
*
* Add `.not` earlier in the chain to negate `.finite`. However, it's
* dangerous to do so. The problem is that it creates uncertain expectations
* by asserting that the subject either isn't a number, or that it's `NaN`, or
* that it's positive `Infinity`, or that it's negative `Infinity`. It's often
* best to identify the exact output that's expected, and then write an
* assertion that only accepts that exact output.
*
* When the target isn't expected to be a number, it's often best to assert
* that it's the expected type, rather than asserting that it isn't one of
* many unexpected types.
*
* expect('foo').to.be.a('string'); // Recommended
* expect('foo').to.not.be.finite; // Not recommended
*
* When the target is expected to be `NaN`, it's often best to assert exactly
* that.
*
* expect(NaN).to.be.NaN; // Recommended
* expect(NaN).to.not.be.finite; // Not recommended
*
* When the target is expected to be positive infinity, it's often best to
* assert exactly that.
*
* expect(Infinity).to.equal(Infinity); // Recommended
* expect(Infinity).to.not.be.finite; // Not recommended
*
* When the target is expected to be negative infinity, it's often best to
* assert exactly that.
*
* expect(-Infinity).to.equal(-Infinity); // Recommended
* expect(-Infinity).to.not.be.finite; // Not recommended
*
* A custom error message can be given as the second argument to `expect`.
*
* expect('foo', 'nooo why fail??').to.be.finite;
*
* @name finite
* @namespace BDD
* @api public
*/
Assertion.addProperty('finite', function(msg) {
var obj = flag(this, 'object');
this.assert(
typeof obj === 'number' && isFinite(obj)
, 'expected #{this} to be a finite number'
, 'expected #{this} to not be a finite number'
);
});
};