feat(FN-3428): add project-node path mapping APIs and agent detail review-b
Merges FN-3428's branch-field contract and merge target override/default behavior tests across core, dashboard, and engine, plus a companion fix that sweeps subsumed autostash orphans and surfaces outcomes on the task feed. Adds project-node path mapping persistence APIs and schema (FN-3503), new ag Fusion-Task-Id: FN-3428
This commit is contained in:
@@ -457,6 +457,17 @@ describe("updateTask", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("omits branch and baseBranch from update payload when unset", async () => {
|
||||
globalThis.fetch = vi.fn().mockReturnValue(mockFetchResponse(true, { ...FAKE_TASK, title: "Updated" }));
|
||||
|
||||
await updateTask("FN-001", { title: "Updated" });
|
||||
|
||||
const call = vi.mocked(globalThis.fetch).mock.calls[0];
|
||||
const body = JSON.parse((call[1] as RequestInit).body as string);
|
||||
expect(body).not.toHaveProperty("branch");
|
||||
expect(body).not.toHaveProperty("baseBranch");
|
||||
});
|
||||
|
||||
it("sends sourceIssue object when source metadata is provided", async () => {
|
||||
const sourceIssue = {
|
||||
provider: "github",
|
||||
@@ -588,6 +599,17 @@ describe("createTask", () => {
|
||||
expect(body.baseBranch).toBe("main");
|
||||
});
|
||||
|
||||
it("omits branch and baseBranch in create payload when unset", async () => {
|
||||
globalThis.fetch = vi.fn().mockReturnValue(mockFetchResponse(true, FAKE_CREATED_TASK));
|
||||
|
||||
await createTask({ description: "Task without branch fields" });
|
||||
|
||||
const call = vi.mocked(globalThis.fetch).mock.calls[0];
|
||||
const body = JSON.parse((call[1] as RequestInit).body as string);
|
||||
expect(body).not.toHaveProperty("branch");
|
||||
expect(body).not.toHaveProperty("baseBranch");
|
||||
});
|
||||
|
||||
it("serializes nodeId in create payload when execution target is specified", async () => {
|
||||
globalThis.fetch = vi.fn().mockReturnValue(mockFetchResponse(true, {
|
||||
...FAKE_CREATED_TASK,
|
||||
|
||||
@@ -664,6 +664,27 @@ describe("Board", () => {
|
||||
expect(todoTasks.map((t: Task) => t.id).sort()).toEqual(["FN-999", "SEARCH-123"]);
|
||||
});
|
||||
|
||||
it("renders server-filtered branch-target results without additional client filtering", () => {
|
||||
const branchFilteredTasks: Task[] = [
|
||||
createTask({
|
||||
id: "FN-3428",
|
||||
description: "Task targeting release branch",
|
||||
column: "todo",
|
||||
branch: "feature/fn-3428",
|
||||
baseBranch: "release/2026-05",
|
||||
}),
|
||||
];
|
||||
|
||||
renderBoard({ tasks: branchFilteredTasks, searchQuery: "release/2026-05" });
|
||||
|
||||
const todoColumn = screen.getByTestId("column-todo");
|
||||
const todoTasks = JSON.parse(todoColumn.getAttribute("data-tasks") || "[]") as Task[];
|
||||
expect(todoTasks).toHaveLength(1);
|
||||
expect(todoTasks[0]?.id).toBe("FN-3428");
|
||||
expect(todoTasks[0]?.branch).toBe("feature/fn-3428");
|
||||
expect(todoTasks[0]?.baseBranch).toBe("release/2026-05");
|
||||
});
|
||||
|
||||
it("shows all tasks for whitespace-only search query (server treats as empty)", () => {
|
||||
const tasks: Task[] = [
|
||||
createTask({ id: "FN-001", description: "First task", column: "todo" }),
|
||||
|
||||
@@ -235,6 +235,32 @@ describe("TaskCard", () => {
|
||||
expect(screen.queryByText("Branch")).toBeNull();
|
||||
});
|
||||
|
||||
it("renders merge target from task.baseBranch, not prInfo.baseBranch metadata", () => {
|
||||
render(
|
||||
<TaskCard
|
||||
task={makeTask({
|
||||
branch: "fusion/fn-001",
|
||||
baseBranch: "release/task-target",
|
||||
prInfo: {
|
||||
url: "https://github.com/runfusion/fusion/pull/10",
|
||||
number: 10,
|
||||
status: "open",
|
||||
title: "PR title",
|
||||
headBranch: "feature/pr-head",
|
||||
baseBranch: "main",
|
||||
commentCount: 0,
|
||||
},
|
||||
})}
|
||||
onOpenDetail={noop}
|
||||
addToast={noop}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(screen.getByText("Base")).toBeDefined();
|
||||
expect(screen.getByText("release/task-target")).toBeDefined();
|
||||
expect(screen.queryByText("main")).toBeNull();
|
||||
});
|
||||
|
||||
it("shows both chips when branch and base branch are both non-default", () => {
|
||||
const { container } = render(
|
||||
<TaskCard
|
||||
|
||||
@@ -1113,6 +1113,32 @@ describe("TaskDetailModal", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("saves changed baseBranch independently of branch", async () => {
|
||||
const { updateTask } = await import("../../api");
|
||||
const mockUpdate = vi.mocked(updateTask);
|
||||
mockUpdate.mockResolvedValueOnce({ id: "FN-001" } as Task);
|
||||
|
||||
const { container } = render(
|
||||
<TaskDetailModal
|
||||
task={makeTask({ id: "FN-001", column: "todo", branch: "feature/fn-3422", baseBranch: "develop" })}
|
||||
onClose={noop}
|
||||
onMoveTask={noopMove}
|
||||
onDeleteTask={noopDelete}
|
||||
onMergeTask={noopMerge}
|
||||
onOpenDetail={noopOpenDetail}
|
||||
addToast={noop}
|
||||
/>,
|
||||
);
|
||||
|
||||
fireEvent.click(container.querySelector(".modal-edit-btn")!);
|
||||
fireEvent.change(container.querySelector("#task-base-branch") as HTMLInputElement, { target: { value: "release/2026-05" } });
|
||||
fireEvent.click(screen.getByText("Save"));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockUpdate).toHaveBeenCalledWith("FN-001", { baseBranch: "release/2026-05" }, undefined);
|
||||
});
|
||||
});
|
||||
|
||||
it("sends null branch fields when working/base branches are cleared", async () => {
|
||||
const { updateTask } = await import("../../api");
|
||||
const mockUpdate = vi.mocked(updateTask);
|
||||
|
||||
Reference in New Issue
Block a user