import test from "node:test"; import assert from "node:assert/strict"; import { createUiConnection } from "../extension/ui-connection.js"; function harness() { const ports = [], events = []; const runtime = { connect() { const messages = [], disconnects = []; const port = { sent: [], dead: false, onMessage: { addListener: listener => messages.push(listener) }, onDisconnect: { addListener: listener => disconnects.push(listener) }, postMessage(message) { if (this.dead) throw new Error("Attempting to use a disconnected port object"); this.sent.push(message); }, respond(result, index = this.sent.length - 1) { messages.forEach(listener => listener({ id: this.sent[index].id, ok: true, result })); }, emit(message) { messages.forEach(listener => listener(message)); }, disconnect() { this.dead = true; disconnects.forEach(listener => listener()); }, }; ports.push(port); return port; } }; const connection = createUiConnection({ runtime, onEvent: event => events.push(event), onDisconnect: () => events.push({ disconnected: true }) }); return { ...connection, ports, events, runtime }; } test("a menu reconnects after worker shutdown, without resending an in-flight operation", async () => { const h = harness(); const pending = h.call("helper_remove"); const rejected = assert.rejects(pending, /Связь с расширением прервана/); const old = h.ports[0]; old.disconnect(); await rejected; const next = h.call("helper_start"); assert.equal(h.ports.length, 2); assert.equal(old.sent.length, 1); assert.equal(h.ports[1].sent[0].action, "helper_start"); h.ports[1].respond({ ready: true }); assert.deepEqual(await next, { ready: true }); // Late events from the old worker must not change the new status. const count = h.events.length; old.emit({ event: "helper", helper: { status: "missing" } }); old.disconnect(); assert.equal(h.events.length, count); h.ports[1].emit({ event: "helper", helper: { status: "running" } }); assert.equal(h.events.at(-1).helper.status, "running"); }); test("a disconnected-port exception before its callback reconnects in the same click", async () => { const h = harness(); const initial = h.call("hello"); h.ports[0].respond({ ready: true }); await initial; h.ports[0].dead = true; const retry = h.call("helper_start"); assert.equal(h.ports.length, 2); h.ports[0].disconnect(); h.ports[1].respond({ ready: true }); assert.equal((await retry).ready, true); assert.equal(h.ports[0].sent.length, 1, "Failed post was never delivered"); }); test("repeated disconnected-port failures stop after one retry with a readable error", async () => { let attempts = 0; const h = createUiConnection({ runtime: { connect() { attempts++; return { onMessage: { addListener() {} }, onDisconnect: { addListener() {} }, postMessage() { throw new Error("Attempting to use a disconnected port object"); } }; } } }); await assert.rejects(h.call("helper_start"), /Проверить подключение/); assert.equal(attempts, 2); }); test("helper errors are delivered once and do not replay updates or removal", async () => { for (const action of ["helper_update", "helper_remove", "download"]) { const h = harness(), result = h.call(action); const rejected = assert.rejects(result, /Дождитесь завершения/); h.ports[0].emit({ id: h.ports[0].sent[0].id, ok: false, error: "Дождитесь завершения загрузок" }); await rejected; assert.equal(h.ports.length, 1); assert.equal(h.ports[0].sent.length, 1); } });