diff --git a/documentation/assertions/array-like/to-contain.md b/documentation/assertions/array-like/to-contain.md index 5caba77ae..719929adf 100644 --- a/documentation/assertions/array-like/to-contain.md +++ b/documentation/assertions/array-like/to-contain.md @@ -46,3 +46,34 @@ not to contain { name: 'Jane Doe' } { name: 'Jane Doe' } // should be removed ] ``` + +You can use the `only` flag to indicate that you want no other items to +be in the subject. + +```js +expect( + [{ name: 'John Doe' }, { name: 'Jane Doe' }], + 'to only contain', + { name: 'Jane Doe' }, + { name: 'John Doe' } +); +``` + +In case there are more items than that in your subject, you will get the +following output: + +```js +expect([{ name: 'Jane Doe' }, { name: 'John Doe' }], 'to only contain', { + name: 'Jane Doe', +}); +``` + +```output +expected [ { name: 'Jane Doe' }, { name: 'John Doe' } ] +to only contain { name: 'Jane Doe' } + +[ + { name: 'Jane Doe' }, + { name: 'John Doe' } // should be removed +] +``` diff --git a/lib/assertions.js b/lib/assertions.js index cf95050ea..3d38b6500 100644 --- a/lib/assertions.js +++ b/lib/assertions.js @@ -766,11 +766,19 @@ module.exports = (expect) => { ); }); - expect.addAssertion(' [not] to contain ', function ( + expect.addAssertion(' [not] to [only] contain ', function ( expect, subject ) { const args = Array.prototype.slice.call(arguments, 2); + + if (expect.flags.only && expect.flags.not) { + expect.errorMode = 'bubble'; + expect.fail( + 'The "not" flag cannot be used together with "to only contain".' + ); + } + expect.withError( () => { args.forEach((arg) => { @@ -782,19 +790,33 @@ module.exports = (expect) => { '[not] to be truthy' ); }); + if (expect.flags.only) { + expect(subject.length, 'to be', args.length); + } }, (e) => { expect.fail({ - diff: - expect.flags.not && - ((output, diff, inspect, equal) => - diff( + diff(output, diff, inspect, equal) { + if (expect.flags.not) { + return diff( subject, Array.prototype.filter.call( subject, (item) => !args.some((arg) => equal(item, arg)) ) - )), + ); + } else if (expect.flags.only) { + var excessItems = Array.prototype.filter.call(subject, (item) => + args.some((arg) => equal(item, arg)) + ); + var missingItems = Array.prototype.filter.call( + args, + (arg) => !subject.some((item) => equal(arg, item)) + ); + + return diff(subject, excessItems.concat(missingItems)); + } + }, }); } ); diff --git a/test/assertions/to-contain.spec.js b/test/assertions/to-contain.spec.js index 00cf78084..df3535f4a 100644 --- a/test/assertions/to-contain.spec.js +++ b/test/assertions/to-contain.spec.js @@ -32,7 +32,7 @@ describe('to contain assertion', () => { ' The assertion does not have a matching signature for:\n' + ' not to contain \n' + ' did you mean:\n' + - ' [not] to contain \n' + + ' [not] to [only] contain \n' + ' [not] to contain ' ); @@ -80,7 +80,7 @@ describe('to contain assertion', () => { ' The assertion does not have a matching signature for:\n' + ' to contain \n' + ' did you mean:\n' + - ' [not] to contain \n' + + ' [not] to [only] contain \n' + ' [not] to contain ' ); }); @@ -166,6 +166,66 @@ describe('to contain assertion', () => { }); }); + describe('with the only flag', () => { + it('should not throw when all items are included in the subject', () => { + expect(function () { + expect( + [{ bar: 456 }, { foo: 123 }], + 'to only contain', + { foo: 123 }, + { bar: 456 } + ); + }, 'not to throw'); + }); + + it('should throw when all items are not included in the subject', () => { + expect( + function () { + expect([{ bar: 456 }], 'to only contain', { foo: 123 }, { bar: 456 }); + }, + 'to throw exception', + 'expected [ { bar: 456 } ] to only contain { foo: 123 }, { bar: 456 }\n' + + '\n' + + '[\n' + + ' { bar: 456 }\n' + + ' // missing { foo: 123 }\n' + + ']' + ); + }); + + it('should throw when all items are included in the subject but subject has other items as well', () => { + expect( + function () { + expect( + [{ bar: 456 }, { foo: 123 }, { baz: 789 }], + 'to only contain', + { foo: 123 }, + { bar: 456 } + ); + }, + 'to throw exception', + 'expected [ { bar: 456 }, { foo: 123 }, { baz: 789 } ]\n' + + 'to only contain { foo: 123 }, { bar: 456 }\n' + + '\n' + + '[\n' + + ' { bar: 456 },\n' + + ' { foo: 123 },\n' + + ' { baz: 789 } // should be removed\n' + + ']' + ); + }); + + it('should throw when combining the not and only flags', () => { + expect( + function () { + expect([{ foo: 123 }], 'not to only contain', { foo: 123 }); + }, + 'to throw exception', + 'The "not" flag cannot be used together with "to only contain".' + ); + }); + }); + it('should not highlight overlapping partial matches', () => { expect( function () {