test(FN-4378): cover delete and leave issue actions in UI flows

Fusion-Task-Id: FN-4378
Fusion-Task-Lineage: d3867ba8-f852-41e3-9db0-584c0ffc23b1
This commit is contained in:
Fusion
2026-05-13 14:01:54 -07:00
committed by gsxdsm
parent 2bbdfe68a4
commit fde918784e
2 changed files with 175 additions and 3 deletions

View File

@@ -123,6 +123,106 @@ describe("TaskCard", () => {
});
});
it("uses githubIssueAction=delete for tracked task delete", async () => {
const onDeleteTask = vi.fn(async () => makeTask());
mockConfirm
.mockResolvedValueOnce(true)
.mockResolvedValueOnce(false)
.mockResolvedValueOnce(true);
render(
<TaskCard
task={makeTask({
column: "triage",
githubTracking: {
enabled: true,
issue: { owner: "owner", repo: "repo", number: 42, url: "https://github.com/owner/repo/issues/42", createdAt: "2026-01-01T00:00:00Z" },
},
} as any)}
onOpenDetail={noop}
addToast={noop}
onDeleteTask={onDeleteTask}
/>,
);
await act(async () => {
fireEvent.click(screen.getByLabelText("Delete task"));
});
await waitFor(() => {
expect(onDeleteTask).toHaveBeenCalledWith("FN-001", { githubIssueAction: "delete" });
});
});
it("uses githubIssueAction=leave for tracked task delete", async () => {
const onDeleteTask = vi.fn(async () => makeTask());
mockConfirm
.mockResolvedValueOnce(true)
.mockResolvedValueOnce(false)
.mockResolvedValueOnce(false);
render(
<TaskCard
task={makeTask({
column: "triage",
githubTracking: {
enabled: true,
issue: { owner: "owner", repo: "repo", number: 42, url: "https://github.com/owner/repo/issues/42", createdAt: "2026-01-01T00:00:00Z" },
},
} as any)}
onOpenDetail={noop}
addToast={noop}
onDeleteTask={onDeleteTask}
/>,
);
await act(async () => {
fireEvent.click(screen.getByLabelText("Delete task"));
});
await waitFor(() => {
expect(onDeleteTask).toHaveBeenCalledWith("FN-001", { githubIssueAction: "leave" });
});
});
it("preserves githubIssueAction on dependency-conflict retry", async () => {
const conflict = new Error("Cannot delete task FN-001: still referenced as a dependency by FN-002.") as Error & { status: number; details: { code: string; dependentIds: string[] } };
conflict.status = 409;
conflict.details = { code: "TASK_HAS_DEPENDENTS", dependentIds: ["FN-002"] };
const onDeleteTask = vi.fn()
.mockRejectedValueOnce(conflict)
.mockResolvedValueOnce(makeTask());
mockConfirm
.mockResolvedValueOnce(true)
.mockResolvedValueOnce(false)
.mockResolvedValueOnce(true)
.mockResolvedValueOnce(true);
render(
<TaskCard
task={makeTask({
column: "triage",
githubTracking: {
enabled: true,
issue: { owner: "owner", repo: "repo", number: 42, url: "https://github.com/owner/repo/issues/42", createdAt: "2026-01-01T00:00:00Z" },
},
} as any)}
onOpenDetail={noop}
addToast={noop}
onDeleteTask={onDeleteTask}
/>,
);
await act(async () => {
fireEvent.click(screen.getByLabelText("Delete task"));
});
await waitFor(() => {
expect(onDeleteTask).toHaveBeenNthCalledWith(2, "FN-001", { removeDependencyReferences: true, githubIssueAction: "delete" });
});
});
it("keeps legacy delete options for untracked task", async () => {
const onDeleteTask = vi.fn(async () => makeTask());
mockConfirm.mockResolvedValueOnce(true);

View File

@@ -128,6 +128,60 @@ describe("TaskDetailModal", () => {
});
});
it("passes githubIssueAction=delete for tracked tasks", async () => {
const onDeleteTask = vi.fn().mockResolvedValue({} as Task);
mockConfirm
.mockResolvedValueOnce(true)
.mockResolvedValueOnce(false)
.mockResolvedValueOnce(true);
render(
<TaskDetailModal
task={makeTask({ githubTracking: { enabled: true, issue: { owner: "owner", repo: "repo", number: 42, url: "https://github.com/owner/repo/issues/42", createdAt: "2026-01-01T00:00:00.000Z" } } })}
onClose={noop}
onMoveTask={noopMove}
onDeleteTask={onDeleteTask}
onMergeTask={noopMerge}
onOpenDetail={noopOpenDetail}
addToast={noop}
/>,
);
fireEvent.click(screen.getByRole("button", { name: /actions/i }));
fireEvent.click(screen.getByRole("menuitem", { name: "Delete" }));
await waitFor(() => {
expect(onDeleteTask).toHaveBeenCalledWith("FN-099", { githubIssueAction: "delete" });
});
});
it("passes githubIssueAction=leave for tracked tasks", async () => {
const onDeleteTask = vi.fn().mockResolvedValue({} as Task);
mockConfirm
.mockResolvedValueOnce(true)
.mockResolvedValueOnce(false)
.mockResolvedValueOnce(false);
render(
<TaskDetailModal
task={makeTask({ githubTracking: { enabled: true, issue: { owner: "owner", repo: "repo", number: 42, url: "https://github.com/owner/repo/issues/42", createdAt: "2026-01-01T00:00:00.000Z" } } })}
onClose={noop}
onMoveTask={noopMove}
onDeleteTask={onDeleteTask}
onMergeTask={noopMerge}
onOpenDetail={noopOpenDetail}
addToast={noop}
/>,
);
fireEvent.click(screen.getByRole("button", { name: /actions/i }));
fireEvent.click(screen.getByRole("menuitem", { name: "Delete" }));
await waitFor(() => {
expect(onDeleteTask).toHaveBeenCalledWith("FN-099", { githubIssueAction: "leave" });
});
});
it("keeps legacy delete payload for untracked tasks", async () => {
const onDeleteTask = vi.fn().mockResolvedValue({} as Task);
mockConfirm.mockResolvedValueOnce(true);
@@ -165,12 +219,14 @@ describe("TaskDetailModal", () => {
.mockResolvedValueOnce({} as Task);
mockConfirm
.mockResolvedValueOnce(true)
.mockResolvedValueOnce(false)
.mockResolvedValueOnce(true)
.mockResolvedValueOnce(true);
render(
<TaskDetailModal
task={makeTask()}
task={makeTask({ githubTracking: { enabled: true, issue: { owner: "owner", repo: "repo", number: 42, url: "https://github.com/owner/repo/issues/42", createdAt: "2026-01-01T00:00:00.000Z" } } })}
onClose={noop}
onMoveTask={noopMove}
onDeleteTask={onDeleteTask}
@@ -190,6 +246,19 @@ describe("TaskDetailModal", () => {
danger: true,
});
expect(mockConfirm).toHaveBeenNthCalledWith(2, {
title: "Linked GitHub Issue",
message: "Choose what to do with owner/repo#42 when deleting FN-099.\n\nClose the issue?",
confirmLabel: "Close Issue",
cancelLabel: "More Options",
});
expect(mockConfirm).toHaveBeenNthCalledWith(3, {
title: "Delete Linked GitHub Issue",
message: "Delete owner/repo#42 on GitHub, or leave it unchanged?",
confirmLabel: "Delete Issue",
cancelLabel: "Leave Unchanged",
danger: true,
});
expect(mockConfirm).toHaveBeenNthCalledWith(4, {
title: "Force Delete Task",
message: "FN-099 is a dependency of FN-100, FN-101.\n\nDelete anyway by removing these dependency references first?",
danger: true,
@@ -197,8 +266,11 @@ describe("TaskDetailModal", () => {
});
await waitFor(() => {
expect(onDeleteTask).toHaveBeenNthCalledWith(1, "FN-099");
expect(onDeleteTask).toHaveBeenNthCalledWith(2, "FN-099", { removeDependencyReferences: true });
expect(onDeleteTask).toHaveBeenNthCalledWith(1, "FN-099", { githubIssueAction: "delete" });
expect(onDeleteTask).toHaveBeenNthCalledWith(2, "FN-099", {
removeDependencyReferences: true,
githubIssueAction: "delete",
});
expect(noop).toHaveBeenCalledWith("Deleted FN-099 after removing dependency references", "info");
});
});