Restore Coding (Ideas) task-detail moves to Todo while preserving supplied workflow field definitions. - Resolve selected workflow columns independently for detail move actions - Use workflow-specific primary action labels and add desktop/mobile regression coverage - Add a patch changeset for the restored detail action Files changed: .changeset/fn-8476-coding-ideas-move.md | 7 ++ .../dashboard/app/components/TaskDetailModal.tsx | 85 +++++++++++++------- .../TaskDetailModal.custom-fields.test.tsx | 9 ++- .../__tests__/TaskDetailModal.rendering.test.tsx | 91 ++++++++++++++++++++++ 4 files changed, 160 insertions(+), 32 deletions(-) Fusion-Task-Id: FN-8476 Fusion-Task-Lineage: 37975987-91e4-47f2-9fa4-57b2074391f9 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
120 lines
4.4 KiB
TypeScript
120 lines
4.4 KiB
TypeScript
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
import { render, screen, waitFor } from "@testing-library/react";
|
|
import type { WorkflowFieldDefinition } from "../../api";
|
|
import {
|
|
makeTask,
|
|
noop,
|
|
noopDelete,
|
|
noopMerge,
|
|
noopMove,
|
|
noopOpenDetail,
|
|
setupTaskDetailModalHooks,
|
|
} from "./TaskDetailModal.test-helpers";
|
|
import { TaskDetailModal } from "../TaskDetailModal";
|
|
import * as dashboardApi from "../../api";
|
|
import { FileBrowserProvider } from "../../context/FileBrowserContext";
|
|
|
|
setupTaskDetailModalHooks();
|
|
|
|
function renderModal(task = makeTask({ column: "done" })) {
|
|
return render(
|
|
<FileBrowserProvider openFile={vi.fn()}>
|
|
<TaskDetailModal
|
|
task={task}
|
|
onClose={noop}
|
|
onMoveTask={noopMove}
|
|
onDeleteTask={noopDelete}
|
|
onMergeTask={noopMerge}
|
|
onOpenDetail={noopOpenDetail}
|
|
addToast={noop}
|
|
/>
|
|
</FileBrowserProvider>,
|
|
);
|
|
}
|
|
|
|
describe("TaskDetailModal custom fields (U13/KTD-14)", () => {
|
|
beforeEach(() => vi.clearAllMocks());
|
|
|
|
it("renders no fields section when the workflow declares no fields (today's UI)", async () => {
|
|
vi.spyOn(dashboardApi, "fetchBoardWorkflows").mockResolvedValue({
|
|
flagEnabled: true,
|
|
defaultWorkflowId: "builtin:coding",
|
|
workflows: [{ id: "builtin:coding", name: "Coding", columns: [] }],
|
|
taskWorkflowIds: {},
|
|
});
|
|
renderModal();
|
|
// Allow the field-defs fetch to settle.
|
|
await waitFor(() => expect(dashboardApi.fetchBoardWorkflows).toHaveBeenCalled());
|
|
expect(screen.queryByTestId("task-fields-section")).toBeNull();
|
|
});
|
|
|
|
it("renders the schema-driven fields section when the workflow declares fields", async () => {
|
|
vi.spyOn(dashboardApi, "fetchBoardWorkflows").mockResolvedValue({
|
|
flagEnabled: true,
|
|
defaultWorkflowId: "builtin:coding",
|
|
workflows: [
|
|
{
|
|
id: "builtin:coding",
|
|
name: "Coding",
|
|
columns: [],
|
|
fields: [
|
|
{ id: "owner", name: "Owner", type: "string", render: { placement: "detail" } },
|
|
],
|
|
},
|
|
],
|
|
taskWorkflowIds: { "FN-001": "builtin:coding" },
|
|
});
|
|
renderModal(makeTask({ id: "FN-001", column: "done", customFields: { owner: "alice" } }));
|
|
await waitFor(() => expect(screen.getByTestId("task-fields-section")).toBeTruthy());
|
|
expect((screen.getByLabelText("Owner") as HTMLInputElement).value).toBe("alice");
|
|
});
|
|
|
|
it("uses workflowFieldDefs prop directly while resolving independent move metadata", async () => {
|
|
const fetchSpy = vi.spyOn(dashboardApi, "fetchBoardWorkflows");
|
|
const defs: WorkflowFieldDefinition[] = [
|
|
{ id: "owner", name: "Owner", type: "string", render: { placement: "detail" } },
|
|
];
|
|
render(
|
|
<FileBrowserProvider openFile={vi.fn()}>
|
|
<TaskDetailModal
|
|
task={makeTask({ id: "FN-002", column: "done", customFields: { owner: "bob" } })}
|
|
workflowFieldDefs={defs}
|
|
onClose={noop}
|
|
onMoveTask={noopMove}
|
|
onDeleteTask={noopDelete}
|
|
onMergeTask={noopMerge}
|
|
onOpenDetail={noopOpenDetail}
|
|
addToast={noop}
|
|
/>
|
|
</FileBrowserProvider>,
|
|
);
|
|
await waitFor(() => expect(screen.getByTestId("task-fields-section")).toBeTruthy());
|
|
expect((screen.getByLabelText("Owner") as HTMLInputElement).value).toBe("bob");
|
|
// Supplied fields remain authoritative; the one fetch resolves move columns independently.
|
|
await waitFor(() => expect(fetchSpy).toHaveBeenCalledTimes(1));
|
|
});
|
|
|
|
it("renders no fields section when workflowFieldDefs prop is an empty array", async () => {
|
|
const fetchSpy = vi.spyOn(dashboardApi, "fetchBoardWorkflows");
|
|
render(
|
|
<FileBrowserProvider openFile={vi.fn()}>
|
|
<TaskDetailModal
|
|
task={makeTask({ id: "FN-003", column: "done" })}
|
|
workflowFieldDefs={[]}
|
|
onClose={noop}
|
|
onMoveTask={noopMove}
|
|
onDeleteTask={noopDelete}
|
|
onMergeTask={noopMerge}
|
|
onOpenDetail={noopOpenDetail}
|
|
addToast={noop}
|
|
/>
|
|
</FileBrowserProvider>,
|
|
);
|
|
// Give React a tick to settle; no section should appear.
|
|
await new Promise((r) => setTimeout(r, 50));
|
|
expect(screen.queryByTestId("task-fields-section")).toBeNull();
|
|
// Empty supplied fields still avoid replacement; the lookup is only for move metadata.
|
|
await waitFor(() => expect(fetchSpy).toHaveBeenCalledTimes(1));
|
|
});
|
|
});
|