Skip to content

Commit

Permalink
fix: support some virtual contexts in toThrow (chaijs#1609)
Browse files Browse the repository at this point in the history
* fix: support some virtual contexts in `toThrow`

This adds support for VM situations where we pass a `RegExp` from
another process.

Note that we don't have a full fix for this stuff until `check-error`
also supports `Error` being from another origin.

* fix: support throwing of unusual errors

Adds support for throwing things like `undefined`, functions, etc.

* chore: upgrade check-error
  • Loading branch information
43081j authored and koddsson committed Oct 6, 2024
1 parent 2859692 commit 7a71c9b
Show file tree
Hide file tree
Showing 7 changed files with 83 additions and 13 deletions.
28 changes: 21 additions & 7 deletions lib/chai/core/assertions.js
Original file line number Diff line number Diff line change
Expand Up @@ -2686,15 +2686,17 @@ function assertThrows (errorLike, errMsgMatcher, msg) {
, negate = flag(this, 'negate') || false;
new Assertion(obj, flagMsg, ssfi, true).is.a('function');

if (errorLike instanceof RegExp || typeof errorLike === 'string') {
if (_.isRegExp(errorLike) || typeof errorLike === 'string') {
errMsgMatcher = errorLike;
errorLike = null;
}

var caughtErr;
let caughtErr;
let errorWasThrown = false;
try {
obj();
} catch (err) {
errorWasThrown = true;
caughtErr = err;
}

Expand All @@ -2718,14 +2720,26 @@ function assertThrows (errorLike, errMsgMatcher, msg) {
errorLikeString = _.checkError.getConstructorName(errorLike);
}

let actual = caughtErr;
if (caughtErr instanceof Error) {
actual = caughtErr.toString();
} else if (typeof caughtErr === 'string') {
actual = caughtErr;
} else if (caughtErr && (typeof caughtErr === 'object' || typeof caughtErr === 'function')) {
try {
actual = _.checkError.getConstructorName(caughtErr);
} catch (_err) {
// somehow wasn't a constructor, maybe we got a function thrown
// or similar
}
}

this.assert(
caughtErr
errorWasThrown
, 'expected #{this} to throw ' + errorLikeString
, 'expected #{this} to not throw an error but #{act} was thrown'
, errorLike && errorLike.toString()
, (caughtErr instanceof Error ?
caughtErr.toString() : (typeof caughtErr === 'string' ? caughtErr : caughtErr &&
_.checkError.getConstructorName(caughtErr)))
, actual
);
}

Expand Down Expand Up @@ -2770,7 +2784,7 @@ function assertThrows (errorLike, errMsgMatcher, msg) {
if (caughtErr && errMsgMatcher !== undefined && errMsgMatcher !== null) {
// Here we check compatible messages
var placeholder = 'including';
if (errMsgMatcher instanceof RegExp) {
if (_.isRegExp(errMsgMatcher)) {
placeholder = 'matching'
}

Expand Down
11 changes: 11 additions & 0 deletions lib/chai/utils/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -94,3 +94,14 @@ export {isNaN} from './isNaN.js';

// getOperator method
export {getOperator} from './getOperator.js';

/**
* Determines if an object is a `RegExp`
* This is used since `instanceof` will not work in virtual contexts
*
* @param {*} obj Object to test
* @returns {boolean}
*/
export function isRegExp(obj) {
return Object.prototype.toString.call(obj) === '[object RegExp]';
}
8 changes: 4 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@
},
"dependencies": {
"assertion-error": "^2.0.1",
"check-error": "^2.0.0",
"check-error": "^2.1.1",
"deep-eql": "^5.0.1",
"loupe": "^3.1.0",
"pathval": "^2.0.0"
Expand Down
8 changes: 8 additions & 0 deletions test/assert.js
Original file line number Diff line number Diff line change
Expand Up @@ -1635,6 +1635,8 @@ describe('assert', function () {
});

it('throws / throw / Throw', function() {
class CustomError extends Error {}

['throws', 'throw', 'Throw'].forEach(function (throws) {
assert[throws](function() { throw new Error('foo'); });
assert[throws](function() { throw new Error(''); }, '');
Expand All @@ -1644,6 +1646,12 @@ describe('assert', function () {
assert[throws](function() { throw new Error('bar'); }, Error, 'bar');
assert[throws](function() { throw new Error(''); }, Error, '');
assert[throws](function() { throw new Error('foo') }, '');
assert[throws](function() { throw ''; }, '');
assert[throws](function() { throw ''; }, /^$/);
assert[throws](function() { throw new Error(''); }, /^$/);
assert[throws](function() { throw undefined; });
assert[throws](function() { throw new CustomError('foo'); });
assert[throws](function() { throw (() => {}); });

var thrownErr = assert[throws](function() { throw new Error('foo'); });
assert(thrownErr instanceof Error, 'assert.' + throws + ' returns error');
Expand Down
34 changes: 34 additions & 0 deletions test/virtual-machines.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import vm from 'node:vm';
import * as chai from '../index.js';

const {assert} = chai;
const vmContext = {assert};
vm.createContext(vmContext);

/**
* Run the code in a virtual context
*
* @param {string} code Code to run
*/
function runCodeInVm(code) {
vm.runInContext(code, vmContext);
}

describe('node virtual machines', function () {
it('throws', function() {
const shouldNotThrow = [
`assert.throws(function() { throw ''; }, /^$/);`,
`assert.throws(function() { throw new Error('bleepbloop'); });`,
`assert.throws(function() { throw new Error(''); });`,
`assert.throws(function() { throw new Error('swoosh'); }, /swoosh/);`
];

for (const code of shouldNotThrow) {
assert.doesNotThrow(
() => {
runCodeInVm(code);
}
);
}
});
});
5 changes: 4 additions & 1 deletion web-test-runner.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@ const commonjs = fromRollup(rollupCommonjs);

export default {
nodeResolve: true,
files: ["test/*.js"],
files: [
"test/*.js",
"!test/virtual-machines.js"
],
plugins: [
commonjs({
include: [
Expand Down

0 comments on commit 7a71c9b

Please sign in to comment.