diff --git a/CHANGELOG.md b/CHANGELOG.md index dd9c3f7dd2c5..602f57bd5183 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -90,6 +90,7 @@ - `[jest-haste-map]` Remove legacy condition for duplicate module detection ([#7333](https://github.com/facebook/jest/pull/7333)) - `[jest-haste-map]` Fix `require` detection with trailing commas and ignore `import typeof` modules ([#7385](https://github.com/facebook/jest/pull/7385)) - `[jest-cli]` Fix to set prettierPath via config file ([#7412](https://github.com/facebook/jest/pull/7412)) +- `[expect]` Test more precisely for class instance getters ([#7477](https://github.com/facebook/jest/pull/7477)) - `[jest-cli]` Support dashed args ([#7497](https://github.com/facebook/jest/pull/7497)) - `[jest-cli]` Fix to run in band tests if watch mode enable when runInBand arg used ([#7518](https://github.com/facebook/jest/pull/7518)) - `[jest-runtime]` Fix mistake as test files when run coverage issue. ([#7506](https://github.com/facebook/jest/pull/7506)) diff --git a/packages/expect/src/__tests__/utils.test.js b/packages/expect/src/__tests__/utils.test.js index 6a5e7d909680..43df140f68a7 100644 --- a/packages/expect/src/__tests__/utils.test.js +++ b/packages/expect/src/__tests__/utils.test.js @@ -13,6 +13,7 @@ const { emptyObject, getObjectSubset, getPath, + hasOwnProperty, subsetEquality, } = require('../utils'); @@ -94,6 +95,45 @@ describe('getPath()', () => { }); }); +describe('hasOwnProperty', () => { + it('does inherit getter from class', () => { + class MyClass { + get key() { + return 'value'; + } + } + expect(hasOwnProperty(new MyClass(), 'key')).toBe(true); + }); + + it('does not inherit setter from class', () => { + class MyClass { + set key(value) {} + } + expect(hasOwnProperty(new MyClass(), 'key')).toBe(false); + }); + + it('does not inherit method from class', () => { + class MyClass { + key() {} + } + expect(hasOwnProperty(new MyClass(), 'key')).toBe(false); + }); + + it('does not inherit property from constructor prototype', () => { + function MyClass() {} + MyClass.prototype.key = 'value'; + expect(hasOwnProperty(new MyClass(), 'key')).toBe(false); + }); + + it('does not inherit __proto__ getter from Object', () => { + expect(hasOwnProperty({}, '__proto__')).toBe(false); + }); + + it('does not inherit toString method from Object', () => { + expect(hasOwnProperty({}, 'toString')).toBe(false); + }); +}); + describe('getObjectSubset()', () => { [ [{a: 'b', c: 'd'}, {a: 'd'}, {a: 'b'}], diff --git a/packages/expect/src/utils.js b/packages/expect/src/utils.js index 24197cc2c50f..a3038e0abd80 100644 --- a/packages/expect/src/utils.js +++ b/packages/expect/src/utils.js @@ -21,18 +21,33 @@ type GetPath = { value?: any, }; -export const hasOwnProperty = (object: Object, value: string) => { - // Account for objects created using unconventional means such as - // `Object.create(null)`, in which case the `object.constructor` is undefined - // See: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/create#Custom_and_Null_objects - const objectConstructor = object.constructor || Object; - - return ( - Object.prototype.hasOwnProperty.call(object, value) || - Object.prototype.hasOwnProperty.call(objectConstructor.prototype, value) +// Return whether object instance inherits getter from its class. +const hasGetterFromConstructor = (object: Object, key: string) => { + const constructor = object.constructor; + if (constructor === Object) { + // A literal object has Object as constructor. + // Therefore, it cannot inherit application-specific getters. + // Furthermore, Object has __proto__ getter which is not relevant. + // Array, Boolean, Number, String constructors don’t have any getters. + return false; + } + if (typeof constructor !== 'function') { + // Object.create(null) constructs object with no constructor nor prototype. + // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/create#Custom_and_Null_objects + return false; + } + + const descriptor = Object.getOwnPropertyDescriptor( + constructor.prototype, + key, ); + return descriptor !== undefined && typeof descriptor.get === 'function'; }; +export const hasOwnProperty = (object: Object, key: string) => + Object.prototype.hasOwnProperty.call(object, key) || + hasGetterFromConstructor(object, key); + export const getPath = ( object: Object, propertyPath: string | Array,