Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Document expect(.not).(array|object)Containing()'s actual behavior #11157

Draft
wants to merge 4 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
34 changes: 28 additions & 6 deletions docs/ExpectAPI.md
Expand Up @@ -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'];
Expand Down Expand Up @@ -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`.

Expand All @@ -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));
});
});
```

Expand Down Expand Up @@ -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
Expand Down
50 changes: 50 additions & 0 deletions packages/expect/src/__tests__/asymmetricMatchers.test.ts
Expand Up @@ -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);
Expand Down