fix(web): allow delete/archive of kanban cards in All Workflows view (#886)

This commit is contained in:
Carlos Florêncio
2026-05-12 19:03:22 +01:00
committed by GitHub
parent 8fee2de145
commit cb338ffa45
2 changed files with 78 additions and 8 deletions
@@ -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,
});
});
});
+3 -8
View File
@@ -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<string | null>(null);
const [archivingTaskId, setArchivingTaskId] = useState<string | null>(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) => {