diff --git a/apps/web/e2e/tests/kanban/card-menu-delete-archive.spec.ts b/apps/web/e2e/tests/kanban/card-menu-delete-archive.spec.ts index 9cec0783ba..45d28fa834 100644 --- a/apps/web/e2e/tests/kanban/card-menu-delete-archive.spec.ts +++ b/apps/web/e2e/tests/kanban/card-menu-delete-archive.spec.ts @@ -1,6 +1,8 @@ import { test, expect } from "../../fixtures/test-base"; import { KanbanPage } from "../../pages/kanban-page"; +const TASK_VISIBLE_TIMEOUT = 10_000; + // Regression: clicking Delete / Archive must show the confirm dialog, not navigate to the task. test.describe("Kanban card actions menu — delete/archive does not navigate", () => { test("clicking Delete in card dropdown shows confirm dialog and does not navigate", async ({ @@ -81,3 +83,76 @@ test.describe("Kanban card actions menu — delete/archive does not navigate", ( await expect(testPage.getByTestId("task-preview-panel")).not.toBeVisible(); }); }); + +// Regression: in "All Workflows" swimlane view, state.kanban.workflowId is null. +// useTaskCRUD's handleDelete/handleArchive used to early-return on that, so the +// dialog closed but no API call ran and the task stayed on the board. +test.describe("Kanban card actions menu — delete/archive in All Workflows view", () => { + // eslint-disable-next-line @typescript-eslint/no-unused-vars -- testPage is listed to force its fixture (user-settings reset) to run before this hook's API seeding; without it the reset wipes our workflow_filter_id on first use + test.beforeEach(async ({ apiClient, seedData, testPage }) => { + // Need a second workflow so resolveDesiredWorkflowId does not auto-select + // the only visible workflow when filter is null. + await apiClient.createWorkflow(seedData.workspaceId, "Secondary Workflow", "simple"); + await apiClient.saveUserSettings({ + workspace_id: seedData.workspaceId, + workflow_filter_id: "", + repository_ids: [], + }); + }); + + test("confirming Delete removes the task from the board in All Workflows view", async ({ + testPage, + apiClient, + seedData, + }) => { + const task = await apiClient.createTask(seedData.workspaceId, "All-Wf Delete Task", { + workflow_id: seedData.workflowId, + workflow_step_id: seedData.startStepId, + }); + const kanban = new KanbanPage(testPage); + await kanban.goto(); + + await expect(kanban.taskCardByTitle("All-Wf Delete Task")).toBeVisible({ + timeout: TASK_VISIBLE_TIMEOUT, + }); + + await kanban.openTaskActionsMenu(task.id); + await testPage.getByRole("menuitem", { name: "Delete" }).click(); + + const dialog = testPage.getByRole("alertdialog"); + await expect(dialog).toBeVisible(); + await dialog.getByRole("button", { name: "Delete" }).click(); + + await expect(kanban.taskCardByTitle("All-Wf Delete Task")).not.toBeVisible({ + timeout: TASK_VISIBLE_TIMEOUT, + }); + }); + + test("confirming Archive removes the task from the board in All Workflows view", async ({ + testPage, + apiClient, + seedData, + }) => { + const task = await apiClient.createTask(seedData.workspaceId, "All-Wf Archive Task", { + workflow_id: seedData.workflowId, + workflow_step_id: seedData.startStepId, + }); + const kanban = new KanbanPage(testPage); + await kanban.goto(); + + await expect(kanban.taskCardByTitle("All-Wf Archive Task")).toBeVisible({ + timeout: TASK_VISIBLE_TIMEOUT, + }); + + await kanban.openTaskActionsMenu(task.id); + await testPage.getByRole("menuitem", { name: "Archive" }).click(); + + const dialog = testPage.getByRole("alertdialog"); + await expect(dialog).toBeVisible(); + await dialog.getByRole("button", { name: "Archive" }).click(); + + await expect(kanban.taskCardByTitle("All-Wf Archive Task")).not.toBeVisible({ + timeout: TASK_VISIBLE_TIMEOUT, + }); + }); +}); diff --git a/apps/web/hooks/use-task-crud.ts b/apps/web/hooks/use-task-crud.ts index 734e458862..9f38e2a57f 100644 --- a/apps/web/hooks/use-task-crud.ts +++ b/apps/web/hooks/use-task-crud.ts @@ -1,7 +1,7 @@ "use client"; import { useCallback, useState } from "react"; -import { useAppStore, useAppStoreApi } from "@/components/state-provider"; +import { useAppStoreApi } from "@/components/state-provider"; import { useTaskActions } from "@/hooks/use-task-actions"; import type { Task } from "@/components/kanban-card"; import type { KanbanState } from "@/lib/state/slices"; @@ -18,7 +18,6 @@ export function useTaskCRUD() { const [deletingTaskId, setDeletingTaskId] = useState(null); const [archivingTaskId, setArchivingTaskId] = useState(null); const { deleteTaskById, archiveTaskById } = useTaskActions(); - const kanban = useAppStore((state) => state.kanban); const store = useAppStoreApi(); const handleCreate = useCallback(() => { @@ -33,8 +32,6 @@ export function useTaskCRUD() { const handleDelete = useCallback( async (task: Task) => { - if (!kanban.workflowId) return; - setDeletingTaskId(task.id); try { await deleteTaskById(task.id); @@ -52,13 +49,11 @@ export function useTaskCRUD() { setDeletingTaskId(null); } }, - [deleteTaskById, kanban.workflowId, store], + [deleteTaskById, store], ); const handleArchive = useCallback( async (task: Task) => { - if (!kanban.workflowId) return; - setArchivingTaskId(task.id); try { await archiveTaskById(task.id); @@ -76,7 +71,7 @@ export function useTaskCRUD() { setArchivingTaskId(null); } }, - [archiveTaskById, kanban.workflowId, store], + [archiveTaskById, store], ); const handleDialogOpenChange = useCallback((open: boolean) => {