import assert from "node:assert/strict"; import { test } from "node:test"; import { commitCanvasImageResultIfMissing, getCanvasImageResultUrlKeys, resolveCanvasImageResultAliases, } from "@/components/canvas/chat-panel/canvas-image-result-sync"; const preview = "https://provider.example/result.png"; const archived = "https://bucket.example/archived.png"; const other = "https://bucket.example/second.png"; const image = (src: string, sourceImageKey = src, sourceMessageId = "message-1") => ({ type: "image", data: { src, metadata: { sourceImageKey, sourceMessageId } }, }); test("archived history replay recognizes the provider preview already on canvas", async () => { const nodes = [image(preview)]; const aliases = await resolveCanvasImageResultAliases({ nodes, messageId: "message-1", imageUrls: [archived], resolveAliases: async (urls) => { assert.deepEqual(urls, [preview]); return { [preview]: archived }; }, }); let writes = 0; const result = commitCanvasImageResultIfMissing({ getNodes: () => nodes, url: archived, aliases, commit: () => ++writes }); assert.equal(result, undefined); assert.equal(writes, 0); assert.equal(nodes.length, 1); }); test("current src and previous source identity both prevent replay", () => { const keys = getCanvasImageResultUrlKeys([image(archived, preview)]); assert(keys.has(preview)); assert(keys.has(archived)); }); test("simultaneous stream and history image loads commit a single node", async () => { const nodes: ReturnType[] = []; let release!: () => void; const loading = new Promise((resolve) => { release = resolve; }); const insert = async () => { // Both paths have already observed an empty canvas before image loading. assert.equal(nodes.length, 0); await loading; return commitCanvasImageResultIfMissing({ getNodes: () => nodes, url: preview, aliases: {}, commit: () => { nodes.push(image(preview)); return "inserted"; }, }); }; const stream = insert(); const history = insert(); release(); assert.deepEqual(await Promise.all([stream, history]), ["inserted", undefined]); assert.equal(nodes.length, 1); }); test("multiple distinct results in one message remain distinct", async () => { const nodes = [image(preview)]; const aliases = await resolveCanvasImageResultAliases({ nodes, messageId: "message-1", imageUrls: [archived, other], resolveAliases: async () => ({ [preview]: archived }), }); for (const url of [archived, other]) { commitCanvasImageResultIfMissing({ getNodes: () => nodes, url, aliases, commit: () => nodes.push(image(url)) }); } assert.equal(nodes.length, 2); assert.equal(nodes[1].data.src, other); }); test("new generations display immediately without an archive lookup", async () => { const aliases = await resolveCanvasImageResultAliases({ nodes: [image(preview, preview, "previous-message")], messageId: "message-2", imageUrls: [other], resolveAliases: async () => { throw new Error("Must not wait for archiving"); }, }); assert.deepEqual(aliases, {}); }); test("an archive lookup failure never treats the URL change as a new result", async () => { await assert.rejects(resolveCanvasImageResultAliases({ nodes: [image(preview)], messageId: "message-1", imageUrls: [archived], resolveAliases: async () => { throw new Error("Temporary network failure"); }, }), /Temporary network failure/); }); test("same-message image batches respect the archive endpoint limit", async () => { const nodes = Array.from({ length: 205 }, (_, i) => image(`https://provider.example/${i}.png`)); const batches: number[] = []; await resolveCanvasImageResultAliases({ nodes, messageId: "message-1", imageUrls: [archived], resolveAliases: async (urls) => { batches.push(urls.length); return {}; }, }); assert.deepEqual(batches, [100, 100, 5]); }); test("synchronization leaves existing manual copies untouched", () => { const nodes = [image(archived, preview), image(archived, preview)]; commitCanvasImageResultIfMissing({ getNodes: () => nodes, url: archived, aliases: {}, commit: () => { throw new Error("Unexpected insertion"); } }); assert.equal(nodes.length, 2); });