Files
PaperClipAI/ui/src/lib/live-runs-cache.test.ts
T
Devin FoleyandPaperclip e335265568 fix(ui): stop idle task run polling with live updates (#17608)
Stop successful empty assigned-task run queries from polling only while the matching company and session connection is open. Preserve active, pending, failed, and disconnected cadence. Share one lifecycle refresh owner between the task and chat views, discover reviewer events without task identity, and refresh after older in-flight reads settle.

All 175 focused tests, 9,079 UI tests, build, typecheck, exact-head Greptile 5/5, and required CI pass. Regression mutations catch reviewer discovery, duplicate reads, readiness, failure state, missing refreshes, and scope cleanup. Earlier E2E cleanup deadlock and server cleanup timeout remain documented; failed-only reruns passed on their same commits.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-10-25 07:54:47 -07:00

81 lines
3.1 KiB
TypeScript

import { describe, expect, it } from "vitest";
import type { LiveRunForIssue } from "../api/heartbeats";
import { liveRunRefetchInterval, patchRunStatusInList, removeRunFromList } from "./live-runs-cache";
function run(id: string, status: string): LiveRunForIssue {
return {
id,
status,
invocationSource: "automation",
triggerDetail: null,
startedAt: null,
finishedAt: null,
createdAt: "2026-07-15T00:00:00.000Z",
agentId: "agent-1",
agentName: "Agent One",
adapterType: "codex_local",
};
}
describe("removeRunFromList", () => {
it("removes the matching run", () => {
const list = [run("a", "running"), run("b", "running")];
expect(removeRunFromList(list, "a")).toEqual([run("b", "running")]);
});
it("returns the same reference when the run isn't present", () => {
const list = [run("a", "running")];
expect(removeRunFromList(list, "zzz")).toBe(list);
});
it("handles undefined", () => {
expect(removeRunFromList(undefined, "a")).toBeUndefined();
});
});
describe("patchRunStatusInList", () => {
it("updates status in place and reports present", () => {
const list = [run("a", "queued"), run("b", "running")];
const { next, present } = patchRunStatusInList(list, "a", "running");
expect(present).toBe(true);
expect(next?.find((r) => r.id === "a")?.status).toBe("running");
expect(next?.find((r) => r.id === "b")).toBe(list[1]); // untouched entry kept by ref
});
it("returns the same reference and present=false when the run isn't in the list", () => {
const list = [run("a", "running")];
const { next, present } = patchRunStatusInList(list, "new", "running");
expect(present).toBe(false);
expect(next).toBe(list);
});
it("returns the same reference (no re-render) when status is unchanged", () => {
const list = [run("a", "running")];
const { next, present } = patchRunStatusInList(list, "a", "running");
expect(present).toBe(true);
expect(next).toBe(list); // unchanged → original reference preserved
});
it("handles undefined", () => {
const { next, present } = patchRunStatusInList(undefined, "a", "running");
expect(present).toBe(false);
expect(next).toBeUndefined();
});
});
describe("liveRunRefetchInterval", () => {
it.each(["pending", "error"] as const)("keeps polling %s queries with retained empty data", (status) => {
expect(liveRunRefetchInterval(true, { status, data: [] }, 1000)).toBe(1000);
expect(liveRunRefetchInterval(true, { status, data: null }, 3000)).toBe(3000);
});
it.each([null, []])("stops known-empty polling with a connected company for %j", (data) => {
expect(liveRunRefetchInterval(true, { status: "success", data }, 1000)).toBe(false);
expect(liveRunRefetchInterval(false, { status: "success", data }, 1000)).toBe(1000);
});
it.each([undefined, {}, [run("active", "running")], "invalid", 0])("preserves polling for unknown or active data %j", (data) => {
expect(liveRunRefetchInterval(true, { status: "success", data }, 3000)).toBe(3000);
expect(liveRunRefetchInterval(false, { status: "success", data }, 3000)).toBe(3000);
});
});