diff --git a/CHANGELOG.md b/CHANGELOG.md index e15bad622f72..2bbc4accd8ca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ ### Fixes +- `[expect, @jest/expect-utils]` `ObjectContaining` support `sumbol` as key ([#14414](https://github.com/jestjs/jest/pull/14414)) - `[expect]` Remove `@types/node` from dependencies ([#14385](https://github.com/jestjs/jest/pull/14385)) - `[jest-core]` Use workers in watch mode by default to avoid crashes ([#14059](https://github.com/facebook/jest/pull/14059) & [#14085](https://github.com/facebook/jest/pull/14085)). - `[jest-reporters]` Update `istanbul-lib-instrument` dependency to v6. ([#14401](https://github.com/jestjs/jest/pull/14401)) diff --git a/packages/expect-utils/src/utils.ts b/packages/expect-utils/src/utils.ts index cb4ab7fda33f..ed5c2e36bf74 100644 --- a/packages/expect-utils/src/utils.ts +++ b/packages/expect-utils/src/utils.ts @@ -47,7 +47,7 @@ const hasPropertyInObject = (object: object, key: string | symbol): boolean => { // the prototype chain for string keys but not for symbols. (Otherwise, it // could find values such as a Set or Map's Symbol.toStringTag, with unexpected // results.) -const getObjectKeys = (object: object) => [ +export const getObjectKeys = (object: object): Array => [ ...Object.keys(object), ...Object.getOwnPropertySymbols(object), ]; diff --git a/packages/expect/src/__tests__/asymmetricMatchers.test.ts b/packages/expect/src/__tests__/asymmetricMatchers.test.ts index 24a326e21899..3fb5f90835ef 100644 --- a/packages/expect/src/__tests__/asymmetricMatchers.test.ts +++ b/packages/expect/src/__tests__/asymmetricMatchers.test.ts @@ -181,6 +181,7 @@ test('ArrayNotContaining throws for non-arrays', () => { }); test('ObjectContaining matches', () => { + const foo = Symbol('foo'); [ objectContaining({}).asymmetricMatch('jest'), objectContaining({foo: 'foo'}).asymmetricMatch({foo: 'foo', jest: 'jest'}), @@ -192,12 +193,15 @@ test('ObjectContaining matches', () => { foo: Buffer.from('foo'), jest: 'jest', }), + objectContaining({[foo]: 'foo'}).asymmetricMatch({[foo]: 'foo'}), ].forEach(test => { jestExpect(test).toEqual(true); }); }); test('ObjectContaining does not match', () => { + const foo = Symbol('foo'); + const bar = Symbol('bar'); [ objectContaining({foo: 'foo'}).asymmetricMatch({bar: 'bar'}), objectContaining({foo: 'foo'}).asymmetricMatch({foo: 'foox'}), @@ -206,6 +210,7 @@ test('ObjectContaining does not match', () => { answer: 42, foo: {bar: 'baz', foobar: 'qux'}, }).asymmetricMatch({foo: {bar: 'baz'}}), + objectContaining({[foo]: 'foo'}).asymmetricMatch({[bar]: 'bar'}), ].forEach(test => { jestExpect(test).toEqual(false); }); @@ -250,9 +255,12 @@ test('ObjectContaining does not mutate the sample', () => { }); test('ObjectNotContaining matches', () => { + const foo = Symbol('foo'); + const bar = Symbol('bar'); [ objectContaining({}).asymmetricMatch(null), objectContaining({}).asymmetricMatch(undefined), + objectNotContaining({[foo]: 'foo'}).asymmetricMatch({[bar]: 'bar'}), objectNotContaining({foo: 'foo'}).asymmetricMatch({bar: 'bar'}), objectNotContaining({foo: 'foo'}).asymmetricMatch({foo: 'foox'}), objectNotContaining({foo: undefined}).asymmetricMatch({}), diff --git a/packages/expect/src/asymmetricMatchers.ts b/packages/expect/src/asymmetricMatchers.ts index 1eb9c52fa66d..5bd543ac5297 100644 --- a/packages/expect/src/asymmetricMatchers.ts +++ b/packages/expect/src/asymmetricMatchers.ts @@ -8,6 +8,7 @@ import { equals, + getObjectKeys, isA, iterableEquality, subsetEquality, @@ -52,7 +53,10 @@ function getPrototype(obj: object) { return obj.constructor.prototype; } -export function hasProperty(obj: object | null, property: string): boolean { +export function hasProperty( + obj: object | null, + property: string | symbol, +): boolean { if (!obj) { return false; } @@ -216,8 +220,10 @@ class ArrayContaining extends AsymmetricMatcher> { } } -class ObjectContaining extends AsymmetricMatcher> { - constructor(sample: Record, inverse = false) { +class ObjectContaining extends AsymmetricMatcher< + Record +> { + constructor(sample: Record, inverse = false) { super(sample, inverse); } @@ -232,14 +238,12 @@ class ObjectContaining extends AsymmetricMatcher> { let result = true; const matcherContext = this.getMatcherContext(); - for (const property in this.sample) { + const objectKeys = getObjectKeys(this.sample); + + for (const key of objectKeys) { if ( - !hasProperty(other, property) || - !equals( - this.sample[property], - other[property], - matcherContext.customTesters, - ) + !hasProperty(other, key) || + !equals(this.sample[key], other[key], matcherContext.customTesters) ) { result = false; break;