Files
kandev/apps/web/hooks/use-task-management-flow.test.ts
T
2026-09-11 02:51:46 +01:00

114 lines
4.0 KiB
TypeScript

import { act, renderHook } from "@testing-library/react";
import { useStore } from "zustand";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { createAppStore, type AppState } from "@/lib/state/store";
import { useTaskManagementFlow } from "./use-task-management-flow";
let store: ReturnType<typeof createAppStore>;
vi.mock("@/components/state-provider", () => ({
useAppStoreApi: () => store,
useAppStore: (selector: (state: AppState) => unknown) => useStore(store, selector),
}));
beforeEach(() => {
store = createAppStore();
store.setState((state) => ({
workspaces: { ...state.workspaces, activeId: "workspace" },
workflows: {
...state.workflows,
items: [{ id: "workflow", name: "Flow", workspaceId: "workspace" }],
},
kanbanMulti: {
...state.kanbanMulti,
snapshots: {
workflow: {
workflowId: "workflow",
workflowName: "Flow",
steps: [],
tasks: ["A", "B"].map((id) => ({
id,
title: id,
workflowId: "workflow",
workflowStepId: "step",
position: 0,
})),
},
},
},
}));
});
describe("captured task flow", () => {
// @covers AC-TASKS-THREADS-ACTIONS-001.3
it.each([true, false])(
"uses hydrated active steps only when the multi-workflow snapshot is a placeholder (%s)",
(isPlaceholder) => {
const steps = [{ id: "next", title: "Next", position: 1, color: "" }];
store.setState((state) => ({
kanban: { ...state.kanban, workflowId: "workflow", steps },
kanbanMulti: {
...state.kanbanMulti,
snapshots: {
workflow: { ...state.kanbanMulti.snapshots.workflow, isPlaceholder },
},
},
}));
const { result } = renderHook(() => useTaskManagementFlow());
act(() => result.current.open("A"));
expect(result.current.stepsByWorkflowId.workflow).toEqual(isPlaceholder ? steps : []);
},
);
it("does not borrow another workflow's steps for a placeholder", () => {
store.setState((state) => ({
kanban: {
...state.kanban,
workflowId: "other",
steps: [{ id: "other-step", title: "Other", position: 0, color: "" }],
},
kanbanMulti: {
...state.kanbanMulti,
snapshots: {
workflow: { ...state.kanbanMulti.snapshots.workflow, isPlaceholder: true },
},
},
}));
const { result } = renderHook(() => useTaskManagementFlow());
act(() => result.current.open("A"));
expect(result.current.stepsByWorkflowId.workflow).toEqual([]);
});
it("does not revive a dismissed flow when the previous workspace becomes active again", () => {
const { result } = renderHook(() => useTaskManagementFlow());
act(() => result.current.open("A"));
act(() =>
store.setState((state) => ({ workspaces: { ...state.workspaces, activeId: "elsewhere" } })),
);
expect(result.current.stage).toBe("closed");
act(() =>
store.setState((state) => ({ workspaces: { ...state.workspaces, activeId: "workspace" } })),
);
expect(result.current.stage).toBe("closed");
});
// @covers AC-TASKS-THREADS-ACTIONS-002.1, AC-TASKS-THREADS-ACTIONS-002.2
it("keeps A through selection and session changes until another menu explicitly opens", () => {
const { result } = renderHook(() => useTaskManagementFlow());
act(() => {
result.current.open("A");
});
act(() => {
store.getState().setActiveSession("B", "session-B");
});
expect(result.current.identity).toEqual({ taskId: "A", workspaceId: "workspace" });
act(() => {
result.current.open("B");
});
expect(result.current.identity?.taskId).toBe("B");
});
// @covers AC-TASKS-THREADS-ACTIONS-002.3
it("does not open a menu for an unresolved target", () => {
const { result } = renderHook(() => useTaskManagementFlow());
act(() => {
result.current.open("missing");
});
expect(result.current.identity).toBeNull();
});
});