mirror of
https://github.com/block/buzz.git
synced 2026-10-31 06:21:34 +00:00
fix(desktop): stabilize workstream PR subscriptions
Signed-off-by: loganj <loganj@squareup.com> Co-authored-by: Amp <amp@ampcode.com>
This commit is contained in:
@@ -236,6 +236,51 @@ test("workstream status hook preserves a synchronous live callback", async () =>
|
||||
cleanup();
|
||||
});
|
||||
|
||||
test("workstream status hook keeps subscriptions stable across equivalent rerenders", async () => {
|
||||
const { act, cleanup, renderHook } = await import("@testing-library/react");
|
||||
const { useWorkstreamPullRequestStatuses } = await import(
|
||||
"./workstreamPullRequestStatus.ts"
|
||||
);
|
||||
const reference = parseWorkstreamPullRequestReference(
|
||||
"https://github.com/block/buzz/pull/42",
|
||||
);
|
||||
let subscribes = 0;
|
||||
let unsubscribes = 0;
|
||||
const registry = {
|
||||
subscribe: (_reference, listener) => {
|
||||
subscribes += 1;
|
||||
listener({ status: "loading" });
|
||||
return () => {
|
||||
unsubscribes += 1;
|
||||
};
|
||||
},
|
||||
};
|
||||
const { rerender, unmount } = renderHook(
|
||||
({ refs }) => useWorkstreamPullRequestStatuses(refs, registry),
|
||||
{ initialProps: { refs: [reference] } },
|
||||
);
|
||||
|
||||
await act(async () => {
|
||||
rerender({ refs: [{ ...reference }] });
|
||||
rerender({ refs: [{ ...reference }] });
|
||||
});
|
||||
assert.equal(subscribes, 1);
|
||||
assert.equal(unsubscribes, 0);
|
||||
|
||||
const replacement = parseWorkstreamPullRequestReference(
|
||||
"https://github.com/block/buzz/pull/43",
|
||||
);
|
||||
await act(async () => {
|
||||
rerender({ refs: [replacement] });
|
||||
});
|
||||
assert.equal(subscribes, 2);
|
||||
assert.equal(unsubscribes, 1);
|
||||
|
||||
unmount();
|
||||
assert.equal(unsubscribes, 2);
|
||||
cleanup();
|
||||
});
|
||||
|
||||
test("workstream status hook removes stale references and preserves unsupported state", async () => {
|
||||
const { act, cleanup, renderHook } = await import("@testing-library/react");
|
||||
const { useWorkstreamPullRequestStatuses } = await import(
|
||||
|
||||
@@ -519,11 +519,26 @@ export function useWorkstreamPullRequestStatuses(
|
||||
const [states, setStates] = React.useState<
|
||||
ReadonlyMap<string, WorkstreamPullRequestDisplayState>
|
||||
>(() => new Map());
|
||||
const referencesByIdentity = React.useMemo(() => {
|
||||
// Cards rebuild parsed references on every render. Subscribe by their
|
||||
// semantic identity instead of that transient array/object identity so a
|
||||
// synchronous registry listener cannot cause an effect resubscribe loop.
|
||||
const referenceIdentityKey = references
|
||||
.map((reference) => reference.identity)
|
||||
.sort()
|
||||
.join("\u0000");
|
||||
const referencesByIdentityRef = React.useRef<{
|
||||
identityKey: string;
|
||||
references: ReadonlyMap<string, WorkstreamPullRequestReference>;
|
||||
} | null>(null);
|
||||
if (referencesByIdentityRef.current?.identityKey !== referenceIdentityKey) {
|
||||
const next = new Map<string, WorkstreamPullRequestReference>();
|
||||
for (const reference of references) next.set(reference.identity, reference);
|
||||
return next;
|
||||
}, [references]);
|
||||
referencesByIdentityRef.current = {
|
||||
identityKey: referenceIdentityKey,
|
||||
references: next,
|
||||
};
|
||||
}
|
||||
const referencesByIdentity = referencesByIdentityRef.current.references;
|
||||
|
||||
React.useEffect(() => {
|
||||
const initial = new Map<string, WorkstreamPullRequestDisplayState>();
|
||||
|
||||
Reference in New Issue
Block a user