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) <noreply@runfusion.ai>
This commit is contained in:
@@ -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(
|
||||
<TaskDetailModal
|
||||
initialTab="definition"
|
||||
task={makeTask({ dependencies: ["FN-001"] })}
|
||||
projectId={projectId}
|
||||
task={task}
|
||||
onOpenDetail={onOpenDetail}
|
||||
onClose={noop}
|
||||
onMoveTask={noopMove}
|
||||
@@ -2218,27 +2219,35 @@ describe("TaskDetailModal", () => {
|
||||
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(
|
||||
<TaskDetailModal
|
||||
initialTab="definition"
|
||||
task={makeTask({ dependencies: ["FN-001"] })}
|
||||
projectId={projectId}
|
||||
task={task}
|
||||
onOpenDetail={onOpenDetail}
|
||||
onClose={noop}
|
||||
onMoveTask={noopMove}
|
||||
@@ -2247,27 +2256,36 @@ describe("TaskDetailModal", () => {
|
||||
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(
|
||||
<TaskDetailModal
|
||||
initialTab="definition"
|
||||
task={makeTask({ dependencies: ["FN-001"] })}
|
||||
projectId={projectId}
|
||||
task={task}
|
||||
tasks={[task, makeTask({ id: "FN-100", dependencies: [task.id] })]}
|
||||
onOpenDetail={onOpenDetail}
|
||||
onClose={noop}
|
||||
onMoveTask={noopMove}
|
||||
@@ -2276,16 +2294,52 @@ describe("TaskDetailModal", () => {
|
||||
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<HTMLElement>(".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(
|
||||
<TaskDetailModal
|
||||
initialTab="definition"
|
||||
task={task}
|
||||
onOpenDetail={onOpenDetail}
|
||||
onClose={noop}
|
||||
onMoveTask={noopMove}
|
||||
onDeleteTask={noopDelete}
|
||||
onMergeTask={noopMerge}
|
||||
addToast={noop}
|
||||
/>,
|
||||
);
|
||||
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();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user