Files
buzz/desktop/src/shared/lib/useResolvedLinkPreviews.test.mjs
thomaspblock 87f8ff82ae feat(desktop): refine repository-aware project workspaces (#6003)
## Summary
- Makes Projects repository-aware across browsing, branch and tag
selection, local and remote source management, work items, commits, and
contextual repository actions.
- Refines project details into consistent single-column reading surfaces
with a resizable, section-aware context pod, while keeping project
conversations available in the attached chat panel.
- Adds persistent sidebar project navigation, direct entity links,
repository discussion channels, contributor identity matching, and
consistent loading and activity presentation.
- Keeps branch-specific controls on code-oriented sections while Tasks,
Reviews, and Channels remain repository-scoped, reducing misleading
context and actions.

Replacement for #5981 with an identical final tree flattened into one
signed-off commit because the required DCO check suite remained stalled.

### Related issue
N/A

### Testing
- [x] Desktop pre-push checks, TypeScript typecheck, and unit tests
- [x] E2E production build with `pnpm build:e2e`
- [x] Focused Playwright smoke coverage for project workspace, task,
review, commit, sidebar, and contextual-panel behavior
- [ ] Attach final before/after screenshots after design review

---------

Signed-off-by: Thomas Petersen <thomasp@squareup.com>
Signed-off-by: Wintermute <165f0c871dd2586bb18b6aa109eeaf57bb2132ff4d27b10120f4368a0f627022@buzz.block.builderlab.xyz>
Co-authored-by: Wintermute <165f0c871dd2586bb18b6aa109eeaf57bb2132ff4d27b10120f4368a0f627022@buzz.block.builderlab.xyz>
2026-08-19 12:50:00 +01:00

902 lines
28 KiB
JavaScript

import assert from "node:assert/strict";
import { after, before, test } from "node:test";
import { JSDOM } from "jsdom";
import {
__linkPreviewMetadataTest,
fetchBuzzEntityMetadata,
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,
};
}
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)).metadata?.imageFetchState,
"transient_failure",
);
assert.equal(calls, 1);
now += 10_000;
assert.equal(
(await loader.load(preview.href)).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)).metadata, null);
assert.equal((await loader.load(preview.href)).metadata, null);
assert.equal(calls, 1);
now += 5 * 60_000;
assert.deepEqual((await loader.load(preview.href)).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"),
loader.load("https://example.com/one#second"),
loader.load("https://example.com/two"),
loader.load("https://example.com/three"),
]);
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, "Open · fix/cards → release · 1234567");
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)).metadata, null);
assert.equal(calls, 1);
loader.invalidateNegative(preview.href);
nextResult = metadata();
assert.deepEqual((await loader.load(preview.href)).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)).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)).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)).metadata, metadata());
assert.equal(calls, 1);
loader.invalidateNegative(preview.href);
assert.deepEqual((await loader.load(preview.href)).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).metadata, metadata());
assert.equal(calls, 1, "the original in-flight fetch resolved, not a retry");
});
// ── 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("<!doctype html><html><body></body></html>", {
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<string, (args: unknown) => Promise<unknown>>} */
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();
}
});