import { describe, it } from "@std/testing/bdd"; import { expect } from "@std/expect"; import { sanitizeForPostMessage } from "./runtime-processor.ts"; describe("sanitizeForPostMessage", () => { describe("primitives", () => { it("passes through null and undefined", () => { expect(sanitizeForPostMessage(null)).toBe(null); expect(sanitizeForPostMessage(undefined)).toBe(undefined); }); it("passes through numbers, strings, and booleans", () => { expect(sanitizeForPostMessage(42)).toBe(42); expect(sanitizeForPostMessage("hello")).toBe("hello"); expect(sanitizeForPostMessage(true)).toBe(true); }); }); describe("functions", () => { it("converts functions to placeholder strings", () => { expect(sanitizeForPostMessage(() => {})).toBe("[Function]"); expect(sanitizeForPostMessage(function named() {})).toBe("[Function]"); }); }); describe("plain objects", () => { it("passes through simple objects", () => { const obj = { name: "test", count: 42 }; expect(sanitizeForPostMessage(obj)).toEqual({ name: "test", count: 42 }); }); it("handles nested objects", () => { const obj = { outer: { inner: { value: 1 } } }; expect(sanitizeForPostMessage(obj)).toEqual({ outer: { inner: { value: 1 } }, }); }); it("converts function properties to placeholders", () => { const obj = { name: "test", callback: () => {} }; expect(sanitizeForPostMessage(obj)).toEqual({ name: "test", callback: "[Function]", }); }); }); describe("arrays", () => { it("handles arrays of primitives", () => { expect(sanitizeForPostMessage([1, 2, 3])).toEqual([1, 2, 3]); }); it("handles arrays of objects", () => { const arr = [{ a: 1 }, { b: 2 }]; expect(sanitizeForPostMessage(arr)).toEqual([{ a: 1 }, { b: 2 }]); }); it("converts function elements to placeholders", () => { const arr = [1, () => {}, 3]; expect(sanitizeForPostMessage(arr)).toEqual([1, "[Function]", 3]); }); }); describe("circular references", () => { it("detects and handles circular references", () => { const obj: Record = { name: "test" }; obj.self = obj; expect(sanitizeForPostMessage(obj)).toEqual({ name: "test", self: "[Circular]", }); }); it("handles circular arrays", () => { const arr: unknown[] = [1, 2]; arr.push(arr); expect(sanitizeForPostMessage(arr)).toEqual([1, 2, "[Circular]"]); }); }); describe("depth limit", () => { it("stops at max depth", () => { const deepObj = { l1: { l2: { l3: { l4: { l5: { l6: "too deep" } } } } }, }; const result = sanitizeForPostMessage(deepObj) as Record; // At depth 5, l6 should be "[Max depth exceeded]" expect( ( ( ((result.l1 as Record).l2 as Record< string, unknown >) .l3 as Record ).l4 as Record ).l5, ).toBe("[Max depth exceeded]"); }); }); describe("objects with throwing property access", () => { it("handles objects with properties that throw on read", () => { // Create an object where reading a specific property throws const obj = { safe: "value", get dangerous(): never { throw new Error("Cannot read this property"); }, }; const result = sanitizeForPostMessage(obj) as Record; expect(result.safe).toBe("value"); expect(result.dangerous).toBe("[Unreadable]"); }); it("handles proxies with throwing get trap - walks keys but marks values unreadable", () => { const throwingProxy = new Proxy( {}, { get() { throw new Error("Cannot access property"); }, ownKeys() { return ["problematic"]; }, getOwnPropertyDescriptor() { return { enumerable: true, configurable: true }; }, }, ); // The proxy itself can be iterated (ownKeys works), but reading values throws const result = sanitizeForPostMessage(throwingProxy) as Record< string, unknown >; expect(result).toEqual({ problematic: "[Unreadable]" }); }); it("handles proxies that throw on Object.keys", () => { const throwingProxy = new Proxy( {}, { ownKeys() { throw new Error("Cannot list keys"); }, }, ); // When we can't iterate, we fall back to placeholder const result = sanitizeForPostMessage(throwingProxy); expect(result).toBe("[Object - uncloneable]"); }); }); describe("mixed structures", () => { it("handles complex nested structures with various types", () => { const complex = { name: "root", items: [ { id: 1, process: () => {} }, { id: 2, nested: { deep: true } }, ], metadata: { count: 42, handler: function handle() {}, }, }; expect(sanitizeForPostMessage(complex)).toEqual({ name: "root", items: [ { id: 1, process: "[Function]" }, { id: 2, nested: { deep: true } }, ], metadata: { count: 42, handler: "[Function]", }, }); }); }); });