import test from "node:test"; import assert from "node:assert/strict"; test("history connects without a helper, opens a tab, and retains failures before download starts", async () => { const url = "https://www.youtube.com/watch?v=jNQXAC9IVRw"; const storage = { jobs: { saved: { id: "saved", title: "Сохранённое видео", kind: "download", status: "complete", result: { path: "D:/Videos/saved.mp4" }, createdAt: 1 }, interrupted: { id: "interrupted", status: "running", createdAt: 2 }, } }; const listeners = {}, tabs = [], nativeRequests = []; const noEvent = { addListener() {} }; const runtime = { id: "history-test", getManifest: () => ({ version: "1.4.5" }), getURL: file => `chrome-extension://history-test/${file}`, onConnect: { addListener: listener => { listeners.connect = listener; } }, onMessage: noEvent, connectNative() { let receive; return { onMessage: { addListener: listener => { receive = listener; } }, onDisconnect: noEvent, disconnect() {}, postMessage(message) { nativeRequests.push(message); queueMicrotask(() => receive({ id: message.id, ok: message.action !== "inspect", result: message.action === "hello" ? { version: "1.3.2" } : { alive: true }, error: "Видео недоступно" })); } }; } }; globalThis.chrome = { runtime, storage: { local: { get: async () => structuredClone(storage), set: async data => Object.assign(storage, structuredClone(data)) } }, tabs: { create: async tab => { tabs.push(tab); }, sendMessage: async () => {} }, notifications: { clear: async () => {}, create: async () => {}, update: async () => true, onButtonClicked: noEvent }, permissions: { contains: async () => false }, action: { onClicked: noEvent, setBadgeText() {}, setBadgeBackgroundColor() {} }, }; await import("../extension/background.js?history-test"); const attach = file => { let send; const events = [], pending = new Map(); listeners.connect({ name: "ytdl-ui", sender: { id: runtime.id, url: runtime.getURL(file) }, onDisconnect: noEvent, onMessage: { addListener: listener => { send = listener; } }, postMessage(message) { if (message.id) { pending.get(message.id)?.(message); pending.delete(message.id); } else events.push(message); } }); return { connected: () => Boolean(send), events, call: (action, payload = {}) => new Promise(resolve => { const id = crypto.randomUUID(); pending.set(id, resolve); send({ id, action, payload }); }) }; }; const history = attach("history.html?theme=dark"), menu = attach("downloader.html?embedded=1"); assert.ok(history.connected()); assert.ok(!attach("history.html.evil").connected()); assert.ok(!attach("content.js").connected()); const state = (await history.call("state")).result; assert.equal(state.jobs.length, 2); assert.equal(state.jobs.find(job => job.id === "interrupted").status, "error"); assert.equal(nativeRequests.length, 0, "Viewing history must not launch or query the helper"); assert.deepEqual((await menu.call("open_history", { theme: "dark" })).result, { opened: true }); assert.deepEqual(tabs, [{ url: runtime.getURL("history.html?theme=dark") }]); const inspection = await menu.call("inspect", { url }); assert.equal(inspection.ok, false); assert.equal(inspection.error, "Видео недоступно"); const failure = Object.values(storage.jobs).find(job => job.kind === "inspection"); assert.equal(failure.error, "Видео недоступно"); assert.equal(failure.url, url); assert.ok(history.events.some(message => message.job?.id === failure.id), "Already open history receives failures immediately"); assert.ok(!JSON.stringify(storage).includes("browserCookies")); });