Skip to content

Commit

Permalink
Merge e7843c2 into bf430b9
Browse files Browse the repository at this point in the history
  • Loading branch information
papandreou committed Sep 30, 2014
2 parents bf430b9 + e7843c2 commit 8b7bbc3
Show file tree
Hide file tree
Showing 6 changed files with 388 additions and 154 deletions.
40 changes: 35 additions & 5 deletions lib/Unexpected.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
/*global window*/
var Assertion = require('./Assertion');
var utils = require('./utils');
var magicpen = require('magicpen');
var truncateStack = utils.truncateStack;
var extend = utils.extend;
var levenshteinDistance = utils.levenshteinDistance;
var leven = require('leven');
var cloneError = utils.cloneError;

var anyType = {
Expand All @@ -19,6 +20,21 @@ var anyType = {
},
diff: function (actual, expected, output, diff, inspect) {
return null;
},
is: function (typeOrTypeName) {
var typeName;
if (typeof typeOrTypeName === 'string') {
typeName = typeOrTypeName;
} else {
typeName = typeOrTypeName.name;
}
if (this.name === typeName) {
return true;
} else if (this.baseType) {
return this.baseType.is(typeName);
} else {
return false;
}
}
};

Expand Down Expand Up @@ -257,7 +273,14 @@ Unexpected.prototype.addType = function (type) {
return inspect.apply(this, arguments);
}
};
this.types.unshift(extendedType);
if (extendedType.identify === false) {
extendedType.identify = function () {
return false;
};
this.types.push(extendedType);
} else {
this.types.unshift(extendedType);
}

return this.expect;
};
Expand Down Expand Up @@ -325,14 +348,21 @@ Unexpected.prototype.setErrorMessage = function (err) {
var message = err.output.clone().append(err.output);

if (err.createDiff) {
var comparison = err.createDiff();
var that = this;
var comparison = err.createDiff(message.clone(), function (actual, expected) {
return that.diff(actual, expected);
}, function (v, depth) {
return that.inspect(v, depth || Infinity);
}, function (actual, expected) {
return that.equal(actual, expected);
});
if (comparison) {
message.nl(2).blue('Diff:').nl(2).append(comparison.diff);
}
}

if (outputFormat === 'html') {
outputFormat = 'text';
outputFormat = typeof window !== 'undefined' && window.mochaPhantomJS ? 'ansi' : 'text';
err.htmlMessage = message.toString('html');
}
err.output = message;
Expand Down Expand Up @@ -448,7 +478,7 @@ Unexpected.prototype.expect = function expect(subject, testDescriptionString) {
assertionsWithScore.push({
type: type,
assertion: assertion,
score: typeMatchBonus - levenshteinDistance(testDescriptionString, assertion)
score: typeMatchBonus - leven(testDescriptionString, assertion)
});
});
}, this);
Expand Down
209 changes: 143 additions & 66 deletions lib/assertions.js
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ module.exports = function (expect) {
expect(subject, '[not] to be ' + this.alternations[0], this.alternations[1]);
});

