Skip to content

Fix instanceof checks #5995

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

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
- `[@jest/types]` Mark deprecated configuration options as `@deprecated` ([#11913](https://github.com/facebook/jest/pull/11913))
- `[jest-cli]` Improve `--help` printout by removing defunct `--browser` option ([#11914](https://github.com/facebook/jest/pull/11914))
- `[jest-haste-map]` Use distinct cache paths for different values of `computeDependencies` ([#11916](https://github.com/facebook/jest/pull/11916))
- `[jest-util]` Install fix for `instanceof` as part of other globals into test environments ([#5995](https://github.com/facebook/jest/pull/5995))

### Chore & Maintenance

Expand Down
14 changes: 14 additions & 0 deletions e2e/__tests__/instanceof.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

import runJest from '../runJest';

test('suite with `instanceof` checks', () => {
const {exitCode} = runJest('instanceof');

expect(exitCode).toBe(0);
});
132 changes: 132 additions & 0 deletions e2e/instanceof/__tests__/test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
/**
* Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
'use strict';

const fs = require('fs');
const http = require('http');
const util = require('util');
const v8 = require('v8');

const readFile = util.promisify(fs.readFile);

function getSuperClass(cls) {
const prototype = Object.getPrototypeOf(cls.prototype);
return prototype ? prototype.constructor : null;
}

const buffers = fs.readdirSync(__dirname);
const buffer = fs.readFileSync(__filename);
const error = (() => {
try {
fs.readFileSync('/');
} catch (e) {
return e;
}
})();
const promise = readFile(__filename);

const nodeArrayType = buffers.constructor;
const nodeErrorType = error.constructor;
const nodePromiseType = promise.constructor;
const nodeUint8ArrayType = getSuperClass(buffer.constructor);
const nodeTypedArrayType = getSuperClass(nodeUint8ArrayType);
const nodeObjectType = getSuperClass(nodeTypedArrayType);

const globalTypedArrayType = getSuperClass(Uint8Array);

test('fs Error', () => {
expect.hasAssertions();

try {
fs.readFileSync('does not exist');
} catch (err) {
expect(err).toBeInstanceOf(Error);
}
});

test('http error', done => {
const request = http.request('http://does-not-exist/blah', res => {
console.log(`STATUS: ${res.statusCode}`);
res.on('end', () => {
done(new Error('Ended before failure'));
});
});

request.once('error', err => {
expect(err).toBeInstanceOf(Error);
done();
});
});

test('Array', () => {
expect([]).toBeInstanceOf(Array);
});

test('array type', () => {
expect(buffers).toBeInstanceOf(Array);
expect(buffers).toBeInstanceOf(Object);
expect([]).toBeInstanceOf(nodeArrayType);
expect([]).toBeInstanceOf(nodeObjectType);
});

test('error type', () => {
expect(error).toBeInstanceOf(Error);
expect(error).toBeInstanceOf(Object);
expect(new Error()).toBeInstanceOf(nodeErrorType);
expect(new Error()).toBeInstanceOf(nodeObjectType);
});

test('promise type', () => {
expect(promise).toBeInstanceOf(Promise);
expect(promise).toBeInstanceOf(Object);
expect(new Promise(resolve => resolve())).toBeInstanceOf(nodePromiseType);
expect(new Promise(resolve => resolve())).toBeInstanceOf(nodeObjectType);
});

test('Uint8Array type', () => {
expect(buffer).toBeInstanceOf(Buffer);
expect(buffer).toBeInstanceOf(Uint8Array);
expect(buffer).toBeInstanceOf(globalTypedArrayType);
expect(buffer).toBeInstanceOf(Object);
expect(new Uint8Array([])).toBeInstanceOf(nodeUint8ArrayType);
expect(new Uint8Array([])).toBeInstanceOf(nodeTypedArrayType);
expect(new Uint8Array([])).toBeInstanceOf(nodeObjectType);
});

test('recognizes typed arrays as objects', () => {
expect(new Uint8Array([1, 2, 3])).toBeInstanceOf(Object);
expect(new Uint8ClampedArray([1, 2, 3])).toBeInstanceOf(Object);
expect(new Uint16Array([1, 2, 3])).toBeInstanceOf(Object);
expect(new Uint32Array([1, 2, 3])).toBeInstanceOf(Object);
expect(new BigUint64Array([])).toBeInstanceOf(Object);
expect(new Int8Array([1, 2, 3])).toBeInstanceOf(Object);
expect(new Int16Array([1, 2, 3])).toBeInstanceOf(Object);
expect(new Int32Array([1, 2, 3])).toBeInstanceOf(Object);
expect(new BigInt64Array([])).toBeInstanceOf(Object);
expect(new Float32Array([1, 2, 3])).toBeInstanceOf(Object);
expect(new Float64Array([1, 2, 3])).toBeInstanceOf(Object);
});

test('recognizes typed arrays as instances of TypedArray', () => {
expect(new Uint8Array([1, 2, 3])).toBeInstanceOf(globalTypedArrayType);
expect(new Uint8ClampedArray([1, 2, 3])).toBeInstanceOf(globalTypedArrayType);
expect(new Uint16Array([1, 2, 3])).toBeInstanceOf(globalTypedArrayType);
expect(new Uint32Array([1, 2, 3])).toBeInstanceOf(globalTypedArrayType);
expect(new BigUint64Array([])).toBeInstanceOf(globalTypedArrayType);
expect(new Int8Array([1, 2, 3])).toBeInstanceOf(globalTypedArrayType);
expect(new Int16Array([1, 2, 3])).toBeInstanceOf(globalTypedArrayType);
expect(new Int32Array([1, 2, 3])).toBeInstanceOf(globalTypedArrayType);
expect(new BigInt64Array([])).toBeInstanceOf(globalTypedArrayType);
expect(new Float32Array([1, 2, 3])).toBeInstanceOf(globalTypedArrayType);
expect(new Float64Array([1, 2, 3])).toBeInstanceOf(globalTypedArrayType);
});

test('v8 serialize/deserialize', () => {
const m1 = new Map();
const m2 = v8.deserialize(v8.serialize(m1));
expect(m1).toEqual(m2);
});
5 changes: 5 additions & 0 deletions e2e/instanceof/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"jest": {
"testEnvironment": "node"
}
}
44 changes: 44 additions & 0 deletions packages/jest-util/src/__tests__/addInstanceOfAlias.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

import addInstanceOfAlias from '../addInstanceOfAlias';

describe('addInstanceOfAlias', () => {
let Color;
let Purple;
let OtherPurple;
let Black;
beforeEach(() => {
Color = class Color {};
Purple = class Purple extends Color {};
OtherPurple = class OtherPurple extends Color {};
Black = class Black extends Color {};
});

it('adds the given alias to the target as an object to include in an instanceof call', () => {
const purple = new Purple();
expect(purple instanceof OtherPurple).toBe(false);
addInstanceOfAlias(OtherPurple, Purple);
expect(purple instanceof OtherPurple).toBe(true);
});

it('preserves the prior instanceof lookup', () => {
const purple = new Purple();
addInstanceOfAlias(OtherPurple, Purple);
expect(purple instanceof Purple).toBe(true);
expect(purple instanceof Color).toBe(true);
expect(purple instanceof Black).toBe(false);
});

describe('when using the same value for the target and alias', () => {
it('throws an error', () => {
expect(() => {
addInstanceOfAlias(Purple, Purple);
}).toThrow(Error);
});
});
});
35 changes: 35 additions & 0 deletions packages/jest-util/src/addInstanceOfAlias.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

// using `Function` is on purpose - it's exactly what we want in this case
// eslint-disable-next-line @typescript-eslint/ban-types
type InstanceOfThing = Function;

export default function addInstanceOfAlias(
target: InstanceOfThing,
alias: InstanceOfThing,
): void {
if (target === alias) {
throw new Error(
'Attempted to call addInstanceOfAlias with the same object for both ' +
'the target and the alias. This will create an infinite loop in ' +
'instanceof checks.',
);
}

const originalHasInstance = target[Symbol.hasInstance];
Object.defineProperty(target, Symbol.hasInstance, {
configurable: true,
value: function aliasedHasInstance(potentialInstance: unknown) {
return (
potentialInstance instanceof alias ||
originalHasInstance.call(this, potentialInstance)
);
},
writable: true,
});
}
6 changes: 6 additions & 0 deletions packages/jest-util/src/installCommonGlobals.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

import * as fs from 'graceful-fs';
import type {Config} from '@jest/types';
import addInstanceOfAlias from './addInstanceOfAlias';
import createProcessObject from './createProcessObject';
import deepCyclicCopy from './deepCyclicCopy';

Expand Down Expand Up @@ -62,5 +63,10 @@ export default function (
};
});

addInstanceOfAlias(globalObject.Error, Error);
addInstanceOfAlias(globalObject.Promise, Promise);
addInstanceOfAlias(globalObject.Object, Object);
addInstanceOfAlias(globalObject.Function, Function);

return Object.assign(globalObject, deepCyclicCopy(globals));
}