major matcher refactor

This commit is contained in:
Ryan Dy & Rajan Agaskar
2009-10-29 17:03:24 -07:00
parent 8b23698852
commit b67d2a265d
30 changed files with 3319 additions and 3098 deletions

View File

@@ -145,18 +145,18 @@ jasmine.Env.prototype.compareObjects_ = function(a, b, mismatchKeys, mismatchVal
for (var property in b) {
if (!hasKey(a, property) && hasKey(b, property)) {
mismatchKeys.push("expected has key '" + property + "', but missing from <b>actual</b>.");
mismatchKeys.push("expected has key '" + property + "', but missing from actual.");
}
}
for (property in a) {
if (!hasKey(b, property) && hasKey(a, property)) {
mismatchKeys.push("<b>expected</b> missing key '" + property + "', but present in actual.");
mismatchKeys.push("expected missing key '" + property + "', but present in actual.");
}
}
for (property in b) {
if (property == '__Jasmine_been_here_before__') continue;
if (!this.equals_(a[property], b[property], mismatchKeys, mismatchValues)) {
mismatchValues.push("'" + property + "' was<br /><br />'" + (b[property] ? jasmine.util.htmlEscape(b[property].toString()) : b[property]) + "'<br /><br />in expected, but was<br /><br />'" + (a[property] ? jasmine.util.htmlEscape(a[property].toString()) : a[property]) + "'<br /><br />in actual.<br />");
mismatchValues.push("'" + property + "' was '" + (b[property] ? jasmine.util.htmlEscape(b[property].toString()) : b[property]) + "' in expected, but was '" + (a[property] ? jasmine.util.htmlEscape(a[property].toString()) : a[property]) + "' in actual.");
}
}

View File

@@ -4,23 +4,23 @@
* @param actual
* @param {jasmine.NestedResults} results
*/
jasmine.Matchers = function(env, actual, results) {
jasmine.Matchers = function(env, actual, spec) {
this.env = env;
this.actual = actual;
this.passing_message = 'Passed.';
this.results_ = results || new jasmine.NestedResults();
this.spec = spec;
};
jasmine.Matchers.pp = function(str) {
return jasmine.util.htmlEscape(jasmine.pp(str));
};
jasmine.Matchers.prototype.results = function() {
return this.results_;
};
jasmine.Matchers.prototype.report = function(result, failing_message, details) {
this.results_.addResult(new jasmine.ExpectationResult(result, result ? this.passing_message : failing_message, details));
var expectationResult = new jasmine.ExpectationResult({
passed: result,
message: failing_message,
details: details
});
this.spec.addMatcherResult(expectationResult);
return result;
};
@@ -29,57 +29,85 @@ jasmine.Matchers.prototype.report = function(result, failing_message, details) {
*
* @param expected
*/
jasmine.Matchers.prototype.toBe = function(expected) {
return this.report(this.actual === expected, 'Expected<br /><br />' + jasmine.Matchers.pp(expected)
+ '<br /><br />to be the same object as<br /><br />' + jasmine.Matchers.pp(this.actual)
+ '<br />');
jasmine.Matchers.addMatcher = function(matcherName, options) {
jasmine.Matchers.prototype[matcherName] = function () {
jasmine.util.extend(this, options);
var expected = jasmine.util.argsToArray(arguments);
var args = [this.actual].concat(expected);
var result = options.test.apply(this, args);
var message;
if (!result) {
message = options.message.apply(this, args);
}
var expectationResult = new jasmine.ExpectationResult({
matcherName: matcherName,
passed: result,
expected: expected,
actual: this.actual,
message: message
});
this.spec.addMatcherResult(expectationResult);
return result;
};
};
/**
* Matcher that compares the actual to the expected using !==
* toBe: compares the actual to the expected using ===
* @param expected
*/
jasmine.Matchers.prototype.toNotBe = function(expected) {
return this.report(this.actual !== expected, 'Expected<br /><br />' + jasmine.Matchers.pp(expected)
+ '<br /><br />to be a different object from actual, but they were the same.');
};
jasmine.Matchers.addMatcher('toBe', {
test: function (actual, expected) {
return actual === expected;
},
message: function(actual, expected) {
return "Expected " + jasmine.pp(actual) + " to be " + jasmine.pp(expected);
}
});
/**
* Matcher that compares the actual to the expected using common sense equality. Handles Objects, Arrays, etc.
* toNotBe: compares the actual to the expected using !==
* @param expected
*/
jasmine.Matchers.addMatcher('toNotBe', {
test: function (actual, expected) {
return actual !== expected;
},
message: function(actual, expected) {
return "Expected " + jasmine.pp(actual) + " to not be " + jasmine.pp(expected);
}
});
/**
* toEqual: compares the actual to the expected using common sense equality. Handles Objects, Arrays, etc.
*
* @param expected
*/
jasmine.Matchers.prototype.toEqual = function(expected) {
var mismatchKeys = [];
var mismatchValues = [];
var formatMismatches = function(name, array) {
if (array.length == 0) return '';
var errorOutput = '<br /><br />Different ' + name + ':<br />';
for (var i = 0; i < array.length; i++) {
errorOutput += array[i] + '<br />';
}
return errorOutput;
};
return this.report(this.env.equals_(this.actual, expected, mismatchKeys, mismatchValues),
'Expected<br /><br />' + jasmine.Matchers.pp(expected)
+ '<br /><br />but got<br /><br />' + jasmine.Matchers.pp(this.actual)
+ '<br />'
+ formatMismatches('Keys', mismatchKeys)
+ formatMismatches('Values', mismatchValues), {
matcherName: 'toEqual', expected: expected, actual: this.actual
});
};
jasmine.Matchers.addMatcher('toEqual', {
test: function (actual, expected) {
return this.env.equals_(actual, expected);
},
message: function(actual, expected) {
return "Expected " + jasmine.pp(actual) + " to equal " + jasmine.pp(expected);
}
});
/**
* Matcher that compares the actual to the expected using the ! of jasmine.Matchers.toEqual
* toNotEqual: compares the actual to the expected using the ! of jasmine.Matchers.toEqual
* @param expected
*/
jasmine.Matchers.prototype.toNotEqual = function(expected) {
return this.report(!this.env.equals_(this.actual, expected),
'Expected ' + jasmine.Matchers.pp(expected) + ' to not equal ' + jasmine.Matchers.pp(this.actual) + ', but it does.');
};
jasmine.Matchers.addMatcher('toNotEqual', {
test: function (actual, expected) {
return !this.env.equals_(actual, expected);
},
message: function(actual, expected) {
return "Expected " + jasmine.pp(actual) + " to not equal " + jasmine.pp(expected);
}
});
/**
* Matcher that compares the actual to the expected using a regular expression. Constructs a RegExp, so takes
@@ -87,77 +115,166 @@ jasmine.Matchers.prototype.toNotEqual = function(expected) {
*
* @param reg_exp
*/
jasmine.Matchers.prototype.toMatch = function(reg_exp) {
return this.report((new RegExp(reg_exp).test(this.actual)),
'Expected ' + jasmine.Matchers.pp(this.actual) + ' to match ' + reg_exp + '.');
};
jasmine.Matchers.addMatcher('toMatch', {
test: function(actual, expected) {
return new RegExp(expected).test(actual);
},
message: function(actual, expected) {
return actual + " does not match the regular expression " + new RegExp(expected).toString();
}
});
/**
* Matcher that compares the actual to the expected using the boolean inverse of jasmine.Matchers.toMatch
* @param reg_exp
*/
jasmine.Matchers.prototype.toNotMatch = function(reg_exp) {
return this.report((!new RegExp(reg_exp).test(this.actual)),
'Expected ' + jasmine.Matchers.pp(this.actual) + ' to not match ' + reg_exp + '.');
};
jasmine.Matchers.addMatcher('toNotMatch', {
test: function(actual, expected) {
return !(new RegExp(expected).test(actual));
},
message: function(actual, expected) {
return actual + " should not match " + new RegExp(expected).toString();
}
});
/**
* Matcher that compares the acutal to undefined.
*/
jasmine.Matchers.prototype.toBeDefined = function() {
return this.report((this.actual !== undefined),
'Expected a value to be defined but it was undefined.');
};
jasmine.Matchers.addMatcher('toBeDefined', {
test: function(actual) {
return (actual !== undefined);
},
message: function() {
return 'Expected actual to not be undefined.';
}
});
/**
* Matcher that compares the acutal to undefined.
*/
jasmine.Matchers.addMatcher('toBeUndefined', {
test: function(actual) {
return (actual === undefined);
},
message: function(actual) {
return 'Expected ' + jasmine.pp(actual) + ' to be undefined.';
}
});
/**
* Matcher that compares the actual to null.
*
*/
jasmine.Matchers.prototype.toBeNull = function() {
return this.report((this.actual === null),
'Expected a value to be null but it was ' + jasmine.Matchers.pp(this.actual) + '.');
};
jasmine.Matchers.addMatcher('toBeNull', {
test: function(actual) {
return (actual === null);
},
message: function(actual) {
return 'Expected ' + jasmine.pp(actual) + ' to be null.';
}
});
/**
* Matcher that boolean not-nots the actual.
*/
jasmine.Matchers.prototype.toBeTruthy = function() {
return this.report(!!this.actual,
'Expected a value to be truthy but it was ' + jasmine.Matchers.pp(this.actual) + '.');
};
jasmine.Matchers.addMatcher('toBeTruthy', {
test: function(actual) {
return !!actual;
},
message: function() {
return 'Expected actual to be truthy';
}
});
/**
* Matcher that boolean nots the actual.
*/
jasmine.Matchers.prototype.toBeFalsy = function() {
return this.report(!this.actual,
'Expected a value to be falsy but it was ' + jasmine.Matchers.pp(this.actual) + '.');
};
jasmine.Matchers.addMatcher('toBeFalsy', {
test: function(actual) {
return !actual;
},
message: function(actual) {
return 'Expected ' + jasmine.pp(actual) + ' to be falsy';
}
});
/**
* Matcher that checks to see if the acutal, a Jasmine spy, was called.
*/
jasmine.Matchers.prototype.wasCalled = function() {
if (!this.actual || !this.actual.isSpy) {
return this.report(false, 'Expected a spy, but got ' + jasmine.Matchers.pp(this.actual) + '.');
jasmine.Matchers.addMatcher('wasCalled', {
getActual_: function() {
var args = jasmine.util.argsToArray(arguments);
if (args.length > 1) {
throw(new Error('wasCalled does not take arguments, use wasCalledWith'));
}
return args.splice(0, 1)[0];
},
test: function() {
var actual = this.getActual_.apply(this, arguments);
if (!actual || !actual.isSpy) {
return false;
}
return actual.wasCalled;
},
message: function() {
var actual = this.getActual_.apply(this, arguments);
if (!actual || !actual.isSpy) {
return 'Actual is not a spy.';
}
return "Expected spy " + actual.identity + " to have been called.";
}
if (arguments.length > 0) {
return this.report(false, 'wasCalled matcher does not take arguments');
}
return this.report((this.actual.wasCalled),
'Expected spy "' + this.actual.identity + '" to have been called, but it was not.');
};
});
/**
* Matcher that checks to see if the acutal, a Jasmine spy, was not called.
*/
jasmine.Matchers.prototype.wasNotCalled = function() {
if (!this.actual || !this.actual.isSpy) {
return this.report(false, 'Expected a spy, but got ' + jasmine.Matchers.pp(this.actual) + '.');
jasmine.Matchers.addMatcher('wasNotCalled', {
getActual_: function() {
var args = jasmine.util.argsToArray(arguments);
return args.splice(0, 1)[0];
},
test: function() {
var actual = this.getActual_.apply(this, arguments);
if (!actual || !actual.isSpy) {
return false;
}
return !actual.wasCalled;
},
message: function() {
var actual = this.getActual_.apply(this, arguments);
if (!actual || !actual.isSpy) {
return 'Actual is not a spy.';
}
return "Expected spy " + actual.identity + " to not have been called.";
}
return this.report((!this.actual.wasCalled),
'Expected spy "' + this.actual.identity + '" to not have been called, but it was.');
};
});
jasmine.Matchers.addMatcher('wasCalledWith', {
test: function() {
var args = jasmine.util.argsToArray(arguments);
var actual = args.splice(0, 1)[0];
if (!actual || !actual.isSpy) {
return false;
}
return this.env.contains_(actual.argsForCall, args);
},
message: function() {
var args = jasmine.util.argsToArray(arguments);
var actual = args.splice(0, 1)[0];
var message;
if (!actual || !actual.isSpy) {
message = 'Actual is not a spy';
} else {
message = "Expected spy to have been called with " + jasmine.pp(args) + " but was called with " + actual.argsForCall;
}
return message;
}
});
/**
* Matcher that checks to see if the acutal, a Jasmine spy, was called with a set of parameters.
@@ -165,78 +282,89 @@ jasmine.Matchers.prototype.wasNotCalled = function() {
* @example
*
*/
jasmine.Matchers.prototype.wasCalledWith = function() {
if (!this.actual || !this.actual.isSpy) {
return this.report(false, 'Expected a spy, but got ' + jasmine.Matchers.pp(this.actual) + '.', {
matcherName: 'wasCalledWith'
});
}
var args = jasmine.util.argsToArray(arguments);
return this.report(this.env.contains_(this.actual.argsForCall, args),
'Expected ' + jasmine.Matchers.pp(this.actual.argsForCall) + ' to contain ' + jasmine.Matchers.pp(args) + ', but it does not.', {
matcherName: 'wasCalledWith', expected: args, actual: this.actual.argsForCall
});
};
/**
* Matcher that checks that the expected item is an element in the actual Array.
*
* @param {Object} item
*/
jasmine.Matchers.prototype.toContain = function(item) {
return this.report(this.env.contains_(this.actual, item),
'Expected ' + jasmine.Matchers.pp(this.actual) + ' to contain ' + jasmine.Matchers.pp(item) + ', but it does not.', {
matcherName: 'toContain', expected: item, actual: this.actual
});
};
jasmine.Matchers.addMatcher('toContain', {
test: function(actual, expected) {
return this.env.contains_(actual, expected);
},
message: function(actual, expected) {
return 'Expected ' + jasmine.pp(actual) + ' to contain ' + jasmine.pp(expected);
}
});
/**
* Matcher that checks that the expected item is NOT an element in the actual Array.
*
* @param {Object} item
*/
jasmine.Matchers.prototype.toNotContain = function(item) {
return this.report(!this.env.contains_(this.actual, item),
'Expected ' + jasmine.Matchers.pp(this.actual) + ' not to contain ' + jasmine.Matchers.pp(item) + ', but it does.');
};
jasmine.Matchers.addMatcher('toNotContain', {
test: function(actual, expected) {
return !this.env.contains_(actual, expected);
},
message: function(actual, expected) {
return 'Expected ' + jasmine.pp(actual) + ' to not contain ' + jasmine.pp(expected);
}
});
jasmine.Matchers.prototype.toBeLessThan = function(expected) {
return this.report(this.actual < expected,
'Expected ' + jasmine.Matchers.pp(this.actual) + ' to be less than ' + jasmine.Matchers.pp(expected) + ', but it was not.');
};
jasmine.Matchers.addMatcher('toBeLessThan', {
test: function(actual, expected) {
return actual < expected;
},
message: function(actual, expected) {
return 'Expected ' + jasmine.pp(actual) + ' to be less than ' + jasmine.pp(expected);
}
});
jasmine.Matchers.prototype.toBeGreaterThan = function(expected) {
return this.report(this.actual > expected,
'Expected ' + jasmine.Matchers.pp(this.actual) + ' to be greater than ' + jasmine.Matchers.pp(expected) + ', but it was not.');
};
jasmine.Matchers.addMatcher('toBeGreaterThan', {
test: function(actual, expected) {
return actual > expected;
},
message: function(actual, expected) {
return 'Expected ' + jasmine.pp(actual) + ' to be greater than ' + jasmine.pp(expected);
}
});
/**
* Matcher that checks that the expected exception was thrown by the actual.
*
* @param {String} expectedException
*/
jasmine.Matchers.prototype.toThrow = function(expectedException) {
var exception = null;
try {
this.actual();
} catch (e) {
exception = e;
}
if (expectedException !== undefined) {
if (exception == null) {
return this.report(false, "Expected function to throw " + jasmine.Matchers.pp(expectedException) + ", but it did not.");
jasmine.Matchers.addMatcher('toThrow', {
getException_: function(actual, expected) {
var exception;
if (typeof actual != 'function') {
throw new Error('Actual is not a function');
}
try {
actual();
} catch (e) {
exception = e;
}
return exception;
},
test: function(actual, expected) {
var result = false;
var exception = this.getException_(actual, expected);
if (exception) {
result = (expected === undefined || this.env.equals_(exception.message || exception, expected.message || expected));
}
return result;
},
message: function(actual, expected) {
var exception = this.getException_(actual, expected);
if (exception && (expected === undefined || !this.env.equals_(exception.message || exception, expected.message || expected))) {
return ["Expected function to throw", expected.message || expected, ", but it threw", exception.message || exception ].join(' ');
} else {
return "Expected function to throw an exception.";
}
return this.report(
this.env.equals_(
exception.message || exception,
expectedException.message || expectedException),
"Expected function to throw " + jasmine.Matchers.pp(expectedException) + ", but it threw " + jasmine.Matchers.pp(exception) + ".");
} else {
return this.report(exception != null, "Expected function to throw an exception, but it did not.");
}
};
});
jasmine.Matchers.Any = function(expectedClass) {
this.expectedClass = expectedClass;

View File

@@ -57,8 +57,12 @@ jasmine.Spec.prototype.addToQueue = function (block) {
}
};
jasmine.Spec.prototype.addMatcherResult = function(result) {
this.results_.addResult(result);
};
jasmine.Spec.prototype.expect = function(actual) {
return new (this.getMatchersClass_())(this.env, actual, this.results_);
return new (this.getMatchersClass_())(this.env, actual, this);
};
jasmine.Spec.prototype.waits = function(timeout) {
@@ -74,7 +78,11 @@ jasmine.Spec.prototype.waitsFor = function(timeout, latchFunction, timeoutMessag
};
jasmine.Spec.prototype.fail = function (e) {
this.results_.addResult(new jasmine.ExpectationResult(false, e ? jasmine.util.formatException(e) : null, null));
var expectationResult = new jasmine.ExpectationResult({
passed: false,
message: e ? jasmine.util.formatException(e) : 'Exception'
});
this.results_.addResult(expectationResult);
};
jasmine.Spec.prototype.getMatchersClass_ = function() {

View File

@@ -51,12 +51,18 @@ jasmine.MessageResult = function(text) {
this.trace = new Error(); // todo: test better
};
jasmine.ExpectationResult = function(passed, message, details) {
jasmine.ExpectationResult = function(params) {
this.type = 'ExpectationResult';
this.passed_ = passed;
this.message = message;
this.details = details;
this.trace = new Error(message); // todo: test better
this.matcherName = params.matcherName;
this.passed_ = params.passed;
this.expected = params.expected;
this.actual = params.actual;
/** @deprecated */
this.details = params.details;
this.message = this.passed_ ? 'Passed.' : params.message;
this.trace = this.passed_ ? '' : new Error(this.message);
};
jasmine.ExpectationResult.prototype.passed = function () {
@@ -524,4 +530,4 @@ jasmine.include = function(url, opt_global) {
return eval(xhr.responseText);
}
};
};

View File

@@ -57,3 +57,8 @@ jasmine.util.argsToArray = function(args) {
return arrayOfArgs;
};
jasmine.util.extend = function(destination, source) {
for (var property in source) destination[property] = source[property];
return destination;
};