Merge pull request #100 from gvanhove/objectContaining

Merging after verifying locally that all specs are green (node, browser)
This commit is contained in:
Davis W. Frank
2011-11-18 13:26:04 -08:00
9 changed files with 393 additions and 9 deletions

View File

@@ -196,6 +196,21 @@ jasmine.any = function(clazz) {
return new jasmine.Matchers.Any(clazz);
};
/**
* Returns a matchable subset of a hash/JSON object. For use in expectations when you don't care about all of the
* attributes on the object.
*
* @example
* // don't care about any other attributes than foo.
* expect(mySpy).toHaveBeenCalledWith(jasmine.hashContaining({foo: "bar"});
*
* @param sample {Object} sample
* @returns matchable object for the sample
*/
jasmine.objectContaining = function (sample) {
return new jasmine.Matchers.ObjectContaining(sample);
};
/**
* Jasmine Spies are test doubles that can act as stubs, spies, fakes or when used in an expecation, mocks.
*
@@ -922,6 +937,14 @@ jasmine.Env.prototype.equals_ = function(a, b, mismatchKeys, mismatchValues) {
return b.matches(a);
}
if (a instanceof jasmine.Matchers.ObjectContaining) {
return a.matches(b);
}
if (b instanceof jasmine.Matchers.ObjectContaining) {
return b.matches(a);
}
if (jasmine.isString_(a) && jasmine.isString_(b)) {
return (a == b);
}
@@ -1477,6 +1500,35 @@ jasmine.Matchers.Any.prototype.toString = function() {
return '<jasmine.any(' + this.expectedClass + ')>';
};
jasmine.Matchers.ObjectContaining = function (sample) {
this.sample = sample;
};
jasmine.Matchers.ObjectContaining.prototype.matches = function(other, mismatchKeys, mismatchValues) {
mismatchKeys = mismatchKeys || [];
mismatchValues = mismatchValues || [];
var env = jasmine.getEnv();
var hasKey = function(obj, keyName) {
return obj != null && obj[keyName] !== jasmine.undefined;
};
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] ? jasmine.util.htmlEscape(other[property].toString()) : other[property]) + "' in expected, but was '" + (this.sample[property] ? jasmine.util.htmlEscape(this.sample[property].toString()) : this.sample[property]) + "' in actual.");
}
}
return (mismatchKeys.length === 0 && mismatchValues.length === 0);
};
jasmine.Matchers.ObjectContaining.prototype.toString = function () {
return "<jasmine.hashContaining(" + jasmine.pp(this.sample) + ")>";
};
/**
* @constructor
*/
@@ -2472,5 +2524,5 @@ jasmine.version_= {
"major": 1,
"minor": 1,
"build": 0,
"revision": 1320442951
"revision": 1299963843
};

2
pages

Submodule pages updated: a9d577eb45...d08ce2de24

View File

