From eb2972bd678b8bc5837ae2014f9847f3858b3784 Mon Sep 17 00:00:00 2001 From: ninevra Date: Fri, 5 Mar 2021 13:21:13 -0800 Subject: [PATCH 1/4] Test Object(Not)Containing with received primitives --- .../src/__tests__/asymmetricMatchers.test.ts | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/packages/expect/src/__tests__/asymmetricMatchers.test.ts b/packages/expect/src/__tests__/asymmetricMatchers.test.ts index ec48bc73a624..ebd3a1746785 100644 --- a/packages/expect/src/__tests__/asymmetricMatchers.test.ts +++ b/packages/expect/src/__tests__/asymmetricMatchers.test.ts @@ -215,6 +215,51 @@ 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); + 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); From bb3f1480b914536c136b8d48752b65f6be22ff4c Mon Sep 17 00:00:00 2001 From: ninevra Date: Fri, 5 Mar 2021 13:22:20 -0800 Subject: [PATCH 2/4] Document actual behavior of Object(Not)Containing --- docs/ExpectAPI.md | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/docs/ExpectAPI.md b/docs/ExpectAPI.md index d1ce4d6dbf84..ca1fc71d0f18 100644 --- a/docs/ExpectAPI.md +++ b/docs/ExpectAPI.md @@ -415,17 +415,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 +479,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 From da3ad5c8d1142ab3aaf3b1baaf75f5685e9a5002 Mon Sep 17 00:00:00 2001 From: ninevra Date: Fri, 5 Mar 2021 13:22:38 -0800 Subject: [PATCH 3/4] Document edge cases of Array(Not)Containing --- docs/ExpectAPI.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/docs/ExpectAPI.md b/docs/ExpectAPI.md index ca1fc71d0f18..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`. From 508d09d3d132950e1ddc2abcf8cbd1b6d1bf482e Mon Sep 17 00:00:00 2001 From: ninevra Date: Fri, 5 Mar 2021 14:20:32 -0800 Subject: [PATCH 4/4] Skip Symbol() test case on Node 10 --- .../expect/src/__tests__/asymmetricMatchers.test.ts | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/packages/expect/src/__tests__/asymmetricMatchers.test.ts b/packages/expect/src/__tests__/asymmetricMatchers.test.ts index ebd3a1746785..5d6db1edc555 100644 --- a/packages/expect/src/__tests__/asymmetricMatchers.test.ts +++ b/packages/expect/src/__tests__/asymmetricMatchers.test.ts @@ -228,9 +228,14 @@ test('ObjectContaining boxes truthy autoboxable primitives', () => { jestExpect( objectContaining({toExponential: any(Function)}).asymmetricMatch(1), ).toBe(true); - jestExpect( - objectContaining({description: any(String)}).asymmetricMatch(Symbol('foo')), - ).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', () => {