import { QuickJSWorker } from "../dist"; describe("Handle and Global API", () => { let worker: QuickJSWorker; beforeEach(() => { worker = new QuickJSWorker(); }); afterEach(async () => { if (worker && !worker.isClosed()) await worker.close(); }); it("supports global.get/set/call", async () => { await worker.global.set("config", { enabled: true, nested: { count: 2 } }); await worker.global.set("sum", (a: number, b: number) => a + b); await expect(worker.global.get("config.nested.count")).resolves.toBe(2); await expect(worker.global.call("sum", [20, 22])).resolves.toBe(42); }); it("supports handle.get/set/call/dispose", async () => { await worker.eval(` globalThis.toolbox = { value: 1, inc(n) { return n + 1; }, }; `); const handle = await worker.handle.get("toolbox"); await expect(handle.get("value")).resolves.toBe(1); await handle.set("value", 9); await expect(handle.get("value")).resolves.toBe(9); await expect(handle.call("inc", [41])).resolves.toBe(42); await handle.dispose(); await expect(handle.get("value")).rejects.toMatchObject({ code: "HANDLE_DISPOSED" }); }); it("supports handle.eval for durable runtime references", async () => { const handle = await worker.handle.eval("({ count: 1, inc() { this.count += 1; return this.count; } })"); await expect(handle.call("inc")).resolves.toBe(2); await expect(handle.get("count")).resolves.toBe(2); }); it("supports handle lookup edge cases", async () => { const missing = await worker.handle.get("globalThis.__missing_handle_value"); expect(missing.rootType).toMatchObject({ type: "undefined", callable: false }); await expect(missing.get()).resolves.toBeUndefined(); await missing.dispose(); await expect(worker.handle.tryGet("globalThis.__missing_handle_value")).resolves.toBeUndefined(); await worker.eval("globalThis.__present_undefined = undefined;"); const handle = await worker.handle.get("globalThis.__present_undefined"); expect(handle.rootType).toMatchObject({ type: "undefined", callable: false }); await expect(handle.get()).resolves.toBeUndefined(); }); it("supports advanced handle operations", async () => { const handle = await worker.handle.eval(`({ a: 1, b: 2, add(x) { this.a += x; return this.a; } })`); await expect(handle.has("a")).resolves.toBe(true); await expect(handle.keys()).resolves.toEqual(expect.arrayContaining(["a", "b", "add"])); await expect(handle.entries()).resolves.toEqual(expect.arrayContaining([["a", 1], ["b", 2]])); await expect(handle.delete("b")).resolves.toBe(true); await expect(handle.has("b")).resolves.toBe(false); await expect( handle.define("x", { value: 42, enumerable: true, configurable: true, writable: true }), ).resolves.toBe(true); await expect(handle.getOwnPropertyDescriptor("x")).resolves.toMatchObject({ value: 42, enumerable: true, configurable: true, writable: true, }); await expect(handle.instanceOf("Object")).resolves.toBe(true); await expect(handle.isCallable()).resolves.toBe(false); await expect(handle.isCallable("add")).resolves.toBe(true); await expect(handle.apply([{ op: "call", path: "add", args: [2] }, { op: "get", path: "a" }])).resolves.toEqual([3, 3]); await expect(handle.toJSON()).resolves.toMatchObject({ a: 3, x: 42 }); const clone = await handle.clone(); await expect(clone.call("add", [1])).resolves.toBe(4); await expect(handle.get("a")).resolves.toBe(4); await clone.dispose(); }); it("supports callable, constructable, and promise handles", async () => { const fn = await worker.handle.eval("(a, b) => a + b"); expect(fn.rootType).toMatchObject({ type: "function", callable: true }); await expect(fn.call([20, 22])).resolves.toBe(42); await fn.dispose(); const ctor = await worker.handle.eval("(class Thing { constructor(v) { this.value = v; } })"); await expect(ctor.construct([9])).resolves.toMatchObject({ value: 9 }); await ctor.dispose(); const promiseHandle = await worker.handle.eval("Promise.resolve(7)"); await expect(promiseHandle.isPromise()).resolves.toBe(true); await expect(promiseHandle.await()).resolves.toBe(7); await expect(promiseHandle.isPromise()).resolves.toBe(false); await expect(promiseHandle.get()).resolves.toBe(7); await promiseHandle.dispose(); }); it("rejects forbidden prototype mutation paths", async () => { const handle = await worker.handle.eval("({ safe: true })"); await expect(handle.set("__proto__.polluted", 1)).rejects.toThrow(/forbidden/i); await expect(handle.set("nested.constructor.value", 1)).rejects.toThrow(/forbidden/i); await expect(handle.set("nested.prototype.value", 1)).rejects.toThrow(/forbidden/i); }); it("serializes bigint and circular references in handle snapshots", async () => { const handle = await worker.handle.eval(` (() => { const value = { id: 1n }; value.self = value; return value; })() `); await expect(handle.toJSON()).resolves.toEqual({ id: { __bigint: "1" }, self: "[Circular]", }); await handle.dispose(); }); });