diff --git a/packages/core/issues/queries.test.ts b/packages/core/issues/queries.test.ts index 7029d87449..afa0d2cae6 100644 --- a/packages/core/issues/queries.test.ts +++ b/packages/core/issues/queries.test.ts @@ -1,5 +1,5 @@ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; -import { QueryClient } from "@tanstack/react-query"; +import { QueryClient, QueryObserver } from "@tanstack/react-query"; import { setApiInstance } from "../api"; import type { ApiClient } from "../api/client"; @@ -15,6 +15,7 @@ import { PROJECT_GANTT_MAX_ISSUES, PROJECT_GANTT_PAGE_LIMIT, childrenByParentsOptions, + childIssuesOptions, compareIssuesForSort, issueFlatExportOptions, issueFlatListOptions, @@ -66,6 +67,12 @@ function installFakeChildrenApi( setApiInstance({ listChildrenByParents } as unknown as ApiClient); } +function installFakeChildApi( + listChildIssues: (parentId: string) => Promise<{ issues: Issue[] }>, +) { + setApiInstance({ listChildIssues } as unknown as ApiClient); +} + function installFakeSearchApi( searchIssues: (params: { q: string }) => Promise, ) { @@ -76,6 +83,36 @@ function makeSearchResult(idx: number, identifier: string) { return { ...makeIssue(idx), identifier, match_source: "title" as const }; } +describe("childIssuesOptions", () => { + it("refetches a cached snapshot when the parent issue is opened again", async () => { + const qc = new QueryClient({ + defaultOptions: { queries: { retry: false, staleTime: Infinity } }, + }); + const parentId = "parent-1"; + const oldChild = makeIssue(1, { parent_issue_id: parentId }); + const newChild = makeIssue(2, { parent_issue_id: parentId }); + const listChildIssues = vi.fn().mockResolvedValue({ + issues: [oldChild, newChild], + }); + installFakeChildApi(listChildIssues); + qc.setQueryData(issueKeys.children(WS_ID, parentId), [oldChild]); + + const observer = new QueryObserver( + qc, + childIssuesOptions(WS_ID, parentId), + ); + const unsubscribe = observer.subscribe(() => {}); + + await vi.waitFor(() => { + expect(listChildIssues).toHaveBeenCalledWith(parentId); + expect(observer.getCurrentResult().data).toEqual([oldChild, newChild]); + }); + + unsubscribe(); + qc.clear(); + }); +}); + describe("projectGanttIssuesOptions", () => { let qc: QueryClient; diff --git a/packages/core/issues/queries.ts b/packages/core/issues/queries.ts index 9860c9c3ec..9b41bdf09f 100644 --- a/packages/core/issues/queries.ts +++ b/packages/core/issues/queries.ts @@ -674,6 +674,12 @@ export function childIssuesOptions(wsId: string, id: string) { return queryOptions({ queryKey: issueKeys.children(wsId, id), queryFn: () => api.listChildIssues(id).then((r) => r.issues), + // Child creation can happen while this workspace is not the active + // realtime subscription (for example, an agent creates it while a + // desktop tab is showing another workspace). The global Infinity + // staleTime would otherwise reuse an incomplete children snapshot when + // the parent is opened again, with no later event guaranteed to heal it. + refetchOnMount: "always", }); }