Skip to content

Commit

Permalink
Now nested assertion can contribute message for parent assertions
Browse files Browse the repository at this point in the history
  • Loading branch information
btd committed Oct 19, 2014
1 parent 1060c8b commit c848516
Show file tree
Hide file tree
Showing 11 changed files with 100 additions and 79 deletions.
52 changes: 37 additions & 15 deletions lib/assertion.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,14 @@ function Assertion(obj, format) {
Assertion.add = function(name, f, isGetter) {
var prop = {enumerable: true};
prop[isGetter ? 'get' : 'value'] = function() {
var context = this.nested = new Assertion(this.obj, this.format);
context.copy = context.copyIfMissing;
var context = new Assertion(this.obj, this.format);
context.anyOne = this.anyOne;

try {
f.apply(context, arguments);
} catch(e) {
//copy data from sub context to this
this.copy(context);
this.params = context.params;

//check for fail
if(e instanceof AssertionError) {
Expand All @@ -33,19 +32,29 @@ Assertion.add = function(name, f, isGetter) {
this.negate = false;
return this;
}
this.assert(false);

this.nestedErrorMessage = e.message;
//positive fail
this.fail();
}
// throw if it is another exception
throw e;
}
//copy data from sub context to this
this.copy(context);
this.params = context.params;

//negative pass
if(this.negate) {
this.assert(false);

context.negate = true;
this.nestedErrorMessage = context.params.message ? context.params.message : context.getMessage();
this.fail();
}

this.obj = context.obj;
this.negate = false;

//positive pass
return this;
};

Expand All @@ -58,6 +67,15 @@ Assertion.alias = function(from, to) {
Object.defineProperty(Assertion.prototype, to, desc);
};

var indent = ' ';
function prependIndent(line) {
return indent + line;
}

function indentLines(text) {
return text.split('\n').map(prependIndent).join('\n');
}

Assertion.prototype = {
constructor: Assertion,

Expand All @@ -72,6 +90,10 @@ Assertion.prototype = {
generatedMessage = true;
}

if(this.nestedErrorMessage && msg != this.nestedErrorMessage) {
msg = msg + '\n' +indentLines(this.nestedErrorMessage);
}

var err = new AssertionError({
message: msg,
actual: this.obj,
Expand All @@ -86,19 +108,19 @@ Assertion.prototype = {
throw err;
},

getMessage: function() {
var actual = 'obj' in this.params ? this.params.obj : this.format(this.obj);
var expected = 'expected' in this.params ? ' ' + this.format(this.params.expected) : '';

return 'expected ' + actual + (this.negate ? ' not ' : ' ') + this.params.operator + (expected);
fail: function() {
return this.assert(false);
},

copy: function(other) {
this.params = other.params;
formattedObj: function() {
return this.format(this.obj);
},

copyIfMissing: function(other) {
if(!this.params) this.params = other.params;
getMessage: function() {
var actual = 'obj' in this.params ? this.params.obj : this.formattedObj();
var expected = 'expected' in this.params ? ' ' + this.format(this.params.expected) : '';

return 'expected ' + actual + (this.negate ? ' not ' : ' ') + this.params.operator + (expected);
},


Expand Down
29 changes: 12 additions & 17 deletions lib/ext/match.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ module.exports = function(should, Assertion) {
var i = should.format;

Assertion.add('match', function(other, description) {
this.params = { operator: 'to match ' + i(other), message: description };
this.params = {operator: 'to match ' + i(other), message: description};

if(!eql(this.obj, other)) {
if(util.isRegExp(other)) { // something - regex
Expand All @@ -29,37 +29,32 @@ module.exports = function(should, Assertion) {
var notMatchedProps = [], matchedProps = [];
util.forOwn(this.obj, function(value, name) {
if(other.exec(value)) matchedProps.push(util.formatProp(name));
else notMatchedProps.push(util.formatProp(name) + ' (' + i(value) +')');
else notMatchedProps.push(util.formatProp(name) + ' (' + i(value) + ')');
}, this);

if(notMatchedProps.length)
this.params.operator += '\n\tnot matched properties: ' + notMatchedProps.join(', ');
this.params.operator += '\n not matched properties: ' + notMatchedProps.join(', ');
if(matchedProps.length)
this.params.operator += '\n\tmatched properties: ' + matchedProps.join(', ');
this.params.operator += '\n matched 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;
}

res = other(this.obj);

if(res instanceof Assertion) {
this.params.operator += '\n\t' + res.getMessage();
this.params.operator += '\n ' + 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 = [];
notMatchedProps = [];
matchedProps = [];

util.forOwn(other, function(value, key) {
try {
Expand All @@ -75,9 +70,9 @@ module.exports = function(should, Assertion) {
}, this);

if(notMatchedProps.length)
this.params.operator += '\n\tnot matched properties: ' + notMatchedProps.join(', ');
this.params.operator += '\n not matched properties: ' + notMatchedProps.join(', ');
if(matchedProps.length)
this.params.operator += '\n\tmatched properties: ' + matchedProps.join(', ');
this.params.operator += '\n matched properties: ' + matchedProps.join(', ');

this.assert(notMatchedProps.length == 0);
} else {
Expand All @@ -87,7 +82,7 @@ module.exports = function(should, Assertion) {
});

Assertion.add('matchEach', function(other, description) {
this.params = { operator: 'to match each ' + i(other), message: description };
this.params = {operator: 'to match each ' + i(other), message: description};

var f = other;

Expand Down
2 changes: 1 addition & 1 deletion lib/ext/number.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ module.exports = function(should, Assertion) {
Assertion.add('Infinity', function() {
this.params = { operator: 'to be Infinity' };

this.is.a.Number
this.obj.should.be.a.Number
.and.not.a.NaN
.and.assert(!isFinite(this.obj));
}, true);
Expand Down
40 changes: 22 additions & 18 deletions lib/ext/property.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,13 @@ module.exports = function(should, Assertion) {
name = String(name);

this.params = {
operator:"to have enumerable property " + util.formatProp(name)
operator: "to have enumerable property " + util.formatProp(name)
};

this.assert(this.obj.propertyIsEnumerable(name));

if(arguments.length > 1){
this.params.operator += " equal to "+i(val);
if(arguments.length > 1) {
this.params.operator += " equal to " + i(val);
this.assert(eql(val, this.obj[name]));
}
});
Expand Down Expand Up @@ -69,9 +69,9 @@ module.exports = function(should, Assertion) {
}

var operator = (props.length === 1 ?
'to have property ' : 'to have '+(this.anyOne? 'any of ' : '')+'properties ') + props.join(', ');
'to have property ' : 'to have ' + (this.anyOne ? 'any of ' : '') + 'properties ') + props.join(', ');

this.params = { operator: operator };
this.params = {obj: this.formattedObj(), operator: operator};

//check that all properties presented
//or if we request one of them that at least one them presented
Expand All @@ -98,9 +98,9 @@ module.exports = function(should, Assertion) {
}

operator = (props.length === 1 ?
'to have property ' : 'to have '+(this.anyOne? 'any of ' : '')+'properties ') + props.join(', ');
'to have property ' : 'to have ' + (this.anyOne ? 'any of ' : '') + 'properties ') + props.join(', ');

this.params = { operator: operator };
this.params = {obj: this.formattedObj(), operator: operator};

//if there is no not matched values
//or there is at least one matched
Expand All @@ -118,7 +118,11 @@ module.exports = function(should, Assertion) {

Assertion.add('ownProperty', function(name, description) {
name = String(name);
this.params = { operator: 'to have own property ' + util.formatProp(name), message: description };
this.params = {
obj: this.formattedObj(),
operator: 'to have own property ' + util.formatProp(name),
message: description
};

this.assert(hasOwnProperty.call(this.obj, name));

Expand All @@ -128,21 +132,21 @@ module.exports = function(should, Assertion) {
Assertion.alias('ownProperty', 'hasOwnProperty');

Assertion.add('empty', function() {
this.params = { operator: 'to be empty' };
this.params = {operator: 'to be empty'};

if(util.isString(this.obj) || util.isArray(this.obj) || util.isArguments(this.obj)) {
this.have.property('length', 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.have.not.ownProperty(prop);
this.obj.should.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 === 1 && util.isString(keys)) keys = [keys];
else if(arguments.length === 0) keys = [];

keys = keys.map(String);
Expand All @@ -165,9 +169,9 @@ module.exports = function(should, Assertion) {
});

var verb = keys.length === 0 ? 'to be empty' :
'to have ' + (keys.length === 1 ? 'key ' : 'keys ');
'to have ' + (keys.length === 1 ? 'key ' : 'keys ');

this.params = { operator: verb + keys.map(util.formatProp).join(', ')};
this.params = {operator: verb + keys.map(util.formatProp).join(', ')};

if(missingKeys.length > 0)
this.params.operator += '\n\tmissing keys: ' + missingKeys.join(', ');
Expand All @@ -182,10 +186,10 @@ module.exports = function(should, Assertion) {

Assertion.add('propertyByPath', function(properties) {
if(arguments.length > 1) properties = aSlice.call(arguments);
else if(arguments.length === 1 && util.isString(properties)) properties = [ properties ];
else if(arguments.length === 1 && util.isString(properties)) properties = [properties];
else if(arguments.length === 0) properties = [];

var allProps = properties.map(util.formatProp);
var allProps = properties.map(util.formatProp);

properties = properties.map(String);

Expand All @@ -195,12 +199,12 @@ module.exports = function(should, Assertion) {

var currentProperty;
while(currentProperty = properties.shift()) {
this.params = { operator: 'to have property by path ' + allProps.join(', ') + ' - failed on ' + util.formatProp(currentProperty) };
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 = { operator: 'to have property by path ' + allProps.join(', ') };
this.params = {obj: this.formattedObj(), operator: 'to have property by path ' + allProps.join(', ')};

this.obj = obj.obj;
});
Expand Down
6 changes: 2 additions & 4 deletions lib/should.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,8 @@
*/


var util = require('./util'),
inspect = util.inspect;


var util = require('./util');
var inspect = require('./inspect').inspect;
var warn = require('./warn');

/**
Expand Down
12 changes: 7 additions & 5 deletions lib/warn.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,24 @@ function generateDeprecated(lines) {
return function(show) {
if(!show) return;

lines.forEach(function(line) {
console.log(WARN, line);
lines.concat(sharedPart).forEach(function(line) {
console.warn(WARN, line);
});
console.log(WARN, 'To disable any warnings add should.warn = false');
}
}

var sharedPart = [
'To disable any warnings add \u001b[33mshould.warn = false\u001b[39m',
'If you think that is not right, raise issue on github https://github.com/shouldjs/should.js/issues'
];

exports.staticShouldUnWrap = generateDeprecated([
'Static version of should was called with primitive type wrapper like should(new Number(10))',
'usually that is unexpected, but if it is not raise an issue at github',
'current version will unwrap it to assert on primitive value for you',
'but that will be changed in future versions, make sure you know what are you doing'
]);

exports.nonStrictEql = generateDeprecated([
'Strict version of eql return different result for this comparison',
'usually it is not right, but if it is ok raise an issue at github',
'it means that e.g { a: 10 } is equal to { a: "10" }, make sure it is expected'
]);
4 changes: 2 additions & 2 deletions test/ext/error.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ describe('error', function() {

err(function(){
(function(){ throw error; }).should.throw(Error, { a: 11 });
}, "expected [Function] to throw exception: expected [Error] to match { a: 11 }\n\tnot matched properties: a (10)");
}, "expected [Function] to throw exception: expected [Error] to match { a: 11 }\n not matched properties: a (10)");
});

it('test .throw(properties) with matching error', function() {
Expand All @@ -121,6 +121,6 @@ describe('error', function() {

err(function(){
(function(){ throw error; }).should.throw({ a: 11 });
}, "expected [Function] to throw exception: expected [Error] to match { a: 11 }\n\tnot matched properties: a (10)");
}, "expected [Function] to throw exception: expected [Error] to match { a: 11 }\n not matched properties: a (10)");
});
});
Loading

0 comments on commit c848516

Please sign in to comment.