diff --git a/docs/release-source/release/matchers.md b/docs/release-source/release/matchers.md index 1f9342878..ad5d44e8c 100644 --- a/docs/release-source/release/matchers.md +++ b/docs/release-source/release/matchers.md @@ -220,6 +220,26 @@ The property might be inherited via the prototype chain. If the optional expecta Same as `sinon.match.has` but the property must be defined by the value itself. Inherited properties are ignored. +#### `sinon.match.hasNested(propertyPath[, expectation])` + +Requires the value to define the given `propertyPath`. Dot (`prop.prop`) and bracket (`prop[0]`) notations are supported as in (Lodash.get)[https://lodash.com/docs/4.4.2#get]. + +The propertyPath might be inherited via the prototype chain. If the optional expectation is given, the value at the propertyPath is deeply compared with the expectation. The expectation can be another matcher. + + +```javascript +sinon.match.hasNested("a[0].b.c"); + +// Where actual is something like +var actual = { "a": [{ "b": { "c": 3 } }] }; + +sinon.match.hasNested("a.b.c"); + +// Where actual is something like +var actual = { "a": { "b": { "c": 3 } } }; +``` + + ## Combining matchers All matchers implement `and` and `or`. This allows to logically combine mutliple matchers. The result is a new matchers that requires both (and) or one of the matchers (or) to return `true`. diff --git a/lib/sinon/match.js b/lib/sinon/match.js index 48eda0ba3..8aecc7303 100644 --- a/lib/sinon/match.js +++ b/lib/sinon/match.js @@ -3,6 +3,7 @@ var deepEqual = require("./util/core/deep-equal").use(match); // eslint-disable-line no-use-before-define var every = require("./util/core/every"); var functionName = require("./util/core/function-name"); +var get = require("lodash.get"); var iterableToString = require("./util/core/iterable-to-string"); var typeOf = require("./util/core/typeOf"); var valueToString = require("./util/core/value-to-string"); @@ -225,6 +226,23 @@ match.hasOwn = createPropertyMatcher(function (actual, property) { return actual.hasOwnProperty(property); }, "hasOwn"); +match.hasNested = function (property, value) { + assertType(property, "string", "property"); + var onlyProperty = arguments.length === 1; + var message = "hasNested(\"" + property + "\""; + if (!onlyProperty) { + message += ", " + valueToString(value); + } + message += ")"; + return match(function (actual) { + if (actual === undefined || actual === null || + get(actual, property) === undefined) { + return false; + } + return onlyProperty || deepEqual(value, get(actual, property)); + }, message); +}; + match.array = match.typeOf("array"); match.array.deepEquals = function (expectation) { diff --git a/package.json b/package.json index bc675f72d..b18c56278 100644 --- a/package.json +++ b/package.json @@ -37,6 +37,7 @@ "dependencies": { "diff": "^3.1.0", "formatio": "1.2.0", + "lodash.get": "^4.4.2", "lolex": "^2.1.2", "native-promise-only": "^0.8.1", "nise": "^1.0.1", diff --git a/test/match-test.js b/test/match-test.js index 898f7481e..41c9eff93 100644 --- a/test/match-test.js +++ b/test/match-test.js @@ -3,7 +3,7 @@ var assert = require("referee").assert; var sinonMatch = require("../lib/sinon/match"); -function propertyMatcherTests(matcher) { +function propertyMatcherTests(matcher, additionalTests) { return function () { it("returns matcher", function () { var has = matcher("foo"); @@ -69,6 +69,10 @@ function propertyMatcherTests(matcher) { assert(has.test({ callback: function () {} })); }); + + if (typeof additionalTests === "function") { + additionalTests(); + } }; } @@ -523,6 +527,22 @@ describe("sinonMatch", function () { describe(".has", propertyMatcherTests(sinonMatch.has)); describe(".hasOwn", propertyMatcherTests(sinonMatch.hasOwn)); + describe(".hasNested", propertyMatcherTests(sinonMatch.hasNested, function () { + + it("compares nested value", function () { + var hasNested = sinonMatch.hasNested("foo.bar", "doo"); + + assert(hasNested.test({ foo: { bar: "doo" } })); + }); + + it("compares nested array value", function () { + var hasNested = sinonMatch.hasNested("foo[0].bar", "doo"); + + assert(hasNested.test({ foo: [{ bar: "doo" }] })); + }); + + })); + describe(".hasSpecial", function () { it("returns true if object has inherited property", function () { var has = sinonMatch.has("toString");