From d3622a0af07ddafe5275e452e3d780bbfda14110 Mon Sep 17 00:00:00 2001 From: James Garbutt <43081j@users.noreply.github.com> Date: Wed, 8 May 2024 14:53:07 +0100 Subject: [PATCH] feat: support virtual contexts (#50) This adds support for virtual contexts by testing `RegExp` and `Error` via `toString` instead of an `instanceof` check. --- index.js | 24 +++++++++++--- package.json | 5 +-- test/virtual-machines.js | 70 +++++++++++++++++++++++++++++++++++++++ web-test-runner.config.js | 9 +++++ 4 files changed, 101 insertions(+), 7 deletions(-) create mode 100644 test/virtual-machines.js create mode 100644 web-test-runner.config.js diff --git a/index.js b/index.js index a5dc1fa..ca950fd 100644 --- a/index.js +++ b/index.js @@ -1,3 +1,17 @@ +function isErrorInstance(obj) { + // eslint-disable-next-line prefer-reflect + return obj instanceof Error || Object.prototype.toString.call(obj) === '[object Error]'; +} + +function isErrorClass(obj) { + return obj === Error || (typeof obj === 'function' && obj.name === 'Error'); +} + +function isRegExp(obj) { + // eslint-disable-next-line prefer-reflect + return Object.prototype.toString.call(obj) === '[object RegExp]'; +} + /** * ### .compatibleInstance(thrown, errorLike) * @@ -13,7 +27,7 @@ */ function compatibleInstance(thrown, errorLike) { - return errorLike instanceof Error && thrown === errorLike; + return isErrorInstance(errorLike) && thrown === errorLike; } /** @@ -33,10 +47,10 @@ function compatibleInstance(thrown, errorLike) { */ function compatibleConstructor(thrown, errorLike) { - if (errorLike instanceof Error) { + if (isErrorInstance(errorLike)) { // If `errorLike` is an instance of any error we compare their constructors return thrown.constructor === errorLike.constructor || thrown instanceof errorLike.constructor; - } else if (errorLike.prototype instanceof Error || errorLike === Error) { + } else if (isErrorClass(Object.getPrototypeOf(errorLike)) || isErrorClass(errorLike)) { // If `errorLike` is a constructor that inherits from Error, we compare `thrown` to `errorLike` directly return thrown.constructor === errorLike || thrown instanceof errorLike; } @@ -60,7 +74,7 @@ function compatibleConstructor(thrown, errorLike) { function compatibleMessage(thrown, errMatcher) { const comparisonString = typeof thrown === 'string' ? thrown : thrown.message; - if (errMatcher instanceof RegExp) { + if (isRegExp(errMatcher)) { return errMatcher.test(comparisonString); } else if (typeof errMatcher === 'string') { return comparisonString.indexOf(errMatcher) !== -1; // eslint-disable-line no-magic-numbers @@ -82,7 +96,7 @@ function compatibleMessage(thrown, errMatcher) { function getConstructorName(errorLike) { let constructorName = errorLike; - if (errorLike instanceof Error) { + if (isErrorInstance(errorLike)) { constructorName = errorLike.constructor.name; } else if (typeof errorLike === 'function') { // If `err` is not an instance of Error it is an error constructor itself or another function. diff --git a/package.json b/package.json index 16c2f1f..4f002d5 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,7 @@ "semantic-release": "semantic-release pre && npm publish && semantic-release post", "pretest": "npm run lint && npm run build", "test": "npm run test:node && npm run test:browser", - "test:browser": "web-test-runner --node-resolve test/", + "test:browser": "web-test-runner", "test:node": "mocha" }, "config": { @@ -53,7 +53,8 @@ "rules": { "complexity": "off", "max-statements": "off", - "prefer-arrow-callback": "off" + "prefer-arrow-callback": "off", + "prefer-reflect": "off" } }, "devDependencies": { diff --git a/test/virtual-machines.js b/test/virtual-machines.js new file mode 100644 index 0000000..e3381a4 --- /dev/null +++ b/test/virtual-machines.js @@ -0,0 +1,70 @@ +import { createContext, runInContext } from 'node:vm'; +import { assert } from 'simple-assert'; +import * as checkError from '../index.js'; + +const vmContext = { checkError }; +createContext(vmContext); + +function runCodeInVm(code) { + return runInContext(`{${ code }}`, vmContext); +} + +describe('node virtual machines', function () { + it('compatibleMessage', function () { + assert(runCodeInVm(` + const errorInstance = new Error('I am an instance'); + checkError.compatibleMessage(errorInstance, /instance$/) === true; + `) === true); + }); + + it('constructorName', function () { + assert(runCodeInVm(` + const errorInstance = new Error('I am an instance'); + checkError.getConstructorName(errorInstance); + `) === 'Error'); + assert(runCodeInVm(` + const derivedInstance = new TypeError('I inherit from Error'); + checkError.getConstructorName(derivedInstance); + `) === 'TypeError'); + }); + + it('compatibleInstance', function () { + assert(runCodeInVm(` + const errorInstance = new Error('I am an instance'); + const sameInstance = errorInstance; + checkError.compatibleInstance(errorInstance, sameInstance); + `) === true); + assert(runCodeInVm(` + const errorInstance = new Error('I am an instance'); + const otherInstance = new Error('I am another'); + checkError.compatibleInstance(errorInstance, otherInstance); + `) === false); + }); + + it('compatibleConstructor', function () { + assert(runCodeInVm(` + const errorInstance = new Error('I am an instance'); + const sameInstance = errorInstance; + checkError.compatibleConstructor(errorInstance, sameInstance); + `) === true); + assert(runCodeInVm(` + const errorInstance = new Error('I am an instance'); + const otherInstance = new Error('I an another instance'); + checkError.compatibleConstructor(errorInstance, otherInstance); + `) === true); + assert(runCodeInVm(` + const errorInstance = new Error('I am an instance'); + const derivedInstance = new TypeError('I inherit from Error'); + checkError.compatibleConstructor(derivedInstance, errorInstance); + `) === true); + assert(runCodeInVm(` + const errorInstance = new Error('I am an instance'); + const derivedInstance = new TypeError('I inherit from Error'); + checkError.compatibleConstructor(errorInstance, derivedInstance); + `) === false); + assert(runCodeInVm(` + const errorInstance = new TypeError('I am an instance'); + checkError.compatibleConstructor(errorInstance, TypeError); + `) === true); + }); +}); diff --git a/web-test-runner.config.js b/web-test-runner.config.js new file mode 100644 index 0000000..9321645 --- /dev/null +++ b/web-test-runner.config.js @@ -0,0 +1,9 @@ +export default { + nodeResolve: true, + files: [ + 'test/*.js', + '!test/virtual-machines.js', + ], + plugins: [ + ], +};