215 lines
5.9 KiB
JavaScript
215 lines
5.9 KiB
JavaScript
/*!
|
|
* Should
|
|
* Copyright(c) 2010-2014 TJ Holowaychuk <tj@vision-media.ca>
|
|
* 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);
|
|
}
|
|
});
|
|
|
|
}; |