Something went wrong. Try again.
Mirror of https://github.com/improsocial/improAn extensible Bluesky client for web
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962import { describe, it } from "node:test";import assert from "node:assert/strict";
// PluginBridge reads window.env.playwright during loadFromSource; provide it// so the import resolves cleanly. Individual tests avoid the real load path.globalThis.window.env = globalThis.window.env ?? { playwright: true };
const { PluginBridge, PluginInstance, SandboxedWorker, Logger, wrapWorkerSource,} = await import("/js/plugins/pluginBridge.js");
class FakeWorker { constructor() { this.listeners = {}; this.posted = []; this.terminated = false; } addEventListener(event, listener) { (this.listeners[event] ??= []).push(listener); } removeEventListener(event, listener) { this.listeners[event] = (this.listeners[event] ?? []).filter( (entry) => entry !== listener, ); } postMessage(message) { this.posted.push(message); } terminate() { this.terminated = true; } emit(event, payload) { (this.listeners[event] ?? []).forEach((listener) => listener(payload)); }}
function makeRealInstance({ onRegister, onHostCall, manifest } = {}) { const worker = new FakeWorker(); const registrations = []; const hostCalls = []; const instance = new PluginInstance( "demo", manifest ?? { id: "demo", version: "1.0.0" }, worker, { onRegister: onRegister ?? ((inst, message) => { registrations.push({ inst, message }); return null; }), onHostCall: onHostCall ?? ((inst, message) => { hostCalls.push({ inst, message }); }), }, ); return { instance, worker, registrations, hostCalls };}
function captureConsole(method, fn) { const captured = []; const original = console[method]; console[method] = (...args) => captured.push(args); try { fn(); } finally { console[method] = original; } return captured;}
// Inject a fake "PluginInstance"-shaped object into the bridge's loaded map// so we can test methods that operate on a loaded plugin without going// through PluginInstance.loadFromSource (which requires a real worker).function makeFakeInstance(pluginId = "demo") { const worker = new FakeWorker(); const calls = []; const instance = { pluginId, worker, unloaded: false, unload() { this.unloaded = true; }, call(handlerId, ...args) { calls.push({ handlerId, args }); return Promise.resolve(); }, _calls: calls, }; return instance;}
function makeStylesLoader() { const mounts = []; const unmounts = []; return { mounts, unmounts, mount(pluginId, css) { mounts.push({ pluginId, css }); }, unmount(pluginId) { unmounts.push(pluginId); }, };}
function makeProvider({ manifest, source, styles } = {}) { return { async getManifest(id) { if (manifest instanceof Error) throw manifest; return manifest ?? { id, version: "1.0.0" }; }, async getSource() { if (source instanceof Error) throw source; return source ?? ""; }, async getStyles() { if (styles instanceof Error) throw styles; return styles ?? null; }, };}
function makeBridge(overrides = {}) { const provider = overrides.provider ?? makeProvider(); const stylesLoader = overrides.stylesLoader ?? makeStylesLoader(); const loadPluginInstance = overrides.loadPluginInstance; return { bridge: new PluginBridge(provider, stylesLoader, loadPluginInstance), stylesLoader, provider, };}
async function expectError(promise) { try { await promise; } catch (error) { return error; } throw new Error("expected promise to reject");}
describe("PluginBridge:wrapWorkerSource", () => { it("prepends a prelude that removes BroadcastChannel/SharedWorker", () => { const wrapped = wrapWorkerSource("console.info('hi')"); assert(wrapped.includes("delete self.BroadcastChannel")); assert(wrapped.includes("delete self.SharedWorker")); assert(wrapped.includes("console.info('hi')")); });});
describe("PluginBridge:isLoaded / getInstance", () => { it("returns false/null when no plugin is loaded", () => { const { bridge } = makeBridge(); assert.deepEqual(bridge.isLoaded("missing"), false); assert.deepEqual(bridge.getInstance("missing"), null); });
it("returns true and the instance once stored", () => { const { bridge } = makeBridge(); const instance = makeFakeInstance("demo"); bridge._loadedPlugins.set("demo", instance); assert.deepEqual(bridge.isLoaded("demo"), true); assert(bridge.getInstance("demo") === instance); });});
describe("PluginBridge:registration targets", () => { it("dispatches to a registered target handler with instance and message", () => { const { bridge } = makeBridge(); const calls = []; const dispose = () => {}; bridge.addRegistrationTarget("sidebarItem", (instance, message) => { calls.push({ pluginId: instance.pluginId, message }); return dispose; }); const instance = makeFakeInstance("p1"); const result = bridge._handleRegistration(instance, { target: "sidebarItem", handlerId: 7, }); assert.deepEqual(calls.length, 1); assert.deepEqual(calls[0].pluginId, "p1"); assert.deepEqual(calls[0].message.handlerId, 7); assert(result === dispose); });
it("returns null when target is unknown", () => { const { bridge } = makeBridge(); const result = bridge._handleRegistration(makeFakeInstance(), { target: "nope", }); assert.deepEqual(result, null); });});
describe("PluginBridge:host calls", () => { it("invokes the handler and posts a hostResult with the value", async () => { const { bridge } = makeBridge(); bridge.addHostMethod("ping", (instance, ...args) => { assert.deepEqual(instance.pluginId, "p1"); assert.deepEqual(args, [1, 2]); return "pong"; }); const instance = makeFakeInstance("p1"); bridge._handleHostCall(instance, { method: "ping", hostCallId: 42, args: [1, 2], }); // handler is invoked through Promise.resolve().then(...), need to flush. await Promise.resolve(); await Promise.resolve(); const message = instance.worker.posted.find( (entry) => entry.type === "hostResult", ); assert.deepEqual(message.hostCallId, 42); assert.deepEqual(message.value, "pong"); });
it("forwards thrown errors as hostResult.error", async () => { const { bridge } = makeBridge(); bridge.addHostMethod("explode", () => { throw new Error("nope"); }); const instance = makeFakeInstance("p1"); const originalError = console.error; console.error = () => {}; try { bridge._handleHostCall(instance, { method: "explode", hostCallId: 1, args: [], }); await Promise.resolve(); await Promise.resolve(); } finally { console.error = originalError; } const message = instance.worker.posted.find( (entry) => entry.type === "hostResult", ); assert.deepEqual(message.error, "nope"); });
it("responds with an error message for unknown host methods", () => { const { bridge } = makeBridge(); const instance = makeFakeInstance("p1"); const originalWarn = console.warn; console.warn = () => {}; try { bridge._handleHostCall(instance, { method: "mystery", hostCallId: 9, args: [], }); } finally { console.warn = originalWarn; } const message = instance.worker.posted.find( (entry) => entry.type === "hostResult", ); assert.deepEqual(message.hostCallId, 9); assert(/unknown host method/.test(message.error)); });
it("does not post a hostResult when hostCallId is missing", async () => { const { bridge } = makeBridge(); bridge.addHostMethod("fire", () => "value"); const instance = makeFakeInstance("p1"); bridge._handleHostCall(instance, { method: "fire", args: [] }); await Promise.resolve(); await Promise.resolve(); assert.deepEqual(instance.worker.posted.length, 0); });});
describe("PluginBridge:handleNodeEvent", () => { it("forwards the event to instance.call with handlerId and virtualEvent", () => { const { bridge } = makeBridge(); const instance = makeFakeInstance("p1"); bridge._loadedPlugins.set("p1", instance); bridge.handleNodeEvent("p1", 12, { kind: "click" }); assert.deepEqual(instance._calls.length, 1); assert.deepEqual(instance._calls[0].handlerId, 12); assert.deepEqual(instance._calls[0].args, [{ kind: "click" }]); });
it("warns and skips when plugin is not loaded", () => { const { bridge } = makeBridge(); const originalWarn = console.warn; let warned = false; console.warn = () => { warned = true; }; try { bridge.handleNodeEvent("missing", 1, {}); } finally { console.warn = originalWarn; } assert(warned); });});
describe("PluginBridge:unloadPlugin", () => { it("unloads the instance, removes it, and unmounts styles", () => { const { bridge, stylesLoader } = makeBridge(); const instance = makeFakeInstance("demo"); bridge._loadedPlugins.set("demo", instance); bridge.unloadPlugin("demo"); assert.deepEqual(instance.unloaded, true); assert.deepEqual(bridge.isLoaded("demo"), false); assert.deepEqual(stylesLoader.unmounts, ["demo"]); });
it("is a no-op when the plugin is not loaded", () => { const { bridge, stylesLoader } = makeBridge(); bridge.unloadPlugin("missing"); assert.deepEqual(stylesLoader.unmounts, []); });});
describe("PluginBridge:loadPlugin error paths", () => { it("throws a manifest error when getManifest rejects", async () => { const provider = makeProvider({ manifest: new Error("bad json") }); const { bridge } = makeBridge({ provider }); const originalWarn = console.warn; console.warn = () => {}; try { const error = await expectError(bridge.loadPlugin("p1", "1.0.0")); assert.deepEqual(error.message, "Failed to load plugin manifest"); } finally { console.warn = originalWarn; } });
it("throws a source error when getSource rejects", async () => { const provider = makeProvider({ source: new Error("404") }); const { bridge } = makeBridge({ provider }); const originalError = console.error; console.error = () => {}; try { const error = await expectError(bridge.loadPlugin("p1", "1.0.0")); assert.deepEqual(error.message, "Failed to load plugin source"); } finally { console.error = originalError; } });
it("throws a styles fetch error when getStyles rejects", async () => { const provider = makeProvider({ styles: new Error("net") }); const { bridge } = makeBridge({ provider }); const originalError = console.error; console.error = () => {}; try { const error = await expectError(bridge.loadPlugin("p1", "1.0.0")); assert.deepEqual(error.message, "Failed to load plugin styles"); } finally { console.error = originalError; } });
it("throws a styles validation error when mount fails", async () => { const provider = makeProvider({ styles: ".x { color: red; }" }); const stylesLoader = makeStylesLoader(); stylesLoader.mount = () => { throw new Error("bad css"); }; const { bridge } = makeBridge({ provider, stylesLoader }); const originalError = console.error; console.error = () => {}; try { const error = await expectError(bridge.loadPlugin("p1", "1.0.0")); assert.deepEqual(error.message, "Plugin styles failed validation"); } finally { console.error = originalError; } });
it("returns early without loading when the plugin is already loaded", async () => { let getManifestCalled = false; const provider = makeProvider(); const originalGetManifest = provider.getManifest; provider.getManifest = async (...args) => { getManifestCalled = true; return originalGetManifest(...args); }; const { bridge } = makeBridge({ provider }); bridge._loadedPlugins.set("p1", makeFakeInstance("p1")); const result = await bridge.loadPlugin("p1", "1.0.0"); assert.deepEqual(result, undefined); assert.deepEqual(getManifestCalled, false); });});
describe("PluginBridge:loadPlugin success path", () => { it("mounts styles, stores the instance, and returns it", async () => { const provider = makeProvider({ source: "// js", styles: ".x {}", manifest: { id: "p1", version: "1.2.3" }, }); const stylesLoader = makeStylesLoader(); const fakeInstance = makeFakeInstance("p1"); const loadCalls = []; const loadPluginInstance = async ( pluginId, manifest, source, callbacks, ) => { loadCalls.push({ pluginId, manifest, source, callbacks }); return fakeInstance; }; const { bridge } = makeBridge({ provider, stylesLoader, loadPluginInstance, }); const originalInfo = console.info; console.info = () => {}; let result; try { result = await bridge.loadPlugin("p1", "1.2.3"); } finally { console.info = originalInfo; } assert(result === fakeInstance); assert.deepEqual(bridge.isLoaded("p1"), true); assert.deepEqual(stylesLoader.mounts, [{ pluginId: "p1", css: ".x {}" }]); assert.deepEqual(loadCalls.length, 1); assert.deepEqual(loadCalls[0].pluginId, "p1"); assert.deepEqual(loadCalls[0].manifest.id, "p1"); assert.deepEqual(loadCalls[0].manifest.version, "1.2.3"); assert.deepEqual(loadCalls[0].source, "// js"); assert(typeof loadCalls[0].callbacks.onRegister === "function"); assert(typeof loadCalls[0].callbacks.onHostCall === "function"); });
it("skips style mounting when getStyles returns null", async () => { const provider = makeProvider({ source: "// js", styles: null }); const stylesLoader = makeStylesLoader(); const loadPluginInstance = async () => makeFakeInstance("p1"); const { bridge } = makeBridge({ provider, stylesLoader, loadPluginInstance, }); const originalInfo = console.info; console.info = () => {}; try { await bridge.loadPlugin("p1", "1.0.0"); } finally { console.info = originalInfo; } assert.deepEqual(stylesLoader.mounts, []); });
it("forwards the manifest to loadPluginInstance", async () => { const manifest = { id: "p1", version: "1.0.0", permissions: { fetch: ["https://api.example.com/*"] }, }; const provider = makeProvider({ manifest, source: "// js" }); const loadCalls = []; const loadPluginInstance = async (pluginId, mft, source, callbacks) => { loadCalls.push({ pluginId, manifest: mft, source, callbacks }); return makeFakeInstance("p1"); }; const { bridge } = makeBridge({ provider, loadPluginInstance }); const originalInfo = console.info; console.info = () => {}; try { await bridge.loadPlugin("p1", "1.0.0"); } finally { console.info = originalInfo; } assert.deepEqual(loadCalls.length, 1); assert(loadCalls[0].manifest === manifest); });
it("unmounts styles and throws an init error when instance loading fails", async () => { const provider = makeProvider({ source: "// js", styles: ".x {}" }); const stylesLoader = makeStylesLoader(); const loadPluginInstance = async () => { throw new Error("worker rejected"); }; const { bridge } = makeBridge({ provider, stylesLoader, loadPluginInstance, }); const originalError = console.error; console.error = () => {}; let error; try { error = await expectError(bridge.loadPlugin("p1", "1.0.0")); } finally { console.error = originalError; } assert.deepEqual(error.message, "Plugin failed during initialization"); assert.deepEqual(stylesLoader.unmounts, ["p1"]); assert.deepEqual(bridge.isLoaded("p1"), false); });
it("routes onRegister and onHostCall callbacks back through the bridge", async () => { const provider = makeProvider({ source: "// js" }); let capturedCallbacks; const fakeInstance = makeFakeInstance("p1"); const loadPluginInstance = async ( pluginId, manifest, source, callbacks, ) => { capturedCallbacks = callbacks; return fakeInstance; }; const { bridge } = makeBridge({ provider, loadPluginInstance }); const registrations = []; bridge.addRegistrationTarget("sidebarItem", (instance, message) => { registrations.push({ pluginId: instance.pluginId, message }); return () => {}; }); bridge.addHostMethod("ping", (instance) => `hi-${instance.pluginId}`); const originalInfo = console.info; console.info = () => {}; try { await bridge.loadPlugin("p1", "1.0.0"); } finally { console.info = originalInfo; } capturedCallbacks.onRegister(fakeInstance, { target: "sidebarItem", handlerId: 1, }); assert.deepEqual(registrations.length, 1); capturedCallbacks.onHostCall(fakeInstance, { method: "ping", hostCallId: 5, args: [], }); await Promise.resolve(); await Promise.resolve(); const message = fakeInstance.worker.posted.find( (entry) => entry.type === "hostResult", ); assert.deepEqual(message.value, "hi-p1"); });});
describe("PluginBridge:loadPlugins", () => { it("aggregates loaded and errored plugins", async () => { const { bridge } = makeBridge(); const fakeInstance = makeFakeInstance("good"); bridge.loadPlugin = async (id) => { if (id === "bad") throw new Error("boom"); return fakeInstance; }; const result = await bridge.loadPlugins([ { id: "good", version: "1.0.0" }, { id: "bad", version: "2.0.0" }, ]); assert.deepEqual(result.loadedPlugins.length, 1); assert(result.loadedPlugins[0] === fakeInstance); assert.deepEqual(result.erroredPlugins.length, 1); assert.deepEqual(result.erroredPlugins[0].pluginId, "bad"); assert.deepEqual(result.erroredPlugins[0].version, "2.0.0"); assert.deepEqual(result.erroredPlugins[0].error.message, "boom"); });});
describe("PluginBridge:reloadPlugin", () => { it("unloads the existing instance before calling loadPlugin", async () => { const { bridge } = makeBridge(); const instance = makeFakeInstance("demo"); bridge._loadedPlugins.set("demo", instance); const loadCalls = []; bridge.loadPlugin = async (id, version, repo) => { loadCalls.push({ id, version, repo }); }; await bridge.reloadPlugin("demo", "2.0.0", "owner/repo"); assert.deepEqual(instance.unloaded, true); assert.deepEqual(loadCalls, [ { id: "demo", version: "2.0.0", repo: "owner/repo" }, ]); });});
describe("PluginInstance:manifest & permissions", () => { it("stores the manifest and parses an empty permissions set by default", () => { const { instance } = makeRealInstance({ manifest: { id: "demo", version: "1.0.0" }, }); assert.deepEqual(instance.manifest.version, "1.0.0"); assert.deepEqual(instance.permissions, {}); });
it("parses fetch permissions declared in the manifest", () => { const { instance } = makeRealInstance({ manifest: { id: "demo", version: "1.0.0", permissions: { fetch: ["https://api.example.com/*"] }, }, }); assert.deepEqual(instance.permissions.fetch, ["https://api.example.com/*"]); });
it("tolerates a manifest with no permissions field", () => { const { instance } = makeRealInstance({ manifest: { id: "demo", version: "1.0.0" }, }); assert.deepEqual(instance.permissions, {}); });});
describe("PluginInstance:waitForReady", () => { it("resolves when a ready message arrives without an error", async () => { const { instance, worker } = makeRealInstance(); const promise = instance.waitForReady(1000); worker.emit("message", { data: { type: "ready" } }); const result = await promise; assert(result === instance); });
it("rejects when the ready message contains an error", async () => { const { instance, worker } = makeRealInstance(); const promise = instance.waitForReady(1000); worker.emit("message", { data: { type: "ready", error: "init failed" } }); let caught; try { await promise; } catch (error) { caught = error; } assert.deepEqual(caught, "init failed"); });
it("rejects with 'Timed out' when no ready message arrives in time", async () => { const { instance } = makeRealInstance(); let caught; try { await instance.waitForReady(10); } catch (error) { caught = error; } assert(caught instanceof Error); assert.deepEqual(caught.message, "Timed out"); });});
describe("PluginInstance:worker message dispatch", () => { it("forwards register messages to onRegister and stores returned disposers", () => { const disposed = []; const dispose = () => disposed.push("yes"); const { instance, worker } = makeRealInstance({ onRegister: () => dispose, }); worker.emit("message", { data: { type: "register", target: "sidebarItem", handlerId: 3 }, }); assert.deepEqual(instance.disposers.length, 1); instance.disposers[0](); assert.deepEqual(disposed, ["yes"]); });
it("does not push a disposer when onRegister returns falsy", () => { const { instance, worker } = makeRealInstance({ onRegister: () => null }); worker.emit("message", { data: { type: "register", target: "x", handlerId: 1 }, }); assert.deepEqual(instance.disposers.length, 0); });
it("forwards hostCall messages to onHostCall", () => { const { instance, worker, hostCalls } = makeRealInstance(); worker.emit("message", { data: { type: "hostCall", method: "showToast", hostCallId: 1, args: [] }, }); assert.deepEqual(hostCalls.length, 1); assert.deepEqual(hostCalls[0].message.method, "showToast"); assert(hostCalls[0].inst === instance); });
it("ignores non-object messages", () => { const { instance, worker } = makeRealInstance(); worker.emit("message", { data: null }); worker.emit("message", { data: "string" }); worker.emit("message", { data: 42 }); assert.deepEqual(instance.disposers.length, 0); });
it("ignores unknown message types", () => { const { instance, worker } = makeRealInstance(); worker.emit("message", { data: { type: "garbage" } }); assert.deepEqual(instance.disposers.length, 0); });
it("logs but does not throw on worker error events", () => { const { worker } = makeRealInstance(); const originalError = console.error; let logged = false; console.error = () => { logged = true; }; try { worker.emit("error", { message: "boom" }); } finally { console.error = originalError; } assert(logged); });});
describe("PluginInstance:call()", () => { it("posts a call message and resolves with the result value", async () => { const { instance, worker } = makeRealInstance(); const promise = instance.call(7, "arg1", "arg2"); const sent = worker.posted[0]; assert.deepEqual(sent.type, "call"); assert.deepEqual(sent.handlerId, 7); assert.deepEqual(sent.args, ["arg1", "arg2"]); assert(typeof sent.callId === "number"); worker.emit("message", { data: { type: "result", callId: sent.callId, value: "ok" }, }); assert.deepEqual(await promise, "ok"); });
it("rejects with an Error when the result carries an error", async () => { const { instance, worker } = makeRealInstance(); const promise = instance.call(1); const sent = worker.posted[0]; worker.emit("message", { data: { type: "result", callId: sent.callId, error: "nope" }, }); let caught; try { await promise; } catch (error) { caught = error; } assert(caught instanceof Error); assert.deepEqual(caught.message, "nope"); });
it("assigns unique callIds to concurrent calls", async () => { const { instance, worker } = makeRealInstance(); const promise1 = instance.call(1); const promise2 = instance.call(2); const first = worker.posted[0]; const second = worker.posted[1]; assert(first.callId !== second.callId); worker.emit("message", { data: { type: "result", callId: second.callId, value: "B" }, }); worker.emit("message", { data: { type: "result", callId: first.callId, value: "A" }, }); assert.deepEqual(await promise1, "A"); assert.deepEqual(await promise2, "B"); });
it("ignores result messages for unknown callIds", () => { const { instance, worker } = makeRealInstance(); worker.emit("message", { data: { type: "result", callId: 9999, value: "x" }, }); assert.deepEqual(instance._pendingCalls.size, 0); });});
describe("PluginInstance:sendEvent", () => { it("posts an event message verbatim", () => { const { instance, worker } = makeRealInstance(); instance.sendEvent("modalDismissed", { modalId: "m1" }); assert.deepEqual(worker.posted[0], { type: "event", event: "modalDismissed", data: { modalId: "m1" }, }); });});
describe("PluginInstance:unload", () => { it("runs each disposer once and terminates the worker", () => { const { instance, worker } = makeRealInstance(); const calls = []; instance.disposers.push(() => calls.push("a")); instance.disposers.push(() => calls.push("b")); instance.unload(); assert.deepEqual(calls, ["a", "b"]); assert.deepEqual(worker.terminated, true); });});
describe("internals:Logger", () => { it("prefixes each log line with the configured prefix", () => { const logger = new Logger("[test]", "info"); const calls = captureConsole("info", () => logger.info("hello", 1)); assert.deepEqual(calls.length, 1); assert.deepEqual(calls[0][0], "[test]"); assert.deepEqual(calls[0][1], "hello"); assert.deepEqual(calls[0][2], 1); });
it("suppresses info when level is warn", () => { const logger = new Logger("[test]", "warn"); const infoCalls = captureConsole("info", () => logger.info("hidden")); const warnCalls = captureConsole("warn", () => logger.warn("shown")); assert.deepEqual(infoCalls.length, 0); assert.deepEqual(warnCalls.length, 1); });
it("suppresses info and warn when level is error", () => { const logger = new Logger("[test]", "error"); const infoCalls = captureConsole("info", () => logger.info("x")); const warnCalls = captureConsole("warn", () => logger.warn("y")); const errorCalls = captureConsole("error", () => logger.error("z")); assert.deepEqual(infoCalls.length, 0); assert.deepEqual(warnCalls.length, 0); assert.deepEqual(errorCalls.length, 1); });
it("suppresses everything at silent level", () => { const logger = new Logger("[test]", "silent"); const infoCalls = captureConsole("info", () => logger.info("x")); const warnCalls = captureConsole("warn", () => logger.warn("y")); const errorCalls = captureConsole("error", () => logger.error("z")); assert.deepEqual(infoCalls.length, 0); assert.deepEqual(warnCalls.length, 0); assert.deepEqual(errorCalls.length, 0); });
it("defaults to warn level when none is provided", () => { const logger = new Logger("[test]"); const infoCalls = captureConsole("info", () => logger.info("x")); const warnCalls = captureConsole("warn", () => logger.warn("y")); assert.deepEqual(infoCalls.length, 0); assert.deepEqual(warnCalls.length, 1); });});
describe("internals:wrapWorkerSource ordering", () => { it("places the prelude before the user source so it runs first", () => { const wrapped = wrapWorkerSource("user();"); const preludeIndex = wrapped.indexOf("delete self.BroadcastChannel"); const sourceIndex = wrapped.indexOf("user();"); assert(preludeIndex >= 0 && sourceIndex >= 0); assert(preludeIndex < sourceIndex); });});
describe("internals:SandboxedWorker", () => { it("appends a sandboxed iframe to document.body and posts init on load", () => { const before = document.body.querySelectorAll("iframe").length; const worker = new SandboxedWorker("// source"); const after = document.body.querySelectorAll("iframe").length; assert.deepEqual(after, before + 1); assert.deepEqual(worker.frame.getAttribute("sandbox"), "allow-scripts"); assert.deepEqual(worker.frame.getAttribute("aria-hidden"), "true"); assert.deepEqual(worker.frame.style.display, "none");
const posted = []; Object.defineProperty(worker.frame, "contentWindow", { configurable: true, value: { postMessage: (message) => posted.push(message) }, }); worker._messageTarget = worker.frame.contentWindow; worker.frame.dispatchEvent(new Event("load")); assert.deepEqual(posted.length, 1); assert.deepEqual(posted[0].type, "init"); assert(typeof posted[0].workerSource === "string"); assert(posted[0].workerSource.includes("// source")); worker.terminate(); });
it("postMessage forwards a 'send' envelope to the iframe", () => { const worker = new SandboxedWorker("// source"); const posted = []; Object.defineProperty(worker.frame, "contentWindow", { configurable: true, value: { postMessage: (message) => posted.push(message) }, }); worker.postMessage({ hello: 1 }); assert.deepEqual(posted, [{ type: "send", payload: { hello: 1 } }]); worker.terminate(); });
it("dispatches message events for fromWorker payloads", () => { const worker = new SandboxedWorker("// source"); const received = []; worker.addEventListener("message", (event) => received.push(event)); const fakeContentWindow = {}; Object.defineProperty(worker.frame, "contentWindow", { configurable: true, value: fakeContentWindow, }); worker._handleWindowMessage({ source: fakeContentWindow, data: { type: "fromWorker", payload: { value: 42 } }, }); assert.deepEqual(received.length, 1); assert.deepEqual(received[0].data, { value: 42 }); worker.terminate(); });
it("dispatches error events for workerError payloads", () => { const worker = new SandboxedWorker("// source"); const received = []; worker.addEventListener("error", (event) => received.push(event)); const fakeContentWindow = {}; Object.defineProperty(worker.frame, "contentWindow", { configurable: true, value: fakeContentWindow, }); worker._handleWindowMessage({ source: fakeContentWindow, data: { type: "workerError", error: "boom" }, }); assert.deepEqual(received.length, 1); assert.deepEqual(received[0].message, "boom"); worker.terminate(); });
it("ignores messages from other window sources", () => { const worker = new SandboxedWorker("// source"); const received = []; worker.addEventListener("message", (event) => received.push(event)); worker._handleWindowMessage({ source: {}, data: { type: "fromWorker", payload: 1 }, }); assert.deepEqual(received.length, 0); worker.terminate(); });
it("terminate removes the iframe and dispatches a terminate event", () => { const worker = new SandboxedWorker("// source"); let terminated = false; worker.addEventListener("terminate", () => { terminated = true; }); const frame = worker.frame; assert(document.body.contains(frame)); worker.terminate(); assert(!document.body.contains(frame)); assert(terminated); });});