From 338caa89b7319cf378e1a4cab3b7c5176c1d457d Mon Sep 17 00:00:00 2001 From: gsxdsm Date: Mon, 3 Aug 2026 12:56:23 -0700 Subject: [PATCH] FN-8767: strengthen dependency-link test assertions Strengthen TaskDetailModal dependency-link coverage with scoped fetch and navigation assertions. - Verify dependency requests retain project scope after the initial modal load. - Cover successful, failed, keyboard-activated, and removal-click dependency interactions. Files changed: ...etailModal.responsive-and-dependencies.test.tsx | 108 +++++++++++++++------ 1 file changed, 81 insertions(+), 27 deletions(-) Fusion-Task-Id: FN-8767 Fusion-Task-Lineage: 656b40de-2e20-481b-b23e-51fa786b05b6 Co-authored-by: Fusion (runfusion.ai) --- ...Modal.responsive-and-dependencies.test.tsx | 110 +++++++++++++----- 1 file changed, 82 insertions(+), 28 deletions(-) diff --git a/packages/dashboard/app/components/__tests__/TaskDetailModal.responsive-and-dependencies.test.tsx b/packages/dashboard/app/components/__tests__/TaskDetailModal.responsive-and-dependencies.test.tsx index 829c3f6266..d6da9ad694 100644 --- a/packages/dashboard/app/components/__tests__/TaskDetailModal.responsive-and-dependencies.test.tsx +++ b/packages/dashboard/app/components/__tests__/TaskDetailModal.responsive-and-dependencies.test.tsx @@ -2195,21 +2195,22 @@ describe("TaskDetailModal", () => { expect(depLink.getAttribute("title")).toContain("FN-001"); }); - it("calls fetchTaskDetail and onOpenDetail when clicking a dependency", async () => { + it("calls fetchTaskDetail with project scope and opens the exact fetched dependency", async () => { const { fetchTaskDetail } = await import("../../api"); const mockFetch = vi.mocked(fetchTaskDetail); - const mockDetail: TaskDetail = { - ...makeTask({ id: "FN-001", description: "Dep 1" }), - prompt: "", - attachments: [], - }; - mockFetch.mockResolvedValueOnce(mockDetail); + const projectId = "project-dependencies"; + const task = makeTask({ dependencies: ["FN-001"] }); + const mockDetail = makeTask({ id: "FN-001", description: "Dep 1" }); const onOpenDetail = vi.fn(); + // The modal fetches its own detail on mount; settle and clear that request before exercising the link. + mockFetch.mockReset(); + mockFetch.mockResolvedValue(task); const { baseElement: container } = render( { addToast={noop} />, ); + await waitFor(() => expect(mockFetch).toHaveBeenCalledWith("FN-099", projectId)); + mockFetch.mockClear(); + mockFetch.mockResolvedValue(mockDetail); - const depLink = container.querySelector(".detail-dep-link")!; - fireEvent.click(depLink); + fireEvent.click(container.querySelector(".detail-dep-link")!); await waitFor(() => { - expect(mockFetch).toHaveBeenCalledWith("FN-001", undefined); - expect(onOpenDetail).toHaveBeenCalledWith(mockDetail); + expect(mockFetch).toHaveBeenCalledTimes(1); + expect(mockFetch).toHaveBeenCalledWith("FN-001", projectId); + expect(onOpenDetail).toHaveBeenCalledExactlyOnceWith(mockDetail); }); }); - it("shows error toast when dependency fetch fails", async () => { + it("shows exactly one error toast and never navigates when dependency fetch fails", async () => { const { fetchTaskDetail } = await import("../../api"); const mockFetch = vi.mocked(fetchTaskDetail); - mockFetch.mockRejectedValueOnce(new Error("Task not found")); + const projectId = "project-dependencies"; + const task = makeTask({ dependencies: ["FN-001"] }); const onOpenDetail = vi.fn(); const addToast = vi.fn(); + // Isolate the rejected link request from the modal's initial detail load. + mockFetch.mockReset(); + mockFetch.mockResolvedValue(task); const { baseElement: container } = render( { addToast={addToast} />, ); + await waitFor(() => expect(mockFetch).toHaveBeenCalledWith("FN-099", projectId)); + mockFetch.mockClear(); + mockFetch.mockRejectedValueOnce(new Error("Task not found")); - const depLink = container.querySelector(".detail-dep-link")!; - fireEvent.click(depLink); + fireEvent.click(container.querySelector(".detail-dep-link")!); await waitFor(() => { - expect(addToast).toHaveBeenCalledWith("Failed to load dependency FN-001", "error"); + expect(mockFetch).toHaveBeenCalledExactlyOnceWith("FN-001", projectId); + expect(addToast).toHaveBeenCalledExactlyOnceWith("Failed to load dependency FN-001", "error"); }); expect(onOpenDetail).not.toHaveBeenCalled(); }); - it("remove button click does not trigger dependency click", async () => { - const { updateTask } = await import("../../api"); + it("activates upstream and blocking-dependent links with Enter and Space", async () => { const { fetchTaskDetail } = await import("../../api"); const mockFetch = vi.mocked(fetchTaskDetail); - mockFetch.mockRejectedValueOnce(new Error("Should not be called")); + const projectId = "project-dependencies"; + const task = makeTask({ dependencies: ["FN-001"] }); + const upstreamDetail = makeTask({ id: "FN-001", description: "Upstream dependency" }); + const blockingDetail = makeTask({ id: "FN-100", description: "Blocking dependent" }); const onOpenDetail = vi.fn(); - render( + mockFetch.mockReset(); + mockFetch.mockResolvedValue(task); + const { baseElement: container } = render( { addToast={noop} />, ); + await waitFor(() => expect(mockFetch).toHaveBeenCalledWith("FN-099", projectId)); + mockFetch.mockClear(); + mockFetch.mockImplementation(async (id) => id === "FN-001" ? upstreamDetail : blockingDetail); - const removeButton = screen.getByTitle(/Remove dependency/); - fireEvent.click(removeButton); + const [upstreamLink, blockingLink] = Array.from(container.querySelectorAll(".detail-dep-link")); + fireEvent.keyDown(upstreamLink, { key: "Enter" }); + fireEvent.keyDown(blockingLink, { key: " " }); + + await waitFor(() => { + expect(mockFetch).toHaveBeenNthCalledWith(1, "FN-001", projectId); + expect(mockFetch).toHaveBeenNthCalledWith(2, "FN-100", projectId); + expect(onOpenDetail).toHaveBeenNthCalledWith(1, upstreamDetail); + expect(onOpenDetail).toHaveBeenNthCalledWith(2, blockingDetail); + }); + }); + + it("remove button click does not fetch or open a dependency", async () => { + const { updateTask, fetchTaskDetail } = await import("../../api"); + const mockFetch = vi.mocked(fetchTaskDetail); + const task = makeTask({ dependencies: ["FN-001"] }); + const onOpenDetail = vi.fn(); + + mockFetch.mockReset(); + mockFetch.mockResolvedValue(task); + render( + , + ); + await waitFor(() => expect(mockFetch).toHaveBeenCalledWith("FN-099", undefined)); + mockFetch.mockClear(); + + fireEvent.click(screen.getByTitle(/Remove dependency/)); - // onOpenDetail should not be called when clicking remove - expect(onOpenDetail).not.toHaveBeenCalled(); - // updateTask should be called to remove the dependency await waitFor(() => { expect(updateTask).toHaveBeenCalledWith("FN-099", { dependencies: [] }, undefined); }); + expect(mockFetch).not.toHaveBeenCalled(); + expect(onOpenDetail).not.toHaveBeenCalled(); }); });