diff --git a/lib/chai/assertion.js b/lib/chai/assertion.js index 491add77f..4d4217fb4 100644 --- a/lib/chai/assertion.js +++ b/lib/chai/assertion.js @@ -46,14 +46,22 @@ module.exports = function (_chai, util) { * in environments that support `Error.captureStackTrace`, and only when * `Chai.config.includeStack` hasn't been set to `false`. * + * - `lockSsfi`: This flag controls whether or not the given `ssfi` flag + * should retain its current value, even as assertions are chained off of + * this object. This is usually set to `true` when creating a new assertion + * from within another assertion. It's also temporarily set to `true` before + * an overwritten assertion gets called by the overwriting assertion. + * * @param {Mixed} obj target of the assertion * @param {String} msg (optional) custom error message - * @param {Function} stack (optional) starting point for removing stack frames + * @param {Function} ssfi (optional) starting point for removing stack frames + * @param {Boolean} lockSsfi (optional) whether or not the ssfi flag is locked * @api private */ - function Assertion (obj, msg, ssfi) { + function Assertion (obj, msg, ssfi, lockSsfi) { flag(this, 'ssfi', ssfi || Assertion); + flag(this, 'lockSsfi', lockSsfi); flag(this, 'object', obj); flag(this, 'message', msg); diff --git a/lib/chai/core/assertions.js b/lib/chai/core/assertions.js index d09e16850..4292c9155 100644 --- a/lib/chai/core/assertions.js +++ b/lib/chai/core/assertions.js @@ -299,7 +299,7 @@ module.exports = function (chai, _) { } function include (val, msg) { - _.expectTypes(this, ['array', 'object', 'string']); + _.expectTypes(this, ['array', 'object', 'string'], flag(this, 'ssfi')); if (msg) flag(this, 'message', msg); var obj = flag(this, 'object') @@ -316,7 +316,8 @@ module.exports = function (chai, _) { props.forEach(function (prop) { var propAssertion = new Assertion(obj); - _.transferFlags(this, propAssertion, false); + _.transferFlags(this, propAssertion, true); + flag(propAssertion, 'lockSsfi', true); if (!negate || props.length === 1) { propAssertion.property(prop, val[prop]); @@ -533,6 +534,7 @@ module.exports = function (chai, _) { Assertion.addProperty('empty', function () { var val = flag(this, 'object') + , ssfi = flag(this, 'ssfi') , itemsCount; switch (_.type(val).toLowerCase()) { @@ -546,13 +548,21 @@ module.exports = function (chai, _) { break; case 'weakmap': case 'weakset': - throw new TypeError('.empty was passed a weak collection'); + throw new AssertionError( + '.empty was passed a weak collection', + undefined, + ssfi + ); case 'function': var msg = '.empty was passed a function ' + _.getName(val); - throw new TypeError(msg.trim()); + throw new AssertionError(msg.trim(), undefined, ssfi); default: if (val !== Object(val)) { - throw new TypeError('.empty was passed non-string primitive ' + _.inspect(val)); + throw new AssertionError( + '.empty was passed non-string primitive ' + _.inspect(val), + undefined, + ssfi + ); } itemsCount = Object.keys(val).length; } @@ -695,16 +705,21 @@ module.exports = function (chai, _) { function assertAbove (n, msg) { if (msg) flag(this, 'message', msg); var obj = flag(this, 'object') - , doLength = flag(this, 'doLength'); + , doLength = flag(this, 'doLength') + , ssfi = flag(this, 'ssfi'); if (doLength) { - new Assertion(obj, msg).to.have.property('length'); + new Assertion(obj, msg, ssfi, true).to.have.property('length'); } else { - new Assertion(obj, msg).is.a('number'); + new Assertion(obj, msg, ssfi, true).is.a('number'); } if (typeof n !== 'number') { - throw new Error('the argument to above must be a number'); + throw new AssertionError( + 'the argument to above must be a number', + undefined, + ssfi + ); } if (doLength) { @@ -756,16 +771,21 @@ module.exports = function (chai, _) { function assertLeast (n, msg) { if (msg) flag(this, 'message', msg); var obj = flag(this, 'object') - , doLength = flag(this, 'doLength'); + , doLength = flag(this, 'doLength') + , ssfi = flag(this, 'ssfi'); if (doLength) { - new Assertion(obj, msg).to.have.property('length'); + new Assertion(obj, msg, ssfi, true).to.have.property('length'); } else { - new Assertion(obj, msg).is.a('number'); + new Assertion(obj, msg, ssfi, true).is.a('number'); } if (typeof n !== 'number') { - throw new Error('the argument to least must be a number'); + throw new AssertionError( + 'the argument to least must be a number', + undefined, + ssfi + ); } if (doLength) { @@ -817,16 +837,21 @@ module.exports = function (chai, _) { function assertBelow (n, msg) { if (msg) flag(this, 'message', msg); var obj = flag(this, 'object') - , doLength = flag(this, 'doLength'); + , doLength = flag(this, 'doLength') + , ssfi = flag(this, 'ssfi'); if (doLength) { - new Assertion(obj, msg).to.have.property('length'); + new Assertion(obj, msg, ssfi, true).to.have.property('length'); } else { - new Assertion(obj, msg).is.a('number'); + new Assertion(obj, msg, ssfi, true).is.a('number'); } if (typeof n !== 'number') { - throw new Error('the argument to below must be a number'); + throw new AssertionError( + 'the argument to below must be a number', + undefined, + ssfi + ); } if (doLength) { @@ -878,16 +903,21 @@ module.exports = function (chai, _) { function assertMost (n, msg) { if (msg) flag(this, 'message', msg); var obj = flag(this, 'object') - , doLength = flag(this, 'doLength'); + , doLength = flag(this, 'doLength') + , ssfi = flag(this, 'ssfi'); if (doLength) { - new Assertion(obj, msg).to.have.property('length'); + new Assertion(obj, msg, ssfi, true).to.have.property('length'); } else { - new Assertion(obj, msg).is.a('number'); + new Assertion(obj, msg, ssfi, true).is.a('number'); } if (typeof n !== 'number') { - throw new Error('the argument to most must be a number'); + throw new AssertionError( + 'the argument to most must be a number', + undefined, + ssfi + ); } if (doLength) { @@ -939,16 +969,21 @@ module.exports = function (chai, _) { if (msg) flag(this, 'message', msg); var obj = flag(this, 'object') , range = start + '..' + finish - , doLength = flag(this, 'doLength'); + , doLength = flag(this, 'doLength') + , ssfi = flag(this, 'ssfi'); if (doLength) { - new Assertion(obj, msg).to.have.property('length'); + new Assertion(obj, msg, ssfi, true).to.have.property('length'); } else { - new Assertion(obj, msg).is.a('number'); + new Assertion(obj, msg, ssfi, true).is.a('number'); } if (typeof start !== 'number' || typeof finish !== 'number') { - throw new Error('the arguments to within must be numbers'); + throw new AssertionError( + 'the arguments to within must be numbers', + undefined, + ssfi + ); } if (doLength) { @@ -990,6 +1025,7 @@ module.exports = function (chai, _) { if (msg) flag(this, 'message', msg); var target = flag(this, 'object') + var ssfi = flag(this, 'ssfi'); var validInstanceOfTarget = constructor === Object(constructor) && ( typeof constructor === 'function' || (typeof Symbol !== 'undefined' && @@ -999,7 +1035,11 @@ module.exports = function (chai, _) { if (!validInstanceOfTarget) { var constructorType = constructor === null ? 'null' : typeof constructor; - throw new Error('The instanceof assertion needs a constructor but ' + constructorType + ' was given.'); + throw new AssertionError( + 'The instanceof assertion needs a constructor but ' + constructorType + ' was given.', + undefined, + ssfi + ); } var isInstanceOf = target instanceof constructor @@ -1124,10 +1164,15 @@ module.exports = function (chai, _) { if (msg) flag(this, 'message', msg); var isNested = flag(this, 'nested') - , isOwn = flag(this, 'own'); + , isOwn = flag(this, 'own') + , ssfi = flag(this, 'ssfi'); if (isNested && isOwn) { - throw new Error('The "nested" and "own" flags cannot be combined.'); + throw new AssertionError( + 'The "nested" and "own" flags cannot be combined.', + undefined, + ssfi + ); } var isDeep = flag(this, 'deep') @@ -1279,8 +1324,9 @@ module.exports = function (chai, _) { function assertLength (n, msg) { if (msg) flag(this, 'message', msg); - var obj = flag(this, 'object'); - new Assertion(obj, msg).to.have.property('length'); + var obj = flag(this, 'object') + , ssfi = flag(this, 'ssfi'); + new Assertion(obj, msg, ssfi, true).to.have.property('length'); var len = obj.length; this.assert( @@ -1338,8 +1384,9 @@ module.exports = function (chai, _) { Assertion.addMethod('string', function (str, msg) { if (msg) flag(this, 'message', msg); - var obj = flag(this, 'object'); - new Assertion(obj, msg).is.a('string'); + var obj = flag(this, 'object') + , ssfi = flag(this, 'ssfi'); + new Assertion(obj, msg, ssfi, true).is.a('string'); this.assert( ~obj.indexOf(str) @@ -1406,6 +1453,7 @@ module.exports = function (chai, _) { var obj = flag(this, 'object') , objType = _.type(obj) , keysType = _.type(keys) + , ssfi = flag(this, 'ssfi') , isDeep = flag(this, 'deep') , str , deepStr = '' @@ -1428,10 +1476,14 @@ module.exports = function (chai, _) { switch (keysType) { case 'Array': - if (arguments.length > 1) throw new Error(mixedArgsMsg); + if (arguments.length > 1) { + throw new AssertionError(mixedArgsMsg, undefined, ssfi); + } break; case 'Object': - if (arguments.length > 1) throw new Error(mixedArgsMsg); + if (arguments.length > 1) { + throw new AssertionError(mixedArgsMsg, undefined, ssfi); + } keys = Object.keys(keys); break; default: @@ -1444,7 +1496,9 @@ module.exports = function (chai, _) { }); } - if (!keys.length) throw new Error('keys required'); + if (!keys.length) { + throw new AssertionError('keys required', undefined, ssfi); + } var len = keys.length , any = flag(this, 'any') @@ -1645,9 +1699,10 @@ module.exports = function (chai, _) { function assertThrows (errorLike, errMsgMatcher, msg) { if (msg) flag(this, 'message', msg); - var obj = flag(this, 'object'); - var negate = flag(this, 'negate') || false; - new Assertion(obj, msg).is.a('function'); + var obj = flag(this, 'object') + , ssfi = flag(this, 'ssfi') + , negate = flag(this, 'negate') || false; + new Assertion(obj, msg, ssfi, true).is.a('function'); if (errorLike instanceof RegExp || typeof errorLike === 'string') { errMsgMatcher = errorLike; @@ -1882,11 +1937,16 @@ module.exports = function (chai, _) { function closeTo(expected, delta, msg) { if (msg) flag(this, 'message', msg); - var obj = flag(this, 'object'); + var obj = flag(this, 'object') + , ssfi = flag(this, 'ssfi'); - new Assertion(obj, msg).is.a('number'); + new Assertion(obj, msg, ssfi, true).is.a('number'); if (typeof expected !== 'number' || typeof delta !== 'number') { - throw new Error('the arguments to closeTo or approximately must be numbers'); + throw new AssertionError( + 'the arguments to closeTo or approximately must be numbers', + undefined, + ssfi + ); } this.assert( @@ -1978,10 +2038,11 @@ module.exports = function (chai, _) { Assertion.addMethod('members', function (subset, msg) { if (msg) flag(this, 'message', msg); - var obj = flag(this, 'object'); + var obj = flag(this, 'object') + , ssfi = flag(this, 'ssfi'); - new Assertion(obj).to.be.an('array'); - new Assertion(subset).to.be.an('array'); + new Assertion(obj, msg, ssfi, true).to.be.an('array'); + new Assertion(subset, msg, ssfi, true).to.be.an('array'); var contains = flag(this, 'contains'); var ordered = flag(this, 'ordered'); @@ -2034,8 +2095,9 @@ module.exports = function (chai, _) { function oneOf (list, msg) { if (msg) flag(this, 'message', msg); - var expected = flag(this, 'object'); - new Assertion(list).to.be.an('array'); + var expected = flag(this, 'object') + , ssfi = flag(this, 'ssfi'); + new Assertion(list, msg, ssfi, true).to.be.an('array'); this.assert( list.indexOf(expected) > -1 @@ -2072,15 +2134,16 @@ module.exports = function (chai, _) { function assertChanges (target, prop, msg) { if (msg) flag(this, 'message', msg); - var fn = flag(this, 'object'); - new Assertion(fn).is.a('function'); + var fn = flag(this, 'object') + , ssfi = flag(this, 'ssfi'); + new Assertion(fn, msg, ssfi, true).is.a('function'); var initial; if (!prop) { - new Assertion(target).is.a('function'); + new Assertion(target, msg, ssfi, true).is.a('function'); initial = target(); } else { - new Assertion(target, msg).to.have.property(prop); + new Assertion(target, msg, ssfi, true).to.have.property(prop); initial = target[prop]; } @@ -2134,20 +2197,21 @@ module.exports = function (chai, _) { function assertIncreases (target, prop, msg) { if (msg) flag(this, 'message', msg); - var fn = flag(this, 'object'); - new Assertion(fn).is.a('function'); + var fn = flag(this, 'object') + , ssfi = flag(this, 'ssfi'); + new Assertion(fn, msg, ssfi, true).is.a('function'); var initial; if (!prop) { - new Assertion(target).is.a('function'); + new Assertion(target, msg, ssfi, true).is.a('function'); initial = target(); } else { - new Assertion(target, msg).to.have.property(prop); + new Assertion(target, msg, ssfi, true).to.have.property(prop); initial = target[prop]; } // Make sure that the target is a number - new Assertion(initial).is.a('number'); + new Assertion(initial, msg, ssfi, true).is.a('number'); fn(); @@ -2198,20 +2262,21 @@ module.exports = function (chai, _) { function assertDecreases (target, prop, msg) { if (msg) flag(this, 'message', msg); - var fn = flag(this, 'object'); - new Assertion(fn).is.a('function'); + var fn = flag(this, 'object') + , ssfi = flag(this, 'ssfi'); + new Assertion(fn, msg, ssfi, true).is.a('function'); var initial; if (!prop) { - new Assertion(target).is.a('function'); + new Assertion(target, msg, ssfi, true).is.a('function'); initial = target(); } else { - new Assertion(target, msg).to.have.property(prop); + new Assertion(target, msg, ssfi, true).to.have.property(prop); initial = target[prop]; } // Make sure that the target is a number - new Assertion(initial).is.a('number'); + new Assertion(initial, msg, ssfi, true).is.a('number'); fn(); diff --git a/lib/chai/interface/assert.js b/lib/chai/interface/assert.js index c9b852ab4..f4abad240 100644 --- a/lib/chai/interface/assert.js +++ b/lib/chai/interface/assert.js @@ -34,7 +34,7 @@ module.exports = function (chai, util) { */ var assert = chai.assert = function (express, errmsg) { - var test = new Assertion(null, null, chai.assert); + var test = new Assertion(null, null, chai.assert, true); test.assert( express , errmsg @@ -82,7 +82,7 @@ module.exports = function (chai, util) { */ assert.isOk = function (val, msg) { - new Assertion(val, msg).is.ok; + new Assertion(val, msg, assert.isOk, true).is.ok; }; /** @@ -102,7 +102,7 @@ module.exports = function (chai, util) { */ assert.isNotOk = function (val, msg) { - new Assertion(val, msg).is.not.ok; + new Assertion(val, msg, assert.isNotOk, true).is.not.ok; }; /** @@ -121,7 +121,7 @@ module.exports = function (chai, util) { */ assert.equal = function (act, exp, msg) { - var test = new Assertion(act, msg, assert.equal); + var test = new Assertion(act, msg, assert.equal, true); test.assert( exp == flag(test, 'object') @@ -149,7 +149,7 @@ module.exports = function (chai, util) { */ assert.notEqual = function (act, exp, msg) { - var test = new Assertion(act, msg, assert.notEqual); + var test = new Assertion(act, msg, assert.notEqual, true); test.assert( exp != flag(test, 'object') @@ -177,7 +177,7 @@ module.exports = function (chai, util) { */ assert.strictEqual = function (act, exp, msg) { - new Assertion(act, msg).to.equal(exp); + new Assertion(act, msg, assert.strictEqual, true).to.equal(exp); }; /** @@ -196,7 +196,7 @@ module.exports = function (chai, util) { */ assert.notStrictEqual = function (act, exp, msg) { - new Assertion(act, msg).to.not.equal(exp); + new Assertion(act, msg, assert.notStrictEqual, true).to.not.equal(exp); }; /** @@ -216,7 +216,7 @@ module.exports = function (chai, util) { */ assert.deepEqual = assert.deepStrictEqual = function (act, exp, msg) { - new Assertion(act, msg).to.eql(exp); + new Assertion(act, msg, assert.deepEqual, true).to.eql(exp); }; /** @@ -235,7 +235,7 @@ module.exports = function (chai, util) { */ assert.notDeepEqual = function (act, exp, msg) { - new Assertion(act, msg).to.not.eql(exp); + new Assertion(act, msg, assert.notDeepEqual, true).to.not.eql(exp); }; /** @@ -254,7 +254,7 @@ module.exports = function (chai, util) { */ assert.isAbove = function (val, abv, msg) { - new Assertion(val, msg).to.be.above(abv); + new Assertion(val, msg, assert.isAbove, true).to.be.above(abv); }; /** @@ -274,7 +274,7 @@ module.exports = function (chai, util) { */ assert.isAtLeast = function (val, atlst, msg) { - new Assertion(val, msg).to.be.least(atlst); + new Assertion(val, msg, assert.isAtLeast, true).to.be.least(atlst); }; /** @@ -293,7 +293,7 @@ module.exports = function (chai, util) { */ assert.isBelow = function (val, blw, msg) { - new Assertion(val, msg).to.be.below(blw); + new Assertion(val, msg, assert.isBelow, true).to.be.below(blw); }; /** @@ -313,7 +313,7 @@ module.exports = function (chai, util) { */ assert.isAtMost = function (val, atmst, msg) { - new Assertion(val, msg).to.be.most(atmst); + new Assertion(val, msg, assert.isAtMost, true).to.be.most(atmst); }; /** @@ -332,7 +332,7 @@ module.exports = function (chai, util) { */ assert.isTrue = function (val, msg) { - new Assertion(val, msg).is['true']; + new Assertion(val, msg, assert.isTrue, true).is['true']; }; /** @@ -351,7 +351,7 @@ module.exports = function (chai, util) { */ assert.isNotTrue = function (val, msg) { - new Assertion(val, msg).to.not.equal(true); + new Assertion(val, msg, assert.isNotTrue, true).to.not.equal(true); }; /** @@ -370,7 +370,7 @@ module.exports = function (chai, util) { */ assert.isFalse = function (val, msg) { - new Assertion(val, msg).is['false']; + new Assertion(val, msg, assert.isFalse, true).is['false']; }; /** @@ -389,7 +389,7 @@ module.exports = function (chai, util) { */ assert.isNotFalse = function (val, msg) { - new Assertion(val, msg).to.not.equal(false); + new Assertion(val, msg, assert.isNotFalse, true).to.not.equal(false); }; /** @@ -407,7 +407,7 @@ module.exports = function (chai, util) { */ assert.isNull = function (val, msg) { - new Assertion(val, msg).to.equal(null); + new Assertion(val, msg, assert.isNull, true).to.equal(null); }; /** @@ -426,7 +426,7 @@ module.exports = function (chai, util) { */ assert.isNotNull = function (val, msg) { - new Assertion(val, msg).to.not.equal(null); + new Assertion(val, msg, assert.isNotNull, true).to.not.equal(null); }; /** @@ -444,7 +444,7 @@ module.exports = function (chai, util) { */ assert.isNaN = function (val, msg) { - new Assertion(val, msg).to.be.NaN; + new Assertion(val, msg, assert.isNaN, true).to.be.NaN; }; /** @@ -461,7 +461,7 @@ module.exports = function (chai, util) { * @api public */ assert.isNotNaN = function (val, msg) { - new Assertion(val, msg).not.to.be.NaN; + new Assertion(val, msg, assert.isNotNaN, true).not.to.be.NaN; }; /** @@ -481,7 +481,7 @@ module.exports = function (chai, util) { */ assert.exists = function (val, msg) { - new Assertion(val, msg).to.exist; + new Assertion(val, msg, assert.exists, true).to.exist; }; /** @@ -503,7 +503,7 @@ module.exports = function (chai, util) { */ assert.notExists = function (val, msg) { - new Assertion(val, msg).to.not.exist; + new Assertion(val, msg, assert.notExists, true).to.not.exist; }; /** @@ -522,7 +522,7 @@ module.exports = function (chai, util) { */ assert.isUndefined = function (val, msg) { - new Assertion(val, msg).to.equal(undefined); + new Assertion(val, msg, assert.isUndefined, true).to.equal(undefined); }; /** @@ -541,7 +541,7 @@ module.exports = function (chai, util) { */ assert.isDefined = function (val, msg) { - new Assertion(val, msg).to.not.equal(undefined); + new Assertion(val, msg, assert.isDefined, true).to.not.equal(undefined); }; /** @@ -560,7 +560,7 @@ module.exports = function (chai, util) { */ assert.isFunction = function (val, msg) { - new Assertion(val, msg).to.be.a('function'); + new Assertion(val, msg, assert.isFunction, true).to.be.a('function'); }; /** @@ -579,7 +579,7 @@ module.exports = function (chai, util) { */ assert.isNotFunction = function (val, msg) { - new Assertion(val, msg).to.not.be.a('function'); + new Assertion(val, msg, assert.isNotFunction, true).to.not.be.a('function'); }; /** @@ -599,7 +599,7 @@ module.exports = function (chai, util) { */ assert.isObject = function (val, msg) { - new Assertion(val, msg).to.be.a('object'); + new Assertion(val, msg, assert.isObject, true).to.be.a('object'); }; /** @@ -619,7 +619,7 @@ module.exports = function (chai, util) { */ assert.isNotObject = function (val, msg) { - new Assertion(val, msg).to.not.be.a('object'); + new Assertion(val, msg, assert.isNotObject, true).to.not.be.a('object'); }; /** @@ -638,7 +638,7 @@ module.exports = function (chai, util) { */ assert.isArray = function (val, msg) { - new Assertion(val, msg).to.be.an('array'); + new Assertion(val, msg, assert.isArray, true).to.be.an('array'); }; /** @@ -657,7 +657,7 @@ module.exports = function (chai, util) { */ assert.isNotArray = function (val, msg) { - new Assertion(val, msg).to.not.be.an('array'); + new Assertion(val, msg, assert.isNotArray, true).to.not.be.an('array'); }; /** @@ -676,7 +676,7 @@ module.exports = function (chai, util) { */ assert.isString = function (val, msg) { - new Assertion(val, msg).to.be.a('string'); + new Assertion(val, msg, assert.isString, true).to.be.a('string'); }; /** @@ -695,7 +695,7 @@ module.exports = function (chai, util) { */ assert.isNotString = function (val, msg) { - new Assertion(val, msg).to.not.be.a('string'); + new Assertion(val, msg, assert.isNotString, true).to.not.be.a('string'); }; /** @@ -714,7 +714,7 @@ module.exports = function (chai, util) { */ assert.isNumber = function (val, msg) { - new Assertion(val, msg).to.be.a('number'); + new Assertion(val, msg, assert.isNumber, true).to.be.a('number'); }; /** @@ -733,7 +733,7 @@ module.exports = function (chai, util) { */ assert.isNotNumber = function (val, msg) { - new Assertion(val, msg).to.not.be.a('number'); + new Assertion(val, msg, assert.isNotNumber, true).to.not.be.a('number'); }; /** @@ -754,7 +754,7 @@ module.exports = function (chai, util) { */ assert.isFinite = function (val, msg) { - new Assertion(val, msg).to.be.finite; + new Assertion(val, msg, assert.isFinite, true).to.be.finite; }; /** @@ -776,7 +776,7 @@ module.exports = function (chai, util) { */ assert.isBoolean = function (val, msg) { - new Assertion(val, msg).to.be.a('boolean'); + new Assertion(val, msg, assert.isBoolean, true).to.be.a('boolean'); }; /** @@ -798,7 +798,7 @@ module.exports = function (chai, util) { */ assert.isNotBoolean = function (val, msg) { - new Assertion(val, msg).to.not.be.a('boolean'); + new Assertion(val, msg, assert.isNotBoolean, true).to.not.be.a('boolean'); }; /** @@ -823,7 +823,7 @@ module.exports = function (chai, util) { */ assert.typeOf = function (val, type, msg) { - new Assertion(val, msg).to.be.a(type); + new Assertion(val, msg, assert.typeOf, true).to.be.a(type); }; /** @@ -843,7 +843,7 @@ module.exports = function (chai, util) { */ assert.notTypeOf = function (val, type, msg) { - new Assertion(val, msg).to.not.be.a(type); + new Assertion(val, msg, assert.notTypeOf, true).to.not.be.a(type); }; /** @@ -865,7 +865,7 @@ module.exports = function (chai, util) { */ assert.instanceOf = function (val, type, msg) { - new Assertion(val, msg).to.be.instanceOf(type); + new Assertion(val, msg, assert.instanceOf, true).to.be.instanceOf(type); }; /** @@ -887,7 +887,8 @@ module.exports = function (chai, util) { */ assert.notInstanceOf = function (val, type, msg) { - new Assertion(val, msg).to.not.be.instanceOf(type); + new Assertion(val, msg, assert.notInstanceOf, true) + .to.not.be.instanceOf(type); }; /** @@ -922,7 +923,7 @@ module.exports = function (chai, util) { */ assert.include = function (exp, inc, msg) { - new Assertion(exp, msg, assert.include).include(inc); + new Assertion(exp, msg, assert.include, true).include(inc); }; /** @@ -958,7 +959,7 @@ module.exports = function (chai, util) { */ assert.notInclude = function (exp, inc, msg) { - new Assertion(exp, msg, assert.notInclude).not.include(inc); + new Assertion(exp, msg, assert.notInclude, true).not.include(inc); }; /** @@ -983,7 +984,7 @@ module.exports = function (chai, util) { */ assert.deepInclude = function (exp, inc, msg) { - new Assertion(exp, msg, assert.include).deep.include(inc); + new Assertion(exp, msg, assert.deepInclude, true).deep.include(inc); }; /** @@ -1008,7 +1009,7 @@ module.exports = function (chai, util) { */ assert.notDeepInclude = function (exp, inc, msg) { - new Assertion(exp, msg, assert.notInclude).not.deep.include(inc); + new Assertion(exp, msg, assert.notDeepInclude, true).not.deep.include(inc); }; /** @@ -1027,7 +1028,7 @@ module.exports = function (chai, util) { */ assert.match = function (exp, re, msg) { - new Assertion(exp, msg).to.match(re); + new Assertion(exp, msg, assert.match, true).to.match(re); }; /** @@ -1046,7 +1047,7 @@ module.exports = function (chai, util) { */ assert.notMatch = function (exp, re, msg) { - new Assertion(exp, msg).to.not.match(re); + new Assertion(exp, msg, assert.notMatch, true).to.not.match(re); }; /** @@ -1067,7 +1068,7 @@ module.exports = function (chai, util) { */ assert.property = function (obj, prop, msg) { - new Assertion(obj, msg).to.have.property(prop); + new Assertion(obj, msg, assert.property, true).to.have.property(prop); }; /** @@ -1087,7 +1088,8 @@ module.exports = function (chai, util) { */ assert.notProperty = function (obj, prop, msg) { - new Assertion(obj, msg).to.not.have.property(prop); + new Assertion(obj, msg, assert.notProperty, true) + .to.not.have.property(prop); }; /** @@ -1109,7 +1111,8 @@ module.exports = function (chai, util) { */ assert.propertyVal = function (obj, prop, val, msg) { - new Assertion(obj, msg).to.have.property(prop, val); + new Assertion(obj, msg, assert.propertyVal, true) + .to.have.property(prop, val); }; /** @@ -1132,7 +1135,8 @@ module.exports = function (chai, util) { */ assert.notPropertyVal = function (obj, prop, val, msg) { - new Assertion(obj, msg).to.not.have.property(prop, val); + new Assertion(obj, msg, assert.notPropertyVal, true) + .to.not.have.property(prop, val); }; /** @@ -1153,7 +1157,8 @@ module.exports = function (chai, util) { */ assert.deepPropertyVal = function (obj, prop, val, msg) { - new Assertion(obj, msg).to.have.deep.property(prop, val); + new Assertion(obj, msg, assert.deepPropertyVal, true) + .to.have.deep.property(prop, val); }; /** @@ -1176,7 +1181,8 @@ module.exports = function (chai, util) { */ assert.notDeepPropertyVal = function (obj, prop, val, msg) { - new Assertion(obj, msg).to.not.have.deep.property(prop, val); + new Assertion(obj, msg, assert.notDeepPropertyVal, true) + .to.not.have.deep.property(prop, val); }; /** @@ -1195,7 +1201,8 @@ module.exports = function (chai, util) { */ assert.ownProperty = function (obj, prop, msg) { - new Assertion(obj, msg).to.have.own.property(prop); + new Assertion(obj, msg, assert.ownProperty, true) + .to.have.own.property(prop); }; /** @@ -1215,7 +1222,8 @@ module.exports = function (chai, util) { */ assert.notOwnProperty = function (obj, prop, msg) { - new Assertion(obj, msg).to.not.have.own.property(prop); + new Assertion(obj, msg, assert.notOwnProperty, true) + .to.not.have.own.property(prop); }; /** @@ -1236,7 +1244,8 @@ module.exports = function (chai, util) { */ assert.ownPropertyVal = function (obj, prop, value, msg) { - new Assertion(obj, msg).to.have.own.property(prop, value); + new Assertion(obj, msg, assert.ownPropertyVal, true) + .to.have.own.property(prop, value); }; /** @@ -1258,7 +1267,8 @@ module.exports = function (chai, util) { */ assert.notOwnPropertyVal = function (obj, prop, value, msg) { - new Assertion(obj, msg).to.not.have.own.property(prop, value); + new Assertion(obj, msg, assert.notOwnPropertyVal, true) + .to.not.have.own.property(prop, value); }; /** @@ -1279,7 +1289,8 @@ module.exports = function (chai, util) { */ assert.deepOwnPropertyVal = function (obj, prop, value, msg) { - new Assertion(obj, msg).to.have.deep.own.property(prop, value); + new Assertion(obj, msg, assert.deepOwnPropertyVal, true) + .to.have.deep.own.property(prop, value); }; /** @@ -1303,7 +1314,8 @@ module.exports = function (chai, util) { */ assert.notDeepOwnPropertyVal = function (obj, prop, value, msg) { - new Assertion(obj, msg).to.not.have.deep.own.property(prop, value); + new Assertion(obj, msg, assert.notDeepOwnPropertyVal, true) + .to.not.have.deep.own.property(prop, value); }; /** @@ -1324,7 +1336,8 @@ module.exports = function (chai, util) { */ assert.nestedProperty = function (obj, prop, msg) { - new Assertion(obj, msg).to.have.nested.property(prop); + new Assertion(obj, msg, assert.nestedProperty, true) + .to.have.nested.property(prop); }; /** @@ -1345,7 +1358,8 @@ module.exports = function (chai, util) { */ assert.notNestedProperty = function (obj, prop, msg) { - new Assertion(obj, msg).to.not.have.nested.property(prop); + new Assertion(obj, msg, assert.notNestedProperty, true) + .to.not.have.nested.property(prop); }; /** @@ -1367,7 +1381,8 @@ module.exports = function (chai, util) { */ assert.nestedPropertyVal = function (obj, prop, val, msg) { - new Assertion(obj, msg).to.have.nested.property(prop, val); + new Assertion(obj, msg, assert.nestedPropertyVal, true) + .to.have.nested.property(prop, val); }; /** @@ -1390,7 +1405,8 @@ module.exports = function (chai, util) { */ assert.notNestedPropertyVal = function (obj, prop, val, msg) { - new Assertion(obj, msg).to.not.have.nested.property(prop, val); + new Assertion(obj, msg, assert.notNestedPropertyVal, true) + .to.not.have.nested.property(prop, val); }; /** @@ -1412,7 +1428,8 @@ module.exports = function (chai, util) { */ assert.deepNestedPropertyVal = function (obj, prop, val, msg) { - new Assertion(obj, msg).to.have.deep.nested.property(prop, val); + new Assertion(obj, msg, assert.deepNestedPropertyVal, true) + .to.have.deep.nested.property(prop, val); }; /** @@ -1436,7 +1453,8 @@ module.exports = function (chai, util) { */ assert.notDeepNestedPropertyVal = function (obj, prop, val, msg) { - new Assertion(obj, msg).to.not.have.deep.nested.property(prop, val); + new Assertion(obj, msg, assert.notDeepNestedPropertyVal, true) + .to.not.have.deep.nested.property(prop, val); } /** @@ -1456,7 +1474,7 @@ module.exports = function (chai, util) { */ assert.lengthOf = function (exp, len, msg) { - new Assertion(exp, msg).to.have.lengthOf(len); + new Assertion(exp, msg, assert.lengthOf, true).to.have.lengthOf(len); }; /** @@ -1480,7 +1498,7 @@ module.exports = function (chai, util) { */ assert.hasAnyKeys = function (obj, keys, msg) { - new Assertion(obj, msg).to.have.any.keys(keys); + new Assertion(obj, msg, assert.hasAnyKeys, true).to.have.any.keys(keys); } /** @@ -1504,7 +1522,7 @@ module.exports = function (chai, util) { */ assert.hasAllKeys = function (obj, keys, msg) { - new Assertion(obj, msg).to.have.all.keys(keys); + new Assertion(obj, msg, assert.hasAllKeys, true).to.have.all.keys(keys); } /** @@ -1532,7 +1550,8 @@ module.exports = function (chai, util) { */ assert.containsAllKeys = function (obj, keys, msg) { - new Assertion(obj, msg).to.contain.all.keys(keys); + new Assertion(obj, msg, assert.containsAllKeys, true) + .to.contain.all.keys(keys); } /** @@ -1556,7 +1575,8 @@ module.exports = function (chai, util) { */ assert.doesNotHaveAnyKeys = function (obj, keys, msg) { - new Assertion(obj, msg).to.not.have.any.keys(keys); + new Assertion(obj, msg, assert.doesNotHaveAnyKeys, true) + .to.not.have.any.keys(keys); } /** @@ -1580,7 +1600,8 @@ module.exports = function (chai, util) { */ assert.doesNotHaveAllKeys = function (obj, keys, msg) { - new Assertion(obj, msg).to.not.have.all.keys(keys); + new Assertion(obj, msg, assert.doesNotHaveAllKeys, true) + .to.not.have.all.keys(keys); } /** @@ -1608,7 +1629,8 @@ module.exports = function (chai, util) { */ assert.hasAnyDeepKeys = function (obj, keys, msg) { - new Assertion(obj, msg).to.have.any.deep.keys(keys); + new Assertion(obj, msg, assert.hasAnyDeepKeys, true) + .to.have.any.deep.keys(keys); } /** @@ -1634,7 +1656,8 @@ module.exports = function (chai, util) { */ assert.hasAllDeepKeys = function (obj, keys, msg) { - new Assertion(obj, msg).to.have.all.deep.keys(keys); + new Assertion(obj, msg, assert.hasAllDeepKeys, true) + .to.have.all.deep.keys(keys); } /** @@ -1660,7 +1683,8 @@ module.exports = function (chai, util) { */ assert.containsAllDeepKeys = function (obj, keys, msg) { - new Assertion(obj, msg).to.contain.all.deep.keys(keys); + new Assertion(obj, msg, assert.containsAllDeepKeys, true) + .to.contain.all.deep.keys(keys); } /** @@ -1686,7 +1710,8 @@ module.exports = function (chai, util) { */ assert.doesNotHaveAnyDeepKeys = function (obj, keys, msg) { - new Assertion(obj, msg).to.not.have.any.deep.keys(keys); + new Assertion(obj, msg, assert.doesNotHaveAnyDeepKeys, true) + .to.not.have.any.deep.keys(keys); } /** @@ -1712,7 +1737,8 @@ module.exports = function (chai, util) { */ assert.doesNotHaveAllDeepKeys = function (obj, keys, msg) { - new Assertion(obj, msg).to.not.have.all.deep.keys(keys); + new Assertion(obj, msg, assert.doesNotHaveAllDeepKeys, true) + .to.not.have.all.deep.keys(keys); } /** @@ -1752,7 +1778,8 @@ module.exports = function (chai, util) { errorLike = null; } - var assertErr = new Assertion(fn, msg).to.throw(errorLike, errMsgMatcher); + var assertErr = new Assertion(fn, msg, assert.throws, true) + .to.throw(errorLike, errMsgMatcher); return flag(assertErr, 'object'); }; @@ -1791,7 +1818,8 @@ module.exports = function (chai, util) { errorLike = null; } - new Assertion(fn, msg).to.not.throw(errorLike, errMsgMatcher); + new Assertion(fn, msg, assert.doesNotThrow, true) + .to.not.throw(errorLike, errMsgMatcher); }; /** @@ -1839,9 +1867,13 @@ module.exports = function (chai, util) { ok = val !== val2; break; default: - throw new Error('Invalid operator "' + operator + '"'); + throw new chai.AssertionError( + 'Invalid operator "' + operator + '"', + undefined, + assert.operator + ); } - var test = new Assertion(ok, msg); + var test = new Assertion(ok, msg, assert.operator, true); test.assert( true === flag(test, 'object') , 'expected ' + util.inspect(val) + ' to be ' + operator + ' ' + util.inspect(val2) @@ -1865,7 +1897,7 @@ module.exports = function (chai, util) { */ assert.closeTo = function (act, exp, delta, msg) { - new Assertion(act, msg).to.be.closeTo(exp, delta); + new Assertion(act, msg, assert.closeTo, true).to.be.closeTo(exp, delta); }; /** @@ -1885,7 +1917,8 @@ module.exports = function (chai, util) { */ assert.approximately = function (act, exp, delta, msg) { - new Assertion(act, msg).to.be.approximately(exp, delta); + new Assertion(act, msg, assert.approximately, true) + .to.be.approximately(exp, delta); }; /** @@ -1905,7 +1938,8 @@ module.exports = function (chai, util) { */ assert.sameMembers = function (set1, set2, msg) { - new Assertion(set1, msg).to.have.same.members(set2); + new Assertion(set1, msg, assert.sameMembers, true) + .to.have.same.members(set2); } /** @@ -1925,7 +1959,8 @@ module.exports = function (chai, util) { */ assert.notSameMembers = function (set1, set2, msg) { - new Assertion(set1, msg).to.not.have.same.members(set2); + new Assertion(set1, msg, assert.notSameMembers, true) + .to.not.have.same.members(set2); } /** @@ -1945,7 +1980,8 @@ module.exports = function (chai, util) { */ assert.sameDeepMembers = function (set1, set2, msg) { - new Assertion(set1, msg).to.have.same.deep.members(set2); + new Assertion(set1, msg, assert.sameDeepMembers, true) + .to.have.same.deep.members(set2); } /** @@ -1965,7 +2001,8 @@ module.exports = function (chai, util) { */ assert.notSameDeepMembers = function (set1, set2, msg) { - new Assertion(set1, msg).to.not.have.same.deep.members(set2); + new Assertion(set1, msg, assert.notSameDeepMembers, true) + .to.not.have.same.deep.members(set2); } /** @@ -1985,7 +2022,8 @@ module.exports = function (chai, util) { */ assert.sameOrderedMembers = function (set1, set2, msg) { - new Assertion(set1, msg).to.have.same.ordered.members(set2); + new Assertion(set1, msg, assert.sameOrderedMembers, true) + .to.have.same.ordered.members(set2); } /** @@ -2005,7 +2043,8 @@ module.exports = function (chai, util) { */ assert.notSameOrderedMembers = function (set1, set2, msg) { - new Assertion(set1, msg).to.not.have.same.ordered.members(set2); + new Assertion(set1, msg, assert.notSameOrderedMembers, true) + .to.not.have.same.ordered.members(set2); } /** @@ -2025,7 +2064,8 @@ module.exports = function (chai, util) { */ assert.sameDeepOrderedMembers = function (set1, set2, msg) { - new Assertion(set1, msg).to.have.same.deep.ordered.members(set2); + new Assertion(set1, msg, assert.sameDeepOrderedMembers, true) + .to.have.same.deep.ordered.members(set2); } /** @@ -2046,7 +2086,8 @@ module.exports = function (chai, util) { */ assert.notSameDeepOrderedMembers = function (set1, set2, msg) { - new Assertion(set1, msg).to.not.have.same.deep.ordered.members(set2); + new Assertion(set1, msg, assert.notSameDeepOrderedMembers, true) + .to.not.have.same.deep.ordered.members(set2); } /** @@ -2066,7 +2107,8 @@ module.exports = function (chai, util) { */ assert.includeMembers = function (superset, subset, msg) { - new Assertion(superset, msg).to.include.members(subset); + new Assertion(superset, msg, assert.includeMembers, true) + .to.include.members(subset); } /** @@ -2086,7 +2128,8 @@ module.exports = function (chai, util) { */ assert.notIncludeMembers = function (superset, subset, msg) { - new Assertion(superset, msg).to.not.include.members(subset); + new Assertion(superset, msg, assert.notIncludeMembers, true) + .to.not.include.members(subset); } /** @@ -2106,7 +2149,8 @@ module.exports = function (chai, util) { */ assert.includeDeepMembers = function (superset, subset, msg) { - new Assertion(superset, msg).to.include.deep.members(subset); + new Assertion(superset, msg, assert.includeDeepMembers, true) + .to.include.deep.members(subset); } /** @@ -2126,7 +2170,8 @@ module.exports = function (chai, util) { */ assert.notIncludeDeepMembers = function (superset, subset, msg) { - new Assertion(superset, msg).to.not.include.deep.members(subset); + new Assertion(superset, msg, assert.notIncludeDeepMembers, true) + .to.not.include.deep.members(subset); } /** @@ -2147,7 +2192,8 @@ module.exports = function (chai, util) { */ assert.includeOrderedMembers = function (superset, subset, msg) { - new Assertion(superset, msg).to.include.ordered.members(subset); + new Assertion(superset, msg, assert.includeOrderedMembers, true) + .to.include.ordered.members(subset); } /** @@ -2169,7 +2215,8 @@ module.exports = function (chai, util) { */ assert.notIncludeOrderedMembers = function (superset, subset, msg) { - new Assertion(superset, msg).to.not.include.ordered.members(subset); + new Assertion(superset, msg, assert.notIncludeOrderedMembers, true) + .to.not.include.ordered.members(subset); } /** @@ -2190,7 +2237,8 @@ module.exports = function (chai, util) { */ assert.includeDeepOrderedMembers = function (superset, subset, msg) { - new Assertion(superset, msg).to.include.deep.ordered.members(subset); + new Assertion(superset, msg, assert.includeDeepOrderedMembers, true) + .to.include.deep.ordered.members(subset); } /** @@ -2213,7 +2261,8 @@ module.exports = function (chai, util) { */ assert.notIncludeDeepOrderedMembers = function (superset, subset, msg) { - new Assertion(superset, msg).to.not.include.deep.ordered.members(subset); + new Assertion(superset, msg, assert.notIncludeDeepOrderedMembers, true) + .to.not.include.deep.ordered.members(subset); } /** @@ -2232,7 +2281,7 @@ module.exports = function (chai, util) { */ assert.oneOf = function (inList, list, msg) { - new Assertion(inList, msg).to.be.oneOf(list); + new Assertion(inList, msg, assert.oneOf, true).to.be.oneOf(list); } /** @@ -2259,7 +2308,7 @@ module.exports = function (chai, util) { prop = null; } - new Assertion(fn, msg).to.change(obj, prop); + new Assertion(fn, msg, assert.changes, true).to.change(obj, prop); } /** @@ -2291,7 +2340,8 @@ module.exports = function (chai, util) { prop = null; } - new Assertion(fn, msg).to.change(obj, prop).by(delta); + new Assertion(fn, msg, assert.changesBy, true) + .to.change(obj, prop).by(delta); } /** @@ -2318,7 +2368,8 @@ module.exports = function (chai, util) { prop = null; } - return new Assertion(fn, msg).to.not.change(obj, prop); + return new Assertion(fn, msg, assert.doesNotChange, true) + .to.not.change(obj, prop); } /** @@ -2350,7 +2401,8 @@ module.exports = function (chai, util) { prop = null; } - new Assertion(fn, msg).to.change(obj, prop).but.not.by(delta); + new Assertion(fn, msg, assert.changesButNotBy, true) + .to.change(obj, prop).but.not.by(delta); } /** @@ -2377,7 +2429,8 @@ module.exports = function (chai, util) { prop = null; } - return new Assertion(fn, msg).to.increase(obj, prop); + return new Assertion(fn, msg, assert.increases, true) + .to.increase(obj, prop); } /** @@ -2409,7 +2462,8 @@ module.exports = function (chai, util) { prop = null; } - new Assertion(fn, msg).to.increase(obj, prop).by(delta); + new Assertion(fn, msg, assert.increasesBy, true) + .to.increase(obj, prop).by(delta); } /** @@ -2436,7 +2490,8 @@ module.exports = function (chai, util) { prop = null; } - return new Assertion(fn, msg).to.not.increase(obj, prop); + return new Assertion(fn, msg, assert.doesNotIncrease, true) + .to.not.increase(obj, prop); } /** @@ -2468,7 +2523,8 @@ module.exports = function (chai, util) { prop = null; } - new Assertion(fn, msg).to.increase(obj, prop).but.not.by(delta); + new Assertion(fn, msg, assert.increasesButNotBy, true) + .to.increase(obj, prop).but.not.by(delta); } /** @@ -2495,7 +2551,8 @@ module.exports = function (chai, util) { prop = null; } - return new Assertion(fn, msg).to.decrease(obj, prop); + return new Assertion(fn, msg, assert.decreases, true) + .to.decrease(obj, prop); } /** @@ -2527,7 +2584,8 @@ module.exports = function (chai, util) { prop = null; } - new Assertion(fn, msg).to.decrease(obj, prop).by(delta); + new Assertion(fn, msg, assert.decreasesBy, true) + .to.decrease(obj, prop).by(delta); } /** @@ -2554,7 +2612,8 @@ module.exports = function (chai, util) { prop = null; } - return new Assertion(fn, msg).to.not.decrease(obj, prop); + return new Assertion(fn, msg, assert.doesNotDecrease, true) + .to.not.decrease(obj, prop); } /** @@ -2586,7 +2645,8 @@ module.exports = function (chai, util) { prop = null; } - return new Assertion(fn, msg).to.not.decrease(obj, prop).by(delta); + return new Assertion(fn, msg, assert.doesNotDecreaseBy, true) + .to.not.decrease(obj, prop).by(delta); } /** @@ -2618,7 +2678,8 @@ module.exports = function (chai, util) { prop = null; } - new Assertion(fn, msg).to.decrease(obj, prop).but.not.by(delta); + new Assertion(fn, msg, assert.decreasesButNotBy, true) + .to.decrease(obj, prop).but.not.by(delta); } /*! @@ -2659,7 +2720,7 @@ module.exports = function (chai, util) { */ assert.isExtensible = function (obj, msg) { - new Assertion(obj, msg).to.be.extensible; + new Assertion(obj, msg, assert.isExtensible, true).to.be.extensible; }; /** @@ -2684,7 +2745,7 @@ module.exports = function (chai, util) { */ assert.isNotExtensible = function (obj, msg) { - new Assertion(obj, msg).to.not.be.extensible; + new Assertion(obj, msg, assert.isNotExtensible, true).to.not.be.extensible; }; /** @@ -2708,7 +2769,7 @@ module.exports = function (chai, util) { */ assert.isSealed = function (obj, msg) { - new Assertion(obj, msg).to.be.sealed; + new Assertion(obj, msg, assert.isSealed, true).to.be.sealed; }; /** @@ -2727,7 +2788,7 @@ module.exports = function (chai, util) { */ assert.isNotSealed = function (obj, msg) { - new Assertion(obj, msg).to.not.be.sealed; + new Assertion(obj, msg, assert.isNotSealed, true).to.not.be.sealed; }; /** @@ -2748,7 +2809,7 @@ module.exports = function (chai, util) { */ assert.isFrozen = function (obj, msg) { - new Assertion(obj, msg).to.be.frozen; + new Assertion(obj, msg, assert.isFrozen, true).to.be.frozen; }; /** @@ -2767,7 +2828,7 @@ module.exports = function (chai, util) { */ assert.isNotFrozen = function (obj, msg) { - new Assertion(obj, msg).to.not.be.frozen; + new Assertion(obj, msg, assert.isNotFrozen, true).to.not.be.frozen; }; /** @@ -2793,7 +2854,7 @@ module.exports = function (chai, util) { */ assert.isEmpty = function(val, msg) { - new Assertion(val, msg).to.be.empty; + new Assertion(val, msg, assert.isEmpty, true).to.be.empty; }; /** @@ -2819,7 +2880,7 @@ module.exports = function (chai, util) { */ assert.isNotEmpty = function(val, msg) { - new Assertion(val, msg).to.not.be.empty; + new Assertion(val, msg, assert.isNotEmpty, true).to.not.be.empty; }; /*! diff --git a/lib/chai/utils/addChainableMethod.js b/lib/chai/utils/addChainableMethod.js index 7cb4e495b..3e1edcbdc 100644 --- a/lib/chai/utils/addChainableMethod.js +++ b/lib/chai/utils/addChainableMethod.js @@ -82,13 +82,24 @@ module.exports = function addChainableMethod(ctx, name, method, chainingBehavior chainableBehavior.chainingBehavior.call(this); var chainableMethodWrapper = function () { - // Use this chainable method wrapper as the starting point for - // removing implementation frames from the stack trace of a failed - // assertion. Note that this is the correct starting point even if - // this assertion has been overwritten since overwriting a chainable - // method merely replaces the saved methods in `ctx.__methods` instead - // of completely replacing the overwritten assertion. - flag(this, 'ssfi', chainableMethodWrapper); + // Setting the `ssfi` flag to `chainableMethodWrapper` causes this + // function to be the starting point for removing implementation + // frames from the stack trace of a failed assertion. + // + // However, we only want to use this function as the starting point if + // the `lockSsfi` flag isn't set. + // + // If the `lockSsfi` flag is set, then this assertion is being + // invoked from inside of another assertion. In this case, the `ssfi` + // flag has already been set by the outer assertion. + // + // Note that overwriting a chainable method merely replaces the saved + // methods in `ctx.__methods` instead of completely replacing the + // overwritten assertion. Therefore, an overwriting assertion won't + // set the `ssfi` or `lockSsfi` flags. + if (!flag(this, 'lockSsfi')) { + flag(this, 'ssfi', chainableMethodWrapper); + } var result = chainableBehavior.method.apply(this, arguments); if (result !== undefined) { diff --git a/lib/chai/utils/addMethod.js b/lib/chai/utils/addMethod.js index 56bd5b818..021f08042 100644 --- a/lib/chai/utils/addMethod.js +++ b/lib/chai/utils/addMethod.js @@ -38,15 +38,19 @@ var transferFlags = require('./transferFlags'); module.exports = function addMethod(ctx, name, method) { var methodWrapper = function () { - // If this assertion hasn't been overwritten, then use this method wrapper - // as the starting point for removing implementation frames from the stack - // trace of a failed assertion. + // Setting the `ssfi` flag to `methodWrapper` causes this function to be the + // starting point for removing implementation frames from the stack trace of + // a failed assertion. // - // Note: If this assertion has been overwritten, and thus the `keep_ssfi` - // flag is set, then the overwriting method wrapper is used as the starting - // point instead. This prevents the overwriting method wrapper from showing - // up in the stack trace since it's invoked before this method wrapper. - if (!flag(this, 'keep_ssfi')) { + // However, we only want to use this function as the starting point if the + // `lockSsfi` flag isn't set. + // + // If the `lockSsfi` flag is set, then either this assertion has been + // overwritten by another assertion, or this assertion is being invoked from + // inside of another assertion. In the first case, the `ssfi` flag has + // already been set by the overwriting assertion. In the second case, the + // `ssfi` flag has already been set by the outer assertion. + if (!flag(this, 'lockSsfi')) { flag(this, 'ssfi', methodWrapper); } diff --git a/lib/chai/utils/addProperty.js b/lib/chai/utils/addProperty.js index d3757e6c4..7cbc8569f 100644 --- a/lib/chai/utils/addProperty.js +++ b/lib/chai/utils/addProperty.js @@ -40,24 +40,22 @@ module.exports = function addProperty(ctx, name, getter) { Object.defineProperty(ctx, name, { get: function propertyGetter() { - // If proxy protection is disabled and this assertion hasn't been - // overwritten, then use this property getter as the starting point for - // removing implementation frames from the stack trace of a failed - // assertion. + // Setting the `ssfi` flag to `propertyGetter` causes this function to + // be the starting point for removing implementation frames from the + // stack trace of a failed assertion. // - // Notes: + // However, we only want to use this function as the starting point if + // the `lockSsfi` flag isn't set and proxy protection is disabled. // - // - If proxy protection is enabled, then the proxy getter is used as - // the starting point instead. This prevents the proxy getter from - // showing up in the stack trace since it's invoked before this - // property getter. + // If the `lockSsfi` flag is set, then either this assertion has been + // overwritten by another assertion, or this assertion is being invoked + // from inside of another assertion. In the first case, the `ssfi` flag + // has already been set by the overwriting assertion. In the second + // case, the `ssfi` flag has already been set by the outer assertion. // - // - If proxy protection is disabled but this assertion has been - // overwritten, and thus the `keep_ssfi` flag is set, then the - // overwriting property getter is used as the starting point instead. - // This prevents the overwriting property getter from showing up in - // the stack trace since it's invoked before this property getter. - if (!isProxyEnabled() && !flag(this, 'keep_ssfi')) { + // If proxy protection is enabled, then the `ssfi` flag has already been + // set by the proxy getter. + if (!isProxyEnabled() && !flag(this, 'lockSsfi')) { flag(this, 'ssfi', propertyGetter); } diff --git a/lib/chai/utils/expectTypes.js b/lib/chai/utils/expectTypes.js index 48d842f7a..2ffcadd41 100644 --- a/lib/chai/utils/expectTypes.js +++ b/lib/chai/utils/expectTypes.js @@ -13,6 +13,8 @@ * * @param {Mixed} obj constructed Assertion * @param {Array} type A list of allowed types for this assertion + * @param {Function} ssfi starting point for removing implementation frames from + * stack trace of AssertionError * @namespace Utils * @name expectTypes * @api public @@ -22,7 +24,7 @@ var AssertionError = require('assertion-error'); var flag = require('./flag'); var type = require('type-detect'); -module.exports = function expectTypes(obj, types) { +module.exports = function expectTypes(obj, types, ssfi) { obj = flag(obj, 'object'); types = types.map(function (t) { return t.toLowerCase(); }); types.sort(); @@ -38,7 +40,9 @@ module.exports = function expectTypes(obj, types) { if (!types.some(function (expected) { return objType === expected; })) { throw new AssertionError( - 'object tested must be ' + str + ', but ' + objType + ' given' + 'object tested must be ' + str + ', but ' + objType + ' given', + undefined, + ssfi ); } }; diff --git a/lib/chai/utils/overwriteMethod.js b/lib/chai/utils/overwriteMethod.js index 004b85cdd..f77a4f493 100644 --- a/lib/chai/utils/overwriteMethod.js +++ b/lib/chai/utils/overwriteMethod.js @@ -54,26 +54,29 @@ module.exports = function overwriteMethod(ctx, name, method) { _super = _method; var overwritingMethodWrapper = function () { - // If proxy protection is disabled and this overwriting assertion hasn't - // been overwritten again by yet another assertion, then use this method - // wrapper as the starting point for removing implementation frames from the - // stack trace of a failed assertion. + // Setting the `ssfi` flag to `overwritingMethodWrapper` causes this + // function to be the starting point for removing implementation frames from + // the stack trace of a failed assertion. // - // Note: If this assertion has been overwritten, and thus the `keep_ssfi` - // flag is set, then the overwriting method wrapper is used as the starting - // point instead. This prevents the overwriting method wrapper from showing - // up in the stack trace since it's invoked before this method wrapper. - if (!flag(this, 'keep_ssfi')) { + // However, we only want to use this function as the starting point if the + // `lockSsfi` flag isn't set. + // + // If the `lockSsfi` flag is set, then either this assertion has been + // overwritten by another assertion, or this assertion is being invoked from + // inside of another assertion. In the first case, the `ssfi` flag has + // already been set by the overwriting assertion. In the second case, the + // `ssfi` flag has already been set by the outer assertion. + if (!flag(this, 'lockSsfi')) { flag(this, 'ssfi', overwritingMethodWrapper); } - // The `keep_ssfi` flag is set so that if this assertion ends up calling - // the overwritten assertion, then the overwritten assertion doesn't attempt - // to use itself as the starting point for removing implementation frames - // from the stack trace of a failed assertion. - flag(this, 'keep_ssfi', true); + // Setting the `lockSsfi` flag to `true` prevents the overwritten assertion + // from changing the `ssfi` flag. By this point, the `ssfi` flag is already + // set to the correct starting point for this assertion. + var origLockSsfi = flag(this, 'lockSsfi'); + flag(this, 'lockSsfi', true); var result = method(_super).apply(this, arguments); - flag(this, 'keep_ssfi', false); + flag(this, 'lockSsfi', origLockSsfi); if (result !== undefined) { return result; diff --git a/lib/chai/utils/overwriteProperty.js b/lib/chai/utils/overwriteProperty.js index 73e62180a..82ed3c876 100644 --- a/lib/chai/utils/overwriteProperty.js +++ b/lib/chai/utils/overwriteProperty.js @@ -52,34 +52,32 @@ module.exports = function overwriteProperty(ctx, name, getter) { Object.defineProperty(ctx, name, { get: function overwritingPropertyGetter() { - // If proxy protection is disabled and this overwriting assertion hasn't - // been overwritten again by yet another assertion, then use this - // property getter as the starting point for removing implementation - // frames from the stack trace of a failed assertion. + // Setting the `ssfi` flag to `overwritingPropertyGetter` causes this + // function to be the starting point for removing implementation frames + // from the stack trace of a failed assertion. // - // Notes: + // However, we only want to use this function as the starting point if + // the `lockSsfi` flag isn't set and proxy protection is disabled. // - // - If proxy protection is enabled, then the proxy getter is used as - // the starting point instead. This prevents the proxy getter from - // showing up in the stack trace since it's invoked before this - // property getter. + // If the `lockSsfi` flag is set, then either this assertion has been + // overwritten by another assertion, or this assertion is being invoked + // from inside of another assertion. In the first case, the `ssfi` flag + // has already been set by the overwriting assertion. In the second + // case, the `ssfi` flag has already been set by the outer assertion. // - // - If proxy protection is disabled but this assertion has been - // overwritten, and thus the `keep_ssfi` flag is set, then the - // overwriting property getter is used as the starting point instead. - // This prevents the overwriting property getter from showing up in - // the stack trace since it's invoked before this property getter. - if (!isProxyEnabled() && !flag(this, 'keep_ssfi')) { + // If proxy protection is enabled, then the `ssfi` flag has already been + // set by the proxy getter. + if (!isProxyEnabled() && !flag(this, 'lockSsfi')) { flag(this, 'ssfi', overwritingPropertyGetter); } - // The `keep_ssfi` flag is set so that if this assertion ends up calling - // the overwritten assertion, then the overwritten assertion doesn't - // attempt to use itself as the starting point for removing - // implementation frames from the stack trace of a failed assertion. - flag(this, 'keep_ssfi', true); + // Setting the `lockSsfi` flag to `true` prevents the overwritten + // assertion from changing the `ssfi` flag. By this point, the `ssfi` + // flag is already set to the correct starting point for this assertion. + var origLockSsfi = flag(this, 'lockSsfi'); + flag(this, 'lockSsfi', true); var result = getter(_super).call(this); - flag(this, 'keep_ssfi', false); + flag(this, 'lockSsfi', origLockSsfi); if (result !== undefined) { return result; diff --git a/lib/chai/utils/proxify.js b/lib/chai/utils/proxify.js index 9ea8b7f49..2b3b0203d 100644 --- a/lib/chai/utils/proxify.js +++ b/lib/chai/utils/proxify.js @@ -75,8 +75,11 @@ module.exports = function proxify(obj, nonChainableMethodName) { // the method wrapper, which is good since this frame will no longer be in // the stack once the method is invoked. Note that Chai builtin assertion // properties such as `__flags` are skipped since this is only meant to - // capture the starting point of an assertion. - if (builtins.indexOf(property) === -1) { + // capture the starting point of an assertion. This step is also skipped + // if the `lockSsfi` flag is set, thus indicating that this assertion is + // being called from within another assertion. In that case, the `ssfi` + // flag is already set to the outer assertion's starting point. + if (builtins.indexOf(property) === -1 && !flag(target, 'lockSsfi')) { flag(target, 'ssfi', proxyGetter); } diff --git a/lib/chai/utils/transferFlags.js b/lib/chai/utils/transferFlags.js index 16684047d..634e0537a 100644 --- a/lib/chai/utils/transferFlags.js +++ b/lib/chai/utils/transferFlags.js @@ -9,8 +9,8 @@ * * Transfer all the flags for `assertion` to `object`. If * `includeAll` is set to `false`, then the base Chai - * assertion flags (namely `object`, `ssfi`, and `message`) - * will not be transferred. + * assertion flags (namely `object`, `ssfi`, `lockSsfi`, + * and `message`) will not be transferred. * * * var newAssertion = new Assertion(); @@ -38,7 +38,7 @@ module.exports = function transferFlags(assertion, object, includeAll) { for (var flag in flags) { if (includeAll || - (flag !== 'object' && flag !== 'ssfi' && flag != 'message')) { + (flag !== 'object' && flag !== 'ssfi' && flag !== 'lockSsfi' && flag != 'message')) { object.__flags[flag] = flags[flag]; } } diff --git a/test/assert.js b/test/assert.js index 5ca21c383..a3f14a9f7 100644 --- a/test/assert.js +++ b/test/assert.js @@ -177,7 +177,7 @@ describe('assert', function () { var t = new Thing(); Thing.prototype = 1337; assert.instanceOf(t, Thing); - }, expectedError); + }, expectedError, true); if (typeof Symbol !== 'undefined' && typeof Symbol.hasInstance !== 'undefined') { err(function(){ @@ -1981,7 +1981,7 @@ describe('assert', function () { err(function() { // isExtensible should not suppress errors, thrown in proxy traps assert[isExtensible](proxy); - }, { name: 'TypeError' }); + }, { name: 'TypeError' }, true); } }); }); @@ -2018,7 +2018,7 @@ describe('assert', function () { err(function() { // isNotExtensible should not suppress errors, thrown in proxy traps assert[isNotExtensible](proxy); - }, { name: 'TypeError' }); + }, { name: 'TypeError' }, true); } }); }); @@ -2058,7 +2058,7 @@ describe('assert', function () { err(function() { // isSealed should not suppress errors, thrown in proxy traps assert[isSealed](proxy); - }, { name: 'TypeError' }); + }, { name: 'TypeError' }, true); } }); }); @@ -2108,7 +2108,7 @@ describe('assert', function () { err(function() { // isNotSealed should not suppress errors, thrown in proxy traps assert[isNotSealed](proxy); - }, { name: 'TypeError' }); + }, { name: 'TypeError' }, true); } }); }); @@ -2148,7 +2148,7 @@ describe('assert', function () { err(function() { // isFrozen should not suppress errors, thrown in proxy traps assert[isFrozen](proxy); - }, { name: 'TypeError' }); + }, { name: 'TypeError' }, true); } }); }); @@ -2161,7 +2161,7 @@ describe('assert', function () { err(function() { assert[isNotFrozen](frozenObject); - }, 'expected {} to not be frozen'); + }, 'expected {} to not be frozen', true); // Making sure ES6-like Object.isFrozen response is respected for all primitive types @@ -2198,7 +2198,7 @@ describe('assert', function () { err(function() { // isNotFrozen should not suppress errors, thrown in proxy traps assert[isNotFrozen](proxy); - }, { name: 'TypeError' }); + }, { name: 'TypeError' }, true); } }); }); diff --git a/test/bootstrap/index.js b/test/bootstrap/index.js index aa0c7ccf6..6e4cf5a91 100644 --- a/test/bootstrap/index.js +++ b/test/bootstrap/index.js @@ -4,9 +4,23 @@ if (typeof window === 'object') { global.chai = require('../..'); } +var isStackSupported = false; +if (typeof Error.captureStackTrace !== 'undefined') { + try { + throw Error(); + } catch (err) { + if (typeof err.stack !== 'undefined') isStackSupported = true; + } +} + /** - * Validate that the given function throws an error. Optionally validate some - * additional properties of the error: + * Validate that the given function throws an error. + * + * By default, also validate that the thrown error's stack trace doesn't contain + * Chai implementation frames. Stack trace validation can be disabled by + * providing a truthy `skipStackTest` argument. + * + * Optionally validate some additional properties of the error: * * If val is a string, validate val equals the error's .message * If val is a regex, validate val matches the error's .message @@ -14,15 +28,25 @@ if (typeof window === 'object') { * * @param {Function} function that's expected to throw an error * @param {Mixed} expected properties of the expected error + * @param {Boolean} skipStackTest if truthy, don't validate stack trace */ -global.err = function (fn, val) { +global.err = function globalErr (fn, val, skipStackTest) { if (chai.util.type(fn) !== 'function') throw new chai.AssertionError('Invalid fn'); try { fn(); } catch (err) { + if (isStackSupported && !skipStackTest) { + chai.expect(err).to.have.property('stack') + .that.has.string('globalErr') + .but.does.not.match( + /at [a-zA-Z]*(Getter|Wrapper|(\.)*assert)/, + 'implementation frames not properly filtered from stack trace' + ); + } + switch (chai.util.type(val).toLowerCase()) { case 'undefined': return; case 'string': return chai.expect(err.message).to.equal(val); diff --git a/test/expect.js b/test/expect.js index a4ca2588c..f8a040375 100644 --- a/test/expect.js +++ b/test/expect.js @@ -58,73 +58,73 @@ describe('expect', function () { it('throws when invalid property follows expect', function () { err(function () { expect(42).pizza; - }, 'Invalid Chai property: pizza'); + }, 'Invalid Chai property: pizza', true); }); it('throws when invalid property follows language chain', function () { err(function () { expect(42).to.pizza; - }, 'Invalid Chai property: pizza'); + }, 'Invalid Chai property: pizza', true); }); it('throws when invalid property follows property assertion', function () { err(function () { expect(42).ok.pizza; - }, 'Invalid Chai property: pizza'); + }, 'Invalid Chai property: pizza', true); }); it('throws when invalid property follows overwritten property assertion', function () { err(function () { expect(42).tmpProperty.pizza; - }, 'Invalid Chai property: pizza'); + }, 'Invalid Chai property: pizza', true); }); it('throws when invalid property follows uncalled method assertion', function () { err(function () { expect(42).equal.pizza; - }, 'Invalid Chai property: equal.pizza. See docs for proper usage of "equal".'); + }, 'Invalid Chai property: equal.pizza. See docs for proper usage of "equal".', true); }); it('throws when invalid property follows called method assertion', function () { err(function () { expect(42).equal(42).pizza; - }, 'Invalid Chai property: pizza'); + }, 'Invalid Chai property: pizza', true); }); it('throws when invalid property follows uncalled overwritten method assertion', function () { err(function () { expect(42).tmpMethod.pizza; - }, 'Invalid Chai property: tmpMethod.pizza. See docs for proper usage of "tmpMethod".'); + }, 'Invalid Chai property: tmpMethod.pizza. See docs for proper usage of "tmpMethod".', true); }); it('throws when invalid property follows called overwritten method assertion', function () { err(function () { expect(42).tmpMethod().pizza; - }, 'Invalid Chai property: pizza'); + }, 'Invalid Chai property: pizza', true); }); it('throws when invalid property follows uncalled chainable method assertion', function () { err(function () { expect(42).a.pizza; - }, 'Invalid Chai property: pizza'); + }, 'Invalid Chai property: pizza', true); }); it('throws when invalid property follows called chainable method assertion', function () { err(function () { expect(42).a('number').pizza; - }, 'Invalid Chai property: pizza'); + }, 'Invalid Chai property: pizza', true); }); it('throws when invalid property follows uncalled overwritten chainable method assertion', function () { err(function () { expect(42).tmpChainableMethod.pizza; - }, 'Invalid Chai property: pizza'); + }, 'Invalid Chai property: pizza', true); }); it('throws when invalid property follows called overwritten chainable method assertion', function () { err(function () { expect(42).tmpChainableMethod().pizza; - }, 'Invalid Chai property: pizza'); + }, 'Invalid Chai property: pizza', true); }); it('doesn\'t throw if invalid property is excluded via config', function () { @@ -165,7 +165,7 @@ describe('expect', function () { it('throws when `.length` follows uncalled method assertion', function () { err(function () { expect('foo').equal.length; - }, 'Invalid Chai property: equal.length. See docs for proper usage of "equal".'); + }, 'Invalid Chai property: equal.length. See docs for proper usage of "equal".', true); }); it('doesn\'t throw when `.length` follows called method assertion', function () { @@ -177,7 +177,7 @@ describe('expect', function () { it('throws when `.length` follows uncalled overwritten method assertion', function () { err(function () { expect('foo').tmpMethod.length; - }, 'Invalid Chai property: tmpMethod.length. See docs for proper usage of "tmpMethod".'); + }, 'Invalid Chai property: tmpMethod.length. See docs for proper usage of "tmpMethod".', true); }); it('doesn\'t throw when `.length` follows called overwritten method assertion', function () { @@ -189,7 +189,7 @@ describe('expect', function () { it('throws when `.length` follows uncalled chainable method assertion', function () { err(function () { expect('foo').a.length; - }, 'Invalid Chai property: a.length. Due to a compatibility issue, "length" cannot directly follow "a". Use "a.lengthOf" instead.'); + }, 'Invalid Chai property: a.length. Due to a compatibility issue, "length" cannot directly follow "a". Use "a.lengthOf" instead.', true); }); it('doesn\'t throw when `.length` follows called chainable method assertion', function () { @@ -201,7 +201,7 @@ describe('expect', function () { it('throws when `.length` follows uncalled overwritten chainable method assertion', function () { err(function () { expect('foo').tmpChainableMethod.length; - }, 'Invalid Chai property: tmpChainableMethod.length. Due to a compatibility issue, "length" cannot directly follow "tmpChainableMethod". Use "tmpChainableMethod.lengthOf" instead.'); + }, 'Invalid Chai property: tmpChainableMethod.length. Due to a compatibility issue, "length" cannot directly follow "tmpChainableMethod". Use "tmpChainableMethod.lengthOf" instead.', true); }); it('doesn\'t throw when `.length` follows called overwritten chainable method assertion', function () { @@ -403,7 +403,7 @@ describe('expect', function () { var t = new Thing(); Thing.prototype = 1337; expect(t).to.an.instanceof(Thing); - }, expectedError) + }, expectedError, true) if (typeof Symbol !== 'undefined' && typeof Symbol.hasInstance !== 'undefined') { err(function(){ @@ -2701,7 +2701,7 @@ describe('expect', function () { err(function() { // .extensible should not suppress errors, thrown in proxy traps expect(proxy).to.be.extensible; - }, { name: 'TypeError' }); + }, { name: 'TypeError' }, true); } }); @@ -2764,7 +2764,7 @@ describe('expect', function () { err(function() { // .sealed should not suppress errors, thrown in proxy traps expect(proxy).to.be.sealed; - }, { name: 'TypeError' }); + }, { name: 'TypeError' }, true); } }); @@ -2827,7 +2827,7 @@ describe('expect', function () { err(function() { // .frozen should not suppress errors, thrown in proxy traps expect(proxy).to.be.frozen; - }, { name: 'TypeError' }); + }, { name: 'TypeError' }, true); } }); }); diff --git a/test/globalErr.js b/test/globalErr.js index 0858fa38a..83d66f4fc 100644 --- a/test/globalErr.js +++ b/test/globalErr.js @@ -56,7 +56,7 @@ describe('globalErr', function () { err(function () { err(function () { throw new Err('cat') }, {message: 'dog'}); - }, 'expected \'cat\' to deeply equal \'dog\''); + }, 'expected \'cat\' to deeply equal \'dog\'', true); }); it('should throw if fn does not throw', function () { @@ -108,4 +108,78 @@ describe('globalErr', function () { }, 'Invalid val') }); }); + + describe('skipStackTest', function () { + // Skip tests if `Error.captureStackTrace` is unsupported + if (typeof Error.captureStackTrace === 'undefined') return; + + try { + throw Error(); + } catch (err) { + // Skip tests if `err.stack` is unsupported + if (typeof err.stack === 'undefined') return; + } + + // Note: `.to.not.throw` isn't used for the assertions that aren't expected + // to throw an error because it'll pollute the very same stack trace which + // is being asserted on. Instead, if `err` throws an error, then Mocha will + // use that error as the reason the test failed. + describe('falsey', function () { + it('should throw if "Getter" is in the stack trace', function () { + err(function () { + err(function fakeGetter () { + throw Error('my stack trace contains a fake implementation frame'); + }); + }, /implementation frames not properly filtered from stack trace/, true); + }); + + it('should throw if "Wrapper" is in the stack trace', function () { + err(function () { + err(function fakeWrapper () { + throw Error('my stack trace contains a fake implementation frame'); + }); + }, /implementation frames not properly filtered from stack trace/, true); + }); + + it('should throw if "assert" is in the stack trace', function () { + err(function () { + err(function assertFake () { + throw Error('my stack trace contains a fake implementation frame'); + }); + }, /implementation frames not properly filtered from stack trace/, true); + }); + + it('shouldn\'t throw if "Getter", "Wrapper", "assert" aren\'t in the stack trace', function () { + err(function safeFnName () { + throw Error('my stack trace doesn\'t contain implementation frames'); + }); + }); + }); + + describe('truthy', function () { + it('shouldn\'t throw if "Getter" is in the stack trace', function () { + err(function fakeGetter () { + throw Error('my stack trace contains a fake implementation frame'); + }, undefined, true); + }); + + it('shouldn\'t throw if "Wrapper" is in the stack trace', function () { + err(function fakeWrapper () { + throw Error('my stack trace contains a fake implementation frame'); + }, undefined, true); + }); + + it('shouldn\'t throw if "assert" is in the stack trace', function () { + err(function assertFake () { + throw Error('my stack trace contains a fake implementation frame'); + }, undefined, true); + }); + + it('shouldn\'t throw if "Getter", "Wrapper", "assert" aren\'t in the stack trace', function () { + err(function safeFnName () { + throw Error('my stack trace doesn\'t contain implementation frames'); + }, undefined, true); + }); + }); + }); }); diff --git a/test/should.js b/test/should.js index 52d2c1cbb..1ac2e6d4c 100644 --- a/test/should.js +++ b/test/should.js @@ -55,73 +55,73 @@ describe('should', function() { it('throws when invalid property follows should', function () { err(function () { (42).should.pizza; - }, 'Invalid Chai property: pizza'); + }, 'Invalid Chai property: pizza', true); }); it('throws when invalid property follows language chain', function () { err(function () { (42).should.to.pizza; - }, 'Invalid Chai property: pizza'); + }, 'Invalid Chai property: pizza', true); }); it('throws when invalid property follows property assertion', function () { err(function () { (42).should.ok.pizza; - }, 'Invalid Chai property: pizza'); + }, 'Invalid Chai property: pizza', true); }); it('throws when invalid property follows overwritten property assertion', function () { err(function () { (42).should.tmpProperty.pizza; - }, 'Invalid Chai property: pizza'); + }, 'Invalid Chai property: pizza', true); }); it('throws when invalid property follows uncalled method assertion', function () { err(function () { (42).should.equal.pizza; - }, 'Invalid Chai property: equal.pizza. See docs for proper usage of "equal".'); + }, 'Invalid Chai property: equal.pizza. See docs for proper usage of "equal".', true); }); it('throws when invalid property follows called method assertion', function () { err(function () { (42).should.equal(42).pizza; - }, 'Invalid Chai property: pizza'); + }, 'Invalid Chai property: pizza', true); }); it('throws when invalid property follows uncalled overwritten method assertion', function () { err(function () { (42).should.tmpMethod.pizza; - }, 'Invalid Chai property: tmpMethod.pizza. See docs for proper usage of "tmpMethod".'); + }, 'Invalid Chai property: tmpMethod.pizza. See docs for proper usage of "tmpMethod".', true); }); it('throws when invalid property follows called overwritten method assertion', function () { err(function () { (42).should.tmpMethod().pizza; - }, 'Invalid Chai property: pizza'); + }, 'Invalid Chai property: pizza', true); }); it('throws when invalid property follows uncalled chainable method assertion', function () { err(function () { (42).should.a.pizza; - }, 'Invalid Chai property: pizza'); + }, 'Invalid Chai property: pizza', true); }); it('throws when invalid property follows called chainable method assertion', function () { err(function () { (42).should.a('number').pizza; - }, 'Invalid Chai property: pizza'); + }, 'Invalid Chai property: pizza', true); }); it('throws when invalid property follows uncalled overwritten chainable method assertion', function () { err(function () { (42).should.tmpChainableMethod.pizza; - }, 'Invalid Chai property: pizza'); + }, 'Invalid Chai property: pizza', true); }); it('throws when invalid property follows called overwritten chainable method assertion', function () { err(function () { (42).should.tmpChainableMethod().pizza; - }, 'Invalid Chai property: pizza'); + }, 'Invalid Chai property: pizza', true); }); it('doesn\'t throw if invalid property is excluded via config', function () { @@ -162,7 +162,7 @@ describe('should', function() { it('throws when `.length` follows uncalled method assertion', function () { err(function () { ('foo').should.equal.length; - }, 'Invalid Chai property: equal.length. See docs for proper usage of "equal".'); + }, 'Invalid Chai property: equal.length. See docs for proper usage of "equal".', true); }); it('doesn\'t throw when `.length` follows called method assertion', function () { @@ -174,7 +174,7 @@ describe('should', function() { it('throws when `.length` follows uncalled overwritten method assertion', function () { err(function () { ('foo').should.tmpMethod.length; - }, 'Invalid Chai property: tmpMethod.length. See docs for proper usage of "tmpMethod".'); + }, 'Invalid Chai property: tmpMethod.length. See docs for proper usage of "tmpMethod".', true); }); it('doesn\'t throw when `.length` follows called overwritten method assertion', function () { @@ -186,7 +186,7 @@ describe('should', function() { it('throws when `.length` follows uncalled chainable method assertion', function () { err(function () { ('foo').should.a.length; - }, 'Invalid Chai property: a.length. Due to a compatibility issue, "length" cannot directly follow "a". Use "a.lengthOf" instead.'); + }, 'Invalid Chai property: a.length. Due to a compatibility issue, "length" cannot directly follow "a". Use "a.lengthOf" instead.', true); }); it('doesn\'t throw when `.length` follows called chainable method assertion', function () { @@ -198,7 +198,7 @@ describe('should', function() { it('throws when `.length` follows uncalled overwritten chainable method assertion', function () { err(function () { ('foo').should.tmpChainableMethod.length; - }, 'Invalid Chai property: tmpChainableMethod.length. Due to a compatibility issue, "length" cannot directly follow "tmpChainableMethod". Use "tmpChainableMethod.lengthOf" instead.'); + }, 'Invalid Chai property: tmpChainableMethod.length. Due to a compatibility issue, "length" cannot directly follow "tmpChainableMethod". Use "tmpChainableMethod.lengthOf" instead.', true); }); it('doesn\'t throw when `.length` follows called overwritten chainable method assertion', function () { @@ -453,7 +453,7 @@ describe('should', function() { var t = new Thing(); Thing.prototype = 1337; t.should.be.an.instanceof(Thing); - }, expectedError); + }, expectedError, true); if (typeof Symbol !== 'undefined' && typeof Symbol.hasInstance !== 'undefined') { err(function(){ diff --git a/test/utilities.js b/test/utilities.js index c2399c8e7..58b43c48e 100644 --- a/test/utilities.js +++ b/test/utilities.js @@ -37,28 +37,54 @@ describe('utilities', function () { var foo = 'bar'; chai.use(function (_chai, utils) { - var obj = {}; + var target = {}; var test = function() {}; - var assertion = new chai.Assertion({}, "message", test); + var assertion = new chai.Assertion(target, "message", test, true); var flag = {}; - utils.flag(obj, 'flagMe', flag); - utils.flag(obj, 'negate', true); - utils.transferFlags(test, obj, false); + utils.flag(assertion, 'flagMe', flag); + utils.flag(assertion, 'negate', true); + var obj = {}; + utils.transferFlags(assertion, obj, false); expect(utils.flag(obj, 'object')).to.equal(undefined); expect(utils.flag(obj, 'message')).to.equal(undefined); expect(utils.flag(obj, 'ssfi')).to.equal(undefined); + expect(utils.flag(obj, 'lockSsfi')).to.equal(undefined); + expect(utils.flag(obj, 'negate')).to.equal(true); + expect(utils.flag(obj, 'flagMe')).to.equal(flag); + }); + }); + + it('transferFlags, includeAll = true', function () { + var foo = 'bar'; + + chai.use(function (_chai, utils) { + var target = {}; + var test = function() {}; + + var assertion = new chai.Assertion(target, "message", test, true); + var flag = {}; + utils.flag(assertion, 'flagMe', flag); + utils.flag(assertion, 'negate', true); + var obj = {}; + utils.transferFlags(assertion, obj, true); + + expect(utils.flag(obj, 'object')).to.equal(target); + expect(utils.flag(obj, 'message')).to.equal("message"); + expect(utils.flag(obj, 'ssfi')).to.equal(test); + expect(utils.flag(obj, 'lockSsfi')).to.equal(true); expect(utils.flag(obj, 'negate')).to.equal(true); expect(utils.flag(obj, 'flagMe')).to.equal(flag); }); }); describe('addMethod', function() { - var assertionConstructor; + var assertionConstructor, utils; before(function() { - chai.use(function(_chai, utils) { + chai.use(function(_chai, _utils) { + utils = _utils; assertionConstructor = _chai.Assertion; expect(_chai.Assertion).to.not.respondTo('eqqqual'); @@ -128,16 +154,39 @@ describe('utilities', function () { var anotherAssertion = expect([1, 2, 3]).to.have.a.lengthOf(3).and.to.be.ok; expect(anotherAssertion.length.constructor).to.equal(assertionConstructor); }); + + it('addMethod sets `ssfi` when `lockSsfi` isn\'t set', function () { + var origAssertion = expect(1); + var origSsfi = utils.flag(origAssertion, 'ssfi'); + + var newAssertion = origAssertion.eqqqual(1); + var newSsfi = utils.flag(newAssertion, 'ssfi'); + + expect(origSsfi).to.not.equal(newSsfi); + }); + + it('addMethod doesn\'t set `ssfi` when `lockSsfi` is set', function () { + var origAssertion = expect(1); + var origSsfi = utils.flag(origAssertion, 'ssfi'); + + utils.flag(origAssertion, 'lockSsfi', true); + + var newAssertion = origAssertion.eqqqual(1); + var newSsfi = utils.flag(newAssertion, 'ssfi'); + + expect(origSsfi).to.equal(newSsfi); + }); }); describe('overwriteMethod', function () { - var assertionConstructor; + var assertionConstructor, utils; before(function() { chai.config.includeStack = false; - chai.use(function(_chai, utils) { + chai.use(function(_chai, _utils) { assertionConstructor = _chai.Assertion; + utils = _utils; _chai.Assertion.addMethod('four', function() { this.assert(this._obj === 4, 'expected #{this} to be 4', 'expected #{this} to not be 4', 4); @@ -290,13 +339,37 @@ describe('utilities', function () { // Ensure that foo returns an Assertion (not a function) expect(expect('four').four()).to.be.an.instanceOf(assertionConstructor); }); + + it('overwriteMethod sets `ssfi` when `lockSsfi` isn\'t set', function () { + var origAssertion = expect(4); + var origSsfi = utils.flag(origAssertion, 'ssfi'); + + var newAssertion = origAssertion.four(); + var newSsfi = utils.flag(newAssertion, 'ssfi'); + + expect(origSsfi).to.not.equal(newSsfi); + }); + + it('overwriteMethod doesn\'t set `ssfi` when `lockSsfi` is set', function () { + var origAssertion = expect(4); + var origSsfi = utils.flag(origAssertion, 'ssfi'); + + utils.flag(origAssertion, 'lockSsfi', true); + + var newAssertion = origAssertion.four(); + var newSsfi = utils.flag(newAssertion, 'ssfi'); + + expect(origSsfi).to.equal(newSsfi); + }); }); describe('addProperty', function() { var assertionConstructor = chai.Assertion; + var utils; before(function() { - chai.use(function (_chai, utils) { + chai.use(function (_chai, _utils) { + utils = _utils; assertionConstructor = _chai.Assertion; _chai.Assertion.addProperty('tea', function () { @@ -362,16 +435,39 @@ describe('utilities', function () { expect(expect([1, 2, 3]).be).to.be.an.instanceOf(assertionConstructor); expect(expect([1, 2, 3]).thing).to.be.an.instanceOf(assertionConstructor); }); + + it('addProperty sets `ssfi` when `lockSsfi` isn\'t set', function () { + var origAssertion = expect(1); + var origSsfi = utils.flag(origAssertion, 'ssfi'); + + var newAssertion = origAssertion.to.be.tea; + var newSsfi = utils.flag(newAssertion, 'ssfi'); + + expect(origSsfi).to.not.equal(newSsfi); + }); + + it('addProperty doesn\'t set `ssfi` when `lockSsfi` is set', function () { + var origAssertion = expect(1); + var origSsfi = utils.flag(origAssertion, 'ssfi'); + + utils.flag(origAssertion, 'lockSsfi', true); + + var newAssertion = origAssertion.to.be.tea; + var newSsfi = utils.flag(newAssertion, 'ssfi'); + + expect(origSsfi).to.equal(newSsfi); + }); }); describe('overwriteProperty', function () { - var assertionConstructor; + var assertionConstructor, utils; before(function() { chai.config.includeStack = false; - chai.use(function(_chai, utils) { + chai.use(function(_chai, _utils) { assertionConstructor = _chai.Assertion; + utils = _utils; _chai.Assertion.addProperty('tea', function () { utils.flag(this, 'tea', 'chai'); @@ -499,6 +595,28 @@ describe('utilities', function () { expect(expect([1, 2, 3]).be).to.be.an.instanceOf(assertionConstructor); expect(expect([1, 2, 3]).foo).to.be.an.instanceOf(assertionConstructor); }); + + it('overwriteProperty sets `ssfi` when `lockSsfi` isn\'t set', function () { + var origAssertion = expect(4); + var origSsfi = utils.flag(origAssertion, 'ssfi'); + + var newAssertion = origAssertion.to.be.four; + var newSsfi = utils.flag(newAssertion, 'ssfi'); + + expect(origSsfi).to.not.equal(newSsfi); + }); + + it('overwriteProperty doesn\'t set `ssfi` when `lockSsfi` is set', function () { + var origAssertion = expect(4); + var origSsfi = utils.flag(origAssertion, 'ssfi'); + + utils.flag(origAssertion, 'lockSsfi', true); + + var newAssertion = origAssertion.to.be.four; + var newSsfi = utils.flag(newAssertion, 'ssfi'); + + expect(origSsfi).to.equal(newSsfi); + }); }); it('getMessage', function () { @@ -699,11 +817,12 @@ describe('utilities', function () { }); describe('addChainableMethod', function() { - var assertionConstructor; + var assertionConstructor, utils; before(function() { - chai.use(function (_chai, utils) { + chai.use(function (_chai, _utils) { assertionConstructor = _chai.Assertion; + utils = _utils; _chai.Assertion.addChainableMethod('x', function () { @@ -782,6 +901,28 @@ describe('utilities', function () { // Ensure that foo returns an Assertion (not a function) expect(expect('bar').foo('bar')).to.be.an.instanceOf(assertionConstructor); }); + + it('addChainableMethod sets `ssfi` when `lockSsfi` isn\'t set', function () { + var origAssertion = expect('x'); + var origSsfi = utils.flag(origAssertion, 'ssfi'); + + var newAssertion = origAssertion.to.be.x(); + var newSsfi = utils.flag(newAssertion, 'ssfi'); + + expect(origSsfi).to.not.equal(newSsfi); + }); + + it('addChainableMethod doesn\'t set `ssfi` when `lockSsfi` is set', function () { + var origAssertion = expect('x'); + var origSsfi = utils.flag(origAssertion, 'ssfi'); + + utils.flag(origAssertion, 'lockSsfi', true); + + var newAssertion = origAssertion.to.be.x(); + var newSsfi = utils.flag(newAssertion, 'ssfi'); + + expect(origSsfi).to.equal(newSsfi); + }); }); describe('overwriteChainableMethod', function() { @@ -887,6 +1028,28 @@ describe('utilities', function () { expect(expect('x').x).to.be.an.instanceOf(assertionConstructor); } }); + + it('overwriteChainableMethod sets `ssfi` when `lockSsfi` isn\'t set', function () { + var origAssertion = expect('x'); + var origSsfi = utils.flag(origAssertion, 'ssfi'); + + var newAssertion = origAssertion.to.be.x(); + var newSsfi = utils.flag(newAssertion, 'ssfi'); + + expect(origSsfi).to.not.equal(newSsfi); + }); + + it('overwriteChainableMethod doesn\'t set `ssfi` when `lockSsfi` is set', function () { + var origAssertion = expect('x'); + var origSsfi = utils.flag(origAssertion, 'ssfi'); + + utils.flag(origAssertion, 'lockSsfi', true); + + var newAssertion = origAssertion.to.be.x(); + var newSsfi = utils.flag(newAssertion, 'ssfi'); + + expect(origSsfi).to.equal(newSsfi); + }); }); it('compareByInspect', function () {