feat(FN-4996): complete Step 2 — add done-task archive-instead flow in TaskCard
Fusion-Task-Id: FN-4996 Fusion-Task-Lineage: 650bb5e4-18f2-41a0-ae46-b9112c4a0d74
This commit is contained in:
committed by
gsxdsm
parent
44562cdf78
commit
4b5c7475f4
@@ -549,7 +549,7 @@ function TaskCardComponent({
|
||||
const sendBackRef = useRef<HTMLDivElement>(null);
|
||||
const [isInViewport, setIsInViewport] = useState(false);
|
||||
const { badgeUpdates, subscribeToBadge, unsubscribeFromBadge } = useBadgeWebSocket(projectId);
|
||||
const { confirm } = useConfirm();
|
||||
const { confirm, confirmWithChoice } = useConfirm();
|
||||
const retryWarningThreshold = useRetryWarning();
|
||||
|
||||
// Touch gesture detection refs
|
||||
@@ -1189,13 +1189,38 @@ function TaskCardComponent({
|
||||
e.stopPropagation();
|
||||
if (!onDeleteTask) return;
|
||||
|
||||
const shouldDelete = await confirm({
|
||||
title: "Delete Task",
|
||||
message: `Delete ${task.id}?`,
|
||||
danger: true,
|
||||
});
|
||||
if (!shouldDelete) {
|
||||
return;
|
||||
if (task.column === "done" && onArchiveTask) {
|
||||
const deleteChoice = await confirmWithChoice({
|
||||
title: "Delete Task",
|
||||
message: `Delete ${task.id}?`,
|
||||
confirmLabel: "Delete",
|
||||
cancelLabel: "Cancel",
|
||||
tertiaryLabel: "Archive Instead",
|
||||
danger: true,
|
||||
});
|
||||
|
||||
if (deleteChoice === "tertiary") {
|
||||
try {
|
||||
await onArchiveTask(task.id);
|
||||
addToast(`Archived ${task.id}`, "success");
|
||||
} catch (err) {
|
||||
addToast(`Failed to archive ${task.id}: ${getErrorMessage(err)}`, "error");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (deleteChoice !== "primary") {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
const shouldDelete = await confirm({
|
||||
title: "Delete Task",
|
||||
message: `Delete ${task.id}?`,
|
||||
danger: true,
|
||||
});
|
||||
if (!shouldDelete) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const trackedIssue = task.githubTracking?.enabled === true ? task.githubTracking.issue : undefined;
|
||||
@@ -1259,7 +1284,7 @@ function TaskCardComponent({
|
||||
addToast(`Failed to delete ${task.id}: ${getErrorMessage(retryErr)}`, "error");
|
||||
}
|
||||
}
|
||||
}, [addToast, confirm, onDeleteTask, task.githubTracking?.enabled, task.githubTracking?.issue, task.id]);
|
||||
}, [addToast, confirm, confirmWithChoice, onArchiveTask, onDeleteTask, task.column, task.githubTracking?.enabled, task.githubTracking?.issue, task.id]);
|
||||
|
||||
const handleOpenFiles = useCallback((e: React.MouseEvent) => {
|
||||
e.stopPropagation();
|
||||
@@ -1614,7 +1639,7 @@ function TaskCardComponent({
|
||||
<Pencil size={12} />
|
||||
</button>
|
||||
)}
|
||||
{task.column === "triage" && onDeleteTask && (
|
||||
{(task.column === "triage" || task.column === "done") && onDeleteTask && (
|
||||
<button
|
||||
className="card-delete-btn"
|
||||
onClick={handleDeleteClick}
|
||||
|
||||
@@ -78,8 +78,9 @@ vi.mock("../../api", () => ({
|
||||
}));
|
||||
|
||||
const mockConfirm = vi.fn<(options: ConfirmOptions) => Promise<boolean>>();
|
||||
const mockConfirmWithChoice = vi.fn<(options: ConfirmOptions) => Promise<"primary" | "tertiary" | "cancel">>();
|
||||
vi.mock("../../hooks/useConfirm", () => ({
|
||||
useConfirm: () => ({ confirm: mockConfirm }),
|
||||
useConfirm: () => ({ confirm: mockConfirm, confirmWithChoice: mockConfirmWithChoice }),
|
||||
}));
|
||||
|
||||
import { uploadAttachment, fetchMission, fetchAgent } from "../../api";
|
||||
@@ -135,6 +136,8 @@ afterEach(() => {
|
||||
badgeUpdatesMock.clear();
|
||||
subscribeToBadgeMock.mockReset();
|
||||
unsubscribeFromBadgeMock.mockReset();
|
||||
mockConfirm.mockReset();
|
||||
mockConfirmWithChoice.mockReset();
|
||||
});
|
||||
|
||||
describe("TaskCard", () => {
|
||||
@@ -268,6 +271,44 @@ describe("TaskCard", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("archives done task when archive-instead is chosen", async () => {
|
||||
const onDeleteTask = vi.fn(async () => makeTask());
|
||||
const onArchiveTask = vi.fn(async () => makeTask({ column: "archived" }));
|
||||
mockConfirmWithChoice.mockResolvedValueOnce("tertiary");
|
||||
|
||||
render(
|
||||
<TaskCard
|
||||
task={makeTask({ column: "done" })}
|
||||
onOpenDetail={noop}
|
||||
addToast={noop}
|
||||
onDeleteTask={onDeleteTask}
|
||||
onArchiveTask={onArchiveTask}
|
||||
/>,
|
||||
);
|
||||
|
||||
await act(async () => {
|
||||
fireEvent.click(screen.getByLabelText("Delete task"));
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(onArchiveTask).toHaveBeenCalledWith("FN-001");
|
||||
expect(onDeleteTask).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
it("keeps two-button delete flow for non-done task", async () => {
|
||||
const onDeleteTask = vi.fn(async () => makeTask());
|
||||
mockConfirm.mockResolvedValueOnce(false);
|
||||
|
||||
render(<TaskCard task={makeTask({ column: "triage" })} onOpenDetail={noop} addToast={noop} onDeleteTask={onDeleteTask} />);
|
||||
|
||||
await act(async () => {
|
||||
fireEvent.click(screen.getByLabelText("Delete task"));
|
||||
});
|
||||
|
||||
expect(mockConfirmWithChoice).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("keeps legacy delete options for untracked task", async () => {
|
||||
const onDeleteTask = vi.fn(async () => makeTask());
|
||||
mockConfirm.mockResolvedValueOnce(true);
|
||||
|
||||
Reference in New Issue
Block a user