123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370 |
- /*
- * 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];
- if(typeof arg === '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) {
- should(obj).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) {
- should(this.obj).have.property('length', 0);
- } else {
- var obj = Object(this.obj); // wrap to reference for booleans and numbers
- for(var prop in obj) {
- should(this.obj).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 && typeof keys === '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;
- });
- };
|