@usebruno/js 0.46.1 → 0.47.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +7 -6
- package/src/bru.js +173 -75
- package/src/bruno-request.js +2 -0
- package/src/cookie-list.js +272 -0
- package/src/index.js +27 -2
- package/src/property-list.js +184 -0
- package/src/readonly-property-list.js +227 -0
- package/src/runtime/assert-runtime.js +8 -10
- package/src/runtime/script-runtime.js +55 -6
- package/src/runtime/test-runtime.js +19 -1
- package/src/runtime/vars-runtime.js +20 -3
- package/src/sandbox/bundle-libraries.js +1 -1
- package/src/sandbox/quickjs/index.js +2 -34
- package/src/sandbox/quickjs/shims/bru.js +31 -1
- package/src/sandbox/quickjs/shims/bruno-response.js +29 -2
- package/src/sandbox/quickjs/shims/bruno-response.spec.js +91 -0
- package/src/sandbox/quickjs/shims/lib/uuid.spec.js +166 -0
- package/src/sandbox/quickjs/shims/require.js +56 -0
- package/src/sandbox/quickjs/shims/require.spec.js +154 -0
- package/src/sandbox/quickjs/shims/test.js +2 -2
- package/src/sandbox/quickjs/utils/property-list-bridge.js +165 -0
- package/src/sandbox/quickjs/utils/test-helpers.js +31 -0
- package/src/utils/error-formatter.js +345 -20
- package/src/utils/error-formatter.spec.js +683 -1
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
const { cleanJson, cleanCircularJson } = require('../../../utils');
|
|
2
2
|
const { marshallToVm } = require('../utils');
|
|
3
|
+
const { createPropertyListBridge } = require('../utils/property-list-bridge');
|
|
3
4
|
|
|
4
5
|
const addBruShimToContext = (vm, bru) => {
|
|
5
6
|
const bruObject = vm.newObject();
|
|
@@ -229,11 +230,29 @@ const addBruShimToContext = (vm, bru) => {
|
|
|
229
230
|
bruRunnerIterationDataObject.dispose();
|
|
230
231
|
|
|
231
232
|
let visualize = vm.newFunction('visualize', function (type, data) {
|
|
232
|
-
|
|
233
|
+
try {
|
|
234
|
+
// Guard against missing handles: vm.dump() accesses .owner on the handle
|
|
235
|
+
// to verify its VM context. When called with no args, the handles are
|
|
236
|
+
// undefined, and vm.dump(undefined) throws a generic
|
|
237
|
+
// "Cannot read properties of undefined (reading 'owner')" instead of
|
|
238
|
+
// letting bru.visualize() throw a meaningful error message.
|
|
239
|
+
bru.visualize(
|
|
240
|
+
type ? vm.dump(type) : undefined,
|
|
241
|
+
data ? vm.dump(data) : undefined
|
|
242
|
+
);
|
|
243
|
+
} catch (err) {
|
|
244
|
+
throw vm.newError(err.message || String(err));
|
|
245
|
+
}
|
|
233
246
|
});
|
|
234
247
|
vm.setProp(bruObject, 'visualize', visualize);
|
|
235
248
|
visualize.dispose();
|
|
236
249
|
|
|
250
|
+
let clearVisualizations = vm.newFunction('clearVisualizations', function () {
|
|
251
|
+
bru.clearVisualizations();
|
|
252
|
+
});
|
|
253
|
+
vm.setProp(bruObject, 'clearVisualizations', clearVisualizations);
|
|
254
|
+
clearVisualizations.dispose();
|
|
255
|
+
|
|
237
256
|
let getSecretVar = vm.newFunction('getSecretVar', function (key) {
|
|
238
257
|
return marshallToVm(bru.getSecretVar(vm.dump(key)), vm);
|
|
239
258
|
});
|
|
@@ -394,6 +413,13 @@ const addBruShimToContext = (vm, bru) => {
|
|
|
394
413
|
sleep.consume((handle) => vm.setProp(bruObject, 'sleep', handle));
|
|
395
414
|
|
|
396
415
|
let bruCookiesObject = vm.newObject();
|
|
416
|
+
const { evalCode: cookiesEvalCode } = createPropertyListBridge(vm, bru.cookies, bruCookiesObject, {
|
|
417
|
+
globalPath: 'globalThis.bru.cookies',
|
|
418
|
+
syncReadMethods: ['get', 'has', 'count', 'indexOf', 'toObject', 'toString'],
|
|
419
|
+
syncReadObjectMethods: ['one', 'all', 'idx', 'toJSON'],
|
|
420
|
+
asyncWriteMethods: ['add', 'upsert', 'remove', 'clear', 'delete'],
|
|
421
|
+
withIterators: true
|
|
422
|
+
});
|
|
397
423
|
|
|
398
424
|
const _jarFn = vm.newFunction('_jar', () => {
|
|
399
425
|
const nativeJar = bru.cookies.jar();
|
|
@@ -571,6 +597,10 @@ const addBruShimToContext = (vm, bru) => {
|
|
|
571
597
|
}
|
|
572
598
|
};
|
|
573
599
|
|
|
600
|
+
{
|
|
601
|
+
${cookiesEvalCode}
|
|
602
|
+
}
|
|
603
|
+
|
|
574
604
|
globalThis.bru.cookies.jar = () => {
|
|
575
605
|
const _jar = globalThis.bru.cookies._jar();
|
|
576
606
|
|
|
@@ -1,8 +1,35 @@
|
|
|
1
1
|
const { marshallToVm } = require('../utils');
|
|
2
2
|
|
|
3
|
+
// Marshal a QuickJS query argument to a host-compatible value.
|
|
4
|
+
// Function handles are wrapped as native callbacks; other values are dumped as-is.
|
|
5
|
+
// Safe because @usebruno/query's get() invokes filters synchronously,
|
|
6
|
+
// so the borrowed arg handle is still valid.
|
|
7
|
+
const toHostQueryArg = (vm, arg) => {
|
|
8
|
+
if (vm.typeof(arg) === 'function') {
|
|
9
|
+
return (item) => {
|
|
10
|
+
const itemHandle = marshallToVm(item, vm);
|
|
11
|
+
const result = vm.callFunction(arg, vm.undefined, itemHandle);
|
|
12
|
+
itemHandle.dispose();
|
|
13
|
+
|
|
14
|
+
if (result.error) {
|
|
15
|
+
const error = vm.dump(result.error);
|
|
16
|
+
result.error.dispose();
|
|
17
|
+
throw error;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const value = vm.dump(result.value);
|
|
21
|
+
result.value.dispose();
|
|
22
|
+
return value;
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return vm.dump(arg);
|
|
27
|
+
};
|
|
28
|
+
|
|
3
29
|
const addBrunoResponseShimToContext = (vm, res) => {
|
|
4
|
-
let resFn = vm.newFunction('res', function (exprStr) {
|
|
5
|
-
|
|
30
|
+
let resFn = vm.newFunction('res', function (exprStr, ...queryArgs) {
|
|
31
|
+
const nativeArgs = queryArgs.map((arg) => toHostQueryArg(vm, arg));
|
|
32
|
+
return marshallToVm(res(vm.dump(exprStr), ...nativeArgs), vm);
|
|
6
33
|
});
|
|
7
34
|
|
|
8
35
|
const status = marshallToVm(res?.status, vm);
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
const { describe, it, expect, beforeAll, beforeEach, afterEach, afterAll } = require('@jest/globals');
|
|
2
|
+
const { newQuickJSWASMModule } = require('quickjs-emscripten');
|
|
3
|
+
const addBrunoResponseShimToContext = require('./bruno-response');
|
|
4
|
+
|
|
5
|
+
describe('bruno response shim', () => {
|
|
6
|
+
let vm, module;
|
|
7
|
+
|
|
8
|
+
beforeAll(async () => {
|
|
9
|
+
module = await newQuickJSWASMModule();
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
beforeEach(() => {
|
|
13
|
+
vm = module.newContext();
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
afterEach(() => {
|
|
17
|
+
if (vm) {
|
|
18
|
+
try {
|
|
19
|
+
vm.dispose();
|
|
20
|
+
} catch (err) {
|
|
21
|
+
console.error('Error disposing vm', err);
|
|
22
|
+
}
|
|
23
|
+
vm = null;
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
afterAll(() => {
|
|
28
|
+
if (module) {
|
|
29
|
+
try {
|
|
30
|
+
module.dispose();
|
|
31
|
+
} catch (err) {
|
|
32
|
+
console.error('Error disposing module', err);
|
|
33
|
+
}
|
|
34
|
+
module = null;
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('forwards response query filter callbacks in safe mode', () => {
|
|
39
|
+
const resources = [
|
|
40
|
+
{ id: 'test', value: 1 },
|
|
41
|
+
{ id: 'other', value: 2 }
|
|
42
|
+
];
|
|
43
|
+
const response = Object.assign(
|
|
44
|
+
(expr, filter) => {
|
|
45
|
+
expect(expr).toBe('resources[?].id');
|
|
46
|
+
return resources.filter(filter).map((item) => item.id);
|
|
47
|
+
},
|
|
48
|
+
{ status: 200, statusText: 'OK', headers: {}, body: { resources } }
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
addBrunoResponseShimToContext(vm, response);
|
|
52
|
+
|
|
53
|
+
const result = vm.evalCode(`
|
|
54
|
+
res('resources[?].id', r => r.id === 'test')
|
|
55
|
+
`);
|
|
56
|
+
|
|
57
|
+
const valueHandle = vm.unwrapResult(result);
|
|
58
|
+
const filtered = vm.dump(valueHandle);
|
|
59
|
+
valueHandle.dispose();
|
|
60
|
+
|
|
61
|
+
expect(filtered).toEqual(['test']);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('still supports object predicates in safe mode', () => {
|
|
65
|
+
const resources = [
|
|
66
|
+
{ id: 'test', value: 1 },
|
|
67
|
+
{ id: 'other', value: 2 }
|
|
68
|
+
];
|
|
69
|
+
const response = Object.assign(
|
|
70
|
+
(expr, predicate) => {
|
|
71
|
+
expect(expr).toBe('resources[?].id');
|
|
72
|
+
return resources.filter((item) =>
|
|
73
|
+
Object.entries(predicate).every(([key, value]) => item[key] === value)
|
|
74
|
+
).map((item) => item.id);
|
|
75
|
+
},
|
|
76
|
+
{ status: 200, statusText: 'OK', headers: {}, body: { resources } }
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
addBrunoResponseShimToContext(vm, response);
|
|
80
|
+
|
|
81
|
+
const result = vm.evalCode(`
|
|
82
|
+
res('resources[?].id', { id: 'other' })
|
|
83
|
+
`);
|
|
84
|
+
|
|
85
|
+
const valueHandle = vm.unwrapResult(result);
|
|
86
|
+
const filtered = vm.dump(valueHandle);
|
|
87
|
+
valueHandle.dispose();
|
|
88
|
+
|
|
89
|
+
expect(filtered).toEqual(['other']);
|
|
90
|
+
});
|
|
91
|
+
});
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
const { describe, it, expect, beforeAll, beforeEach, afterEach, afterAll } = require('@jest/globals');
|
|
2
|
+
const { newQuickJSWASMModule } = require('quickjs-emscripten');
|
|
3
|
+
const { validate: uuidValidate, version: uuidVersion } = require('uuid');
|
|
4
|
+
const addUuidShimToContext = require('./uuid');
|
|
5
|
+
const { addRequireShimToContext } = require('../require');
|
|
6
|
+
const { createEvalHelper } = require('../../utils/test-helpers');
|
|
7
|
+
|
|
8
|
+
describe('uuid shim tests', () => {
|
|
9
|
+
let vm, module, evalAndDump;
|
|
10
|
+
|
|
11
|
+
beforeAll(async () => {
|
|
12
|
+
module = await newQuickJSWASMModule();
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
beforeEach(async () => {
|
|
16
|
+
vm = module.newContext();
|
|
17
|
+
evalAndDump = createEvalHelper(vm);
|
|
18
|
+
await addUuidShimToContext(vm);
|
|
19
|
+
addRequireShimToContext(vm, { enableLocalModules: false });
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
afterEach(() => {
|
|
23
|
+
if (vm) {
|
|
24
|
+
try {
|
|
25
|
+
vm.dispose();
|
|
26
|
+
} catch (err) {
|
|
27
|
+
// Ignore disposal errors
|
|
28
|
+
}
|
|
29
|
+
vm = null;
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
afterAll(() => {
|
|
34
|
+
if (module) {
|
|
35
|
+
try {
|
|
36
|
+
module.dispose();
|
|
37
|
+
} catch (err) {
|
|
38
|
+
// Ignore disposal errors
|
|
39
|
+
}
|
|
40
|
+
module = null;
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Validates that a string is a valid UUID of the expected version
|
|
46
|
+
*/
|
|
47
|
+
function expectUuidVersion(uuid, expectedVersion) {
|
|
48
|
+
expect(uuidValidate(uuid)).toBe(true);
|
|
49
|
+
expect(uuidVersion(uuid)).toBe(expectedVersion);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
describe('uuid version functions', () => {
|
|
53
|
+
const versionTests = [
|
|
54
|
+
{ fn: 'v1', version: 1 },
|
|
55
|
+
{ fn: 'v4', version: 4 },
|
|
56
|
+
{ fn: 'v6', version: 6, note: 'RFC 9562' },
|
|
57
|
+
{ fn: 'v7', version: 7, note: 'RFC 9562' }
|
|
58
|
+
];
|
|
59
|
+
|
|
60
|
+
it.each(versionTests)('$fn should generate valid uuid and be accessible via require', ({ fn, version }) => {
|
|
61
|
+
// Test direct access
|
|
62
|
+
const directUuid = evalAndDump(`globalThis.uuid.${fn}()`);
|
|
63
|
+
expectUuidVersion(directUuid, version);
|
|
64
|
+
|
|
65
|
+
// Test require with destructuring
|
|
66
|
+
const requireUuid = evalAndDump(`const { ${fn} } = require('uuid'); ${fn}()`);
|
|
67
|
+
expectUuidVersion(requireUuid, version);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('v7 should be accessible with alias pattern (v7: uuidv7) - issue #7333', () => {
|
|
71
|
+
const uuid = evalAndDump(`
|
|
72
|
+
const { v7: uuidv7 } = require('uuid');
|
|
73
|
+
uuidv7();
|
|
74
|
+
`);
|
|
75
|
+
expectUuidVersion(uuid, 7);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it('v7 should generate time-ordered uuids', () => {
|
|
79
|
+
const [uuid1, uuid2] = evalAndDump(`
|
|
80
|
+
const { v7 } = require('uuid');
|
|
81
|
+
[v7(), v7()];
|
|
82
|
+
`);
|
|
83
|
+
// v7 UUIDs are lexicographically sortable by design (timestamp in most significant bits)
|
|
84
|
+
expect(uuid1 <= uuid2).toBe(true);
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
describe('name-based uuid functions (v3, v5)', () => {
|
|
89
|
+
const DNS_NAMESPACE = '6ba7b810-9dad-11d1-80b4-00c04fd430c8';
|
|
90
|
+
|
|
91
|
+
it.each([
|
|
92
|
+
{ fn: 'v3', version: 3 },
|
|
93
|
+
{ fn: 'v5', version: 5 }
|
|
94
|
+
])('$fn should generate valid uuid with namespace', ({ fn, version }) => {
|
|
95
|
+
const uuid = evalAndDump(`
|
|
96
|
+
const { ${fn} } = require('uuid');
|
|
97
|
+
${fn}('example.com', '${DNS_NAMESPACE}');
|
|
98
|
+
`);
|
|
99
|
+
expectUuidVersion(uuid, version);
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
describe('conversion functions', () => {
|
|
104
|
+
it('should convert between v1 and v6 using v1ToV6 and v6ToV1', () => {
|
|
105
|
+
const [v1Uuid, v6FromV1, v6Uuid, v1FromV6] = evalAndDump(`
|
|
106
|
+
const { v1, v6, v1ToV6, v6ToV1 } = require('uuid');
|
|
107
|
+
const v1Uuid = v1();
|
|
108
|
+
const v6Uuid = v6();
|
|
109
|
+
[v1Uuid, v1ToV6(v1Uuid), v6Uuid, v6ToV1(v6Uuid)];
|
|
110
|
+
`);
|
|
111
|
+
|
|
112
|
+
expectUuidVersion(v1Uuid, 1);
|
|
113
|
+
expectUuidVersion(v6FromV1, 6);
|
|
114
|
+
expectUuidVersion(v6Uuid, 6);
|
|
115
|
+
expectUuidVersion(v1FromV6, 1);
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
describe('utility functions', () => {
|
|
120
|
+
it('should validate uuids correctly', () => {
|
|
121
|
+
const [isValid, isInvalid] = evalAndDump(`
|
|
122
|
+
const { v4, validate } = require('uuid');
|
|
123
|
+
[validate(v4()), validate('not-a-uuid')];
|
|
124
|
+
`);
|
|
125
|
+
expect(isValid).toBe(true);
|
|
126
|
+
expect(isInvalid).toBe(false);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it('should detect uuid version correctly', () => {
|
|
130
|
+
const [v4Version, v7Version] = evalAndDump(`
|
|
131
|
+
const { v4, v7, version } = require('uuid');
|
|
132
|
+
[version(v4()), version(v7())];
|
|
133
|
+
`);
|
|
134
|
+
expect(v4Version).toBe(4);
|
|
135
|
+
expect(v7Version).toBe(7);
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it('should parse and stringify uuid', () => {
|
|
139
|
+
const [original, stringified, isObject, length] = evalAndDump(`
|
|
140
|
+
const { v4, parse, stringify } = require('uuid');
|
|
141
|
+
const original = v4();
|
|
142
|
+
const parsed = parse(original);
|
|
143
|
+
[original, stringify(parsed), typeof parsed === 'object', Object.keys(parsed).length];
|
|
144
|
+
`);
|
|
145
|
+
expect(original).toBe(stringified);
|
|
146
|
+
expect(isObject).toBe(true);
|
|
147
|
+
expect(length).toBe(16);
|
|
148
|
+
});
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
describe('issue #7333 regression test', () => {
|
|
152
|
+
it('should work with the exact pattern from the bug report', () => {
|
|
153
|
+
const [typeOfFn, id1, id2] = evalAndDump(`
|
|
154
|
+
const { v7: uuidv7 } = require('uuid');
|
|
155
|
+
const id1 = uuidv7();
|
|
156
|
+
const id2 = uuidv7();
|
|
157
|
+
[typeof uuidv7, id1, id2];
|
|
158
|
+
`);
|
|
159
|
+
|
|
160
|
+
expect(typeOfFn).toBe('function');
|
|
161
|
+
expectUuidVersion(id1, 7);
|
|
162
|
+
expectUuidVersion(id2, 7);
|
|
163
|
+
expect(id1).not.toBe(id2);
|
|
164
|
+
});
|
|
165
|
+
});
|
|
166
|
+
});
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Returns JavaScript code that sets up the require() function in the QuickJS VM.
|
|
3
|
+
* This module loader looks up modules from globalThis.requireObject and optionally
|
|
4
|
+
* supports loading local modules if the necessary context (bru.cwd, __brunoLoadLocalModule) is available.
|
|
5
|
+
*
|
|
6
|
+
* @param {Object} options
|
|
7
|
+
* @param {boolean} options.enableLocalModules - Whether to enable local module loading (requires bru context)
|
|
8
|
+
* @returns {string} JavaScript code to eval in the VM
|
|
9
|
+
*/
|
|
10
|
+
function getRequireCode() {
|
|
11
|
+
return `
|
|
12
|
+
globalThis.require = (mod) => {
|
|
13
|
+
let lib = globalThis.requireObject[mod];
|
|
14
|
+
let isModuleAPath = (module) => (module?.startsWith('.') || (typeof bru !== 'undefined' && module?.startsWith(bru.cwd())))
|
|
15
|
+
if (lib) {
|
|
16
|
+
return lib;
|
|
17
|
+
}
|
|
18
|
+
else if (isModuleAPath(mod)) {
|
|
19
|
+
// fetch local module
|
|
20
|
+
let localModuleCode = globalThis.__brunoLoadLocalModule(mod);
|
|
21
|
+
|
|
22
|
+
// compile local module as iife
|
|
23
|
+
(function (){
|
|
24
|
+
const initModuleExportsCode = "const module = { exports: {} };"
|
|
25
|
+
const copyModuleExportsCode = "\\n;globalThis.requireObject[mod] = module.exports;";
|
|
26
|
+
const patchedRequire = ${`
|
|
27
|
+
"\\n;" +
|
|
28
|
+
"let require = (subModule) => isModuleAPath(subModule) ? globalThis.require(path.resolve(bru.cwd(), mod, '..', subModule)) : globalThis.require(subModule)" +
|
|
29
|
+
"\\n;"
|
|
30
|
+
`}
|
|
31
|
+
eval(initModuleExportsCode + patchedRequire + localModuleCode + copyModuleExportsCode);
|
|
32
|
+
})();
|
|
33
|
+
|
|
34
|
+
// resolve module
|
|
35
|
+
return globalThis.requireObject[mod];
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
throw new Error("Cannot find module " + mod);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
`;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Adds the require() function to a QuickJS VM context
|
|
46
|
+
* @param {Object} vm - QuickJS VM context
|
|
47
|
+
* @param {Object} options - Options passed to getRequireCode
|
|
48
|
+
*/
|
|
49
|
+
function addRequireShimToContext(vm) {
|
|
50
|
+
vm.evalCode(getRequireCode());
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
module.exports = {
|
|
54
|
+
getRequireCode,
|
|
55
|
+
addRequireShimToContext
|
|
56
|
+
};
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
const { describe, it, expect, beforeAll, beforeEach, afterEach, afterAll } = require('@jest/globals');
|
|
2
|
+
const { newQuickJSWASMModule } = require('quickjs-emscripten');
|
|
3
|
+
const { addRequireShimToContext, getRequireCode } = require('./require');
|
|
4
|
+
const { createEvalHelper } = require('../utils/test-helpers');
|
|
5
|
+
|
|
6
|
+
describe('require shim tests', () => {
|
|
7
|
+
let vm, module, evalAndDump;
|
|
8
|
+
|
|
9
|
+
beforeAll(async () => {
|
|
10
|
+
module = await newQuickJSWASMModule();
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
beforeEach(() => {
|
|
14
|
+
vm = module.newContext();
|
|
15
|
+
evalAndDump = createEvalHelper(vm);
|
|
16
|
+
// Initialize empty requireObject
|
|
17
|
+
vm.evalCode('globalThis.requireObject = {}');
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
afterEach(() => {
|
|
21
|
+
if (vm) {
|
|
22
|
+
try {
|
|
23
|
+
vm.dispose();
|
|
24
|
+
} catch (err) {
|
|
25
|
+
// Ignore disposal errors
|
|
26
|
+
}
|
|
27
|
+
vm = null;
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
afterAll(() => {
|
|
32
|
+
if (module) {
|
|
33
|
+
try {
|
|
34
|
+
module.dispose();
|
|
35
|
+
} catch (err) {
|
|
36
|
+
// Ignore disposal errors
|
|
37
|
+
}
|
|
38
|
+
module = null;
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
describe('getRequireCode', () => {
|
|
43
|
+
it('should return a string', () => {
|
|
44
|
+
expect(typeof getRequireCode()).toBe('string');
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('should contain require function definition', () => {
|
|
48
|
+
const code = getRequireCode();
|
|
49
|
+
expect(code).toContain('globalThis.require');
|
|
50
|
+
expect(code).toContain('requireObject');
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
describe('addRequireShimToContext', () => {
|
|
55
|
+
it('should add require function to the VM context', () => {
|
|
56
|
+
addRequireShimToContext(vm);
|
|
57
|
+
const typeOfRequire = evalAndDump('typeof globalThis.require');
|
|
58
|
+
expect(typeOfRequire).toBe('function');
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('should return module from requireObject', () => {
|
|
62
|
+
addRequireShimToContext(vm);
|
|
63
|
+
|
|
64
|
+
// Register a mock module
|
|
65
|
+
vm.evalCode(`
|
|
66
|
+
globalThis.requireObject['test-module'] = { foo: 'bar', answer: 42 };
|
|
67
|
+
`);
|
|
68
|
+
|
|
69
|
+
const result = evalAndDump(`
|
|
70
|
+
const mod = require('test-module');
|
|
71
|
+
[mod.foo, mod.answer];
|
|
72
|
+
`);
|
|
73
|
+
|
|
74
|
+
expect(result).toEqual(['bar', 42]);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('should support destructuring from required modules', () => {
|
|
78
|
+
addRequireShimToContext(vm, { enableLocalModules: false });
|
|
79
|
+
|
|
80
|
+
vm.evalCode(`
|
|
81
|
+
globalThis.requireObject['my-lib'] = {
|
|
82
|
+
greet: (name) => 'Hello, ' + name,
|
|
83
|
+
VERSION: '1.0.0'
|
|
84
|
+
};
|
|
85
|
+
`);
|
|
86
|
+
|
|
87
|
+
const [greeting, version] = evalAndDump(`
|
|
88
|
+
const { greet, VERSION } = require('my-lib');
|
|
89
|
+
[greet('World'), VERSION];
|
|
90
|
+
`);
|
|
91
|
+
|
|
92
|
+
expect(greeting).toBe('Hello, World');
|
|
93
|
+
expect(version).toBe('1.0.0');
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it('should support aliased destructuring', () => {
|
|
97
|
+
addRequireShimToContext(vm);
|
|
98
|
+
|
|
99
|
+
vm.evalCode(`
|
|
100
|
+
globalThis.requireObject['utils'] = { v1: () => 'version-1' };
|
|
101
|
+
`);
|
|
102
|
+
|
|
103
|
+
const result = evalAndDump(`
|
|
104
|
+
const { v1: getVersion } = require('utils');
|
|
105
|
+
getVersion();
|
|
106
|
+
`);
|
|
107
|
+
|
|
108
|
+
expect(result).toBe('version-1');
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it('should throw error for unknown modules', () => {
|
|
112
|
+
addRequireShimToContext(vm);
|
|
113
|
+
|
|
114
|
+
const result = vm.evalCode(`
|
|
115
|
+
try {
|
|
116
|
+
require('non-existent-module');
|
|
117
|
+
'no error';
|
|
118
|
+
} catch (e) {
|
|
119
|
+
e.message;
|
|
120
|
+
}
|
|
121
|
+
`);
|
|
122
|
+
const handle = vm.unwrapResult(result);
|
|
123
|
+
const errorMessage = vm.dump(handle);
|
|
124
|
+
handle.dispose();
|
|
125
|
+
|
|
126
|
+
expect(errorMessage).toContain('Cannot find module');
|
|
127
|
+
expect(errorMessage).toContain('non-existent-module');
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it('should allow requiring the same module multiple times', () => {
|
|
131
|
+
addRequireShimToContext(vm);
|
|
132
|
+
|
|
133
|
+
vm.evalCode(`
|
|
134
|
+
globalThis.requireObject['counter'] = { count: 0 };
|
|
135
|
+
`);
|
|
136
|
+
|
|
137
|
+
const result = evalAndDump(`
|
|
138
|
+
const mod1 = require('counter');
|
|
139
|
+
const mod2 = require('counter');
|
|
140
|
+
mod1 === mod2;
|
|
141
|
+
`);
|
|
142
|
+
|
|
143
|
+
expect(result).toBe(true);
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
describe('enableLocalModules option', () => {
|
|
148
|
+
it('should include local module loading code when enabled', () => {
|
|
149
|
+
const code = getRequireCode();
|
|
150
|
+
expect(code).toContain('isModuleAPath');
|
|
151
|
+
expect(code).toContain('__brunoLoadLocalModule');
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
});
|
|
@@ -69,8 +69,8 @@ const addBruShimToContext = (vm, __brunoTestResults) => {
|
|
|
69
69
|
Object.defineProperty(proto, 'json', {
|
|
70
70
|
get: function () {
|
|
71
71
|
var obj = this._obj;
|
|
72
|
-
var isJson = typeof obj === 'object' && obj !== null &&
|
|
73
|
-
Object.prototype.toString.call(obj) === '[object Object]';
|
|
72
|
+
var isJson = typeof obj === 'object' && obj !== null &&
|
|
73
|
+
(Array.isArray(obj) || Object.prototype.toString.call(obj) === '[object Object]');
|
|
74
74
|
this.assert(isJson, 'expected #{this} to be JSON', 'expected #{this} not to be JSON');
|
|
75
75
|
return this;
|
|
76
76
|
},
|