fix(FN-2020): prevent duplicate retry toasts

- Remove the pre-retry info toast from TaskDetailModal retry handling
- Keep a single completion toast with updated success copy: "Retried {taskId}"
- Add regression tests to assert exactly one toast is shown on retry success
- Add regression tests to assert exactly one toast is shown on retry failure
This commit is contained in:
Fusion
2026-04-17 15:31:59 -07:00
committed by gsxdsm
parent a054db8859
commit d8f3cb065a
2 changed files with 77 additions and 2 deletions

View File

@@ -716,10 +716,9 @@ export function TaskDetailModal({
const handleRetry = useCallback(() => {
if (!onRetryTask) return;
onClose();
addToast(`Retrying ${task.id}...`, "info");
onRetryTask(task.id)
.then(() => {
addToast(`Retry started for ${task.id}`, "success");
addToast(`Retried ${task.id}`, "success");
})
.catch((err: any) => {
addToast(err.message, "error");

View File

@@ -337,6 +337,82 @@ describe("TaskDetailModal", () => {
expect(onRetryTask).toHaveBeenCalledWith("FN-099");
});
it("shows exactly one success toast when retry succeeds", async () => {
const onClose = vi.fn();
const onRetryTask = vi.fn(async () => ({}) as Task);
const addToast = vi.fn();
render(
<TaskDetailModal
task={makeTask({ column: "in-review", status: "failed" })}
onClose={onClose}
onMoveTask={noopMove}
onDeleteTask={noopDelete}
onMergeTask={noopMerge}
onOpenDetail={noopOpenDetail}
onRetryTask={onRetryTask}
addToast={addToast}
/>,
);
// Open Actions dropdown and click Retry
const actionsBtn = screen.getByRole("button", { name: /actions/i });
await act(async () => {
fireEvent.click(actionsBtn);
});
const retryBtn = screen.getByRole("menuitem", { name: "Retry" });
await act(async () => {
fireEvent.click(retryBtn);
});
// Wait for the promise to resolve
await act(async () => {});
// Only one toast — the success toast, no info toast
expect(addToast).toHaveBeenCalledTimes(1);
expect(addToast).toHaveBeenCalledWith("Retried FN-099", "success");
});
it("shows exactly one error toast when retry fails", async () => {
const onClose = vi.fn();
const onRetryTask = vi.fn(async () => {
throw new Error("Server error");
});
const addToast = vi.fn();
render(
<TaskDetailModal
task={makeTask({ column: "in-review", status: "failed" })}
onClose={onClose}
onMoveTask={noopMove}
onDeleteTask={noopDelete}
onMergeTask={noopMerge}
onOpenDetail={noopOpenDetail}
onRetryTask={onRetryTask}
addToast={addToast}
/>,
);
// Open Actions dropdown and click Retry
const actionsBtn = screen.getByRole("button", { name: /actions/i });
await act(async () => {
fireEvent.click(actionsBtn);
});
const retryBtn = screen.getByRole("menuitem", { name: "Retry" });
await act(async () => {
fireEvent.click(retryBtn);
});
// Wait for the promise to reject
await act(async () => {});
// Only one toast — the error toast
expect(addToast).toHaveBeenCalledTimes(1);
expect(addToast).toHaveBeenCalledWith("Server error", "error");
});
it("shows 'Move to Todo' in Move dropdown for in-review tasks (not 'Retry')", () => {
render(
<TaskDetailModal