expect.addAssertion(['string', 'array', 'object'], 'to be (the empty|an empty|a non-empty) (string|array|object)', function (expect, subject) {
expect.addAssertion(['string', 'array-like', 'object'], 'to be (the empty|an empty|a non-empty) (string|array|object)', function (expect, subject) {
expect(subject, 'to be a', this.alternations[1]);
expect(subject, this.alternations[0] === 'a non-empty' ? 'not to be empty' : 'to be empty');
});
Expand All @@ -90,8 +90,7 @@ module.exports = function (expect) {
expect(String(subject).match(regexp), '[not] to be truthy');
} catch (e) {
if (e._isUnexpected && this.flags.not) {
e.createDiff = function () {
var output = expect.output.clone();
e.createDiff = function (output) {
var lastIndex = 0;
function flushUntilIndex(i) {
if (i > lastIndex) {
Expand Down Expand Up @@ -148,7 +147,7 @@ module.exports = function (expect) {
});
} catch (e) {
if (e._isUnexpected) {
e.createDiff = function () {
e.createDiff = function (output, diff) {
var expected = extend({}, properties);
var actual = {};
for (var propertyName in subject) {
Expand All @@ -159,7 +158,7 @@ module.exports = function (expect) {
actual[propertyName] = subject[propertyName];
}
}
return expect.diff(actual, expected);
return diff(actual, expected);
};
}
expect.fail(e);
Expand Down Expand Up @@ -209,56 +208,54 @@ module.exports = function (expect) {
}
});

expect.addAssertion(['string', 'array'], '[not] to contain', function (expect, subject) {
expect.addAssertion('string', '[not] to contain', function (expect, subject) {
var args = Array.prototype.slice.call(arguments, 2);

if ('string' === typeof subject) {
try {
args.forEach(function (arg) {
expect(subject.indexOf(arg) !== -1, '[not] to be truthy');
});
} catch (e) {
if (e._isUnexpected && this.flags.not) {
e.createDiff = function () {
var output = expect.output.clone();
var lastIndex = 0;
function flushUntilIndex(i) {
if (i > lastIndex) {
output.text(subject.substring(lastIndex, i));
lastIndex = i;
}
try {
args.forEach(function (arg) {
expect(subject.indexOf(arg) !== -1, '[not] to be truthy');
});
} catch (e) {
if (e._isUnexpected && this.flags.not) {
e.createDiff = function (output) {
var lastIndex = 0;
function flushUntilIndex(i) {
if (i > lastIndex) {
output.text(subject.substring(lastIndex, i));
lastIndex = i;
}
subject.replace(new RegExp(args.map(function (arg) {
return utils.escapeRegExpMetaChars(String(arg));
}).join('|'), 'g'), function ($0, index) {
flushUntilIndex(index);
lastIndex += $0.length;
output.diffRemovedHighlight($0);
});
flushUntilIndex(subject.length);
return {diff: output};
};
}
expect.fail(e);
}
subject.replace(new RegExp(args.map(function (arg) {
return utils.escapeRegExpMetaChars(String(arg));
}).join('|'), 'g'), function ($0, index) {
flushUntilIndex(index);
lastIndex += $0.length;
output.diffRemovedHighlight($0);
});
flushUntilIndex(subject.length);
return {diff: output};
};
}
} else {
// array
try {
args.forEach(function (arg) {
expect(subject && subject.some(function (item) { return expect.equal(item, arg); }), '[not] to be truthy');
});
} catch (e) {
if (e._isUnexpected && this.flags.not) {
e.createDiff = function () {
return expect.diff(subject, subject.filter(function (item) {
return !args.some(function (arg) {
return expect.equal(item, arg);
});
}));
};
}
expect.fail(e);
expect.fail(e);
}
});

expect.addAssertion('array-like', '[not] to contain', function (expect, subject) {
var args = Array.prototype.slice.call(arguments, 2);
try {
args.forEach(function (arg) {
expect(subject && Array.prototype.some.call(subject, function (item) { return expect.equal(item, arg); }), '[not] to be truthy');
});
} catch (e) {
if (e._isUnexpected && this.flags.not) {
e.createDiff = function (output, diff, inspect, equal) {
return diff(subject, Array.prototype.filter.call(subject, function (item) {
return !args.some(function (arg) {
return equal(item, arg);
});
}));
};
}
expect.fail(e);
}
});

Expand Down Expand Up @@ -304,8 +301,8 @@ module.exports = function (expect) {
expect(expect.equal(value, subject), '[not] to be true');
} catch (e) {
if (!this.flags.not && e._isUnexpected) {
e.createDiff = function () {
return expect.diff(subject, value);
e.createDiff = function (output, diff) {
return diff(subject, value);
};
}
expect.fail(e);
Expand Down Expand Up @@ -368,7 +365,7 @@ module.exports = function (expect) {
this.errorMode = 'bubble';

var errors = {};
Object.keys(subject).forEach(function (key, index) {
expect.findTypeOf(subject).getKeys(subject).forEach(function (key, index) {
try {
if (typeof extraArgs[0] === 'function') {
extraArgs[0](subject[key], index);
Expand Down Expand Up @@ -411,21 +408,20 @@ module.exports = function (expect) {
}
});

expect.addAssertion('array', 'to be (a|an) [non-empty] array whose items satisfy', function (expect, subject) { // ...
expect.addAssertion('array-like', 'to be (a|an) [non-empty] array whose items satisfy', function (expect, subject) { // ...
var extraArgs = Array.prototype.slice.call(arguments, 2);
if (extraArgs.length === 0) {
throw new Error('Assertion "' + this.testDescription + '" expects a third argument');
}
this.errorMode = 'nested';
expect(subject, 'to be an array');
if (this.flags['non-empty']) {
expect(subject, 'to be non-empty');
}
this.errorMode = 'bubble';
expect.apply(expect, [subject, 'to be a map whose values satisfy'].concat(extraArgs));
expect.apply(expect, [subject, 'to be an map whose values satisfy'].concat(extraArgs));
});

expect.addAssertion('array', 'to be (a|an) [non-empty] array of (strings|numbers|booleans|arrays|objects|functions|regexps|regexes|regular expressions)', function (expect, subject) {
expect.addAssertion('array-like', 'to be (a|an) [non-empty] array of (strings|numbers|booleans|arrays|objects|functions|regexps|regexes|regular expressions)', function (expect, subject) {
if (this.flags['non-empty']) {
expect(subject, 'to be non-empty');
}
Expand Down Expand Up @@ -522,19 +518,100 @@ module.exports = function (expect) {
this.errorMode = 'bubble'; // to satisfy assertion 'to be a number' => to be a number
expect.apply(expect, Array.prototype.slice.call(arguments, 1));
} else if (typeof value === 'function') {
// FIXME: If expect.fn, it should be possible to produce a better error message
// FIXME: If expect.it, it should be possible to produce a better error message
value(subject);
} else if (isRegExp(value)) {
expect(subject, 'to match', value);
} else {
var type = expect.findTypeOf(subject, value);
if (type.name === 'object' || type.name === 'array') {
expect(subject, 'to be an object');
Object.keys(value).forEach(function (key) {
expect(subject[key], 'to [exhaustively] satisfy', value[key]);
});
if (this.flags.exhaustively) {
expect(subject, 'to only have keys', Object.keys(value));
if (type.is('array-like') || type.is('object')) {
try {
expect(subject, 'to be an object');
type.getKeys(value).forEach(function (key) {
expect(subject[key], 'to [exhaustively] satisfy', value[key]);
});
if (this.flags.exhaustively) {
expect(subject, 'to only have keys', Object.keys(value));
}
} catch (e) {
if (e._isUnexpected) {
var flags = this.flags;
e.createDiff = function (output, diff, inspect, equal) {
var result = {
diff: output,
inline: true
};

var keyIndex = {};
Object.keys(subject).concat(Object.keys(value)).forEach(function (key) {
if (!(key in result)) {
keyIndex[key] = key;
}
});

var keys = Object.keys(keyIndex);

output.text('{').nl().indentLines();

keys.forEach(function (key, index) {
output.i().block(function () {
var valueOutput;
var annotation = output.clone();
var conflicting;
try {
expect(subject[key], 'to [exhaustively] satisfy', value[key]);
} catch (e) {
conflicting = e;
}
var isInlineDiff = false;
if (conflicting) {
if (!(key in value)) {
if (flags.exhaustively) {
annotation.error('should be removed');
}
} else {
var keyDiff = conflicting.createDiff && conflicting.createDiff(output.clone(), diff, inspect, equal);
if (!keyDiff || (keyDiff && !keyDiff.inline)) {
annotation.error('should satisfy: ')
.block(inspect(value[key]));

if (keyDiff) {
annotation.nl().append(keyDiff.diff);
}
} else {
isInlineDiff = true;
valueOutput = keyDiff.diff;
}
}
}

var last = index === keys.length - 1;
if (!valueOutput) {
valueOutput = inspect(subject[key], conflicting ? Infinity : 1);
}

if (/^[a-z\$\_][a-z0-9\$\_]*$/i.test(key)) {
this.key(key);
} else {
this.append(inspect(key));
}
this.text(':').sp();
valueOutput.text(last ? '' : ',');
if (isInlineDiff) {
this.append(valueOutput);
} else {
this.block(valueOutput);
}
this.block(annotation.prependLinesWith('error', ' // '));
}).nl();
});

output.outdentLines().text('}');

return result;
};
}
expect.fail(e);
}
} else {
expect(subject, 'to equal', value);
Expand Down
Loading

0 comments on commit 8b7bbc3

Please sign in to comment.