Skip to content

Commit

Permalink
Replace hasOwnProperty with hasPropertyInObject
Browse files Browse the repository at this point in the history
  • Loading branch information
souldzin committed Aug 21, 2020
1 parent 3467338 commit 1dde15b
Show file tree
Hide file tree
Showing 2 changed files with 5 additions and 70 deletions.
41 changes: 0 additions & 41 deletions packages/expect/src/__tests__/utils.test.ts
Expand Up @@ -11,7 +11,6 @@ import {
emptyObject,
getObjectSubset,
getPath,
hasOwnProperty,
iterableEquality,
subsetEquality,
} from '../utils';
Expand Down Expand Up @@ -107,46 +106,6 @@ 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: unknown) {}
}
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';
// @ts-expect-error
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'}],
Expand Down
34 changes: 5 additions & 29 deletions packages/expect/src/utils.ts
Expand Up @@ -21,7 +21,10 @@ type GetPath = {
value?: unknown;
};

const hasPropertyInObject = (object: object, key: string) => {
/**
* Checks if `hasOwnProperty(object, key)` up the prototype chain, stopping at `Object.prototype`.
*/
const hasPropertyInObject = (object: object, key: string): boolean => {
const shouldTerminate =
!object || typeof object !== 'object' || object === Object.prototype;

Expand All @@ -35,33 +38,6 @@ const hasPropertyInObject = (object: object, key: string) => {
);
};

// 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): boolean =>
Object.prototype.hasOwnProperty.call(object, key) ||
hasGetterFromConstructor(object, key);

export const getPath = (
object: Record<string, any>,
propertyPath: string | Array<string>,
Expand Down Expand Up @@ -143,7 +119,7 @@ export const getObjectSubset = (
seenReferences.set(object, trimmed);

Object.keys(object)
.filter(key => hasOwnProperty(subset, key))
.filter(key => hasPropertyInObject(subset, key))
.forEach(key => {
trimmed[key] = seenReferences.has(object[key])
? seenReferences.get(object[key])
Expand Down

0 comments on commit 1dde15b

Please sign in to comment.