Squashed matchers refactor - matchers now unit-testable apart from Expectation and Spec.
This commit is contained in:
32
src/core/Any.js
Normal file
32
src/core/Any.js
Normal file
@@ -0,0 +1,32 @@
|
||||
getJasmineRequireObj().Any = function() {
|
||||
|
||||
function Any(expectedObject) {
|
||||
this.expectedObject = expectedObject;
|
||||
}
|
||||
|
||||
Any.prototype.jasmineMatches = function(other) {
|
||||
if (this.expectedObject == String) {
|
||||
return typeof other == 'string' || other instanceof String;
|
||||
}
|
||||
|
||||
if (this.expectedObject == Number) {
|
||||
return typeof other == 'number' || other instanceof Number;
|
||||
}
|
||||
|
||||
if (this.expectedObject == Function) {
|
||||
return typeof other == 'function' || other instanceof Function;
|
||||
}
|
||||
|
||||
if (this.expectedObject == Object) {
|
||||
return typeof other == 'object';
|
||||
}
|
||||
|
||||
return other instanceof this.expectedObject;
|
||||
};
|
||||
|
||||
Any.prototype.jasmineToString = function() {
|
||||
return '<jasmine.any(' + this.expectedClass + ')>';
|
||||
};
|
||||
|
||||
return Any;
|
||||
};
|
||||
170
src/core/Env.js
170
src/core/Env.js
@@ -29,18 +29,24 @@ getJasmineRequireObj().Env = function(j$) {
|
||||
this.nextSuiteId_ = 0;
|
||||
this.equalityTesters_ = [];
|
||||
|
||||
// wrap matchers
|
||||
this.matchersClass = function() {
|
||||
j$.Matchers.apply(this, arguments);
|
||||
var customEqualityTesters = [];
|
||||
this.addCustomEqualityTester = function(tester) {
|
||||
customEqualityTesters.push(tester);
|
||||
};
|
||||
j$.util.inherit(this.matchersClass, j$.Matchers);
|
||||
|
||||
j$.Matchers.wrapInto_(j$.Matchers.prototype, this.matchersClass);
|
||||
j$.Expectation.addCoreMatchers(j$.matchers);
|
||||
|
||||
var expectationFactory = function(actual, spec) {
|
||||
var expect = new (self.matchersClass)(self, actual, spec);
|
||||
expect.not = new (self.matchersClass)(self, actual, spec, true);
|
||||
return expect;
|
||||
return j$.Expectation.Factory({
|
||||
util: j$.matchersUtil,
|
||||
customEqualityTesters: customEqualityTesters,
|
||||
actual: actual,
|
||||
addExpectationResult: addExpectationResult
|
||||
});
|
||||
|
||||
function addExpectationResult(passed, result) {
|
||||
return spec.addExpectationResult(passed, result);
|
||||
}
|
||||
};
|
||||
|
||||
var specStarted = function(spec) {
|
||||
@@ -68,7 +74,7 @@ getJasmineRequireObj().Env = function(j$) {
|
||||
};
|
||||
};
|
||||
|
||||
var specConstructor = j$.Spec;
|
||||
var specConstructor = j$.Spec; // TODO: inline this
|
||||
|
||||
var getSpecName = function(spec, currentSuite) {
|
||||
return currentSuite.getFullName() + ' ' + spec.description + '.';
|
||||
@@ -147,6 +153,8 @@ getJasmineRequireObj().Env = function(j$) {
|
||||
|
||||
function specResultCallback(result) {
|
||||
self.removeAllSpies();
|
||||
j$.Expectation.resetMatchers();
|
||||
customEqualityTesters.length = 0;
|
||||
self.clock.uninstall();
|
||||
self.currentSpec = null;
|
||||
self.reporter.specDone(result);
|
||||
@@ -191,15 +199,8 @@ getJasmineRequireObj().Env = function(j$) {
|
||||
};
|
||||
}
|
||||
|
||||
//TODO: shim Spec addMatchers behavior into Env. Should be rewritten to remove globals, etc.
|
||||
Env.prototype.addMatchers = function(matchersPrototype) {
|
||||
var parent = this.matchersClass;
|
||||
var newMatchersClass = function() {
|
||||
parent.apply(this, arguments);
|
||||
};
|
||||
j$.util.inherit(newMatchersClass, parent);
|
||||
j$.Matchers.wrapInto_(matchersPrototype, newMatchersClass);
|
||||
this.matchersClass = newMatchersClass;
|
||||
Env.prototype.addMatchers = function(matchersToAdd) {
|
||||
j$.Expectation.addMatchers(matchersToAdd);
|
||||
};
|
||||
|
||||
Env.prototype.version = function() {
|
||||
@@ -333,138 +334,5 @@ getJasmineRequireObj().Env = function(j$) {
|
||||
return this.topSuite;
|
||||
};
|
||||
|
||||
Env.prototype.compareRegExps_ = function(a, b, mismatchKeys, mismatchValues) {
|
||||
if (a.source != b.source)
|
||||
mismatchValues.push("expected pattern /" + b.source + "/ is not equal to the pattern /" + a.source + "/");
|
||||
|
||||
if (a.ignoreCase != b.ignoreCase)
|
||||
mismatchValues.push("expected modifier i was" + (b.ignoreCase ? " " : " not ") + "set and does not equal the origin modifier");
|
||||
|
||||
if (a.global != b.global)
|
||||
mismatchValues.push("expected modifier g was" + (b.global ? " " : " not ") + "set and does not equal the origin modifier");
|
||||
|
||||
if (a.multiline != b.multiline)
|
||||
mismatchValues.push("expected modifier m was" + (b.multiline ? " " : " not ") + "set and does not equal the origin modifier");
|
||||
|
||||
if (a.sticky != b.sticky)
|
||||
mismatchValues.push("expected modifier y was" + (b.sticky ? " " : " not ") + "set and does not equal the origin modifier");
|
||||
|
||||
return (mismatchValues.length === 0);
|
||||
};
|
||||
|
||||
Env.prototype.compareObjects_ = function(a, b, mismatchKeys, mismatchValues) {
|
||||
if (a.__Jasmine_been_here_before__ === b && b.__Jasmine_been_here_before__ === a) {
|
||||
return true;
|
||||
}
|
||||
|
||||
a.__Jasmine_been_here_before__ = b;
|
||||
b.__Jasmine_been_here_before__ = a;
|
||||
|
||||
var hasKey = function(obj, keyName) {
|
||||
return obj !== null && !j$.util.isUndefined(obj[keyName]);
|
||||
};
|
||||
|
||||
for (var property in b) {
|
||||
if (!hasKey(a, property) && hasKey(b, property)) {
|
||||
mismatchKeys.push("expected has key '" + property + "', but missing from actual.");
|
||||
}
|
||||
}
|
||||
for (property in a) {
|
||||
if (!hasKey(b, property) && hasKey(a, property)) {
|
||||
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 '" + (b[property] ? j$.util.htmlEscape(b[property].toString()) : b[property]) + "' in expected, but was '" + (a[property] ? j$.util.htmlEscape(a[property].toString()) : a[property]) + "' in actual.");
|
||||
}
|
||||
}
|
||||
|
||||
if (j$.isArray_(a) && j$.isArray_(b) && a.length != b.length) {
|
||||
mismatchValues.push("arrays were not the same length");
|
||||
}
|
||||
|
||||
delete a.__Jasmine_been_here_before__;
|
||||
delete b.__Jasmine_been_here_before__;
|
||||
return (mismatchKeys.length === 0 && mismatchValues.length === 0);
|
||||
};
|
||||
|
||||
Env.prototype.equals_ = function(a, b, mismatchKeys, mismatchValues) {
|
||||
mismatchKeys = mismatchKeys || [];
|
||||
mismatchValues = mismatchValues || [];
|
||||
|
||||
for (var i = 0; i < this.equalityTesters_.length; i++) {
|
||||
var equalityTester = this.equalityTesters_[i];
|
||||
var result = equalityTester(a, b, this, mismatchKeys, mismatchValues);
|
||||
if (!j$.util.isUndefined(result)) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
if (a === b) return true;
|
||||
|
||||
if (j$.util.isUndefined(a) || a === null || j$.util.isUndefined(b) || b === null) {
|
||||
return (j$.util.isUndefined(a) && j$.util.isUndefined(b));
|
||||
}
|
||||
|
||||
if (j$.isDomNode(a) && j$.isDomNode(b)) {
|
||||
return a === b;
|
||||
}
|
||||
|
||||
if (a instanceof Date && b instanceof Date) {
|
||||
return a.getTime() == b.getTime();
|
||||
}
|
||||
|
||||
if (a.jasmineMatches) {
|
||||
return a.jasmineMatches(b);
|
||||
}
|
||||
|
||||
if (b.jasmineMatches) {
|
||||
return b.jasmineMatches(a);
|
||||
}
|
||||
|
||||
if (a instanceof j$.Matchers.ObjectContaining) {
|
||||
return a.matches(b);
|
||||
}
|
||||
|
||||
if (b instanceof j$.Matchers.ObjectContaining) {
|
||||
return b.matches(a);
|
||||
}
|
||||
|
||||
if (j$.isString_(a) && j$.isString_(b)) {
|
||||
return (a == b);
|
||||
}
|
||||
|
||||
if (j$.isNumber_(a) && j$.isNumber_(b)) {
|
||||
return (a == b);
|
||||
}
|
||||
|
||||
if (a instanceof RegExp && b instanceof RegExp) {
|
||||
return this.compareRegExps_(a, b, mismatchKeys, mismatchValues);
|
||||
}
|
||||
|
||||
if (typeof a === "object" && typeof b === "object") {
|
||||
return this.compareObjects_(a, b, mismatchKeys, mismatchValues);
|
||||
}
|
||||
|
||||
//Straight check
|
||||
return (a === b);
|
||||
};
|
||||
|
||||
Env.prototype.contains_ = function(haystack, needle) {
|
||||
if (j$.isArray_(haystack)) {
|
||||
for (var i = 0; i < haystack.length; i++) {
|
||||
if (this.equals_(haystack[i], needle)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return haystack.indexOf(needle) >= 0;
|
||||
};
|
||||
|
||||
Env.prototype.addEqualityTester = function(equalityTester) {
|
||||
this.equalityTesters_.push(equalityTester);
|
||||
};
|
||||
|
||||
return Env;
|
||||
};
|
||||
|
||||
94
src/core/Expectation.js
Normal file
94
src/core/Expectation.js
Normal file
@@ -0,0 +1,94 @@
|
||||
getJasmineRequireObj().Expectation = function() {
|
||||
|
||||
var matchers = {};
|
||||
|
||||
function Expectation(options) {
|
||||
this.util = options.util || { buildFailureMessage: function() {} };
|
||||
this.customEqualityTesters = options.customEqualityTesters || [];
|
||||
this.actual = options.actual;
|
||||
this.addExpectationResult = options.addExpectationResult || function(){};
|
||||
this.isNot = options.isNot;
|
||||
|
||||
for (var matcherName in matchers) {
|
||||
this[matcherName] = matchers[matcherName];
|
||||
}
|
||||
}
|
||||
|
||||
Expectation.prototype.wrapCompare = function(name, matcherFactory) {
|
||||
return function() {
|
||||
var args = Array.prototype.slice.call(arguments, 0),
|
||||
expected = args.slice(0),
|
||||
message = "";
|
||||
|
||||
args.unshift(this.actual);
|
||||
|
||||
var result = matcherFactory(this.util, this.customEqualityTesters).compare.apply(null, args);
|
||||
|
||||
if (this.isNot) {
|
||||
result.pass = !result.pass;
|
||||
}
|
||||
|
||||
if (!result.pass) {
|
||||
if (!result.message) {
|
||||
args.unshift(this.isNot);
|
||||
args.unshift(name);
|
||||
message = this.util.buildFailureMessage.apply(null, args);
|
||||
} else {
|
||||
message = result.message;
|
||||
}
|
||||
}
|
||||
|
||||
if (expected.length == 1) {
|
||||
expected = expected[0];
|
||||
}
|
||||
|
||||
// TODO: how many of these params are needed?
|
||||
this.addExpectationResult(
|
||||
result.pass,
|
||||
{
|
||||
matcherName: name,
|
||||
passed: result.pass,
|
||||
message: message,
|
||||
actual: this.actual,
|
||||
expected: expected // TODO: this may need to be arrayified/sliced
|
||||
}
|
||||
);
|
||||
};
|
||||
};
|
||||
|
||||
Expectation.addCoreMatchers = function(matchers) {
|
||||
var prototype = Expectation.prototype;
|
||||
for (var matcherName in matchers) {
|
||||
var matcher = matchers[matcherName];
|
||||
prototype[matcherName] = prototype.wrapCompare(matcherName, matcher);
|
||||
}
|
||||
};
|
||||
|
||||
Expectation.addMatchers = function(matchersToAdd) {
|
||||
for (var name in matchersToAdd) {
|
||||
var matcher = matchersToAdd[name];
|
||||
matchers[name] = Expectation.prototype.wrapCompare(name, matcher);
|
||||
}
|
||||
};
|
||||
|
||||
Expectation.resetMatchers = function() {
|
||||
for (var name in matchers) {
|
||||
delete matchers[name];
|
||||
}
|
||||
};
|
||||
|
||||
Expectation.Factory = function(options) {
|
||||
options = options || {};
|
||||
|
||||
var expect = new Expectation(options);
|
||||
|
||||
// TODO: this would be nice as its own Object - NegativeExpectation
|
||||
// TODO: copy instead of mutate options
|
||||
options.isNot = true;
|
||||
expect.not = new Expectation(options);
|
||||
|
||||
return expect;
|
||||
};
|
||||
|
||||
return Expectation;
|
||||
};
|
||||
@@ -1,303 +0,0 @@
|
||||
getJasmineRequireObj().Matchers = function(j$) {
|
||||
function Matchers(env, actual, spec, opt_isNot) {
|
||||
//TODO: true dependency: equals, contains
|
||||
this.env = env;
|
||||
this.actual = actual;
|
||||
this.spec = spec;
|
||||
this.isNot = opt_isNot || false;
|
||||
}
|
||||
|
||||
Matchers.wrapInto_ = function(prototype, matchersClass) {
|
||||
for (var methodName in prototype) {
|
||||
var orig = prototype[methodName];
|
||||
matchersClass.prototype[methodName] = Matchers.matcherFn_(methodName, orig);
|
||||
}
|
||||
};
|
||||
|
||||
Matchers.matcherFn_ = function(matcherName, matcherFunction) {
|
||||
return function() {
|
||||
var matcherArgs = j$.util.argsToArray(arguments);
|
||||
var result = matcherFunction.apply(this, arguments);
|
||||
|
||||
if (this.isNot) {
|
||||
result = !result;
|
||||
}
|
||||
|
||||
var message;
|
||||
if (!result) {
|
||||
if (this.message) {
|
||||
message = this.message.apply(this, arguments);
|
||||
if (j$.isArray_(message)) {
|
||||
message = message[this.isNot ? 1 : 0];
|
||||
}
|
||||
} else {
|
||||
var englishyPredicate = matcherName.replace(/[A-Z]/g, function(s) { return ' ' + s.toLowerCase(); });
|
||||
message = "Expected " + j$.pp(this.actual) + (this.isNot ? " not " : " ") + englishyPredicate;
|
||||
if (matcherArgs.length > 0) {
|
||||
for (var i = 0; i < matcherArgs.length; i++) {
|
||||
if (i > 0) message += ",";
|
||||
message += " " + j$.pp(matcherArgs[i]);
|
||||
}
|
||||
}
|
||||
message += ".";
|
||||
}
|
||||
}
|
||||
|
||||
this.spec.addExpectationResult(result, {
|
||||
matcherName: matcherName,
|
||||
passed: result,
|
||||
expected: matcherArgs.length > 1 ? matcherArgs : matcherArgs[0],
|
||||
actual: this.actual,
|
||||
message: message
|
||||
});
|
||||
return void 0;
|
||||
};
|
||||
};
|
||||
|
||||
Matchers.prototype.toBe = function(expected) {
|
||||
return this.actual === expected;
|
||||
};
|
||||
|
||||
Matchers.prototype.toNotBe = function(expected) {
|
||||
return this.actual !== expected;
|
||||
};
|
||||
|
||||
Matchers.prototype.toEqual = function(expected) {
|
||||
return this.env.equals_(this.actual, expected);
|
||||
};
|
||||
|
||||
Matchers.prototype.toNotEqual = function(expected) {
|
||||
return !this.env.equals_(this.actual, expected);
|
||||
};
|
||||
|
||||
Matchers.prototype.toMatch = function(expected) {
|
||||
return new RegExp(expected).test(this.actual);
|
||||
};
|
||||
|
||||
Matchers.prototype.toNotMatch = function(expected) {
|
||||
return !(new RegExp(expected).test(this.actual));
|
||||
};
|
||||
|
||||
Matchers.prototype.toBeDefined = function() {
|
||||
return !j$.util.isUndefined(this.actual);
|
||||
};
|
||||
|
||||
Matchers.prototype.toBeUndefined = function() {
|
||||
return j$.util.isUndefined(this.actual);
|
||||
};
|
||||
|
||||
Matchers.prototype.toBeNull = function() {
|
||||
return (this.actual === null);
|
||||
};
|
||||
|
||||
Matchers.prototype.toBeNaN = function() {
|
||||
this.message = function() {
|
||||
return [ "Expected " + j$.pp(this.actual) + " to be NaN." ];
|
||||
};
|
||||
|
||||
return (this.actual !== this.actual);
|
||||
};
|
||||
|
||||
Matchers.prototype.toBeTruthy = function() {
|
||||
return !!this.actual;
|
||||
};
|
||||
|
||||
Matchers.prototype.toBeFalsy = function() {
|
||||
return !this.actual;
|
||||
};
|
||||
|
||||
Matchers.prototype.toHaveBeenCalled = function() {
|
||||
if (arguments.length > 0) {
|
||||
throw new Error('toHaveBeenCalled does not take arguments, use toHaveBeenCalledWith');
|
||||
}
|
||||
|
||||
if (!j$.isSpy(this.actual)) {
|
||||
throw new Error('Expected a spy, but got ' + j$.pp(this.actual) + '.');
|
||||
}
|
||||
|
||||
this.message = function() {
|
||||
return [
|
||||
"Expected spy " + this.actual.identity + " to have been called.",
|
||||
"Expected spy " + this.actual.identity + " not to have been called."
|
||||
];
|
||||
};
|
||||
|
||||
return this.actual.wasCalled;
|
||||
};
|
||||
|
||||
// TODO: kill this for 2.0
|
||||
Matchers.prototype.wasCalled = Matchers.prototype.toHaveBeenCalled;
|
||||
|
||||
Matchers.prototype.wasNotCalled = function() {
|
||||
if (arguments.length > 0) {
|
||||
throw new Error('wasNotCalled does not take arguments');
|
||||
}
|
||||
|
||||
if (!j$.isSpy(this.actual)) {
|
||||
throw new Error('Expected a spy, but got ' + j$.pp(this.actual) + '.');
|
||||
}
|
||||
|
||||
this.message = function() {
|
||||
return [
|
||||
"Expected spy " + this.actual.identity + " to not have been called.",
|
||||
"Expected spy " + this.actual.identity + " to have been called."
|
||||
];
|
||||
};
|
||||
|
||||
return !this.actual.wasCalled;
|
||||
};
|
||||
|
||||
Matchers.prototype.toHaveBeenCalledWith = function() {
|
||||
var expectedArgs = j$.util.argsToArray(arguments);
|
||||
if (!j$.isSpy(this.actual)) {
|
||||
throw new Error('Expected a spy, but got ' + j$.pp(this.actual) + '.');
|
||||
}
|
||||
this.message = function() {
|
||||
var invertedMessage = "Expected spy " + this.actual.identity + " not to have been called with " + j$.pp(expectedArgs) + " but it was.";
|
||||
var positiveMessage = "";
|
||||
if (this.actual.callCount === 0) {
|
||||
positiveMessage = "Expected spy " + this.actual.identity + " to have been called with " + j$.pp(expectedArgs) + " but it was never called.";
|
||||
} else {
|
||||
positiveMessage = "Expected spy " + this.actual.identity + " to have been called with " + j$.pp(expectedArgs) + " but actual calls were " + j$.pp(this.actual.argsForCall).replace(/^\[ | \]$/g, '');
|
||||
}
|
||||
return [positiveMessage, invertedMessage];
|
||||
};
|
||||
|
||||
return this.env.contains_(this.actual.argsForCall, expectedArgs);
|
||||
};
|
||||
|
||||
// TODO: kill for 2.0
|
||||
Matchers.prototype.wasCalledWith = Matchers.prototype.toHaveBeenCalledWith;
|
||||
|
||||
// TODO: kill for 2.0
|
||||
Matchers.prototype.wasNotCalledWith = function() {
|
||||
var expectedArgs = j$.util.argsToArray(arguments);
|
||||
if (!j$.isSpy(this.actual)) {
|
||||
throw new Error('Expected a spy, but got ' + j$.pp(this.actual) + '.');
|
||||
}
|
||||
|
||||
this.message = function() {
|
||||
return [
|
||||
"Expected spy not to have been called with " + j$.pp(expectedArgs) + " but it was",
|
||||
"Expected spy to have been called with " + j$.pp(expectedArgs) + " but it was"
|
||||
];
|
||||
};
|
||||
|
||||
return !this.env.contains_(this.actual.argsForCall, expectedArgs);
|
||||
};
|
||||
|
||||
Matchers.prototype.toContain = function(expected) {
|
||||
return this.env.contains_(this.actual, expected);
|
||||
};
|
||||
|
||||
Matchers.prototype.toNotContain = function(expected) {
|
||||
return !this.env.contains_(this.actual, expected);
|
||||
};
|
||||
|
||||
Matchers.prototype.toBeLessThan = function(expected) {
|
||||
return this.actual < expected;
|
||||
};
|
||||
|
||||
Matchers.prototype.toBeGreaterThan = function(expected) {
|
||||
return this.actual > expected;
|
||||
};
|
||||
|
||||
Matchers.prototype.toBeCloseTo = function(expected, precision) {
|
||||
if (precision !== 0) {
|
||||
precision = precision || 2;
|
||||
}
|
||||
return Math.abs(expected - this.actual) < (Math.pow(10, -precision) / 2);
|
||||
};
|
||||
|
||||
Matchers.prototype.toThrow = function(expected) {
|
||||
var result = false;
|
||||
var exception, exceptionMessage;
|
||||
if (typeof this.actual != 'function') {
|
||||
throw new Error('Actual is not a function');
|
||||
}
|
||||
try {
|
||||
this.actual();
|
||||
} catch (e) {
|
||||
exception = e;
|
||||
}
|
||||
|
||||
if (exception) {
|
||||
exceptionMessage = exception.message || exception;
|
||||
result = (j$.util.isUndefined(expected) || this.env.equals_(exceptionMessage, expected.message || expected) || (j$.isA_("RegExp", expected) && expected.test(exceptionMessage)));
|
||||
}
|
||||
|
||||
var not = this.isNot ? "not " : "";
|
||||
var regexMatch = j$.isA_("RegExp", expected) ? " an exception matching" : "";
|
||||
|
||||
this.message = function() {
|
||||
if (exception) {
|
||||
return ["Expected function " + not + "to throw" + regexMatch, expected ? expected.message || expected : "an exception", ", but it threw", exceptionMessage].join(' ');
|
||||
} else {
|
||||
return "Expected function to throw an exception.";
|
||||
}
|
||||
};
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
Matchers.Any = function(expectedClass) {
|
||||
this.expectedClass = expectedClass;
|
||||
};
|
||||
|
||||
Matchers.Any.prototype.jasmineMatches = function(other) {
|
||||
if (this.expectedClass == String) {
|
||||
return typeof other == 'string' || other instanceof String;
|
||||
}
|
||||
|
||||
if (this.expectedClass == Number) {
|
||||
return typeof other == 'number' || other instanceof Number;
|
||||
}
|
||||
|
||||
if (this.expectedClass == Function) {
|
||||
return typeof other == 'function' || other instanceof Function;
|
||||
}
|
||||
|
||||
if (this.expectedClass == Object) {
|
||||
return typeof other == 'object';
|
||||
}
|
||||
|
||||
return other instanceof this.expectedClass;
|
||||
};
|
||||
|
||||
Matchers.Any.prototype.jasmineToString = function() {
|
||||
return '<jasmine.any(' + this.expectedClass + ')>';
|
||||
};
|
||||
|
||||
Matchers.ObjectContaining = function(sample) {
|
||||
this.sample = sample;
|
||||
};
|
||||
|
||||
Matchers.ObjectContaining.prototype.jasmineMatches = function(other, mismatchKeys, mismatchValues) {
|
||||
mismatchKeys = mismatchKeys || [];
|
||||
mismatchValues = mismatchValues || [];
|
||||
|
||||
var env = j$.getEnv();
|
||||
|
||||
var hasKey = function(obj, keyName) {
|
||||
return obj !== null && !j$.util.isUndefined(obj[keyName]);
|
||||
};
|
||||
|
||||
for (var property in this.sample) {
|
||||
if (!hasKey(other, property) && hasKey(this.sample, property)) {
|
||||
mismatchKeys.push("expected has key '" + property + "', but missing from actual.");
|
||||
}
|
||||
else if (!env.equals_(this.sample[property], other[property], mismatchKeys, mismatchValues)) {
|
||||
mismatchValues.push("'" + property + "' was '" + (other[property] ? j$.util.htmlEscape(other[property].toString()) : other[property]) + "' in expected, but was '" + (this.sample[property] ? j$.util.htmlEscape(this.sample[property].toString()) : this.sample[property]) + "' in actual.");
|
||||
}
|
||||
}
|
||||
|
||||
return (mismatchKeys.length === 0 && mismatchValues.length === 0);
|
||||
};
|
||||
|
||||
Matchers.ObjectContaining.prototype.jasmineToString = function() {
|
||||
return "<jasmine.objectContaining(" + j$.pp(this.sample) + ")>";
|
||||
};
|
||||
|
||||
return Matchers;
|
||||
|
||||
};
|
||||
32
src/core/ObjectContaining.js
Normal file
32
src/core/ObjectContaining.js
Normal file
@@ -0,0 +1,32 @@
|
||||
getJasmineRequireObj().ObjectContaining = function(j$) {
|
||||
|
||||
function ObjectContaining(sample) {
|
||||
this.sample = sample;
|
||||
}
|
||||
|
||||
ObjectContaining.prototype.jasmineMatches = function(other, mismatchKeys, mismatchValues) {
|
||||
mismatchKeys = mismatchKeys || [];
|
||||
mismatchValues = mismatchValues || [];
|
||||
|
||||
var hasKey = function(obj, keyName) {
|
||||
return obj !== null && !j$.util.isUndefined(obj[keyName]);
|
||||
};
|
||||
|
||||
for (var property in this.sample) {
|
||||
if (!hasKey(other, property) && hasKey(this.sample, property)) {
|
||||
mismatchKeys.push("expected has key '" + property + "', but missing from actual.");
|
||||
}
|
||||
else if (!j$.matchersUtil.equals(this.sample[property], other[property], mismatchKeys, mismatchValues)) {
|
||||
mismatchValues.push("'" + property + "' was '" + (other[property] ? j$.util.htmlEscape(other[property].toString()) : other[property]) + "' in expected, but was '" + (this.sample[property] ? j$.util.htmlEscape(this.sample[property].toString()) : this.sample[property]) + "' in actual.");
|
||||
}
|
||||
}
|
||||
|
||||
return (mismatchKeys.length === 0 && mismatchValues.length === 0);
|
||||
};
|
||||
|
||||
ObjectContaining.prototype.jasmineToString = function() {
|
||||
return "<jasmine.objectContaining(" + j$.pp(this.sample) + ")>";
|
||||
};
|
||||
|
||||
return ObjectContaining;
|
||||
};
|
||||
268
src/core/matchers.js
Normal file
268
src/core/matchers.js
Normal file
@@ -0,0 +1,268 @@
|
||||
getJasmineRequireObj().matchers = function() {
|
||||
matchers = {};
|
||||
|
||||
matchers.toBe = function() {
|
||||
return {
|
||||
compare: function(actual, expected) {
|
||||
return {
|
||||
pass: actual === expected
|
||||
};
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
matchers.toBeCloseTo = function() {
|
||||
return {
|
||||
compare: function(actual, expected, precision) {
|
||||
if (precision !== 0) {
|
||||
precision = precision || 2;
|
||||
}
|
||||
|
||||
return {
|
||||
pass: Math.abs(expected - actual) < (Math.pow(10, -precision) / 2)
|
||||
};
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
matchers.toBeDefined = function() {
|
||||
return {
|
||||
compare: function(actual) {
|
||||
return {
|
||||
pass: (void 0 !== actual)
|
||||
};
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
matchers.toBeFalsy = function() {
|
||||
return {
|
||||
compare: function(actual) {
|
||||
return {
|
||||
pass: !!!actual
|
||||
};
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
matchers.toBeGreaterThan = function() {
|
||||
return {
|
||||
compare: function(actual, expected) {
|
||||
return {
|
||||
pass: actual > expected
|
||||
};
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
matchers.toBeLessThan = function() {
|
||||
return {
|
||||
|
||||
compare: function(actual, expected) {
|
||||
return {
|
||||
pass: actual < expected
|
||||
};
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
matchers.toBeNaN = function() {
|
||||
return {
|
||||
compare: function(actual) {
|
||||
var result = {
|
||||
pass: (actual !== actual)
|
||||
};
|
||||
|
||||
if (result.pass) {
|
||||
result.message = "Expected actual not to be NaN.";
|
||||
} else {
|
||||
result.message = "Expected " + j$.pp(actual) + " to be NaN.";
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
matchers.toBeNull = function() {
|
||||
return {
|
||||
compare: function(actual) {
|
||||
return {
|
||||
pass: actual === null
|
||||
};
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
matchers.toBeTruthy = function() {
|
||||
return {
|
||||
compare: function(actual) {
|
||||
return {
|
||||
pass: !!actual
|
||||
};
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
matchers.toBeUndefined = function() {
|
||||
return {
|
||||
compare: function(actual) {
|
||||
return {
|
||||
pass: void 0 === actual
|
||||
};
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
matchers.toEqual = function(util, customEqualityTesters) {
|
||||
customEqualityTesters = customEqualityTesters || [];
|
||||
|
||||
return {
|
||||
compare: function(actual, expected) {
|
||||
var result = {
|
||||
pass: false
|
||||
};
|
||||
|
||||
result.pass = util.equals(actual, expected, customEqualityTesters);
|
||||
|
||||
return result;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
matchers.toHaveBeenCalled = function() {
|
||||
return {
|
||||
compare: function(actual) {
|
||||
var result = {};
|
||||
|
||||
if (!j$.isSpy(actual)) {
|
||||
throw new Error('Expected a spy, but got ' + j$.pp(actual) + '.');
|
||||
}
|
||||
|
||||
if (arguments.length > 1) {
|
||||
throw new Error('toHaveBeenCalled does not take arguments, use toHaveBeenCalledWith');
|
||||
}
|
||||
|
||||
result.pass = actual.wasCalled;
|
||||
|
||||
result.message = result.pass ?
|
||||
"Expected spy " + actual.identity + " not to have been called." :
|
||||
"Expected spy " + actual.identity + " to have been called.";
|
||||
|
||||
return result;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
matchers.toHaveBeenCalledWith = function(util) {
|
||||
return {
|
||||
compare: function() {
|
||||
var args = Array.prototype.slice.call(arguments, 0),
|
||||
actual = args[0],
|
||||
expectedArgs = args.slice(1);
|
||||
|
||||
if (!j$.isSpy(actual)) {
|
||||
throw new Error('Expected a spy, but got ' + j$.pp(actual) + '.');
|
||||
}
|
||||
|
||||
return {
|
||||
pass: util.contains(actual.argsForCall, expectedArgs)
|
||||
};
|
||||
},
|
||||
message: function(actual) {
|
||||
return {
|
||||
affirmative: "Expected spy " + actual.identity + " to have been called.",
|
||||
negative: "Expected spy " + actual.identity + " not to have been called."
|
||||
};
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
matchers.toMatch = function() {
|
||||
return {
|
||||
compare: function(actual, expected) {
|
||||
var regexp = new RegExp(expected);
|
||||
|
||||
return {
|
||||
pass: regexp.test(actual)
|
||||
};
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
matchers.toThrow = function() {
|
||||
return {
|
||||
compare: function(actual, expected) {
|
||||
var result = { pass: false },
|
||||
exception;
|
||||
|
||||
if (typeof actual != "function") {
|
||||
throw new Error("Actual is not a Function");
|
||||
}
|
||||
|
||||
if (expectedCannotBeTreatedAsException()) {
|
||||
throw new Error("Expected cannot be treated as an exception.");
|
||||
}
|
||||
|
||||
try {
|
||||
actual();
|
||||
} catch (e) {
|
||||
exception = new Error(e);
|
||||
}
|
||||
|
||||
if (!exception) {
|
||||
result.message = "Expected function to throw an exception.";
|
||||
return result;
|
||||
}
|
||||
|
||||
if (void 0 == expected) {
|
||||
result.pass = true;
|
||||
result.message = "Expected function not to throw an exception.";
|
||||
} else if (exception.message == expected) {
|
||||
result.pass = true;
|
||||
result.message = "Expected function not to throw an exception \"" + expected + "\".";
|
||||
} else if (exception.message == expected.message) {
|
||||
result.pass = true;
|
||||
result.message = "Expected function not to throw an exception \"" + expected.message + "\".";
|
||||
} else if (expected instanceof RegExp) {
|
||||
if (expected.test(exception.message)) {
|
||||
result.pass = true;
|
||||
result.message = "Expected function not to throw an exception matching " + expected + ".";
|
||||
} else {
|
||||
result.pass = false;
|
||||
result.message = "Expected function to throw an exception matching " + expected + ".";
|
||||
}
|
||||
} else {
|
||||
result.pass = false;
|
||||
result.message = "Expected function to throw an exception \"" + (expected.message || expected) + "\".";
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
function expectedCannotBeTreatedAsException() {
|
||||
return !(
|
||||
(void 0 == expected) ||
|
||||
(expected instanceof Error) ||
|
||||
(typeof expected == "string") ||
|
||||
(expected instanceof RegExp)
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
matchers.toContain = function(util, customEqualityTesters) {
|
||||
customEqualityTesters = customEqualityTesters || [];
|
||||
|
||||
return {
|
||||
compare: function(actual, expected) {
|
||||
|
||||
return {
|
||||
pass: util.contains(actual, expected, customEqualityTesters)
|
||||
};
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
return matchers;
|
||||
};
|
||||
177
src/core/matchersUtil.js
Normal file
177
src/core/matchersUtil.js
Normal file
@@ -0,0 +1,177 @@
|
||||
getJasmineRequireObj().matchersUtil = function(j$) {
|
||||
// TODO: what to do about jasmine.pp not being inject? move to JSON.stringify? gut PrettyPrinter?
|
||||
|
||||
return {
|
||||
equals: function(a, b, customTesters) {
|
||||
customTesters = customTesters || [];
|
||||
|
||||
return eq(a, b, [], [], customTesters);
|
||||
},
|
||||
|
||||
contains: function(haystack, needle, customTesters) {
|
||||
customTesters = customTesters || [];
|
||||
|
||||
if (Object.prototype.toString.apply(haystack) === "[object Array]") {
|
||||
for (var i = 0; i < haystack.length; i++) {
|
||||
if (eq(haystack[i], needle, [], [], customTesters)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return haystack.indexOf(needle) >= 0;
|
||||
},
|
||||
|
||||
buildFailureMessage: function() {
|
||||
var args = Array.prototype.slice.call(arguments, 0),
|
||||
matcherName = args[0],
|
||||
isNot = args[1],
|
||||
actual = args[2],
|
||||
expected = args.slice(3),
|
||||
englishyPredicate = matcherName.replace(/[A-Z]/g, function(s) { return ' ' + s.toLowerCase(); });
|
||||
|
||||
var message = "Expected " +
|
||||
j$.pp(actual) +
|
||||
(isNot ? " not " : " ") +
|
||||
englishyPredicate;
|
||||
|
||||
if (expected.length > 0) {
|
||||
for (var i = 0; i < expected.length; i++) {
|
||||
if (i > 0) message += ",";
|
||||
message += " " + j$.pp(expected[i]);
|
||||
}
|
||||
}
|
||||
|
||||
return message + ".";
|
||||
}
|
||||
};
|
||||
|
||||
// Equality function lovingly adapted from isEqual in
|
||||
// [Underscore](http://underscorejs.org)
|
||||
function eq(a, b, aStack, bStack, customTesters) {
|
||||
var result = true;
|
||||
|
||||
for (var i = 0; i < customTesters.length; i++) {
|
||||
result = customTesters[i](a, b);
|
||||
if (result) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (a instanceof j$.Any) {
|
||||
result = a.jasmineMatches(b);
|
||||
if (result) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (b instanceof j$.Any) {
|
||||
result = b.jasmineMatches(a);
|
||||
if (result) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (b instanceof j$.ObjectContaining) {
|
||||
result = b.jasmineMatches(a);
|
||||
if (result) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Identical objects are equal. `0 === -0`, but they aren't identical.
|
||||
// See the [Harmony `egal` proposal](http://wiki.ecmascript.org/doku.php?id=harmony:egal).
|
||||
if (a === b) return a !== 0 || 1 / a == 1 / b;
|
||||
// A strict comparison is necessary because `null == undefined`.
|
||||
if (a === null || b === null) return a === b;
|
||||
var className = Object.prototype.toString.call(a);
|
||||
if (className != Object.prototype.toString.call(b)) return false;
|
||||
switch (className) {
|
||||
// Strings, numbers, dates, and booleans are compared by value.
|
||||
case '[object String]':
|
||||
// Primitives and their corresponding object wrappers are equivalent; thus, `"5"` is
|
||||
// equivalent to `new String("5")`.
|
||||
return a == String(b);
|
||||
case '[object Number]':
|
||||
// `NaN`s are equivalent, but non-reflexive. An `egal` comparison is performed for
|
||||
// other numeric values.
|
||||
return a != +a ? b != +b : (a === 0 ? 1 / a == 1 / b : a == +b);
|
||||
case '[object Date]':
|
||||
case '[object Boolean]':
|
||||
// Coerce dates and booleans to numeric primitive values. Dates are compared by their
|
||||
// millisecond representations. Note that invalid dates with millisecond representations
|
||||
// of `NaN` are not equivalent.
|
||||
return +a == +b;
|
||||
// RegExps are compared by their source patterns and flags.
|
||||
case '[object RegExp]':
|
||||
return a.source == b.source &&
|
||||
a.global == b.global &&
|
||||
a.multiline == b.multiline &&
|
||||
a.ignoreCase == b.ignoreCase;
|
||||
}
|
||||
if (typeof a != 'object' || typeof b != 'object') return false;
|
||||
// Assume equality for cyclic structures. The algorithm for detecting cyclic
|
||||
// structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`.
|
||||
var length = aStack.length;
|
||||
while (length--) {
|
||||
// Linear search. Performance is inversely proportional to the number of
|
||||
// unique nested structures.
|
||||
if (aStack[length] == a) return bStack[length] == b;
|
||||
}
|
||||
// Add the first object to the stack of traversed objects.
|
||||
aStack.push(a);
|
||||
bStack.push(b);
|
||||
var size = 0;
|
||||
// Recursively compare objects and arrays.
|
||||
if (className == '[object Array]') {
|
||||
// Compare array lengths to determine if a deep comparison is necessary.
|
||||
size = a.length;
|
||||
result = size == b.length;
|
||||
if (result) {
|
||||
// Deep compare the contents, ignoring non-numeric properties.
|
||||
while (size--) {
|
||||
if (!(result = eq(a[size], b[size], aStack, bStack, customTesters))) break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Objects with different constructors are not equivalent, but `Object`s
|
||||
// from different frames are.
|
||||
var aCtor = a.constructor, bCtor = b.constructor;
|
||||
if (aCtor !== bCtor && !(isFunction(aCtor) && (aCtor instanceof aCtor) &&
|
||||
isFunction(bCtor) && (bCtor instanceof bCtor))) {
|
||||
return false;
|
||||
}
|
||||
// Deep compare objects.
|
||||
for (var key in a) {
|
||||
if (has(a, key)) {
|
||||
// Count the expected number of properties.
|
||||
size++;
|
||||
// Deep compare each member.
|
||||
if (!(result = has(b, key) && eq(a[key], b[key], aStack, bStack, customTesters))) break;
|
||||
}
|
||||
}
|
||||
// Ensure that both objects contain the same number of properties.
|
||||
if (result) {
|
||||
for (key in b) {
|
||||
if (has(b, key) && !(size--)) break;
|
||||
}
|
||||
result = !size;
|
||||
}
|
||||
}
|
||||
// Remove the first object from the stack of traversed objects.
|
||||
aStack.pop();
|
||||
bStack.pop();
|
||||
|
||||
return result;
|
||||
|
||||
function has(obj, key) {
|
||||
return obj.hasOwnProperty(key);
|
||||
}
|
||||
|
||||
function isFunction(obj) {
|
||||
return typeof obj === 'function';
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -12,13 +12,17 @@ getJasmineRequireObj().core = function(jRequire) {
|
||||
|
||||
jRequire.base(j$);
|
||||
j$.util = jRequire.util();
|
||||
j$.Any = jRequire.Any();
|
||||
j$.Clock = jRequire.Clock();
|
||||
j$.DelayedFunctionScheduler = jRequire.DelayedFunctionScheduler();
|
||||
j$.Env = jRequire.Env(j$);
|
||||
j$.ExceptionFormatter = jRequire.ExceptionFormatter();
|
||||
j$.Expectation = jRequire.Expectation();
|
||||
j$.buildExpectationResult = jRequire.buildExpectationResult();
|
||||
j$.JsApiReporter = jRequire.JsApiReporter();
|
||||
j$.Matchers = jRequire.Matchers(j$);
|
||||
j$.matchers = jRequire.matchers(j$);
|
||||
j$.matchersUtil = jRequire.matchersUtil(j$);
|
||||
j$.ObjectContaining = jRequire.ObjectContaining(j$);
|
||||
j$.StringPrettyPrinter = jRequire.StringPrettyPrinter(j$);
|
||||
j$.QueueRunner = jRequire.QueueRunner();
|
||||
j$.ReportDispatcher = jRequire.ReportDispatcher();
|
||||
|
||||
Reference in New Issue
Block a user