/*! * Should * Copyright(c) 2010-2014 TJ Holowaychuk * MIT Licensed */ var util = require('../util'), 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) { this.have.property('length', n, description); }); Assertion.alias('length', 'lengthOf'); var hasOwnProperty = Object.prototype.hasOwnProperty; 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' }; 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) { 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); // 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)); } }); var verb = keys.length === 0 ? 'to be empty' : 'to have ' + (keys.length === 1 ? 'key ' : 'keys '); this.params = { operator: verb + keys.map(i).join(', ')}; if(missingKeys.length > 0) this.params.operator += '\n\tmissing keys: ' + missingKeys.join(', '); if(extraKeys.length > 0) this.params.operator += '\n\textra keys: ' + extraKeys.join(', '); this.assert(missingKeys.length === 0 && extraKeys.length === 0); }); 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); } }); 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); } }); };