diff --git a/lib/chai/assertion.js b/lib/chai/assertion.js index 4d4217fb4..8ed93ae7e 100644 --- a/lib/chai/assertion.js +++ b/lib/chai/assertion.js @@ -138,11 +138,21 @@ module.exports = function (_chai, util) { if (!ok) { msg = util.getMessage(this, arguments); var actual = util.getActual(this, arguments); - throw new AssertionError(msg, { + var assertionErrorObjectProperties = { actual: actual , expected: expected , showDiff: showDiff - }, (config.includeStack) ? this.assert : flag(this, 'ssfi')); + }; + + var operator = util.getOperator(this, arguments); + if (operator) { + assertionErrorObjectProperties.operator = operator; + } + + throw new AssertionError( + msg, + assertionErrorObjectProperties, + (config.includeStack) ? this.assert : flag(this, 'ssfi')); } }; diff --git a/lib/chai/utils/getOperator.js b/lib/chai/utils/getOperator.js new file mode 100644 index 000000000..f7d10ef17 --- /dev/null +++ b/lib/chai/utils/getOperator.js @@ -0,0 +1,55 @@ +var type = require('type-detect'); + +var flag = require('./flag'); + +function isObjectType(obj) { + var objectType = type(obj); + var objectTypes = ['Array', 'Object', 'function']; + + return objectTypes.indexOf(objectType) !== -1; +} + +/** + * ### .getOperator(message) + * + * Extract the operator from error message. + * Operator defined is based on below link + * https://nodejs.org/api/assert.html#assert_assert. + * + * Returns the `operator` or `undefined` value for an Assertion. + * + * @param {Object} object (constructed Assertion) + * @param {Arguments} chai.Assertion.prototype.assert arguments + * @namespace Utils + * @name getOperator + * @api public + */ + +module.exports = function getOperator(obj, args) { + var operator = flag(obj, 'operator'); + var negate = flag(obj, 'negate'); + var expected = args[3]; + var msg = negate ? args[2] : args[1]; + + if (operator) { + return operator; + } + + if (typeof msg === 'function') msg = msg(); + + msg = msg || ''; + if (!msg) { + return undefined; + } + + if (/\shave\s/.test(msg)) { + return undefined; + } + + var isObject = isObjectType(expected); + if (/\snot\s/.test(msg)) { + return isObject ? 'notDeepStrictEqual' : 'notStrictEqual'; + } + + return isObject ? 'deepStrictEqual' : 'strictEqual'; +}; diff --git a/lib/chai/utils/index.js b/lib/chai/utils/index.js index d4f329c75..c12a38ed5 100644 --- a/lib/chai/utils/index.js +++ b/lib/chai/utils/index.js @@ -170,3 +170,9 @@ exports.isProxyEnabled = require('./isProxyEnabled'); */ exports.isNaN = require('./isNaN'); + +/*! + * getOperator method + */ + +exports.getOperator = require('./getOperator'); \ No newline at end of file diff --git a/test/globalErr.js b/test/globalErr.js index 2641c2aaf..71a23454a 100644 --- a/test/globalErr.js +++ b/test/globalErr.js @@ -43,6 +43,115 @@ describe('globalErr', function () { }); }); + it('should pass operator if possible during none object comparison', function () { + err(function () { + expect('cat').to.equal('dog'); + }, { + message: 'expected \'cat\' to equal \'dog\'' + , expected: 'dog' + , actual: 'cat' + , operator: 'strictEqual' + }); + + err(function () { + expect('cat').to.not.equal('cat'); + }, { + message: 'expected \'cat\' to not equal \'cat\'' + , expected: 'cat' + , actual: 'cat' + , operator: 'notStrictEqual' + }); + }); + + it('should pass operator if possible during plain object comparison', function () { + var val1 = { + propVal1: 'val1' + }; + + var val2 = { + propVal2: 'val2' + }; + + err(function () { + expect(val1).to.equal(val2); + }, { + message: "expected { propVal1: 'val1' } to equal { propVal2: 'val2' }" + , expected: val2 + , actual: val1 + , operator: 'deepStrictEqual' + }); + + err(function () { + expect(val1).to.not.equal(val1); + }, { + message: "expected { propVal1: 'val1' } to not equal { propVal1: 'val1' }" + , expected: val1 + , actual: val1 + , operator: 'notDeepStrictEqual' + }); + }); + + it('should pass operator if possible during function comparison', function () { + function f1 () { + this.propF1 = 'propF1'; + } + + function f2 () { + this.propF2 = 'propF2'; + } + + err(function () { + expect(f1).to.equal(f2); + }, { + message: "expected [Function: f1] to equal [Function: f2]" + , expected: f2 + , actual: f1 + , operator: 'deepStrictEqual' + }); + + err(function () { + expect(f1).to.not.equal(f1); + }, { + message: "expected [Function: f1] to not equal [Function: f1]" + , expected: f1 + , actual: f1 + , operator: 'notDeepStrictEqual' + }); + }); + + it('should pass operator if possible during object comparison', function () { + var val1 = [ + 'string1' + , 'string2' + , 'string3' + , 'string4' + ]; + + var val2 = [ + 'string5' + , 'string6' + , 'string7' + , 'string8' + ]; + err(function () { + expect(val1).to.equal(val2); + }, { + message: 'expected [ Array(4) ] to equal [ Array(4) ]' + , expected: val2 + , actual: val1 + , operator: 'deepStrictEqual' + }); + + err(function () { + expect(val1).to.not.equal(val1); + }, { + message: 'expected [ Array(4) ] to not equal [ Array(4) ]' + , expected: val1 + , actual: val1 + , operator: 'notDeepStrictEqual' + }); + }); + it('should throw if regex val does not match error message', function () { err(function () { err(function () { throw new Err('cat') }, /dog/); diff --git a/test/utilities.js b/test/utilities.js index bb3db59f3..2e21147d4 100644 --- a/test/utilities.js +++ b/test/utilities.js @@ -1324,4 +1324,185 @@ describe('utilities', function () { }); } }); + + describe('getOperator', function() { + it('Must return operator if the "operator" flag is set', function() { + chai.use(function(_chai, _) { + expect(_.getOperator({}, [])).to.equal(undefined); + expect(_.getOperator({}, [null, null, null])).to.equal(undefined); + + var obj = {}; + _.flag(obj, 'operator', 'my-operator'); + expect(_.getOperator(obj, [])).to.equal('my-operator'); + }); + }); + + it('Must return undefined if message is partial assertions', function() { + chai.use(function(_chai, _) { + expect( + _.getOperator({}, [null, 'to have the same ordered', null, 'test']) + ).to.equal(undefined); + }); + }); + + it('Must return deepStrictEqual if "expected" is a object and assertion is for equal', function() { + chai.use(function(_chai, _) { + var expected = Object.create({ + dummyProperty1: 'dummyProperty1', + dummyProperty2: 'dummyProperty2', + dummyProperty3: 'dummyProperty3' + }); + + var obj = {}; + _.flag(obj, 'negate', false); + + expect( + _.getOperator(obj, [ + null, + 'expect #{this} deep equal to #{exp}', + 'expect #{this} not deep equal to #{exp}', + expected + ]) + ).to.equal('deepStrictEqual'); + }); + }); + + it('Must return deepStrictEqual if "expected" is a function and assertion is for equal', function() { + chai.use(function(_chai, _) { + function expected () { + this.prop = 'prop'; + } + + var obj = {}; + _.flag(obj, 'negate', false); + + expect( + _.getOperator(obj, [ + null, + 'expect #{this} deep equal to #{exp}', + 'expect #{this} not deep equal to #{exp}', + expected + ]) + ).to.equal('deepStrictEqual'); + }); + }); + + it('Must return deepStrictEqual if "expected" is an array and assertion is for equal', function() { + chai.use(function(_chai, _) { + var expected = [ + 'item 1' + ]; + + var obj = {}; + _.flag(obj, 'negate', false); + + expect( + _.getOperator(obj, [ + null, + 'expect #{this} deep equal to #{exp}', + 'expect #{this} not deep equal to #{exp}', + expected + ]) + ).to.equal('deepStrictEqual'); + }); + }); + + it('Must return strictEqual if "expected" is a string and assertion is for equal', function() { + chai.use(function(_chai, _) { + var expected = 'someString'; + + var obj = {}; + _.flag(obj, 'negate', false); + + expect( + _.getOperator(obj, [ + null, + 'expect #{this} equal to #{exp}', + 'expect #{this} not equal to #{exp}', + expected + ]) + ).to.equal('strictEqual'); + }); + }); + + it('Must return notDeepStrictEqual if "expected" is a object and assertion is for inequality', function() { + chai.use(function(_chai, _) { + var expected = Object.create({ + dummyProperty1: 'dummyProperty1', + dummyProperty2: 'dummyProperty2', + dummyProperty3: 'dummyProperty3' + }); + + var obj = {}; + _.flag(obj, 'negate', true); + + expect( + _.getOperator(obj, [ + null, + 'expect #{this} deep equal to #{exp}', + 'expect #{this} not deep equal to #{exp}', + expected + ]) + ).to.equal('notDeepStrictEqual'); + }); + }); + + it('Must return notDeepStrictEqual if "expected" is a function and assertion is for inequality', function() { + chai.use(function(_chai, _) { + function expected () { + this.prop = 'prop'; + } + + var obj = {}; + _.flag(obj, 'negate', true); + + expect( + _.getOperator(obj, [ + null, + 'expect #{this} deep equal to #{exp}', + 'expect #{this} not deep equal to #{exp}', + expected + ]) + ).to.equal('notDeepStrictEqual'); + }); + }); + + it('Must return notDeepStrictEqual if "expected" is an array and assertion is for inequality', function() { + chai.use(function(_chai, _) { + var expected = [ + 'item 1' + ]; + + var obj = {}; + _.flag(obj, 'negate', true); + + expect( + _.getOperator(obj, [ + null, + 'expect #{this} deep equal to #{exp}', + 'expect #{this} not deep equal to #{exp}', + expected + ]) + ).to.equal('notDeepStrictEqual'); + }); + }); + + it('Must return notStrictEqual if "expected" is a string and assertion is for inequality', function() { + chai.use(function(_chai, _) { + var expected = 'someString'; + + var obj = {}; + _.flag(obj, 'negate', true); + + expect( + _.getOperator(obj, [ + null, + 'expect #{this} equal to #{exp}', + 'expect #{this} not equal to #{exp}', + expected + ]) + ).to.equal('notStrictEqual'); + }); + }); + }); });