Don't require matchers and asymmetric equality testers to pass custom object formatters back to Jasmine

This makes it easier to write high quality matchers and asymmetric equality
testers, and is also a step toward supporting custom object formatters.

Previously, Jasmine passed custom object formatters as the second argument
to matcher factories and as and the second argument to asymmetric equality
testers' `asymmetricMatch` method. Matchers and asymmetric equality testers
were responsible for passing the custom object formatters to methods like
`matchersUtil#equals`:

  function toEqual(util, customEqualityTesters) {
    return {
      compare: function(actual, expected) {
        // ...
        result.pass = util.equals(actual, expected, customEqualityTesters, diffBuilder);

And:

  ArrayContaining.prototype.asymmetricMatch = function(other, customTesters) {
    // ...
    for (var i = 0; i < this.sample.length; i++) {
      var item = this.sample[i];
      if (!j$.matchersUtil.contains(other, item, customTesters)) {
        return false;
      }
    }

With this change, that is no longer necessary. Matchers and asymmetric
equality testers can ignore the existence of custom equality testers and
still fully support them:

  function toEqual(util) {
    return {
      compare: function(actual, expected) {
        // ...
        result.pass = util.equals(actual, expected, diffBuilder);

And:

  ArrayContaining.prototype.asymmetricMatch = function(other, matchersUtil) {
    // ...
    for (var i = 0; i < this.sample.length; i++) {
      var item = this.sample[i];
      if (!matchersUtil.contains(other, item)) {
        return false;
      }
    }

The old interfaces are still supported, for now, but will be deprecated
in a future commit and removed in the next major release after that.

In addition to making matchers and custom equality testers simpler,
this change sets the stage for adding support for custom object
formatters. Those will be architecturally similar to custom equality
testers, and by injecting a `MatchersUtil` instance everywhere we can
add them without requiring user code to pass them around as used to be
the case with custom object formatters.
This commit is contained in:
Steve Gravrock
2019-09-21 10:58:09 -07:00
committed by Steve Gravrock
parent a00f995c68
commit dec67bd535
43 changed files with 1214 additions and 455 deletions
@@ -15,26 +15,30 @@ describe("ArrayContaining", function() {
it("matches when the item is in the actual", function() {
var containing = new jasmineUnderTest.ArrayContaining(["foo"]);
var matchersUtil = new jasmineUnderTest.MatchersUtil();
expect(containing.asymmetricMatch(["foo"])).toBe(true);
expect(containing.asymmetricMatch(["foo"], matchersUtil)).toBe(true);
});
it("matches when additional items are in the actual", function() {
var containing = new jasmineUnderTest.ArrayContaining(["foo"]);
var matchersUtil = new jasmineUnderTest.MatchersUtil();
expect(containing.asymmetricMatch(["foo", "bar"])).toBe(true);
expect(containing.asymmetricMatch(["foo", "bar"], matchersUtil)).toBe(true);
});
it("does not match when the item is not in the actual", function() {
var containing = new jasmineUnderTest.ArrayContaining(["foo"]);
var matchersUtil = new jasmineUnderTest.MatchersUtil();
expect(containing.asymmetricMatch(["bar"])).toBe(false);
expect(containing.asymmetricMatch(["bar"], matchersUtil)).toBe(false);
});
it("does not match when the actual is not an array", function() {
var containing = new jasmineUnderTest.ArrayContaining(["foo"]);
var matchersUtil = new jasmineUnderTest.MatchersUtil();
expect(containing.asymmetricMatch("foo")).toBe(false);
expect(containing.asymmetricMatch("foo", matchersUtil)).toBe(false);
});
it("jasmineToStrings itself", function() {
@@ -52,7 +56,8 @@ describe("ArrayContaining", function() {
}
};
var containing = new jasmineUnderTest.ArrayContaining(["fooVal"]);
var matchersUtil = new jasmineUnderTest.MatchersUtil({customTesters: [tester]});
expect(containing.asymmetricMatch(["fooBar"], [tester])).toBe(true);
expect(containing.asymmetricMatch(["fooBar"], matchersUtil)).toBe(true);
});
});
@@ -1,8 +1,9 @@
describe("ArrayWithExactContents", function() {
it("matches an array with the same items in a different order", function() {
var matcher = new jasmineUnderTest.ArrayWithExactContents(['a', 2, /a/]);
var matchersUtil = new jasmineUnderTest.MatchersUtil();
expect(matcher.asymmetricMatch([2, 'a', /a/])).toBe(true);
expect(matcher.asymmetricMatch([2, 'a', /a/], matchersUtil)).toBe(true);
});
it("does not work when not passed an array", function() {
@@ -15,15 +16,17 @@ describe("ArrayWithExactContents", function() {
it("does not match when an item is missing", function() {
var matcher = new jasmineUnderTest.ArrayWithExactContents(['a', 2, /a/]);
var matchersUtil = new jasmineUnderTest.MatchersUtil();
expect(matcher.asymmetricMatch(['a', 2])).toBe(false);
expect(matcher.asymmetricMatch(['a', 2, undefined])).toBe(false);
expect(matcher.asymmetricMatch(['a', 2], matchersUtil)).toBe(false);
expect(matcher.asymmetricMatch(['a', 2, undefined], matchersUtil)).toBe(false);
});
it("does not match when there is an extra item", function() {
var matcher = new jasmineUnderTest.ArrayWithExactContents(['a']);
var matchersUtil = new jasmineUnderTest.MatchersUtil();
expect(matcher.asymmetricMatch(['a', 2])).toBe(false);
expect(matcher.asymmetricMatch(['a', 2], matchersUtil)).toBe(false);
});
it("jasmineToStrings itself", function() {
@@ -41,7 +44,8 @@ describe("ArrayWithExactContents", function() {
}
};
var matcher = new jasmineUnderTest.ArrayWithExactContents(["fooVal"]);
var matchersUtil = new jasmineUnderTest.MatchersUtil({customTesters: [tester]});
expect(matcher.asymmetricMatch(["fooBar"], [tester])).toBe(true);
expect(matcher.asymmetricMatch(["fooBar"], matchersUtil)).toBe(true);
});
});
@@ -31,8 +31,9 @@ describe('MapContaining', function() {
['foo', [1, 2, 3]],
]);
var containing = new jasmineUnderTest.MapContaining(containingMap);
var matchersUtil = new jasmineUnderTest.MatchersUtil();
expect(containing.asymmetricMatch(actualMap)).toBe(true);
expect(containing.asymmetricMatch(actualMap, matchersUtil)).toBe(true);
});
it('does not match when a key is not in actual', function() {
@@ -46,8 +47,9 @@ describe('MapContaining', function() {
['foo', [1, 2, 3]],
]);
var containing = new jasmineUnderTest.MapContaining(containingMap);
var matchersUtil = new jasmineUnderTest.MatchersUtil();
expect(containing.asymmetricMatch(actualMap)).toBe(false);
expect(containing.asymmetricMatch(actualMap, matchersUtil)).toBe(false);
});
it('does not match when a value is not in actual', function() {
@@ -61,8 +63,9 @@ describe('MapContaining', function() {
['foo', [1, 2]],
]);
var containing = new jasmineUnderTest.MapContaining(containingMap);
var matchersUtil = new jasmineUnderTest.MatchersUtil();
expect(containing.asymmetricMatch(actualMap)).toBe(false);
expect(containing.asymmetricMatch(actualMap, matchersUtil)).toBe(false);
});
it('matches when all the key/value pairs in sample have asymmetric matches in actual', function() {
@@ -74,17 +77,18 @@ describe('MapContaining', function() {
var containingMap = new MapI([
[
jasmine.stringMatching(/^foo\d/),
jasmineUnderTest.stringMatching(/^foo\d/),
'bar'
],
[
'baz',
jasmine.arrayContaining([2, 3])
jasmineUnderTest.arrayContaining([2, 3])
],
]);
var containing = new jasmineUnderTest.MapContaining(containingMap);
var matchersUtil = new jasmineUnderTest.MatchersUtil();
expect(containing.asymmetricMatch(actualMap)).toBe(true);
expect(containing.asymmetricMatch(actualMap, matchersUtil)).toBe(true);
});
it('does not match when a key in sample has no asymmetric matches in actual', function() {
@@ -95,17 +99,18 @@ describe('MapContaining', function() {
var containingMap = new MapI([
[
jasmine.stringMatching(/^foo\d/),
jasmineUnderTest.stringMatching(/^foo\d/),
'bar'
],
[
'baz',
jasmine.arrayContaining([2, 3])
jasmineUnderTest.arrayContaining([2, 3])
],
]);
var containing = new jasmineUnderTest.MapContaining(containingMap);
var matchersUtil = new jasmineUnderTest.MatchersUtil();
expect(containing.asymmetricMatch(actualMap)).toBe(false);
expect(containing.asymmetricMatch(actualMap, matchersUtil)).toBe(false);
});
it('does not match when a value in sample has no asymmetric matches in actual', function() {
@@ -116,17 +121,18 @@ describe('MapContaining', function() {
var containingMap = new MapI([
[
jasmine.stringMatching(/^foo\d/),
jasmineUnderTest.stringMatching(/^foo\d/),
'bar'
],
[
'baz',
jasmine.arrayContaining([4, 5])
jasmineUnderTest.arrayContaining([4, 5])
],
]);
var containing = new jasmineUnderTest.MapContaining(containingMap);
var matchersUtil = new jasmineUnderTest.MatchersUtil();
expect(containing.asymmetricMatch(actualMap)).toBe(false);
expect(containing.asymmetricMatch(actualMap, matchersUtil)).toBe(false);
});
it('matches recursively', function() {
@@ -147,8 +153,9 @@ describe('MapContaining', function() {
],
]);
var containing = new jasmineUnderTest.MapContaining(containingMap);
var matchersUtil = new jasmineUnderTest.MatchersUtil();
expect(containing.asymmetricMatch(actualMap)).toBe(true);
expect(containing.asymmetricMatch(actualMap, matchersUtil)).toBe(true);
});
it('uses custom equality testers', function() {
@@ -158,8 +165,9 @@ describe('MapContaining', function() {
}
var actualMap = new MapI([['foo', -1]]);
var containing = new jasmineUnderTest.MapContaining(new MapI([['foo', -2]]));
var matchersUtil = new jasmineUnderTest.MatchersUtil({customTesters: [tester]});
expect(containing.asymmetricMatch(actualMap, [tester])).toBe(true);
expect(containing.asymmetricMatch(actualMap, matchersUtil)).toBe(true);
});
it('does not match when actual is not a map', function() {
@@ -2,8 +2,9 @@ describe("ObjectContaining", function() {
it("matches any actual to an empty object", function() {
var containing = new jasmineUnderTest.ObjectContaining({});
var matchersUtil = new jasmineUnderTest.MatchersUtil();
expect(containing.asymmetricMatch("foo")).toBe(true);
expect(containing.asymmetricMatch("foo", matchersUtil)).toBe(true);
});
it("does not match an empty object actual", function() {
@@ -16,20 +17,23 @@ describe("ObjectContaining", function() {
it("matches when the key/value pair is present in the actual", function() {
var containing = new jasmineUnderTest.ObjectContaining({foo: "fooVal"});
var matchersUtil = new jasmineUnderTest.MatchersUtil();
expect(containing.asymmetricMatch({foo: "fooVal", bar: "barVal"})).toBe(true);
expect(containing.asymmetricMatch({foo: "fooVal", bar: "barVal"}, matchersUtil)).toBe(true);
});
it("does not match when the key/value pair is not present in the actual", function() {
var containing = new jasmineUnderTest.ObjectContaining({foo: "fooVal"});
var matchersUtil = new jasmineUnderTest.MatchersUtil();
expect(containing.asymmetricMatch({bar: "barVal", quux: "quuxVal"})).toBe(false);
expect(containing.asymmetricMatch({bar: "barVal", quux: "quuxVal"}, matchersUtil)).toBe(false);
});
it("does not match when the key is present but the value is different in the actual", function() {
var containing = new jasmineUnderTest.ObjectContaining({foo: "other"});
var matchersUtil = new jasmineUnderTest.MatchersUtil();
expect(containing.asymmetricMatch({foo: "fooVal", bar: "barVal"})).toBe(false);
expect(containing.asymmetricMatch({foo: "fooVal", bar: "barVal"}, matchersUtil)).toBe(false);
});
it("jasmineToString's itself", function() {
@@ -40,34 +44,39 @@ describe("ObjectContaining", function() {
it("matches recursively", function() {
var containing = new jasmineUnderTest.ObjectContaining({one: new jasmineUnderTest.ObjectContaining({two: {}})});
var matchersUtil = new jasmineUnderTest.MatchersUtil();
expect(containing.asymmetricMatch({one: {two: {}}})).toBe(true);
expect(containing.asymmetricMatch({one: {two: {}}}, matchersUtil)).toBe(true);
});
it("matches when key is present with undefined value", function() {
var containing = new jasmineUnderTest.ObjectContaining({ one: undefined });
var matchersUtil = new jasmineUnderTest.MatchersUtil();
expect(containing.asymmetricMatch({ one: undefined })).toBe(true);
expect(containing.asymmetricMatch({ one: undefined }, matchersUtil)).toBe(true);
});
it("does not match when key with undefined value is not present", function() {
var containing = new jasmineUnderTest.ObjectContaining({ one: undefined });
var matchersUtil = new jasmineUnderTest.MatchersUtil();
expect(containing.asymmetricMatch({})).toBe(false);
expect(containing.asymmetricMatch({}, matchersUtil)).toBe(false);
});
it("matches defined properties", function(){
var containing = new jasmineUnderTest.ObjectContaining({ foo: "fooVal" });
var matchersUtil = new jasmineUnderTest.MatchersUtil();
var definedPropertyObject = {};
Object.defineProperty(definedPropertyObject, "foo", {
get: function() { return "fooVal" }
});
expect(containing.asymmetricMatch(definedPropertyObject)).toBe(true);
expect(containing.asymmetricMatch(definedPropertyObject, matchersUtil)).toBe(true);
});
it("matches prototype properties", function(){
var containing = new jasmineUnderTest.ObjectContaining({ foo: "fooVal" });
var matchersUtil = new jasmineUnderTest.MatchersUtil();
var prototypeObject = {foo: "fooVal"};
var obj;
@@ -81,7 +90,7 @@ describe("ObjectContaining", function() {
obj = new Foo();
}
expect(containing.asymmetricMatch(obj)).toBe(true);
expect(containing.asymmetricMatch(obj, matchersUtil)).toBe(true);
});
it("uses custom equality testers", function() {
@@ -93,7 +102,8 @@ describe("ObjectContaining", function() {
}
};
var containing = new jasmineUnderTest.ObjectContaining({foo: "fooVal"});
var matchersUtil = new jasmineUnderTest.MatchersUtil({customTesters: [tester]});
expect(containing.asymmetricMatch({foo: "fooBar"}, [tester])).toBe(true);
expect(containing.asymmetricMatch({foo: "fooBar"}, matchersUtil)).toBe(true);
});
});
@@ -28,8 +28,9 @@ describe('SetContaining', function() {
[1, 2, 3], {'foo': 'bar'}
]);
var containing = new jasmineUnderTest.SetContaining(containingSet);
var matchersUtil = new jasmineUnderTest.MatchersUtil();
expect(containing.asymmetricMatch(actualSet)).toBe(true);
expect(containing.asymmetricMatch(actualSet, matchersUtil)).toBe(true);
});
it('does not match when a value is not in actual', function() {
@@ -41,8 +42,9 @@ describe('SetContaining', function() {
[1, 2], {'foo': 'bar'}
]);
var containing = new jasmineUnderTest.SetContaining(containingSet);
var matchersUtil = new jasmineUnderTest.MatchersUtil();
expect(containing.asymmetricMatch(actualSet)).toBe(false);
expect(containing.asymmetricMatch(actualSet, matchersUtil)).toBe(false);
});
it('matches when all the values in sample have asymmetric matches in actual', function() {
@@ -51,12 +53,13 @@ describe('SetContaining', function() {
]);
var containingSet = new SetI([
jasmine.stringMatching(/^foo\d/),
jasmine.arrayContaining([2, 3]),
jasmineUnderTest.stringMatching(/^foo\d/),
jasmineUnderTest.arrayContaining([2, 3]),
]);
var containing = new jasmineUnderTest.SetContaining(containingSet);
var matchersUtil = new jasmineUnderTest.MatchersUtil();
expect(containing.asymmetricMatch(actualSet)).toBe(true);
expect(containing.asymmetricMatch(actualSet, matchersUtil)).toBe(true);
});
it('does not match when a value in sample has no asymmetric matches in actual', function() {
@@ -69,8 +72,9 @@ describe('SetContaining', function() {
jasmine.arrayContaining([2, 3]),
]);
var containing = new jasmineUnderTest.SetContaining(containingSet);
var matchersUtil = new jasmineUnderTest.MatchersUtil();
expect(containing.asymmetricMatch(actualSet)).toBe(false);
expect(containing.asymmetricMatch(actualSet, matchersUtil)).toBe(false);
});
it('matches recursively', function() {
@@ -82,8 +86,9 @@ describe('SetContaining', function() {
new jasmineUnderTest.SetContaining(new SetI(['bar'])), 'foo'
]);
var containing = new jasmineUnderTest.SetContaining(containingSet);
var matchersUtil = new jasmineUnderTest.MatchersUtil();
expect(containing.asymmetricMatch(actualSet)).toBe(true);
expect(containing.asymmetricMatch(actualSet, matchersUtil)).toBe(true);
});
it('uses custom equality testers', function() {
@@ -93,8 +98,9 @@ describe('SetContaining', function() {
}
var actualSet = new SetI(['foo', -1]);
var containing = new jasmineUnderTest.SetContaining(new SetI([-2, 'foo']));
var matchersUtil = new jasmineUnderTest.MatchersUtil({customTesters: [tester]});
expect(containing.asymmetricMatch(actualSet, [tester])).toBe(true);
expect(containing.asymmetricMatch(actualSet, matchersUtil)).toBe(true);
});
it('does not match when actual is not a set', function() {