Skip to content

Commit 67016ef

Browse files
committed
feat(assertObjectMatchSpec): Compare object keys when doing a full match #44
1 parent 3ebe10a commit 67016ef

File tree

3 files changed

+206
-80
lines changed

3 files changed

+206
-80
lines changed

src/core/assertions.js

+48-25
Original file line numberDiff line numberDiff line change
@@ -28,41 +28,59 @@ const RuleName = Object.freeze({
2828
})
2929

3030
/**
31-
* Count object properties including nested objects ones.
32-
* If a property is an object, its key is ignored.
31+
* Acts as `Object.keys()`, but runs recursively,
32+
* another difference is that when one of the key refers to
33+
* a non-empty object, it's gonna be ignored.
34+
*
35+
* Keys for nested objects are prefixed with their parent key.
36+
*
37+
* Also note that this is not fully interoperable with `lodash.get`
38+
* for example as keys themselves can contain dots or special characters.
3339
*
3440
* @example
35-
* Assertions.countNestedProperties({
41+
* Assertions.objectKeysDeep({
3642
* a: true,
3743
* b: true,
3844
* c: true,
3945
* })
40-
* // => 3
41-
* Assertions.countNestedProperties({
46+
* // => ["a", "b", "c"]
47+
* Assertions.objectKeysDeep({
4248
* a: true,
4349
* b: true,
4450
* c: {
45-
* a: true,
46-
* b: true,
51+
* d: true,
52+
* e: {},
53+
* f: {
54+
* g: true
55+
* }
4756
* },
4857
* })
49-
* // => 4 (c is ignored because it's a nested object)
58+
* // => ["a", "b", "c.d", "c.e", "c.f.g"] (c and c.f are ignored as non empty nested objects)
5059
*
5160
* @param {Object} object
52-
* @return {number}
61+
* @param {Array} [keysAccumulator = []]
62+
* @param {string} [parentPath = ""]
63+
* @return {Array}
5364
*/
54-
exports.countNestedProperties = (object) => {
55-
let propertiesCount = 0
56-
Object.keys(object).forEach((key) => {
57-
if (!_.isEmpty(object[key]) && typeof object[key] === 'object') {
58-
const count = exports.countNestedProperties(object[key])
59-
propertiesCount += count
60-
} else {
61-
propertiesCount++
62-
}
63-
})
65+
exports.objectKeysDeep = (object, keysAccumulator = [], parentPath = '') => {
66+
if (_.isPlainObject(object) || Array.isArray(object)) {
67+
Object.keys(object).forEach((key) => {
68+
if (
69+
!_.isEmpty(object[key]) &&
70+
(_.isPlainObject(object[key]) || Array.isArray(object[key]))
71+
) {
72+
keysAccumulator = exports.objectKeysDeep(
73+
object[key],
74+
keysAccumulator,
75+
`${parentPath}${key}.`
76+
)
77+
} else {
78+
keysAccumulator.push(`${parentPath}${key}`)
79+
}
80+
})
81+
}
6482

65-
return propertiesCount
83+
return keysAccumulator
6684
}
6785

6886
/**
@@ -106,10 +124,11 @@ exports.countNestedProperties = (object) => {
106124
* @param {boolean} [exact=false] - if `true`, specification must match all object's properties
107125
*/
108126
exports.assertObjectMatchSpec = (object, spec, exact = false) => {
127+
const specPath = new Set()
109128
spec.forEach(({ field, matcher, value }) => {
110129
const currentValue = _.get(object, field)
111130
const expectedValue = Cast.value(value)
112-
131+
specPath.add(field)
113132
const rule = exports.getMatchingRule(matcher)
114133

115134
switch (rule.name) {
@@ -208,11 +227,12 @@ exports.assertObjectMatchSpec = (object, spec, exact = false) => {
208227

209228
// We check we have exactly the same number of properties as expected
210229
if (exact === true) {
211-
const propertiesCount = exports.countNestedProperties(object)
230+
const objectKeys = exports.objectKeysDeep(object)
231+
const specObjectKeys = Array.from(specPath)
212232
expect(
213-
propertiesCount,
233+
objectKeys,
214234
'Expected json response to fully match spec, but it does not'
215-
).to.be.equal(spec.length)
235+
).to.be.deep.equal(specObjectKeys)
216236
}
217237
}
218238

@@ -261,7 +281,10 @@ exports.getMatchingRule = (matcher) => {
261281

262282
const relativeDateGroups = relativeDateRegex.exec(matcher)
263283
if (relativeDateGroups) {
264-
return { name: RuleName.RelativeDate, isNegated: !!relativeDateGroups[1] }
284+
return {
285+
name: RuleName.RelativeDate,
286+
isNegated: !!relativeDateGroups[1],
287+
}
265288
}
266289

267290
expect.fail(`Matcher "${matcher}" did not match any supported assertions`)

0 commit comments

Comments
 (0)