import assert from "node:assert/strict"; import { after, before, test } from "node:test"; import { JSDOM } from "jsdom"; import { __linkPreviewMetadataTest, fetchBuzzEntityMetadata, loadLinkPreviewMetadata, isBuzzEntityPreview, resetLinkPreviewMetadataCache, resolveLinkPreview, withEntityFallbacks, } from "./useResolvedLinkPreviews.ts"; const preview = { kind: "generic-link", href: "https://example.com/story", provider: "example.com", title: "example.com/story", typeLabel: "link", }; function metadata(overrides = {}) { return { title: "A story", siteName: "Example", description: "Story description", imageDataUrl: null, imageDomain: null, imageFetchState: "none", imageRetryAfterMs: null, ...overrides, }; } function deferred() { let reject; let resolve; const promise = new Promise((resolvePromise, rejectPromise) => { reject = rejectPromise; resolve = resolvePromise; }); return { promise, reject, resolve }; } test("pending external metadata reserves the image treatment", () => { assert.deepEqual(resolveLinkPreview(preview, undefined), { ...preview, imageState: "pending", }); }); test("pending Buzz entity metadata remains image-less", () => { const entityPreview = { kind: "buzz-repository", href: `buzz://repo?owner=${"cd".repeat(32)}&d=buzz`, provider: "Buzz", title: "buzz", typeLabel: "repo", }; assert.deepEqual(resolveLinkPreview(entityPreview, undefined), { ...entityPreview, imageState: "none", }); }); test("resolved image metadata keeps the reserved image treatment", () => { const resolved = resolveLinkPreview(preview, { title: "A story", siteName: "Example", imageDataUrl: "data:image/jpeg;base64,abc", imageDomain: "cdn.example.com", }); assert.equal(resolved.imageState, "image"); assert.equal(resolved.provider, "Example"); assert.equal(resolved.imageDomain, "cdn.example.com"); }); test("resolved metadata without a complete image collapses to the compact treatment", () => { const resolved = resolveLinkPreview(preview, { title: "A story", siteName: "Example", imageDataUrl: null, imageDomain: null, }); assert.equal(resolved.imageState, "none"); assert.equal(resolved.imageDataUrl, null); assert.equal(resolved.imageDomain, null); }); test("transient and rejected image fetches use the stable fallback treatment", () => { const transient = resolveLinkPreview( preview, metadata({ imageFetchState: "transient_failure", imageRetryAfterMs: 900_000, }), ); const rejected = resolveLinkPreview( preview, metadata({ imageFetchState: "rejected" }), ); assert.equal(transient.imageState, "fallback"); assert.equal(rejected.imageState, "fallback"); }); test("metadata cache keys deduplicate URL fragments", () => { assert.equal( __linkPreviewMetadataTest.metadataCacheKey( "https://github.com/block/buzz/pull/3834#issuecomment-1", ), "https://github.com/block/buzz/pull/3834", ); }); test("transient metadata expires at the server retry boundary", () => { assert.equal( __linkPreviewMetadataTest.metadataExpiry( metadata({ imageFetchState: "transient_failure", imageRetryAfterMs: 900_000, }), 1_000, ), 901_000, ); }); test("metadata loader retries transient images after the server cooldown", async () => { let now = 1_000; let calls = 0; const loader = __linkPreviewMetadataTest.createMetadataLoader({ fetcher: async () => { calls += 1; return calls === 1 ? metadata({ imageFetchState: "transient_failure", imageRetryAfterMs: 10_000, }) : metadata({ imageDataUrl: "data:image/jpeg;base64,abc", imageDomain: "images.example.com", imageFetchState: "image", }); }, now: () => now, }); assert.equal( (await loader.load(preview.href).promise).metadata?.imageFetchState, "transient_failure", ); assert.equal(calls, 1); now += 10_000; assert.equal( (await loader.load(preview.href).promise).metadata?.imageFetchState, "image", ); assert.equal(calls, 2); }); test("metadata loader retries rejected requests after the negative-cache TTL", async () => { let now = 1_000; let calls = 0; const loader = __linkPreviewMetadataTest.createMetadataLoader({ fetcher: async () => { calls += 1; if (calls === 1) throw new Error("temporary failure"); return metadata(); }, now: () => now, }); assert.equal((await loader.load(preview.href).promise).metadata, null); assert.equal((await loader.load(preview.href).promise).metadata, null); assert.equal(calls, 1); now += 5 * 60_000; assert.deepEqual( (await loader.load(preview.href).promise).metadata, metadata(), ); assert.equal(calls, 2); }); test("metadata loader coalesces fragment variants and bounds concurrency", async () => { let active = 0; let maxActive = 0; let calls = 0; const loader = __linkPreviewMetadataTest.createMetadataLoader({ concurrency: 2, fetcher: async () => { calls += 1; active += 1; maxActive = Math.max(maxActive, active); await new Promise((resolve) => setImmediate(resolve)); active -= 1; return metadata(); }, }); await Promise.all([ loader.load("https://example.com/one#first").promise, loader.load("https://example.com/one#second").promise, loader.load("https://example.com/two").promise, loader.load("https://example.com/three").promise, ]); assert.equal(calls, 3); assert.equal(maxActive, 2); }); test("withEntityFallbacks re-adds previews dropped by null metadata", () => { const entityPreview = { kind: "buzz-pull-request", href: `buzz://pr?id=${"ab".repeat(32)}&owner=${"cd".repeat(32)}&d=buzz`, provider: "Buzz", title: `buzz #${"ab".repeat(4)}`, typeLabel: "Review", }; assert.deepEqual(withEntityFallbacks([entityPreview], []), [ { ...entityPreview, imageState: "none" }, ]); }); test("withEntityFallbacks keeps resolved previews and preserves order", () => { const first = { kind: "buzz-repository", href: `buzz://repo?owner=${"cd".repeat(32)}&d=buzz`, provider: "Buzz", title: "buzz", typeLabel: "repo", }; const second = { kind: "buzz-issue", href: `buzz://issue?id=${"ef".repeat(32)}&owner=${"cd".repeat(32)}&d=buzz`, provider: "Buzz", title: `buzz #${"ef".repeat(4)}`, typeLabel: "Task", }; const resolvedSecond = { ...second, title: "Fix the preview cards", imageState: "none", }; assert.deepEqual(withEntityFallbacks([first, second], [resolvedSecond]), [ { ...first, imageState: "none" }, resolvedSecond, ]); }); test("entity fallback eligibility is kind-scoped", () => { assert.equal( isBuzzEntityPreview({ ...preview, kind: "buzz-repository", href: `buzz://repo?owner=${"cd".repeat(32)}&d=buzz`, }), true, ); assert.equal( isBuzzEntityPreview({ ...preview, href: "buzz://future?id=example" }), false, ); }); test("withEntityFallbacks still drops unresolved external links", () => { assert.deepEqual(withEntityFallbacks([preview], []), []); assert.deepEqual( withEntityFallbacks([{ ...preview, href: "buzz://future?id=example" }], []), [], ); }); function relayEvent({ id, kind, pubkey, content = "", tags = [], createdAt = 1, }) { return { id, kind, pubkey, created_at: createdAt, content, tags, sig: "" }; } test("Buzz PR metadata includes repository identity and trusted root context", async () => { const owner = "cd".repeat(32); const attacker = "ef".repeat(32); const id = "ab".repeat(32); const repoAddress = `30617:${owner}:buzz`; const commit = "1234567".padEnd(40, "0"); const events = [ relayEvent({ id: "01".repeat(32), kind: 30617, pubkey: owner, tags: [ ["d", "buzz"], ["name", "Buzz Desktop"], ["default-branch", "main"], ], }), relayEvent({ id, kind: 1618, pubkey: owner, content: "Body", tags: [ ["a", repoAddress], ["subject", "Restore entity cards"], ["branch-name", "fix/cards"], ["target-branch", "release"], ["c", commit], ], }), relayEvent({ id: "02".repeat(32), kind: 1633, pubkey: attacker, createdAt: 20, tags: [["e", id]], }), relayEvent({ id: "03".repeat(32), kind: 1630, pubkey: owner, createdAt: 10, tags: [["e", id]], }), ...Array.from({ length: 25 }, (_, index) => relayEvent({ id: index.toString(16).padStart(64, "0"), kind: 1633, pubkey: owner, createdAt: 100 + index, tags: [["e", index.toString(16).padStart(64, "f")]], }), ), ]; const fetchEvents = async (filter) => events .filter( (event) => (!filter.kinds || filter.kinds.includes(event.kind)) && (!filter.ids || filter.ids.includes(event.id)) && (!filter.authors || filter.authors.includes(event.pubkey)) && (!filter["#d"] || event.tags.some( (tag) => tag[0] === "d" && filter["#d"].includes(tag[1]), )) && (!filter["#a"] || event.tags.some( (tag) => tag[0] === "a" && filter["#a"].includes(tag[1]), )) && (!filter["#e"] || event.tags.some( (tag) => tag[0] === "e" && filter["#e"].includes(tag[1]), )), ) .sort((left, right) => right.created_at - left.created_at) .slice(0, filter.limit); const result = await fetchBuzzEntityMetadata( `buzz://pr?id=${id}&owner=${owner}&d=buzz`, fetchEvents, ); assert.equal(result?.siteName, "Buzz Desktop"); assert.equal(result?.title, "Restore entity cards"); assert.equal(result?.description, null); assert.equal(result?.faviconDataUrl, null); assert.equal(result?.imageDataUrl, null); }); test("Buzz entity roots reject ambiguous repository tags", async () => { const owner = "cd".repeat(32); const attacker = "ef".repeat(32); const targetAddress = `30617:${owner}:buzz`; const attackerAddress = `30617:${attacker}:other`; const repository = relayEvent({ id: "01".repeat(32), kind: 30617, pubkey: owner, tags: [ ["d", "buzz"], ["name", "Buzz Desktop"], ["default-branch", "main"], ], }); for (const [type, kind] of [ ["pr", 1618], ["issue", 1621], ]) { const id = (type === "pr" ? "ab" : "bc").repeat(32); const root = relayEvent({ id, kind, pubkey: attacker, tags: [ ["a", attackerAddress], ["a", targetAddress], ["subject", "Misbound entity"], ], }); const result = await fetchBuzzEntityMetadata( `buzz://${type}?id=${id}&owner=${owner}&d=buzz`, async (filter) => filter.kinds?.includes(30617) ? [repository] : filter.ids?.includes(id) ? [root] : [], ); assert.equal(result, null, `${type} with multiple repository tags`); } }); test("Buzz repository metadata stays image-less and exposes default branch", async () => { const owner = "cd".repeat(32); const result = await fetchBuzzEntityMetadata( `buzz://repo?owner=${owner}&d=relay-tools`, async () => [ relayEvent({ id: "01".repeat(32), kind: 30617, pubkey: owner, content: "Fallback description", tags: [ ["d", "relay-tools"], ["name", "Relay Tools"], ["description", "Operator tooling for relays"], ["status", "active"], ["default-branch", "trunk"], ], }), ], ); assert.equal(result?.siteName, "Relay Tools"); assert.equal(result?.title, "Operator tooling for relays"); assert.equal(result?.description, "active · default: trunk"); assert.equal(result?.faviconDataUrl, null); assert.equal(result?.imageDataUrl, null); assert.equal(result?.imageDomain, null); }); test("Buzz project metadata resolves from the 30621 announcement", async () => { const owner = "cd".repeat(32); const result = await fetchBuzzEntityMetadata( `buzz://project?owner=${owner}&d=pollinator`, async () => [ relayEvent({ id: "01".repeat(32), kind: 30621, pubkey: owner, tags: [ ["d", "pollinator"], ["name", "Pollinator"], ["description", "Cross-repo pollination pipeline"], ["a", `30617:${owner}:flappy-bee`], ["a", `30617:${owner}:hive-tools`], ], }), ], ); assert.equal(result?.siteName, "Pollinator"); assert.equal(result?.title, "Cross-repo pollination pipeline"); assert.equal(result?.description, "2 repositories"); assert.equal(result?.imageDataUrl, null); }); test("Buzz project metadata declines a missing or invalid announcement", async () => { const owner = "cd".repeat(32); assert.equal( await fetchBuzzEntityMetadata( `buzz://project?owner=${owner}&d=pollinator`, async () => [], ), null, ); // Two `d` tags fail NIP-MP envelope validation. assert.equal( await fetchBuzzEntityMetadata( `buzz://project?owner=${owner}&d=pollinator`, async () => [ relayEvent({ id: "01".repeat(32), kind: 30621, pubkey: owner, tags: [ ["d", "pollinator"], ["d", "duplicate"], ], }), ], ), null, ); }); test("invalidateNegative drops a cached null miss so the next load refetches", async () => { // A URL freshly entering the composer clears a stale hard miss (null) so it // refetches, instead of riding the cached blank. const now = 1_000; let calls = 0; let nextResult = null; const loader = __linkPreviewMetadataTest.createMetadataLoader({ fetcher: async () => { calls += 1; return nextResult; }, now: () => now, }); assert.equal((await loader.load(preview.href).promise).metadata, null); assert.equal(calls, 1); loader.invalidateNegative(preview.href); nextResult = metadata(); assert.deepEqual( (await loader.load(preview.href).promise).metadata, metadata(), ); assert.equal(calls, 2); }); test("invalidateNegative drops a cached transient failure so the next load refetches", async () => { // A transient image failure is a NEGATIVE by contract (option docs + the // loader's own retry boundary), so re-entering the composer must refetch it // — not reuse the cached transient entry. Regression for the leak where // invalidateNegative only cleared hard `null` misses. (PR #5510) const now = 1_000; let calls = 0; const loader = __linkPreviewMetadataTest.createMetadataLoader({ fetcher: async () => { calls += 1; return calls === 1 ? metadata({ imageFetchState: "transient_failure", imageRetryAfterMs: 10_000, }) : metadata({ imageDataUrl: "data:image/jpeg;base64,abc", imageDomain: "images.example.com", imageFetchState: "image", }); }, now: () => now, }); assert.equal( (await loader.load(preview.href).promise).metadata?.imageFetchState, "transient_failure", ); assert.equal(calls, 1); // Bust well before the retry boundary (now is frozen); the cache-bust — not // the cooldown — is what forces the refetch. loader.invalidateNegative(preview.href); assert.equal( (await loader.load(preview.href).promise).metadata?.imageFetchState, "image", ); assert.equal(calls, 2); }); test("invalidateNegative leaves a healthy cached hit untouched", async () => { // A settled positive (instant card, no redundant fetch) must survive a bust // so passive scroll re-renders keep riding the cache. const now = 1_000; let calls = 0; const loader = __linkPreviewMetadataTest.createMetadataLoader({ fetcher: async () => { calls += 1; return metadata(); }, now: () => now, }); assert.deepEqual( (await loader.load(preview.href).promise).metadata, metadata(), ); assert.equal(calls, 1); loader.invalidateNegative(preview.href); assert.deepEqual( (await loader.load(preview.href).promise).metadata, metadata(), ); assert.equal(calls, 1); }); test("invalidateNegative leaves an in-flight fetch untouched", async () => { // A fetch still in flight is cached as a Promise, not a resolved entry. // Busting mid-flight must not cancel or duplicate it: the pending load // resolves normally and no second fetch is started. const now = 1_000; let calls = 0; let releaseFetch; const loader = __linkPreviewMetadataTest.createMetadataLoader({ fetcher: () => { calls += 1; return new Promise((resolve) => { releaseFetch = () => resolve(metadata()); }); }, now: () => now, }); const pending = loader.load(preview.href); assert.equal(calls, 1); // Bust while the fetch is still in flight — the Promise entry is left alone. loader.invalidateNegative(preview.href); assert.equal(calls, 1, "no redundant fetch started by the bust"); releaseFetch(); assert.deepEqual((await pending.promise).metadata, metadata()); assert.equal(calls, 1, "the original in-flight fetch resolved, not a retry"); }); test("an orphaned metadata load is cancelled after its re-entry grace period", async () => { let cancelled = false; const loader = __linkPreviewMetadataTest.createMetadataLoader({ fetcher: (_href, signal) => new Promise((_resolve, reject) => { signal.addEventListener( "abort", () => { cancelled = true; reject(new Error("cancelled")); }, { once: true }, ); }), }); const load = loader.load(preview.href); const rejection = assert.rejects(load.promise, /cancelled/); load.cancel(); await new Promise((resolve) => setTimeout(resolve, 1_050)); assert.equal(cancelled, true); await rejection; }); test("new metadata demand immediately reclaims slots from orphaned loads", async () => { const started = []; const cancelled = []; const releases = new Map(); const loader = __linkPreviewMetadataTest.createMetadataLoader({ concurrency: 2, fetcher: (href, signal) => new Promise((resolve, reject) => { started.push(href); releases.set(href, () => resolve(metadata())); signal.addEventListener( "abort", () => { cancelled.push(href); reject(new Error("cancelled")); }, { once: true }, ); }), }); const firstHref = "https://example.com/stalled-one"; const secondHref = "https://example.com/stalled-two"; const thirdHref = "https://example.com/ordinary"; const first = loader.load(firstHref); const second = loader.load(secondHref); const firstSettled = first.promise.catch(() => undefined); const secondSettled = second.promise.catch(() => undefined); first.cancel(); second.cancel(); const third = loader.load(thirdHref); await new Promise((resolve) => setImmediate(resolve)); assert.deepEqual(started, [firstHref, secondHref, thirdHref]); assert.deepEqual(new Set(cancelled), new Set([firstHref, secondHref])); releases.get(thirdHref)(); assert.deepEqual((await third.promise).metadata, metadata()); await Promise.all([firstSettled, secondSettled]); }); // ── Hook-level regression: retained resolved-state invalidation on re-entry ─── // // The loader tests above prove `invalidateNegative` drops the SHARED loader // cache entry. But `useResolvedLinkPreviews` also retains its OWN // `resolvedMetadata` React state, and the render that scheduled the // invalidating effect has already read the stale negative from it. Dropping the // loader key alone leaves that local key in place, so a re-entered // `transient_failure` still resolves to a `snapshotReady` fallback the composer // can turn into a sendable snapshot tag from STALE metadata before the retry // lands. This drives the REAL hook to prove the local key is cleared too, so // the re-entry renders as pending (no `snapshotReady`) until the retry wins. // (PR #5510) Regression: without the local-state clear the re-entry assertion // below sees `snapshotReady: true` instead of pending. const dom = new JSDOM("", { url: "http://localhost", }); before(() => { Object.assign(globalThis, { document: dom.window.document, HTMLElement: dom.window.HTMLElement, IS_REACT_ACT_ENVIRONMENT: true, window: dom.window, }); // @tauri-apps/api/core reads window.__TAURI_INTERNALS__.invoke at call time. // A per-test handler map lets each test control the fetch resolution timing. dom.window.__TAURI_INTERNALS__ = { invoke: (cmd, args) => { const handler = ipcHandlers.get(cmd); return handler ? handler(args) : Promise.reject(new Error(`unmocked Tauri command: ${cmd}`)); }, transformCallback: () => Math.random(), }; }); after(() => dom.window.close()); /** @type {Map Promise>} */ const ipcHandlers = new Map(); const hookPreview = { kind: "generic-link", href: "https://example.com/hook-re-entry", provider: "example.com", title: "example.com/hook-re-entry", typeLabel: "link", }; test("hook clears retained resolved state so a re-entered transient failure renders pending until the retry wins", async () => { const { act, cleanup, renderHook } = await import("@testing-library/react"); const { useResolvedLinkPreviews } = await import( "./useResolvedLinkPreviews.ts" ); // Isolate from any loader state leaked by earlier tests in this process. resetLinkPreviewMetadataCache(); ipcHandlers.clear(); // Gate every fetch so we can observe the state between renders. Call 1 caches // a transient failure; call 2 (the re-entry retry) succeeds. let calls = 0; const releases = []; ipcHandlers.set("fetch_link_preview_metadata", () => { calls += 1; const attempt = calls; return new Promise((resolve) => { releases.push(() => resolve( attempt === 1 ? metadata({ imageFetchState: "transient_failure", imageRetryAfterMs: 900_000, }) : metadata({ imageDataUrl: "data:image/jpeg;base64,abc", imageDomain: "images.example.com", imageFetchState: "image", }), ), ); }); }); // scheduleAfterPaint uses requestAnimationFrame -> setTimeout(0); flush both // plus a microtask turn so the queued load() actually fires. const flushScheduledLoads = async () => { await act(async () => { await new Promise((resolve) => setTimeout(resolve, 0)); }); }; const settle = async () => { await act(async () => { // Release any pending fetch and let its .then() commit setResolvedMetadata. while (releases.length > 0) releases.shift()(); await new Promise((resolve) => setTimeout(resolve, 0)); }); }; try { const { result, rerender, unmount } = renderHook( ({ previews }) => useResolvedLinkPreviews(previews, { refetchNewNegatives: true }), { initialProps: { previews: [hookPreview] } }, ); // 1. Initial paste: pending until the fetch resolves to a transient failure. assert.equal(result.current[0].imageState, "pending"); assert.equal(result.current[0].snapshotReady, undefined); await flushScheduledLoads(); await settle(); // Transient failure cached: a sendable fallback (this is the state that must // NOT survive re-entry as ready). assert.equal(result.current[0].imageState, "fallback"); assert.equal(result.current[0].snapshotReady, true); assert.equal(calls, 1); // 2. URL removed from the composer. rerender({ previews: [] }); assert.deepEqual(result.current, []); // 3. URL re-entered. The invalidation effect must drop BOTH the loader entry // and the retained local key, so this settles to pending with no // snapshotReady — not the stale fallback. Without the local-state clear // the hook returns snapshotReady: true here. rerender({ previews: [hookPreview] }); await act(async () => {}); assert.equal( result.current[0].imageState, "pending", "re-entered transient failure must render pending, not a stale fallback", ); assert.equal( result.current[0].snapshotReady, undefined, "no snapshotReady before the retry resolves — nothing sendable from stale metadata", ); // 4. Successful retry wins. await flushScheduledLoads(); await settle(); assert.equal(result.current[0].imageState, "image"); assert.equal(result.current[0].snapshotReady, true); assert.equal(calls, 2, "the re-entry triggered a fresh fetch"); unmount(); } finally { cleanup(); ipcHandlers.clear(); } }); // ── Hook-level regression: in-flight shared entry defeats the local clear ───── // // The shared metadataLoader is intentionally shared across every hook instance // (composer + message list). `invalidateNegative` deliberately leaves an // in-flight Promise entry alone — but that means when a URL re-enters the // composer WHILE another instance has a fetch in flight for the same canonical // URL, the shared drop is a no-op. Gating the local-state clear on that drop // (the earlier fix) leaves this hook's retained `transient_failure` in place, // so it keeps returning `snapshotReady: true` — a sendable tag built from stale // metadata — until that shared fetch resolves. This drives the real hook // through that exact interleaving to prove the local negative is cleared on // re-entry regardless of the shared entry's shape. (PR #5510, follow-up.) const otherHookPreview = { kind: "generic-link", href: "https://example.com/hook-inflight-re-entry", provider: "example.com", title: "example.com/hook-inflight-re-entry", typeLabel: "link", }; test("hook clears a re-entered negative even when the shared cache holds an in-flight fetch, not a settled entry", async () => { const { act, cleanup, renderHook } = await import("@testing-library/react"); const { useResolvedLinkPreviews } = await import( "./useResolvedLinkPreviews.ts" ); resetLinkPreviewMetadataCache(); ipcHandlers.clear(); // Gate every fetch. Call 1 (composer's initial paste) resolves to a transient // failure. Call 2 is the SHARED in-flight fetch a message-list instance starts // after the shared cache is dropped; it is left unreleased so it is still a // Promise in the shared cache when the composer re-enters. let calls = 0; const releases = []; ipcHandlers.set("fetch_link_preview_metadata", () => { calls += 1; const attempt = calls; return new Promise((resolve) => { releases.push(() => resolve( attempt === 1 ? metadata({ imageFetchState: "transient_failure", imageRetryAfterMs: 900_000, }) : metadata({ imageDataUrl: "data:image/jpeg;base64,abc", imageDomain: "images.example.com", imageFetchState: "image", }), ), ); }); }); const flushScheduledLoads = async () => { await act(async () => { await new Promise((resolve) => setTimeout(resolve, 0)); }); }; const releaseAll = async () => { await act(async () => { while (releases.length > 0) releases.shift()(); await new Promise((resolve) => setTimeout(resolve, 0)); }); }; const composer = renderHook( ({ previews }) => useResolvedLinkPreviews(previews, { refetchNewNegatives: true }), { initialProps: { previews: [otherHookPreview] } }, ); // A passive message-list instance sharing the same loader (no refetch). const messageList = renderHook( ({ previews }) => useResolvedLinkPreviews(previews, {}), { initialProps: { previews: [] } }, ); try { // 1. Composer paste resolves to a transient failure -> sendable fallback. await flushScheduledLoads(); await act(async () => { releases.shift()(); // release call 1 only await new Promise((resolve) => setTimeout(resolve, 0)); }); assert.equal(composer.result.current[0].imageState, "fallback"); assert.equal(composer.result.current[0].snapshotReady, true); assert.equal(calls, 1); // 2. Composer URL leaves. composer.rerender({ previews: [] }); assert.deepEqual(composer.result.current, []); // 3. The shared negative is dropped (simulating expiry/eviction), then a // message-list instance starts a fresh fetch for the SAME canonical URL // and it is left IN FLIGHT — a Promise, not a settled entry, in the // shared cache. act(() => { resetLinkPreviewMetadataCache(); }); messageList.rerender({ previews: [otherHookPreview] }); await flushScheduledLoads(); assert.equal(calls, 2, "message-list started the shared in-flight fetch"); // 4. Composer re-enters WHILE that fetch is in flight. invalidateNegative is // a no-op (the shared entry is a Promise), so the earlier drop-gated clear // would leave the retained transient failure — a sendable tag from stale // metadata. The local negative must be cleared regardless: pending, no // snapshotReady. composer.rerender({ previews: [otherHookPreview] }); await act(async () => {}); assert.equal( composer.result.current[0].imageState, "pending", "re-entry during a shared in-flight fetch must render pending, not a stale fallback", ); assert.equal( composer.result.current[0].snapshotReady, undefined, "no snapshotReady while the shared fetch is in flight — nothing sendable from stale metadata", ); // The composer coalesced onto the in-flight fetch — no third request. assert.equal(calls, 2, "re-entry coalesced onto the in-flight fetch"); // 5. The shared fetch resolves successfully; both instances settle. await releaseAll(); await flushScheduledLoads(); assert.equal(composer.result.current[0].imageState, "image"); assert.equal(composer.result.current[0].snapshotReady, true); } finally { composer.unmount(); messageList.unmount(); cleanup(); ipcHandlers.clear(); } }); test("preparation abort releases only its own shared metadata lease", async () => { const { prepareLinkPreview, resetLinkPreviewPreparations } = await import( "../../features/messages/lib/linkPreviewPreparationStore.ts" ); resetLinkPreviewMetadataCache(); resetLinkPreviewPreparations(); ipcHandlers.clear(); const href = "https://example.com/shared-pending"; let fetchRequest; const cancelledRequestIds = []; ipcHandlers.set("fetch_link_preview_metadata", (args) => { const request = deferred(); fetchRequest = { ...args, ...request }; return request.promise; }); ipcHandlers.set("cancel_link_preview_metadata", ({ requestId }) => { cancelledRequestIds.push(requestId); return Promise.resolve(); }); ipcHandlers.set("release_link_preview_metadata", () => Promise.resolve()); try { const composerLoad = loadLinkPreviewMetadata(href); const preparation = prepareLinkPreview({ ...hookPreview, href }); await new Promise((resolve) => setImmediate(resolve)); assert.ok(fetchRequest, "shared metadata reached the native fetch seam"); resetLinkPreviewPreparations(); assert.equal(await preparation, null); await new Promise((resolve) => setTimeout(resolve, 1_050)); assert.deepEqual( cancelledRequestIds, [], "preparation listener plus finally must not release the composer lease", ); fetchRequest.resolve(metadata()); assert.deepEqual(await composerLoad.promise, metadata()); const finalHref = `${href}/final`; const finalLoad = loadLinkPreviewMetadata(finalHref); await new Promise((resolve) => setImmediate(resolve)); const finalRequest = fetchRequest; const finalRejection = assert.rejects(finalLoad.promise); finalLoad.cancel(); finalLoad.cancel(); await new Promise((resolve) => setTimeout(resolve, 1_050)); assert.deepEqual( cancelledRequestIds, [finalRequest.requestId], "one final consumer release permits native abort", ); finalRequest.reject(new Error("cancelled by test native seam")); await finalRejection; } finally { resetLinkPreviewPreparations(); resetLinkPreviewMetadataCache(); ipcHandlers.clear(); } }); test("removing two stalled previews cancels native work and admits a third URL", async () => { const { act, cleanup, renderHook } = await import("@testing-library/react"); const { useResolvedLinkPreviews } = await import( "./useResolvedLinkPreviews.ts" ); resetLinkPreviewMetadataCache(); ipcHandlers.clear(); const firstPreview = { ...hookPreview, href: "https://example.com/stalled-one", }; const secondPreview = { ...hookPreview, href: "https://example.com/stalled-two", }; const thirdPreview = { ...hookPreview, href: "https://example.com/ordinary" }; const pending = new Map(); const started = []; const cancelled = []; ipcHandlers.set("fetch_link_preview_metadata", ({ href, requestId }) => { started.push(href); return new Promise((resolve, reject) => { pending.set(requestId, { href, reject, resolve }); }); }); ipcHandlers.set("cancel_link_preview_metadata", ({ requestId }) => { const request = pending.get(requestId); if (!request) return Promise.resolve(); cancelled.push(request.href); pending.delete(requestId); request.reject(new Error("cancelled by test native seam")); return Promise.resolve(); }); ipcHandlers.set("release_link_preview_metadata", () => Promise.resolve()); const flushScheduledLoads = async () => { await act(async () => { await new Promise((resolve) => setTimeout(resolve, 0)); }); }; const hook = renderHook(({ previews }) => useResolvedLinkPreviews(previews), { initialProps: { previews: [firstPreview, secondPreview] }, }); try { await flushScheduledLoads(); assert.deepEqual(started, [firstPreview.href, secondPreview.href]); hook.rerender({ previews: [thirdPreview] }); await flushScheduledLoads(); assert.deepEqual( new Set(cancelled), new Set([firstPreview.href, secondPreview.href]), ); assert.deepEqual(started, [ firstPreview.href, secondPreview.href, thirdPreview.href, ]); const third = [...pending.values()].find( (request) => request.href === thirdPreview.href, ); assert.ok(third, "third URL reached the native fetch seam"); await act(async () => { third.resolve(metadata()); await new Promise((resolve) => setTimeout(resolve, 0)); }); assert.equal(hook.result.current[0].title, "A story"); } finally { hook.unmount(); cleanup(); ipcHandlers.clear(); } });