e2c076c71c
* feat(web): improve mobile task and kanban navigation * feat(web): refine mobile task navigation * fix(web): address mobile navigation review feedback * test(web): cover task title field modes * fix(web): keep mobile board navigator open across workflows * fix(web): address mobile workflow review feedback
106 lines
3.5 KiB
TypeScript
106 lines
3.5 KiB
TypeScript
import { act, renderHook } from "@testing-library/react";
|
|
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
import { useTaskWorkflowMove } from "@/hooks/use-task-workflow-move";
|
|
import { bulkMoveSelectedTasks } from "@/lib/api";
|
|
|
|
const mockToast = vi.fn();
|
|
|
|
vi.mock("@/components/toast-provider", () => ({
|
|
useToast: () => ({ toast: mockToast }),
|
|
}));
|
|
|
|
vi.mock("@/lib/api", () => ({
|
|
bulkMoveSelectedTasks: vi.fn(),
|
|
}));
|
|
|
|
const mockBulkMoveSelectedTasks = vi.mocked(bulkMoveSelectedTasks);
|
|
const TASK_1 = "task-1";
|
|
const TASK_2 = "task-2";
|
|
const TARGET_WORKFLOW_ID = "wf-2";
|
|
const TARGET_STEP_ID = "step-2";
|
|
|
|
describe("useTaskWorkflowMove", () => {
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
});
|
|
|
|
it("dedupes and filters task ids before moving", async () => {
|
|
mockBulkMoveSelectedTasks.mockResolvedValue({ moved_count: 2 });
|
|
const { result } = renderHook(() => useTaskWorkflowMove());
|
|
|
|
await act(async () => {
|
|
await result.current([TASK_1, "", TASK_1, TASK_2], TARGET_WORKFLOW_ID, TARGET_STEP_ID);
|
|
});
|
|
|
|
expect(mockBulkMoveSelectedTasks).toHaveBeenCalledWith({
|
|
task_ids: [TASK_1, TASK_2],
|
|
target_workflow_id: TARGET_WORKFLOW_ID,
|
|
target_step_id: TARGET_STEP_ID,
|
|
});
|
|
});
|
|
|
|
it("does nothing for an empty task id list", async () => {
|
|
const { result } = renderHook(() => useTaskWorkflowMove());
|
|
|
|
await act(async () => {
|
|
await result.current([""], TARGET_WORKFLOW_ID, TARGET_STEP_ID);
|
|
});
|
|
|
|
expect(mockBulkMoveSelectedTasks).not.toHaveBeenCalled();
|
|
expect(mockToast).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("shows workflow and same-workflow step success messages", async () => {
|
|
mockBulkMoveSelectedTasks
|
|
.mockResolvedValueOnce({ moved_count: 1 })
|
|
.mockResolvedValueOnce({ moved_count: 2 })
|
|
.mockResolvedValueOnce({ moved_count: 0 })
|
|
.mockResolvedValueOnce({ moved_count: 1 });
|
|
const { result } = renderHook(() => useTaskWorkflowMove());
|
|
|
|
await act(async () => {
|
|
await result.current([TASK_1], TARGET_WORKFLOW_ID, TARGET_STEP_ID);
|
|
await result.current([TASK_1, TASK_2], TARGET_WORKFLOW_ID, TARGET_STEP_ID);
|
|
await result.current([TASK_1], TARGET_WORKFLOW_ID, TARGET_STEP_ID);
|
|
await result.current([TASK_1], TARGET_WORKFLOW_ID, TARGET_STEP_ID, "step");
|
|
});
|
|
|
|
expect(mockToast).toHaveBeenNthCalledWith(1, {
|
|
title: "Moved task to workflow",
|
|
description: "Switch to the destination workflow to see it.",
|
|
variant: "success",
|
|
});
|
|
expect(mockToast).toHaveBeenNthCalledWith(2, {
|
|
title: "Moved 2 tasks to workflow",
|
|
description: "Switch to the destination workflow to see them.",
|
|
variant: "success",
|
|
});
|
|
expect(mockToast).toHaveBeenNthCalledWith(3, {
|
|
title: "Moved 0 tasks to workflow",
|
|
description: "Switch to the destination workflow to see them.",
|
|
variant: "success",
|
|
});
|
|
expect(mockToast).toHaveBeenNthCalledWith(4, {
|
|
title: "Moved task to step",
|
|
description: "The task is now in the selected step.",
|
|
variant: "success",
|
|
});
|
|
});
|
|
|
|
it("shows an error toast and rethrows move failures", async () => {
|
|
const error = new Error("cannot move running task");
|
|
mockBulkMoveSelectedTasks.mockRejectedValue(error);
|
|
const { result } = renderHook(() => useTaskWorkflowMove());
|
|
|
|
await expect(result.current([TASK_1], TARGET_WORKFLOW_ID, TARGET_STEP_ID)).rejects.toThrow(
|
|
error,
|
|
);
|
|
|
|
expect(mockToast).toHaveBeenCalledWith({
|
|
title: "Failed to move task",
|
|
description: "cannot move running task",
|
|
variant: "error",
|
|
});
|
|
});
|
|
});
|