Skip to content

Commit d3622a0

Browse files
authored
feat: support virtual contexts (#50)
This adds support for virtual contexts by testing `RegExp` and `Error` via `toString` instead of an `instanceof` check.
1 parent fd56c9e commit d3622a0

File tree

4 files changed

+101
-7
lines changed

4 files changed

+101
-7
lines changed

index.js

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,17 @@
1+
function isErrorInstance(obj) {
2+
// eslint-disable-next-line prefer-reflect
3+
return obj instanceof Error || Object.prototype.toString.call(obj) === '[object Error]';
4+
}
5+
6+
function isErrorClass(obj) {
7+
return obj === Error || (typeof obj === 'function' && obj.name === 'Error');
8+
}
9+
10+
function isRegExp(obj) {
11+
// eslint-disable-next-line prefer-reflect
12+
return Object.prototype.toString.call(obj) === '[object RegExp]';
13+
}
14+
115
/**
216
* ### .compatibleInstance(thrown, errorLike)
317
*
@@ -13,7 +27,7 @@
1327
*/
1428

1529
function compatibleInstance(thrown, errorLike) {
16-
return errorLike instanceof Error && thrown === errorLike;
30+
return isErrorInstance(errorLike) && thrown === errorLike;
1731
}
1832

1933
/**
@@ -33,10 +47,10 @@ function compatibleInstance(thrown, errorLike) {
3347
*/
3448

3549
function compatibleConstructor(thrown, errorLike) {
36-
if (errorLike instanceof Error) {
50+
if (isErrorInstance(errorLike)) {
3751
// If `errorLike` is an instance of any error we compare their constructors
3852
return thrown.constructor === errorLike.constructor || thrown instanceof errorLike.constructor;
39-
} else if (errorLike.prototype instanceof Error || errorLike === Error) {
53+
} else if (isErrorClass(Object.getPrototypeOf(errorLike)) || isErrorClass(errorLike)) {
4054
// If `errorLike` is a constructor that inherits from Error, we compare `thrown` to `errorLike` directly
4155
return thrown.constructor === errorLike || thrown instanceof errorLike;
4256
}
@@ -60,7 +74,7 @@ function compatibleConstructor(thrown, errorLike) {
6074

6175
function compatibleMessage(thrown, errMatcher) {
6276
const comparisonString = typeof thrown === 'string' ? thrown : thrown.message;
63-
if (errMatcher instanceof RegExp) {
77+
if (isRegExp(errMatcher)) {
6478
return errMatcher.test(comparisonString);
6579
} else if (typeof errMatcher === 'string') {
6680
return comparisonString.indexOf(errMatcher) !== -1; // eslint-disable-line no-magic-numbers
@@ -82,7 +96,7 @@ function compatibleMessage(thrown, errMatcher) {
8296

8397
function getConstructorName(errorLike) {
8498
let constructorName = errorLike;
85-
if (errorLike instanceof Error) {
99+
if (isErrorInstance(errorLike)) {
86100
constructorName = errorLike.constructor.name;
87101
} else if (typeof errorLike === 'function') {
88102
// If `err` is not an instance of Error it is an error constructor itself or another function.

package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
"semantic-release": "semantic-release pre && npm publish && semantic-release post",
3333
"pretest": "npm run lint && npm run build",
3434
"test": "npm run test:node && npm run test:browser",
35-
"test:browser": "web-test-runner --node-resolve test/",
35+
"test:browser": "web-test-runner",
3636
"test:node": "mocha"
3737
},
3838
"config": {
@@ -53,7 +53,8 @@
5353
"rules": {
5454
"complexity": "off",
5555
"max-statements": "off",
56-
"prefer-arrow-callback": "off"
56+
"prefer-arrow-callback": "off",
57+
"prefer-reflect": "off"
5758
}
5859
},
5960
"devDependencies": {

test/virtual-machines.js

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import { createContext, runInContext } from 'node:vm';
2+
import { assert } from 'simple-assert';
3+
import * as checkError from '../index.js';
4+
5+
const vmContext = { checkError };
6+
createContext(vmContext);
7+
8+
function runCodeInVm(code) {
9+
return runInContext(`{${ code }}`, vmContext);
10+
}
11+
12+
describe('node virtual machines', function () {
13+
it('compatibleMessage', function () {
14+
assert(runCodeInVm(`
15+
const errorInstance = new Error('I am an instance');
16+
checkError.compatibleMessage(errorInstance, /instance$/) === true;
17+
`) === true);
18+
});
19+
20+
it('constructorName', function () {
21+
assert(runCodeInVm(`
22+
const errorInstance = new Error('I am an instance');
23+
checkError.getConstructorName(errorInstance);
24+
`) === 'Error');
25+
assert(runCodeInVm(`
26+
const derivedInstance = new TypeError('I inherit from Error');
27+
checkError.getConstructorName(derivedInstance);
28+
`) === 'TypeError');
29+
});
30+
31+
it('compatibleInstance', function () {
32+
assert(runCodeInVm(`
33+
const errorInstance = new Error('I am an instance');
34+
const sameInstance = errorInstance;
35+
checkError.compatibleInstance(errorInstance, sameInstance);
36+
`) === true);
37+
assert(runCodeInVm(`
38+
const errorInstance = new Error('I am an instance');
39+
const otherInstance = new Error('I am another');
40+
checkError.compatibleInstance(errorInstance, otherInstance);
41+
`) === false);
42+
});
43+
44+
it('compatibleConstructor', function () {
45+
assert(runCodeInVm(`
46+
const errorInstance = new Error('I am an instance');
47+
const sameInstance = errorInstance;
48+
checkError.compatibleConstructor(errorInstance, sameInstance);
49+
`) === true);
50+
assert(runCodeInVm(`
51+
const errorInstance = new Error('I am an instance');
52+
const otherInstance = new Error('I an another instance');
53+
checkError.compatibleConstructor(errorInstance, otherInstance);
54+
`) === true);
55+
assert(runCodeInVm(`
56+
const errorInstance = new Error('I am an instance');
57+
const derivedInstance = new TypeError('I inherit from Error');
58+
checkError.compatibleConstructor(derivedInstance, errorInstance);
59+
`) === true);
60+
assert(runCodeInVm(`
61+
const errorInstance = new Error('I am an instance');
62+
const derivedInstance = new TypeError('I inherit from Error');
63+
checkError.compatibleConstructor(errorInstance, derivedInstance);
64+
`) === false);
65+
assert(runCodeInVm(`
66+
const errorInstance = new TypeError('I am an instance');
67+
checkError.compatibleConstructor(errorInstance, TypeError);
68+
`) === true);
69+
});
70+
});

web-test-runner.config.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
export default {
2+
nodeResolve: true,
3+
files: [
4+
'test/*.js',
5+
'!test/virtual-machines.js',
6+
],
7+
plugins: [
8+
],
9+
};

0 commit comments

Comments
 (0)