From 068f57d5a3cb674abdb061df5ed55ece1198cdc9 Mon Sep 17 00:00:00 2001 From: Denis Bardadym Date: Tue, 14 Jan 2014 13:26:21 +0400 Subject: [PATCH] Make .containDeep more strict to avoid potential misunderstanding. --- History.md | 1 + Readme.md | 26 ++- lib/ext/assert.js | 10 +- lib/ext/bool.js | 2 +- lib/ext/deprecated.js | 28 ++-- lib/ext/error.js | 30 ++-- lib/ext/http.js | 17 +- lib/ext/match.js | 189 ++++++++++----------- lib/ext/number.js | 8 +- lib/ext/property.js | 338 +++++++++++++++++++------------------- lib/ext/string.js | 10 +- lib/ext/type.js | 29 ++-- lib/should.js | 2 +- lib/util.js | 2 +- test/ext/property.test.js | 23 +-- 15 files changed, 368 insertions(+), 347 deletions(-) diff --git a/History.md b/History.md index f723b48..6fb53a5 100644 --- a/History.md +++ b/History.md @@ -11,6 +11,7 @@ * Breaking change: .keys now check as is - no keys in args means no keys in object * Deprecated: assert extension * Deprecated: .include and .includeEql + * Now all assertions define only positive cases, should.js take care about negations and chains 2.1.1 / 2013-12-05 ================== diff --git a/Readme.md b/Readme.md index 894f739..eb77b68 100644 --- a/Readme.md +++ b/Readme.md @@ -72,6 +72,7 @@ should.be.exactly(window); ``` *should.js* uses EcmaScript 5 very extensively so any browser that support ES5 is supported. (IE <=8 not supported). +See [kangax's compat table](http://kangax.github.io/es5-compat-table) to know which exactly. You can easy install it with again npm or bower: ``` @@ -370,7 +371,6 @@ Assert given value to contain something *.eql* to otherValue. See examples to un [[1],[2],[3, 4]].should.not.containEql([3]); ({ b: 10 }).should.containEql({ b: 10 }); -[1, 2, 3].should.containEql(1); ([1, 2, { a: 10 }]).should.containEql({ a: 10 }); [1, 2, 3].should.not.containEql({ a: 1 }); @@ -380,24 +380,32 @@ Assert given value to contain something *.eql* to otherValue. See examples to un ## .containDeep(otherValue) -Assert given value to contain something *.eql* to otherValue within depth. Again see examples: +Assert given value to contain something *.eql* to otherValue within depth. +Again see examples: ```javascript 'hello boy'.should.containDeep('boy'); -[1,2,3].should.containDeep(3); +[1,2,3].should.containDeep([3]); +[1,2,3].should.containDeep([1, 3]); +//but not +[1,2,3].should.containDeep([3, 1]); ({ a: { b: 10 }, b: { c: 10, d: 11, a: { b: 10, c: 11} }}).should .containDeep({ a: { b: 10 }, b: { c: 10, a: { c: 11 }}}); -[1, 2, 3, { a: { b: { d: 12 }}}].should.containDeep({ a: { b: {d: 12}}}); +[1, 2, 3, { a: { b: { d: 12 }}}].should.containDeep([{ a: { b: {d: 12}}}]); -[[1],[2],[3]].should.containDeep([3]); -[[1],[2],[3, 4]].should.containDeep([3]); -[{a: 'a'}, {b: 'b', c: 'c'}].should.containDeep({a: 'a'}); -[{a: 'a'}, {b: 'b', c: 'c'}].should.containDeep({b: 'b'}); +[[1],[2],[3]].should.containDeep([[3]]); +[[1],[2],[3, 4]].should.containDeep([[3]]); +[{a: 'a'}, {b: 'b', c: 'c'}].should.containDeep([{a: 'a'}]); +[{a: 'a'}, {b: 'b', c: 'c'}].should.containDeep([{b: 'b'}]); ``` -It does not search somewhere in depth it check all pattern in depth. It is important meaning. +It does not search somewhere in depth it check all pattern in depth. Object checked +by properties key and value, arrays checked like sub sequences. Everyting compared using .eql. +Main difference with `.containEql` is that this assertion require full type chain - +if asserted value is an object, otherValue should be also an object (which is sub object of given). +The same true for arrays, otherValue should be an array which compared to be subsequence of given object. ## .match(otherValue) diff --git a/lib/ext/assert.js b/lib/ext/assert.js index 91a3cc0..9bf436f 100644 --- a/lib/ext/assert.js +++ b/lib/ext/assert.js @@ -33,8 +33,7 @@ module.exports = function(should) { should.exist = should.exists = function(obj, msg) { if(null == obj) { throw new AssertionError({ - message: msg || ('expected ' + i(obj) + ' to exist') - , stackStartFunction: should.exist + message: msg || ('expected ' + i(obj) + ' to exist'), stackStartFunction: should.exist }); } }; @@ -48,11 +47,10 @@ module.exports = function(should) { */ should.not = {}; - should.not.exist = should.not.exists = function(obj, msg){ - if (null != obj) { + should.not.exist = should.not.exists = function(obj, msg) { + if(null != obj) { throw new AssertionError({ - message: msg || ('expected ' + i(obj) + ' to not exist') - , stackStartFunction: should.not.exist + message: msg || ('expected ' + i(obj) + ' to not exist'), stackStartFunction: should.not.exist }); } }; diff --git a/lib/ext/bool.js b/lib/ext/bool.js index 42c73a9..04da82b 100644 --- a/lib/ext/bool.js +++ b/lib/ext/bool.js @@ -14,7 +14,7 @@ module.exports = function(should, Assertion) { }, true); Assertion.add('ok', function() { - this.params = { operator: 'to be truthy' }; + this.params = { operator: 'to be truthy' }; this.assert(this.obj); }, true); diff --git a/lib/ext/deprecated.js b/lib/ext/deprecated.js index 3f512fc..3656d07 100644 --- a/lib/ext/deprecated.js +++ b/lib/ext/deprecated.js @@ -5,27 +5,29 @@ */ var util = require('../util'), - eql = require('../eql'); + eql = require('../eql'); -module.exports = function (should, Assertion) { - var i = should.format; - - Assertion.add('include', function(obj, description){ - if (!Array.isArray(this.obj) && !util.isString(this.obj)){ - this.params = { operator: 'to include an object equal to ' + i(obj), message: description }; +module.exports = function(should, Assertion) { + var i = should.format; + + Assertion.add('include', function(obj, description) { + if(!Array.isArray(this.obj) && !util.isString(this.obj)) { + this.params = { operator: 'to include an object equal to ' + i(obj), message: description }; var cmp = {}; - for (var key in obj) cmp[key] = this.obj[key]; + for(var key in obj) cmp[key] = this.obj[key]; this.assert(eql(cmp, obj)); } else { - this.params = { operator: 'to include ' + i(obj), message: description }; + this.params = { operator: 'to include ' + i(obj), message: description }; this.assert(~this.obj.indexOf(obj)); } - }) + }); Assertion.add('includeEql', function(obj, description) { - this.params = { operator: 'to include an object equal to ' + i(obj), message: description }; + this.params = { operator: 'to include an object equal to ' + i(obj), message: description }; - this.assert(this.obj.some(function(item) { return eql(obj, item); })); + this.assert(this.obj.some(function(item) { + return eql(obj, item); + })); }); -} \ No newline at end of file +}; \ No newline at end of file diff --git a/lib/ext/error.js b/lib/ext/error.js index 93a934a..eac98e4 100644 --- a/lib/ext/error.js +++ b/lib/ext/error.js @@ -5,9 +5,9 @@ */ module.exports = function(should, Assertion) { - var i = should.format; + var i = should.format; - Assertion.add('throw', function(message) { + Assertion.add('throw', function(message) { var fn = this.obj , err = {} , errorInfo = '' @@ -16,30 +16,30 @@ module.exports = function(should, Assertion) { try { fn(); ok = false; - } catch (e) { + } catch(e) { err = e; } - if (ok) { - if ('string' == typeof message) { + if(ok) { + if('string' == typeof message) { ok = message == err.message; - } else if (message instanceof RegExp) { + } else if(message instanceof RegExp) { ok = message.test(err.message); - } else if ('function' == typeof message) { + } else if('function' == typeof message) { ok = err instanceof message; } - if (message && !ok) { - if ('string' == typeof message) { + if(message && !ok) { + if('string' == typeof message) { errorInfo = " with a message matching '" + message + "', but got '" + err.message + "'"; - } else if (message instanceof RegExp) { + } else if(message instanceof RegExp) { errorInfo = " with a message matching " + message + ", but got '" + err.message + "'"; - } else if ('function' == typeof message) { + } else if('function' == typeof message) { errorInfo = " of type " + message.name + ", but got " + err.constructor.name; } } else { - errorInfo = " (got "+i(err)+")"; - } + errorInfo = " (got " + i(err) + ")"; + } } this.params = { operator: 'to throw exception' + errorInfo }; @@ -47,5 +47,5 @@ module.exports = function(should, Assertion) { this.assert(ok); }); - Assertion.alias('throw', 'throwError'); -} \ No newline at end of file + Assertion.alias('throw', 'throwError'); +}; \ No newline at end of file diff --git a/lib/ext/http.js b/lib/ext/http.js index 47a814b..f5d24fc 100644 --- a/lib/ext/http.js +++ b/lib/ext/http.js @@ -7,28 +7,27 @@ //var statusCodes = require('http').STATUS_CODES; module.exports = function(should, Assertion) { - var i = should.format; - Assertion.add('header', function(field, val){ + Assertion.add('header', function(field, val) { this .have.property('headers') .and.have.property(field.toLowerCase(), val); }); Assertion.add('status', function(code) { - //this.params = { operator: 'to have response code ' + code + ' ' + i(statusCodes[code]) + //this.params = { operator: 'to have response code ' + code + ' ' + i(statusCodes[code]) // + ', but got ' + this.obj.statusCode + ' ' + i(statusCodes[this.obj.statusCode]) } this.have.property('statusCode', code); - }) + }); Assertion.add('json', function() { this.have.property('headers') - .and.have.property('content-type').include('application/json'); - }, true) + .and.have.property('content-type').include('application/json'); + }, true); Assertion.add('html', function() { this.have.property('headers') - .and.have.property('content-type').include('text/html'); - }, true) -} \ No newline at end of file + .and.have.property('content-type').include('text/html'); + }, true); +}; \ No newline at end of file diff --git a/lib/ext/match.js b/lib/ext/match.js index 45f0e81..f0322ae 100644 --- a/lib/ext/match.js +++ b/lib/ext/match.js @@ -5,103 +5,108 @@ */ var util = require('../util'), - eql = require('../eql'); + eql = require('../eql'); module.exports = function(should, Assertion) { - var i = should.format; - - Assertion.add('match', function(other, description) { - this.params = { operator: 'to match ' + i(other), message: description }; - - if(!eql(this.obj, other)) { - if(util.isRegExp(other)) { // something - regex - - if(util.isString(this.obj)) { - - this.assert(other.exec(this.obj)); - } else if(Array.isArray(this.obj)) { - - this.obj.forEach(function(item) { - this.assert(other.exec(item));// should we try to convert to String and exec? - }, this); - } else if(util.isObject(this.obj)) { - - var notMatchedProps = [], matchedProps = []; - util.forOwn(this.obj, function(value, name) { - if(other.exec(value)) matchedProps.push(i(name)); - else notMatchedProps.push(i(name)); - }, this); - - if(notMatchedProps.length) - this.params.operator += '\n\tnot matched properties: ' + notMatchedProps.join(', '); - if(matchedProps.length) - this.params.operator += '\n\tmatched properties: ' + matchedProps.join(', '); - - this.assert(notMatchedProps.length == 0); - } // should we try to convert to String and exec? - } else if(util.isFunction(other)) { - try { - var res = other(this.obj); - } catch(e) { - if(e instanceof should.AssertionError) { - this.params.operator += '\n\t' + e.message; - } - throw e; - } - - if(res instanceof Assertion) { - this.params.operator += '\n\t' + res.getMessage(); - } - - //if we throw exception ok - it is used .should inside - if(util.isBoolean(res)) { - this.assert(res); // if it is just boolean function assert on it - } - } else if(util.isObject(other)) { // try to match properties (for Object and Array) - var notMatchedProps = [], matchedProps = []; - - util.forOwn(other, function(value, key) { - try { - this.obj[key].should.match(value); - matchedProps.push(key); - } catch(e) { - if(e instanceof should.AssertionError) { - notMatchedProps.push(key); - } else { - throw e; - } - } - }, this); - - if(notMatchedProps.length) - this.params.operator += '\n\tnot matched properties: ' + notMatchedProps.join(', '); - if(matchedProps.length) - this.params.operator += '\n\tmatched properties: ' + matchedProps.join(', '); - - this.assert(notMatchedProps.length == 0); - } else { - this.assert(false); - } - } + var i = should.format; + + Assertion.add('match', function(other, description) { + this.params = { operator: 'to match ' + i(other), message: description }; + + if(!eql(this.obj, other)) { + if(util.isRegExp(other)) { // something - regex + + if(util.isString(this.obj)) { + + this.assert(other.exec(this.obj)); + } else if(Array.isArray(this.obj)) { + + this.obj.forEach(function(item) { + this.assert(other.exec(item));// should we try to convert to String and exec? + }, this); + } else if(util.isObject(this.obj)) { + + var notMatchedProps = [], matchedProps = []; + util.forOwn(this.obj, function(value, name) { + if(other.exec(value)) matchedProps.push(i(name)); + else notMatchedProps.push(i(name)); + }, this); + + if(notMatchedProps.length) + this.params.operator += '\n\tnot matched properties: ' + notMatchedProps.join(', '); + if(matchedProps.length) + this.params.operator += '\n\tmatched properties: ' + matchedProps.join(', '); + + this.assert(notMatchedProps.length == 0); + } // should we try to convert to String and exec? + } else if(util.isFunction(other)) { + var res; + try { + res = other(this.obj); + } catch(e) { + if(e instanceof should.AssertionError) { + this.params.operator += '\n\t' + e.message; + } + throw e; + } + + if(res instanceof Assertion) { + this.params.operator += '\n\t' + res.getMessage(); + } + + //if we throw exception ok - it is used .should inside + if(util.isBoolean(res)) { + this.assert(res); // if it is just boolean function assert on it + } + } else if(util.isObject(other)) { // try to match properties (for Object and Array) + notMatchedProps = []; matchedProps = []; + + util.forOwn(other, function(value, key) { + try { + this.obj[key].should.match(value); + matchedProps.push(key); + } catch(e) { + if(e instanceof should.AssertionError) { + notMatchedProps.push(key); + } else { + throw e; + } + } + }, this); + + if(notMatchedProps.length) + this.params.operator += '\n\tnot matched properties: ' + notMatchedProps.join(', '); + if(matchedProps.length) + this.params.operator += '\n\tmatched properties: ' + matchedProps.join(', '); + + this.assert(notMatchedProps.length == 0); + } else { + this.assert(false); + } + } }); - Assertion.add('matchEach', function(other, description) { - this.params = { operator: 'to match each ' + i(other), message: description }; + Assertion.add('matchEach', function(other, description) { + this.params = { operator: 'to match each ' + i(other), message: description }; - var f = other; + var f = other; - if(util.isRegExp(other)) - f = function(it) { return !!other.exec(it); }; - else if(!util.isFunction(other)) - f = function(it) { return eql(it, other); }; + if(util.isRegExp(other)) + f = function(it) { + return !!other.exec(it); + }; + else if(!util.isFunction(other)) + f = function(it) { + return eql(it, other); + }; - util.forOwn(this.obj, function(value, key) { - var res = f(value, key); + util.forOwn(this.obj, function(value, key) { + var res = f(value, key); - //if we throw exception ok - it is used .should inside - if(util.isBoolean(res)) { - this.assert(res); // if it is just boolean function assert on it - } - }, this); - }); -} \ No newline at end of file + //if we throw exception ok - it is used .should inside + if(util.isBoolean(res)) { + this.assert(res); // if it is just boolean function assert on it + } + }, this); + }); +}; \ No newline at end of file diff --git a/lib/ext/number.js b/lib/ext/number.js index ad80c4e..3b59c87 100644 --- a/lib/ext/number.js +++ b/lib/ext/number.js @@ -20,8 +20,8 @@ module.exports = function(should, Assertion) { .and.assert(!isFinite(this.obj)); }, true); - Assertion.add('within', function(start, finish, description){ - this.params = { operator: 'to be within '+ start + '..' + finish, message: description }; + Assertion.add('within', function(start, finish, description) { + this.params = { operator: 'to be within ' + start + '..' + finish, message: description }; this.assert(this.obj >= start && this.obj <= finish); }); @@ -32,13 +32,13 @@ module.exports = function(should, Assertion) { this.assert(Math.abs(this.obj - value) <= delta); }); - Assertion.add('above', function(n, description){ + Assertion.add('above', function(n, description) { this.params = { operator: 'to be above ' + n, message: description }; this.assert(this.obj > n); }); - Assertion.add('below', function(n, description){ + Assertion.add('below', function(n, description) { this.params = { operator: 'to be below ' + n, message: description }; this.assert(this.obj < n); diff --git a/lib/ext/property.js b/lib/ext/property.js index 873fb5b..1edd9e6 100644 --- a/lib/ext/property.js +++ b/lib/ext/property.js @@ -5,86 +5,86 @@ */ var util = require('../util'), - eql = require('../eql'); + eql = require('../eql'); var aSlice = Array.prototype.slice; module.exports = function(should, Assertion) { - var i = should.format; - - Assertion.add('property', function(name, val) { - if(arguments.length > 1) { - var p = {}; - p[name] = val; - this.have.properties(p); - } else { - this.have.properties(name); - } - this.obj = this.obj[name]; - }); - - Assertion.add('properties', function(names) { - var values = {}; - if(arguments.length > 1) { - names = aSlice.call(arguments); - } else if(!Array.isArray(names)) { - if(util.isString(names)) { - names = [names]; - } else { - values = names; - names = Object.keys(names); - } - } - - var obj = Object(this.obj), missingProperties = []; - - //just enumerate properties and check if they all present - names.forEach(function(name) { - if(!(name in obj)) missingProperties.push(i(name)); - }); - - var props = missingProperties; - if(props.length === 0) { - props = names.map(i); - } - - var operator = (props.length === 1 ? - 'to have property ' : 'to have properties ') + props.join(', '); - - this.params = { operator: operator }; - - this.assert(missingProperties.length === 0); - - // check if values in object matched expected - var valueCheckNames = Object.keys(values); - if(valueCheckNames.length) { - var wrongValues = []; - props = []; - - // now check values, as there we have all properties - valueCheckNames.forEach(function(name) { - var value = values[name]; - if(!eql(obj[name], value)) { - wrongValues.push(i(name) + ' of ' + i(value) + ' (got ' + i(obj[name]) +')'); - } else { - props.push(i(name) + ' of ' + i(value)); - } - }); - - if(wrongValues.length > 0) { - props = wrongValues; - } - - operator = (props.length === 1 ? - 'to have property ' : 'to have properties ') + props.join(', '); - - this.params = { operator: operator }; - - this.assert(wrongValues.length === 0); - } - }); - - Assertion.add('length', function(n, description){ + var i = should.format; + + Assertion.add('property', function(name, val) { + if(arguments.length > 1) { + var p = {}; + p[name] = val; + this.have.properties(p); + } else { + this.have.properties(name); + } + this.obj = this.obj[name]; + }); + + Assertion.add('properties', function(names) { + var values = {}; + if(arguments.length > 1) { + names = aSlice.call(arguments); + } else if(!Array.isArray(names)) { + if(util.isString(names)) { + names = [names]; + } else { + values = names; + names = Object.keys(names); + } + } + + var obj = Object(this.obj), missingProperties = []; + + //just enumerate properties and check if they all present + names.forEach(function(name) { + if(!(name in obj)) missingProperties.push(i(name)); + }); + + var props = missingProperties; + if(props.length === 0) { + props = names.map(i); + } + + var operator = (props.length === 1 ? + 'to have property ' : 'to have properties ') + props.join(', '); + + this.params = { operator: operator }; + + this.assert(missingProperties.length === 0); + + // check if values in object matched expected + var valueCheckNames = Object.keys(values); + if(valueCheckNames.length) { + var wrongValues = []; + props = []; + + // now check values, as there we have all properties + valueCheckNames.forEach(function(name) { + var value = values[name]; + if(!eql(obj[name], value)) { + wrongValues.push(i(name) + ' of ' + i(value) + ' (got ' + i(obj[name]) + ')'); + } else { + props.push(i(name) + ' of ' + i(value)); + } + }); + + if(wrongValues.length > 0) { + props = wrongValues; + } + + operator = (props.length === 1 ? + 'to have property ' : 'to have properties ') + props.join(', '); + + this.params = { operator: operator }; + + this.assert(wrongValues.length === 0); + } + }); + + Assertion.add('length', function(n, description) { this.have.property('length', n, description); }); @@ -92,124 +92,124 @@ module.exports = function(should, Assertion) { var hasOwnProperty = Object.prototype.hasOwnProperty; - Assertion.add('ownProperty', function(name, description){ - this.params = { operator: 'to have own property ' + i(name), message: description }; + Assertion.add('ownProperty', function(name, description) { + this.params = { operator: 'to have own property ' + i(name), message: description }; this.assert(hasOwnProperty.call(this.obj, name)); - + this.obj = this.obj[name]; }); Assertion.alias('hasOwnProperty', 'ownProperty'); Assertion.add('empty', function() { - this.params = { operator: 'to be empty' }; + this.params = { operator: 'to be empty' }; if(util.isString(this.obj) || Array.isArray(this.obj) || util.isArguments(this.obj)) { this.have.property('length', 0); } else { var obj = Object(this.obj); // wrap to reference for booleans and numbers - for (var prop in obj) { + for(var prop in obj) { this.have.not.ownProperty(prop); } } }, true); Assertion.add('keys', function(keys) { - if(arguments.length > 1) keys = aSlice.call(arguments); - else if(arguments.length === 1 && util.isString(keys)) keys = [ keys ]; - else if(arguments.length === 0) keys = []; - - var obj = Object(this.obj); + if(arguments.length > 1) keys = aSlice.call(arguments); + else if(arguments.length === 1 && util.isString(keys)) keys = [ keys ]; + else if(arguments.length === 0) keys = []; + + var obj = Object(this.obj); + + // first check if some keys are missing + var missingKeys = []; + keys.forEach(function(key) { + if(!hasOwnProperty.call(this.obj, key)) + missingKeys.push(i(key)); + }, this); + + // second check for extra keys + var extraKeys = []; + Object.keys(obj).forEach(function(key) { + if(keys.indexOf(key) < 0) { + extraKeys.push(i(key)); + } + }); - // first check if some keys are missing - var missingKeys = []; - keys.forEach(function(key) { - if(!hasOwnProperty.call(this.obj, key)) - missingKeys.push(i(key)); - }, this); + var verb = keys.length === 0 ? 'to be empty' : + 'to have ' + (keys.length === 1 ? 'key ' : 'keys '); - // second check for extra keys - var extraKeys = []; - Object.keys(obj).forEach(function(key) { - if(keys.indexOf(key) < 0) { - extraKeys.push(i(key)); - } - }); + this.params = { operator: verb + keys.map(i).join(', ')}; - var verb = keys.length === 0 ? 'to be empty' : - 'to have '+(keys.length === 1 ? 'key ' : 'keys ') + if(missingKeys.length > 0) + this.params.operator += '\n\tmissing keys: ' + missingKeys.join(', '); - this.params = { operator: verb + keys.map(i).join(', ')}; + if(extraKeys.length > 0) + this.params.operator += '\n\textra keys: ' + extraKeys.join(', '); - if(missingKeys.length > 0) - this.params.operator += '\n\tmissing keys: ' + missingKeys.join(', '); + this.assert(missingKeys.length === 0 && extraKeys.length === 0); + }); - if(extraKeys.length > 0) - this.params.operator += '\n\textra keys: ' + extraKeys.join(', '); + Assertion.alias("keys", "key"); + + Assertion.add('containEql', function(other) { + this.params = { operator: 'to contain ' + i(other) }; + var obj = this.obj; + if(Array.isArray(obj)) { + this.assert(obj.some(function(item) { + return eql(item, other); + })); + } else if(util.isString(obj)) { + // expect obj to be string + this.assert(obj.indexOf(String(other)) >= 0); + } else if(util.isObject(obj)) { + // object contains object case + util.forOwn(other, function(value, key) { + obj[key].should.eql(value); + }); + } else { + //other uncovered cases + this.assert(false); + } + }); - this.assert(missingKeys.length === 0 && extraKeys.length === 0); + Assertion.add('containDeep', function(other) { + this.params = { operator: 'to contain ' + i(other) }; + + var obj = this.obj; + if(Array.isArray(obj)) { + if(Array.isArray(other)) { + var otherIdx = 0; + obj.forEach(function(item) { + try { + should(item).not.be.null.and.containDeep(other[otherIdx]); + otherIdx++; + } catch(e) { + if(e instanceof should.AssertionError) { + return; + } + throw e; + } + }); + this.assert(otherIdx == other.length); + //search array contain other as sub sequence + } else { + this.assert(false); + } + } else if(util.isString(obj)) {// expect other to be string + this.assert(obj.indexOf(String(other)) >= 0); + } else if(util.isObject(obj)) {// object contains object case + if(util.isObject(other)) { + util.forOwn(other, function(value, key) { + should(obj[key]).not.be.null.and.containDeep(value); + }); + } else {//one of the properties contain value + this.assert(false); + } + } else { + this.eql(other); + } }); - Assertion.alias("keys", "key"); - - Assertion.add('containEql', function(other) { - this.params = { operator: 'to contain ' + i(other) }; - var obj = this.obj; - if(Array.isArray(obj)) { - this.assert(obj.some(function(item) { - return eql(item, other); - })); - } else if(util.isString(obj)) { - // expect obj to be string - this.assert(obj.indexOf(String(other)) >= 0); - } else if(util.isObject(obj)) { - // object contains object case - Object.keys(other).forEach(function(key) { - Object(obj[key]).should.eql(other[key]); - }, this); - } else { - //other uncovered cases - this.assert(false); - } - }); - - Assertion.add('containDeep', function(other) { - this.params = { operator: 'to contain ' + i(other) }; - var obj = this.obj; - if(Array.isArray(obj)) { - if(Array.isArray(other)) { - this.assert(other.every(function(item) { - try { - obj.should.containDeep(item); - return true; - } catch(e) { - return false; - } - })); - } else { - this.assert(obj.some(function(item) { - try { - item.should.containDeep(other); - return true; - } catch (e) { - return false; - } - })); - } - } else if(util.isString(obj)) { - // expect obj to be string - this.assert(obj.indexOf(String(other)) >= 0); - } else if(util.isObject(obj)) { - // object contains object case - Object.keys(other).forEach(function(key) { - Object(obj[key]).should.containDeep(other[key]); - }, this); - } else { - //other uncovered cases - this.eql(other); - } - }); - - -} \ No newline at end of file +}; \ No newline at end of file diff --git a/lib/ext/string.js b/lib/ext/string.js index e89eb37..c3c6fea 100644 --- a/lib/ext/string.js +++ b/lib/ext/string.js @@ -6,14 +6,14 @@ module.exports = function(should, Assertion) { Assertion.add('startWith', function(str, description) { - this.params = { operator: 'to start with ' + should.format(str), message: description }; + this.params = { operator: 'to start with ' + should.format(str), message: description }; - this.assert(0 === this.obj.indexOf(str)); + this.assert(0 === this.obj.indexOf(str)); }); Assertion.add('endWith', function(str, description) { - this.params = { operator: 'to end with ' + should.format(str), message: description }; + this.params = { operator: 'to end with ' + should.format(str), message: description }; - this.assert(this.obj.indexOf(str, this.obj.length - str.length) >= 0); + this.assert(this.obj.indexOf(str, this.obj.length - str.length) >= 0); }); -} \ No newline at end of file +}; \ No newline at end of file diff --git a/lib/ext/type.js b/lib/ext/type.js index 72739eb..9ab9adf 100644 --- a/lib/ext/type.js +++ b/lib/ext/type.js @@ -14,59 +14,64 @@ module.exports = function(should, Assertion) { }, true); Assertion.add('arguments', function() { - this.params = { operator: 'to be arguments' }; - + this.params = { operator: 'to be arguments' }; + this.assert(util.isArguments(this.obj)); }, true); Assertion.add('type', function(type, description) { - this.params = { operator: 'to have type ' + type, message: description }; + this.params = { operator: 'to have type ' + type, message: description }; (typeof this.obj).should.be.exactly(type, description); }); - Assertion.add('instanceof', function(constructor, description){ - this.params = { operator: 'to be an instance of ' + constructor.name, message: description }; + Assertion.add('instanceof', function(constructor, description) { + this.params = { operator: 'to be an instance of ' + constructor.name, message: description }; this.assert(Object(this.obj) instanceof constructor); }); Assertion.add('Function', function() { - this.params = { operator: 'to be a function' }; + this.params = { operator: 'to be a function' }; this.assert(util.isFunction(this.obj)); }, true); Assertion.add('Object', function() { - this.params = { operator: 'to be an object' }; + this.params = { operator: 'to be an object' }; this.assert(util.isObject(this.obj)); }, true); Assertion.add('String', function() { - this.params = { operator: 'to be a string' }; + this.params = { operator: 'to be a string' }; this.assert(util.isString(this.obj)); }, true); Assertion.add('Array', function() { - this.params = { operator: 'to be an array' }; + this.params = { operator: 'to be an array' }; this.assert(Array.isArray(this.obj)); }, true); Assertion.add('Boolean', function() { - this.params = { operator: 'to be a boolean' }; + this.params = { operator: 'to be a boolean' }; this.assert(util.isBoolean(this.obj)); }, true); Assertion.add('Error', function() { - this.params = { operator: 'to be an error' }; + this.params = { operator: 'to be an error' }; this.assert(util.isError(this.obj)); }, true); - + + Assertion.add('null', function() { + this.params = { operator: 'to be null' }; + + this.assert(this.obj === null); + }, true); Assertion.alias('instanceof', 'instanceOf'); }; \ No newline at end of file diff --git a/lib/should.js b/lib/should.js index 3bbc0c5..816c617 100644 --- a/lib/should.js +++ b/lib/should.js @@ -74,7 +74,7 @@ Assertion.add = function(name, f, isGetter) { }; Object.defineProperty(Assertion.prototype, name, prop); -} +}; Assertion.alias = function(from, to) { Assertion.prototype[to] = Assertion.prototype[from] diff --git a/lib/util.js b/lib/util.js index 9b8d7a3..2fdddd5 100644 --- a/lib/util.js +++ b/lib/util.js @@ -12,7 +12,7 @@ */ exports.isWrapperType = function(obj) { return isNumber(obj) || isString(obj) || isBoolean(obj); -} +}; /** * Merge object b with object a. diff --git a/test/ext/property.test.js b/test/ext/property.test.js index e01fa29..66bfaff 100644 --- a/test/ext/property.test.js +++ b/test/ext/property.test.js @@ -87,7 +87,7 @@ module.exports = { }, "expected 'asd' to have property 'foo'"); }, - 'test keys(array)': function(){ + 'test keys(array)': function(){ ({ foo: 1 }).should.have.keys(['foo']); ({ foo: 1, bar: 2 }).should.have.keys(['foo', 'bar']); ({ foo: 1, bar: 2 }).should.have.keys('foo', 'bar'); @@ -166,24 +166,27 @@ module.exports = { 'test containDeep': function() { 'hello boy'.should.containDeep('boy'); - [1,2,3].should.containDeep(3); ({ a: { b: 10 }, b: { c: 10, d: 11, a: { b: 10, c: 11} }}).should .containDeep({ a: { b: 10 }, b: { c: 10, a: { c: 11 }}}); - [1, 2, 3, { a: { b: { d: 12 }}}].should.containDeep({ a: { b: {d: 12}}}); + [1, 2, 3, { a: { b: { d: 12 }}}].should.containDeep([{ a: { b: {d: 12}}}]); - [[1],[2],[3]].should.containDeep([3]); - [[1],[2],[3, 4]].should.containDeep([3]); - [{a: 'a'}, {b: 'b', c: 'c'}].should.containDeep({a: 'a'}); - [{a: 'a'}, {b: 'b', c: 'c'}].should.containDeep({b: 'b'}); + [[1, [2, 3], 3], [2]].should.not.containDeep([1, 2]); + + [[1],[2],[3]].should.containDeep([[3]]); + [[1],[2],[3, 4]].should.containDeep([[3]]); + [[1],[2],[3, 4]].should.containDeep([[1], [3]]); + [[1],[2],[3, 4]].should.not.containDeep([[3], [1]]); + [{a: 'a'}, {b: 'b', c: 'c'}].should.containDeep([{a: 'a'}]); + [{a: 'a'}, {b: 'b', c: 'c'}].should.containDeep([{b: 'b'}]); err(function() { 'hello boy'.should.not.containDeep('boy'); }, "expected 'hello boy' not to contain 'boy'"); err(function() { - [{a: 'a'}, {b: 'b', c: 'c'}].should.not.containDeep({b: 'b'}); - }, "expected [ { a: 'a' }, { b: 'b', c: 'c' } ] not to contain { b: 'b' }"); + [{a: 'a'}, {b: 'b', c: 'c'}].should.not.containDeep([{b: 'b'}]); + }, "expected [ { a: 'a' }, { b: 'b', c: 'c' } ] not to contain [ { b: 'b' } ]"); } -} \ No newline at end of file +}; \ No newline at end of file