UNPKG

cloudboost-tv

Version:

Database Service that does Storage, Search, Real-time and a whole lot more.

372 lines (318 loc) 11.5 kB
/*! * Should * Copyright(c) 2010-2014 TJ Holowaychuk <tj@vision-media.ca> * MIT Licensed */ var util = require('../util'); var eql = require('should-equal'); var aSlice = Array.prototype.slice; module.exports = function(should, Assertion) { var i = should.format; /** * Asserts given object has some descriptor. **On success it change given object to be value of property**. * * @name propertyWithDescriptor * @memberOf Assertion * @category assertion property * @param {string} name Name of property * @param {Object} desc Descriptor like used in Object.defineProperty (not required to add all properties) * @example * * ({ a: 10 }).should.have.propertyWithDescriptor('a', { enumerable: true }); */ Assertion.add('propertyWithDescriptor', function(name, desc) { this.params = {actual: this.obj, operator: 'to have own property with descriptor ' + i(desc)}; var obj = this.obj; this.have.ownProperty(name); should(Object.getOwnPropertyDescriptor(Object(obj), name)).have.properties(desc); }); function processPropsArgs() { var args = {}; if(arguments.length > 1) { args.names = aSlice.call(arguments); } else { var arg = arguments[0]; var t = should.type(arg); if(t == should.type.STRING) { args.names = [arg]; } else if(util.isIndexable(arg)) { args.names = arg; } else { args.names = Object.keys(arg); args.values = arg; } } return args; } /** * Asserts given object has enumerable property with optionally value. **On success it change given object to be value of property**. * * @name enumerable * @memberOf Assertion * @category assertion property * @param {string} name Name of property * @param {*} [val] Optional property value to check * @example * * ({ a: 10 }).should.have.enumerable('a'); */ Assertion.add('enumerable', function(name, val) { name = util.convertPropertyName(name); this.params = { operator: "to have enumerable property " + util.formatProp(name) + (arguments.length > 1 ? " equal to " + i(val): "") }; var desc = { enumerable: true }; if(arguments.length > 1) desc.value = val; this.have.propertyWithDescriptor(name, desc); }); /** * Asserts given object has enumerable properties * * @name enumerables * @memberOf Assertion * @category assertion property * @param {Array|...string|Object} names Names of property * @example * * ({ a: 10, b: 10 }).should.have.enumerables('a'); */ Assertion.add('enumerables', function(names) { var args = processPropsArgs.apply(null, arguments); this.params = { operator: "to have enumerables " + args.names.map(util.formatProp) }; var obj = this.obj; args.names.forEach(function(name) { obj.should.have.enumerable(name); }); }); /** * Asserts given object has property with optionally value. **On success it change given object to be value of property**. * * @name property * @memberOf Assertion * @category assertion property * @param {string} name Name of property * @param {*} [val] Optional property value to check * @example * * ({ a: 10 }).should.have.property('a'); */ Assertion.add('property', function(name, val) { name = util.convertPropertyName(name); if(arguments.length > 1) { var p = {}; p[name] = val; this.have.properties(p); } else { this.have.properties(name); } this.obj = this.obj[name]; }); /** * Asserts given object has properties. On this method affect .any modifier, which allow to check not all properties. * * @name properties * @memberOf Assertion * @category assertion property * @param {Array|...string|Object} names Names of property * @example * * ({ a: 10 }).should.have.properties('a'); * ({ a: 10, b: 20 }).should.have.properties([ 'a' ]); * ({ a: 10, b: 20 }).should.have.properties({ b: 20 }); */ Assertion.add('properties', function(names) { var values = {}; if(arguments.length > 1) { names = aSlice.call(arguments); } else if(!Array.isArray(names)) { if(typeof names == 'string' || typeof names == 'symbol') { 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(util.formatProp(name)); }); var props = missingProperties; if(props.length === 0) { props = names.map(util.formatProp); } else if(this.anyOne) { props = names.filter(function(name) { return missingProperties.indexOf(util.formatProp(name)) < 0; }).map(util.formatProp); } var operator = (props.length === 1 ? 'to have property ' : 'to have ' + (this.anyOne ? 'any of ' : '') + 'properties ') + props.join(', '); this.params = {obj: this.obj, operator: operator}; //check that all properties presented //or if we request one of them that at least one them presented this.assert(missingProperties.length === 0 || (this.anyOne && missingProperties.length != names.length)); // 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).result) { wrongValues.push(util.formatProp(name) + ' of ' + i(value) + ' (got ' + i(obj[name]) + ')'); } else { props.push(util.formatProp(name) + ' of ' + i(value)); } }); if((wrongValues.length !== 0 && !this.anyOne) || (this.anyOne && props.length === 0)) { props = wrongValues; } operator = (props.length === 1 ? 'to have property ' : 'to have ' + (this.anyOne ? 'any of ' : '') + 'properties ') + props.join(', '); this.params = {obj: this.obj, operator: operator}; //if there is no not matched values //or there is at least one matched this.assert(wrongValues.length === 0 || (this.anyOne && wrongValues.length != valueCheckNames.length)); } }); /** * Asserts given object has property `length` with given value `n` * * @name length * @alias Assertion#lengthOf * @memberOf Assertion * @category assertion property * @param {number} n Expected length * @param {string} [description] Optional message * @example * * [1, 2].should.have.length(2); */ Assertion.add('length', function(n, description) { this.have.property('length', n, description); }); Assertion.alias('length', 'lengthOf'); var hasOwnProperty = Object.prototype.hasOwnProperty; /** * Asserts given object has own property. **On success it change given object to be value of property**. * * @name ownProperty * @alias Assertion#hasOwnProperty * @memberOf Assertion * @category assertion property * @param {string} name Name of property * @param {string} [description] Optional message * @example * * ({ a: 10 }).should.have.ownProperty('a'); */ Assertion.add('ownProperty', function(name, description) { name = util.convertPropertyName(name); this.params = { actual: this.obj, operator: 'to have own property ' + util.formatProp(name), message: description }; this.assert(hasOwnProperty.call(this.obj, name)); this.obj = this.obj[name]; }); Assertion.alias('ownProperty', 'hasOwnProperty'); /** * Asserts given object is empty. For strings, arrays and arguments it checks .length property, for objects it checks keys. * * @name empty * @memberOf Assertion * @category assertion property * @example * * ''.should.be.empty; * [].should.be.empty; * ({}).should.be.empty; */ Assertion.add('empty', function() { this.params = {operator: 'to be empty'}; if(util.length(this.obj) !== void 0) { this.obj.should.have.property('length', 0); } else { var obj = Object(this.obj); // wrap to reference for booleans and numbers for(var prop in obj) { this.obj.should.not.have.ownProperty(prop); } } }, true); /** * Asserts given object has exact keys. Compared to `properties`, `keys` does not accept Object as a argument. * * @name keys * @alias Assertion#key * @memberOf Assertion * @category assertion property * @param {Array|...string} [keys] Keys to check * @example * * ({ a: 10 }).should.have.keys('a'); * ({ a: 10, b: 20 }).should.have.keys('a', 'b'); * ({ a: 10, b: 20 }).should.have.keys([ 'a', 'b' ]); * ({}).should.have.keys(); */ Assertion.add('keys', function(keys) { if(arguments.length > 1) keys = aSlice.call(arguments); else if(arguments.length === 1 && should.type(keys) == should.type.STRING) keys = [keys]; else if(arguments.length === 0) keys = []; keys = keys.map(String); 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(util.formatProp(key)); }, this); // second check for extra keys var extraKeys = []; Object.keys(obj).forEach(function(key) { if(keys.indexOf(key) < 0) { extraKeys.push(util.formatProp(key)); } }); var verb = keys.length === 0 ? 'to be empty' : 'to have ' + (keys.length === 1 ? 'key ' : 'keys '); this.params = {operator: verb + keys.map(util.formatProp).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"); /** * Asserts given object has nested property in depth by path. **On success it change given object to be value of final property**. * * @name propertyByPath * @memberOf Assertion * @category assertion property * @param {Array|...string} properties Properties path to search * @example * * ({ a: {b: 10}}).should.have.propertyByPath('a', 'b').eql(10); */ Assertion.add('propertyByPath', function(properties) { if(arguments.length > 1) properties = aSlice.call(arguments); else if(arguments.length === 1 && typeof properties == 'string') properties = [properties]; else if(arguments.length === 0) properties = []; var allProps = properties.map(util.formatProp); properties = properties.map(String); var obj = should(Object(this.obj)); var foundProperties = []; var currentProperty; while(currentProperty = properties.shift()) { this.params = {operator: 'to have property by path ' + allProps.join(', ') + ' - failed on ' + util.formatProp(currentProperty)}; obj = obj.have.property(currentProperty); foundProperties.push(currentProperty); } this.params = {obj: this.obj, operator: 'to have property by path ' + allProps.join(', ')}; this.obj = obj.obj; }); };