@@ -352,6 +352,33 @@ describe("jasmine.Matchers", function() {
}]).toEqual(["a", jasmine.any(Function)])).toPass();
});
describe("toEqual with an object implementing jasmineMatches", function () {
var matcher;
beforeEach(function () {
matcher = {
jasmineMatches: jasmine.createSpy("jasmineMatches")
};
});
describe("on the left side", function () {
it("uses the jasmineMatches function", function () {
matcher.jasmineMatches.andReturn(false);
expect(match(matcher).toEqual("foo")).toFail();
matcher.jasmineMatches.andReturn(true);
expect(match(matcher).toEqual("foo")).toPass();
});
});
describe("on the right side", function () {
it("uses the jasmineMatches function", function () {
matcher.jasmineMatches.andReturn(false);
expect(match("foo").toEqual(matcher)).toFail();
matcher.jasmineMatches.andReturn(true);
expect(match("foo").toEqual(matcher)).toPass();
});
});
});
it("toEqual handles circular objects ok", function() {
expect(match({foo: "bar", baz: jasmine.undefined}).toEqual({foo: "bar", baz: jasmine.undefined})).toPass();
expect(match({foo:['bar','baz','quux']}).toEqual({foo:['bar','baz','quux']})).toPass();
@@ -830,6 +857,252 @@ describe("jasmine.Matchers", function() {
});
});
describe("ObjectContaining", function () {
describe("with an empty object", function () {
var containing;
beforeEach(function () {
containing = new jasmine.Matchers.ObjectContaining({});
});
it("matches everything", function () {
expect(containing.jasmineMatches("foo", [], [])).toBe(true);
});
it("says it didn't expect to contain anything", function () {
expect(containing.jasmineToString()).toEqual("<jasmine.objectContaining({ })>");
});
});
describe("with an object with items in it", function () {
var containing, mismatchKeys, mismatchValues;
beforeEach(function () {
mismatchKeys = [];
mismatchValues = [];
containing = new jasmine.Matchers.ObjectContaining({foo: "fooVal", bar: "barVal"});
});
it("doesn't match an empty object", function () {
expect(containing.jasmineMatches({}, mismatchKeys, mismatchValues)).toBe(false);
});
it("doesn't match an object with none of the specified options", function () {
expect(containing.jasmineMatches({baz:"stuff"}, mismatchKeys, mismatchValues)).toBe(false);
});
it("adds a message for each missing key", function () {
containing.jasmineMatches({foo: "fooVal"}, mismatchKeys, mismatchValues);
expect(mismatchKeys.length).toEqual(1);
});
it("doesn't match an object when the values are different", function () {
expect(containing.jasmineMatches({foo:"notFoo", bar:"notBar"}, mismatchKeys, mismatchValues)).toBe(false);
});
it("adds a message when values don't match", function () {
containing.jasmineMatches({foo: "fooVal", bar: "notBar"}, mismatchKeys, mismatchValues);
expect(mismatchValues.length).toEqual(1);
});
it("doesn't match an object with only one of the values matching", function () {
expect(containing.jasmineMatches({foo:"notFoo", bar:"barVal"}, mismatchKeys, mismatchValues)).toBe(false);
});
it("matches when all the values are the same", function () {
expect(containing.jasmineMatches({foo: "fooVal", bar: "barVal"}, mismatchKeys, mismatchValues)).toBe(true);
});
it("matches when there are additional values", function () {
expect(containing.jasmineMatches({foo: "fooVal", bar: "barVal", baz: "bazVal"}, mismatchKeys, mismatchValues)).toBe(true);
});
it("doesn't modify missingKeys or missingValues when match is successful", function () {
containing.jasmineMatches({foo: "fooVal", bar: "barVal"}, mismatchKeys, mismatchValues);
expect(mismatchKeys.length).toEqual(0);
expect(mismatchValues.length).toEqual(0);
});
it("says what it expects to contain", function () {
expect(containing.jasmineToString()).toEqual("<jasmine.objectContaining(" + jasmine.pp({foo:"fooVal", bar:"barVal"}) + ")>");
});
});
describe("in real life", function () {
var method;
beforeEach(function () {
method = jasmine.createSpy("method");
method({a:"b", c:"d"});
});
it("works correctly for positive matches", function () {
expect(method).toHaveBeenCalledWith(jasmine.objectContaining({a:"b"}));
});
it("works correctly for negative matches", function () {
expect(method).not.toHaveBeenCalledWith(jasmine.objectContaining({z:"x"}));
});
});
});
describe("Matchers.Any", function () {
var any;
describe(".jasmineToString", function () {
describe("with Object", function () {
it("says it's looking for an object", function () {
any = jasmine.any(Object);
expect(any.jasmineToString()).toMatch(/<jasmine\.any\(function Object.*\)>/);
});
});
describe("with Function", function () {
it("says it's looking for a function", function () {
any = jasmine.any(Function);
expect(any.jasmineToString()).toMatch(/<jasmine\.any\(function Function.*\)>/);
});
});
describe("with String", function () {
it("says it's looking for a string", function () {
any = jasmine.any(String);
expect(any.jasmineToString()).toMatch(/<jasmine\.any\(function String.*\)>/);
});
});
describe("with Number", function () {
it("says it's looking for a number", function () {
any = jasmine.any(Number);
expect(any.jasmineToString()).toMatch(/<jasmine\.any\(function Number.*\)>/);
});
});
describe("with some other defined 'class'", function () {
it("says it's looking for an object", function () {
function MyClass () {}
any = jasmine.any(MyClass);
expect(any.jasmineToString()).toMatch(/<jasmine\.any\(function MyClass.*\)>/);
});
});
});
describe(".jasmineMatches", function () {
describe("with Object", function () {
beforeEach(function () {
any = jasmine.any(Object);
});
it("matches an empty object", function () {
expect(any.jasmineMatches({})).toEqual(true);
});
it("matches a newed up object", function () {
expect(any.jasmineMatches(new Object())).toEqual(true);
});
it("doesn't match a string", function () {
expect(any.jasmineMatches("")).toEqual(false);
});
it("doesn't match a number", function () {
expect(any.jasmineMatches(123)).toEqual(false);
});
it("doesn't match a function", function () {
expect(any.jasmineMatches(function () {})).toEqual(false);
});
});
describe("with Function", function () {
beforeEach(function () {
any = jasmine.any(Function);
});
it("doesn't match an object", function () {
expect(any.jasmineMatches({})).toEqual(false);
});
it("doesn't match a string", function () {
expect(any.jasmineMatches("")).toEqual(false);
});
it("doesn't match a number", function () {
expect(any.jasmineMatches(123)).toEqual(false);
});
it("matches a function", function () {
expect(any.jasmineMatches(function () {})).toEqual(true);
});
});
describe("with Number", function () {
beforeEach(function () {
any = jasmine.any(Number);
});
it("doesn't match an object", function () {
expect(any.jasmineMatches({})).toEqual(false);
});
it("doesn't match a string", function () {
expect(any.jasmineMatches("")).toEqual(false);
});
it("matches a number", function () {
expect(any.jasmineMatches(123)).toEqual(true);
});
it("doesn't match a function", function () {
expect(any.jasmineMatches(function () {})).toEqual(false);
});
});
describe("with String", function () {
beforeEach(function () {
any = jasmine.any(String);
});
it("doesn't match an object", function () {
expect(any.jasmineMatches({})).toEqual(false);
});
it("matches a string", function () {
expect(any.jasmineMatches("")).toEqual(true);
});
it("doesn't match a number", function () {
expect(any.jasmineMatches(123)).toEqual(false);
});
it("doesn't match a function", function () {
expect(any.jasmineMatches(function () {})).toEqual(false);
});
});
describe("with some defined 'class'", function () {
function MyClass () {}
beforeEach(function () {
any = jasmine.any(MyClass);
});
it("doesn't match an object", function () {
expect(any.jasmineMatches({})).toEqual(false);
});
it("doesn't match a string", function () {
expect(any.jasmineMatches("")).toEqual(false);
});
it("doesn't match a number", function () {
expect(any.jasmineMatches(123)).toEqual(false);
});
it("doesn't match a function", function () {
expect(any.jasmineMatches(function () {})).toEqual(false);
});
it("matches an instance of the defined class", function () {
expect(any.jasmineMatches(new MyClass())).toEqual(true);
});
});
});
});
describe("all matchers", function() {
it("should return null, for future-proofing, since we might eventually allow matcher chaining", function() {
expect(match(true).toBe(true)).toBeUndefined();

View File

@@ -83,5 +83,12 @@ describe("jasmine.pp", function () {
expect(jasmine.pp(jasmine.createSpy("something"))).toEqual("spy on something");
});
it("should stringify objects that implement jasmineToString", function () {
var obj = {
jasmineToString: function () { return "strung"; }
};
expect(jasmine.pp(obj)).toEqual("strung");
});
});

View File

@@ -230,11 +230,19 @@ jasmine.Env.prototype.equals_ = function(a, b, mismatchKeys, mismatchValues) {
return a.getTime() == b.getTime();
}
if (a instanceof jasmine.Matchers.Any) {
if (a.jasmineMatches) {
return a.jasmineMatches(b);
}
if (b.jasmineMatches) {
return b.jasmineMatches(a);
}
if (a instanceof jasmine.Matchers.ObjectContaining) {
return a.matches(b);
}
if (b instanceof jasmine.Matchers.Any) {
if (b instanceof jasmine.Matchers.ObjectContaining) {
return b.matches(a);
}

View File

@@ -345,7 +345,7 @@ jasmine.Matchers.Any = function(expectedClass) {
this.expectedClass = expectedClass;
};
jasmine.Matchers.Any.prototype.matches = function(other) {
jasmine.Matchers.Any.prototype.jasmineMatches = function(other) {
if (this.expectedClass == String) {
return typeof other == 'string' || other instanceof String;
}
@@ -365,7 +365,36 @@ jasmine.Matchers.Any.prototype.matches = function(other) {
return other instanceof this.expectedClass;
};
jasmine.Matchers.Any.prototype.toString = function() {
jasmine.Matchers.Any.prototype.jasmineToString = function() {
return '<jasmine.any(' + this.expectedClass + ')>';
};
jasmine.Matchers.ObjectContaining = function (sample) {
this.sample = sample;
};
jasmine.Matchers.ObjectContaining.prototype.jasmineMatches = function(other, mismatchKeys, mismatchValues) {
mismatchKeys = mismatchKeys || [];
mismatchValues = mismatchValues || [];
var env = jasmine.getEnv();
var hasKey = function(obj, keyName) {
return obj != null && obj[keyName] !== jasmine.undefined;
};
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] ? jasmine.util.htmlEscape(other[property].toString()) : other[property]) + "' in expected, but was '" + (this.sample[property] ? jasmine.util.htmlEscape(this.sample[property].toString()) : this.sample[property]) + "' in actual.");
}
}
return (mismatchKeys.length === 0 && mismatchValues.length === 0);
};
jasmine.Matchers.ObjectContaining.prototype.jasmineToString = function () {
return "<jasmine.objectContaining(" + jasmine.pp(this.sample) + ")>";
};

View File

@@ -23,8 +23,8 @@ jasmine.PrettyPrinter.prototype.format = function(value) {
this.emitScalar('null');
} else if (value === jasmine.getGlobal()) {
this.emitScalar('<global>');
} else if (value instanceof jasmine.Matchers.Any) {
this.emitScalar(value.toString());
} else if (value.jasmineToString) {
this.emitScalar(value.jasmineToString());
} else if (typeof value === 'string') {
this.emitString(value);
} else if (jasmine.isSpy(value)) {

View File

@@ -196,6 +196,21 @@ jasmine.any = function(clazz) {
return new jasmine.Matchers.Any(clazz);
};
/**
* Returns a matchable subset of a JSON object. For use in expectations when you don't care about all of the
* attributes on the object.
*
* @example
* // don't care about any other attributes than foo.
* expect(mySpy).toHaveBeenCalledWith(jasmine.objectContaining({foo: "bar"});
*
* @param sample {Object} sample
* @returns matchable object for the sample
*/
jasmine.objectContaining = function (sample) {
return new jasmine.Matchers.ObjectContaining(sample);
};
/**
* Jasmine Spies are test doubles that can act as stubs, spies, fakes or when used in an expecation, mocks.
*

View File

@@ -31,7 +31,7 @@ end
def count_specs_in(files)
files.inject(0) do |count, file|
File.read(file).scan(/\sit\(/) {|s| count += 1}
File.read(file).scan(/\sit\s*\(/) {|s| count += 1}
count
end
end