diff --git a/docs/ExpectAPI.md b/docs/ExpectAPI.md index d1ce4d6dbf84..2a94db94b5a6 100644 --- a/docs/ExpectAPI.md +++ b/docs/ExpectAPI.md @@ -320,13 +320,15 @@ test('randocall calls its callback with a number', () => { ### `expect.arrayContaining(array)` -`expect.arrayContaining(array)` matches a received array which contains all of the elements in the expected array. That is, the expected array is a **subset** of the received array. Therefore, it matches a received array which contains elements that are **not** in the expected array. +`expect.arrayContaining(array)` matches the received value if it is an array which contains all of the elements in the expected array. That is, the expected array is a **subset** of the received array. You can use it instead of a literal value: - in `toEqual` or `toBeCalledWith` - to match a property in `objectContaining` or `toMatchObject` +As a special case, `expect.arrayContaining([])` matches any value, including non-arrays. + ```js describe('arrayContaining', () => { const expected = ['Alice', 'Bob']; @@ -397,7 +399,9 @@ The `expect.hasAssertions()` call ensures that the `prepareState` callback actua ### `expect.not.arrayContaining(array)` -`expect.not.arrayContaining(array)` matches a received array which does not contain all of the elements in the expected array. That is, the expected array **is not a subset** of the received array. +`expect.not.arrayContaining(array)` matches the received value if it is not an array or if it is an array that does not contain all of the elements in the expected array. That is, the expected array **is not a subset** of the received value. + +As a special case, `expect.not.arrayContaining([])` matches nothing. It is the inverse of `expect.arrayContaining`. @@ -415,17 +419,33 @@ describe('not.arrayContaining', () => { ### `expect.not.objectContaining(object)` -`expect.not.objectContaining(object)` matches any received object that does not recursively match the expected properties. That is, the expected object **is not a subset** of the received object. Therefore, it matches a received object which contains properties that are **not** in the expected object. +`expect.not.objectContaining(object)` matches any received value (object or primitive) which **does not** have (own or inherited) properties matching each of the enumerable (own or inherited) properties of the expected object. + +That is, it matches any value which lacks one or more of the enumerable properties of the expected object, as well as any value which has a property not matching the corresponding property of the expected object. + +Primitive received values will be coerced to objects iff they are truthy. It is the inverse of `expect.objectContaining`. ```js describe('not.objectContaining', () => { - const expected = {foo: 'bar'}; - it('matches if the actual object does not contain expected key: value pairs', () => { + const expected = {foo: 'bar'}; + expect({bar: 'baz'}).toEqual(expect.not.objectContaining(expected)); }); + + it('boxes truthy primitive values', () => { + const expected = {toExponential: expect.any(Function)}; + + expect(1).not.toEqual(expect.not.objectContaining(expected)); + }); + + it('does not box falsy primitive values', () => { + const expected = {toExponential: expect.any(Function)}; + + expect(0).toEqual(expect.not.objectContaining(expected)); + }); }); ``` @@ -463,10 +483,12 @@ describe('not.stringMatching', () => { ### `expect.objectContaining(object)` -`expect.objectContaining(object)` matches any received object that recursively matches the expected properties. That is, the expected object is a **subset** of the received object. Therefore, it matches a received object which contains properties that **are present** in the expected object. +`expect.objectContaining(object)` matches any received value (object or primitive) that has (own or inherited) properties matching all enumerable (own or inherited) properties of the expected object. Instead of literal property values in the expected object, you can use matchers, `expect.anything()`, and so on. +Primitive received values will be coerced to objects iff they are truthy; for example, `expect.objectContaining({toExponential: expect.any(Function)})` matches `1` and does not match `0`. + For example, let's say that we expect an `onPress` function to be called with an `Event` object, and all we need to verify is that the event has `event.x` and `event.y` properties. We can do that with: ```js diff --git a/packages/expect/src/__tests__/asymmetricMatchers.test.ts b/packages/expect/src/__tests__/asymmetricMatchers.test.ts index ec48bc73a624..5d6db1edc555 100644 --- a/packages/expect/src/__tests__/asymmetricMatchers.test.ts +++ b/packages/expect/src/__tests__/asymmetricMatchers.test.ts @@ -215,6 +215,56 @@ test('ObjectContaining throws for non-objects', () => { jestExpect(() => objectContaining(1337).asymmetricMatch()).toThrow(); }); +test('ObjectContaining matches primitives', () => { + jestExpect(objectContaining({}).asymmetricMatch(null)).toBe(true); + jestExpect(objectContaining({}).asymmetricMatch(undefined)).toBe(true); +}); + +test('ObjectContaining boxes truthy autoboxable primitives', () => { + jestExpect(objectContaining({0: 'f'}).asymmetricMatch('foo')).toBe(true); + jestExpect( + objectContaining({length: any(Number)}).asymmetricMatch('foo'), + ).toBe(true); + jestExpect( + objectContaining({toExponential: any(Function)}).asymmetricMatch(1), + ).toBe(true); + // Symbol.prototype.description isn't present on Node 10 + if (Symbol('foo').description) { + jestExpect( + objectContaining({description: any(String)}).asymmetricMatch( + Symbol('foo'), + ), + ).toBe(true); + } +}); + +test('ObjectContaining does not box falsy autoboxable primitives', () => { + jestExpect( + objectContaining({toExponential: any(Function)}).asymmetricMatch(0), + ).toBe(false); + jestExpect(objectContaining({length: any(Number)}).asymmetricMatch('')).toBe( + false, + ); +}); + +test('ObjectNotContaining boxes truthy autoboxable primitives', () => { + jestExpect( + objectNotContaining({toExponential: any(Function)}).asymmetricMatch(1), + ).toBe(false); + jestExpect( + objectNotContaining({length: any(Number)}).asymmetricMatch('foo'), + ).toBe(false); +}); + +test('ObjectNotContaining does not box falsy autoboxable primitives', () => { + jestExpect( + objectNotContaining({toExponential: any(Function)}).asymmetricMatch(0), + ).toBe(true); + jestExpect( + objectNotContaining({length: any(Number)}).asymmetricMatch(''), + ).toBe(true); +}); + test('ObjectContaining does not mutate the sample', () => { const sample = {foo: {bar: {}}}; const sample_json = JSON.stringify(sample);