Skip to content

Commit

Permalink
Merge ab7f576 into cd4c62f
Browse files Browse the repository at this point in the history
  • Loading branch information
alexjeffburke committed Dec 11, 2019
2 parents cd4c62f + ab7f576 commit e831026
Show file tree
Hide file tree
Showing 3 changed files with 225 additions and 6 deletions.
16 changes: 16 additions & 0 deletions documentation/assertions/object/to-have-properties.md
Expand Up @@ -12,6 +12,22 @@ expect(['a', { c: 'c' }, 'd'], 'to have properties', {
});
```

When validating the object against an array of properties, the `only` flag can
be used to assert that only the specified properties are present:

```js
expect({ foo: 123, bar: 456 }, 'to only have properties', ['foo']);
```

```output
expected { foo: 123, bar: 456 } to only have properties [ 'foo' ]
{
foo: 123,
bar: 456 // should be removed
}
```

Using the `own` flag, you can assert presence of an own properties.

```js
Expand Down
108 changes: 104 additions & 4 deletions lib/assertions.js
Expand Up @@ -319,7 +319,7 @@ module.exports = expect => {
);

expect.addAssertion(
'<object> [not] to have [own] properties <array>',
'<object> [not] to [only] have [own] properties <array>',
(expect, subject, propertyNames) => {
const unsupportedPropertyNames = [];
propertyNames.forEach(propertyName => {
Expand All @@ -344,9 +344,109 @@ module.exports = expect => {
this.outdentLines();
});
}
propertyNames.forEach(propertyName => {
expect(subject, '[not] to have [own] property', String(propertyName));
});

if (expect.flags.only) {
if (expect.flags.not) {
expect.errorMode = 'bubble';
expect.fail(
'The "not" flag cannot be used together with "to only have properties".'
);
}
if (expect.flags.own) {
expect.errorMode = 'bubble';
expect.fail(
'The "own" flag cannot be used together with "to only have properties".'
);
}
const subjectType = expect.subjectType;
const subjectKeys = subjectType.getKeys(subject).filter(
key =>
// include only those keys whose value is not undefined
typeof subjectType.valueForKey(subject, key) !== 'undefined'
);

expect.withError(
() => {
expect(subjectKeys.length === propertyNames.length, 'to be true');
// now catch differing property names
const keyInValue = {};
propertyNames.forEach(key => {
keyInValue[key] = true;
});
subjectKeys.forEach(key =>
expect(
Object.prototype.hasOwnProperty.call(keyInValue, key),
'to be true'
)
);
},
() => {
expect.fail({
diff: (output, diff, inspect, equal) => {
output.inline = true;

const keyInValue = {};
propertyNames.forEach(key => {
keyInValue[key] = true;
});

subjectType.prefix(output, subject);
output.nl().indentLines();

subjectKeys.forEach((key, index) => {
const propertyOutput = subjectType.property(
output.clone(),
key,
inspect(subjectType.valueForKey(subject, key))
);
const delimiterOutput = subjectType.delimiter(
output.clone(),
index,
subjectKeys.length
);

output
.i()
.block(function() {
this.append(propertyOutput).amend(delimiterOutput);
if (
!Object.prototype.hasOwnProperty.call(keyInValue, key)
) {
this.sp().annotationBlock(function() {
this.error('should be removed');
});
} else {
delete keyInValue[key];
}
})
.nl();
});

// list any remaining value properties as missing
Object.keys(keyInValue).forEach(valueKey => {
output
.i()
.annotationBlock(function() {
this.error('missing')
.sp()
.append(inspect(valueKey));
})
.nl();
});

output.outdentLines();
subjectType.suffix(output, subject);

return output;
}
});
}
);
} else {
propertyNames.forEach(propertyName => {
expect(subject, '[not] to have [own] property', String(propertyName));
});
}
}
);

Expand Down
107 changes: 105 additions & 2 deletions test/assertions/to-have-properties.spec.js
Expand Up @@ -195,7 +195,7 @@ describe('to have properties assertion', () => {
' The assertion does not have a matching signature for:\n' +
' <object> to have properties <string> <string>\n' +
' did you mean:\n' +
' <object> [not] to have [own] properties <array>\n' +
' <object> [not] to [only] have [own] properties <array>\n' +
' <object> to have [own] properties <object>'
);

Expand All @@ -211,7 +211,7 @@ describe('to have properties assertion', () => {
' The assertion does not have a matching signature for:\n' +
' <object> not to have properties <object>\n' +
' did you mean:\n' +
' <object> [not] to have [own] properties <array>'
' <object> [not] to [only] have [own] properties <array>'
);
});

Expand Down Expand Up @@ -289,4 +289,107 @@ describe('to have properties assertion', () => {
);
});
});

describe('with the "only" flag', () => {
it('should pass', () => {
expect(function() {
expect({ foo: 123, bar: 456 }, 'to only have properties', [
'foo',
'bar'
]);
}, 'not to throw');
});

it('should pass regardless of ordering', () => {
expect(function() {
expect({ foo: 123, bar: 456, baz: 768 }, 'to only have properties', [
'baz',
'foo',
'bar'
]);
}, 'not to throw');
});

it('should fail with a diff and mark properties to be removed', () => {
expect(
function() {
expect({ foo: 123, bar: 456 }, 'to only have properties', ['foo']);
},
'to error',
"expected { foo: 123, bar: 456 } to only have properties [ 'foo' ]\n" +
'\n' +
'{\n' +
' foo: 123,\n' +
' bar: 456 // should be removed\n' +
'}'
);
});

it('should fail with a diff and mark properties that are missing', () => {
expect(
function() {
expect({ foo: 123, bar: 456 }, 'to only have properties', [
'foo',
'baz'
]);
},
'to error',
"expected { foo: 123, bar: 456 } to only have properties [ 'foo', 'baz' ]\n" +
'\n' +
'{\n' +
' foo: 123,\n' +
' bar: 456 // should be removed\n' +
" // missing 'baz'\n" +
'}'
);
});

it('should fail with a diff while avoiding the prototype chain', () => {
expect(
function() {
expect({ toString: 'foobar' }, 'to only have properties', []);
},
'to error',
"expected { toString: 'foobar' } to only have properties []\n" +
'\n' +
'{\n' +
" toString: 'foobar' // should be removed\n" +
'}'
);
});

it('should ignore undefined properties (pass)', () => {
expect(function() {
expect({ foo: 123, bar: undefined }, 'to only have properties', [
'foo'
]);
}, 'not to throw');
});

it('should ignore undefined properties (fail)', () => {
expect(function() {
expect({ 123: undefined }, 'to only have properties', ['123']);
}, 'to throw');
});

it('should error if used with the not flag', () => {
expect(
function() {
expect({ foo: 123 }, 'not to only have properties', ['foo']);
},
'to throw',
'The "not" flag cannot be used together with "to only have properties".'
);
});

it('should error if used with the own flag', () => {
expect(
function() {
expect({ foo: 123 }, 'to only have own properties', ['foo']);
},
'to throw',
'The "own" flag cannot be used together with "to only have properties".'
);
});
});
});

0 comments on commit e831026

Please sign in to comment.