import { render, screen, fireEvent, waitFor } from "@testing-library/react"; import { beforeEach, describe, expect, it, vi } from "vitest"; import type { PrInfo } from "@fusion/core"; const prPanelState = vi.hoisted(() => ({ latestPrInfo: undefined as PrInfo | undefined, latestAutoMerge: undefined as boolean | undefined, latestIsManualPrFlow: undefined as boolean | undefined, })); const prCreateModalState = vi.hoisted(() => ({ latestProps: null as any, })); const taskReviewTabState = vi.hoisted(() => ({ latestProps: null as any, })); vi.mock("../PrPanel", () => ({ PrPanel: (props: any) => { prPanelState.latestPrInfo = props.prInfo; prPanelState.latestAutoMerge = props.autoMerge; prPanelState.latestIsManualPrFlow = props.isManualPrFlow; return (
{props.autoMerge ? (
Auto-merge will handle this task automatically.
) : ( )}
{props.prInfo?.number ?? "none"}
); }, })); vi.mock("../PrCreateModal", () => ({ PrCreateModal: (props: any) => { prCreateModalState.latestProps = props; if (!props.open) { return null; } return (
); }, })); vi.mock("../TaskReviewTab", () => ({ TaskReviewTab: (props: any) => { taskReviewTabState.latestProps = props; const effectiveAutoMerge = props.task.autoMerge ?? props.autoMergeEnabled; const showCreatePr = props.task.column === "in-review" && !props.task.prInfo && props.prAuthAvailable === true && !effectiveAutoMerge && typeof props.onRequestCreatePr === "function"; return showCreatePr ? ( ) : null; }, })); import { makeTask, noop, noopDelete, noopMerge, noopMove, noopOpenDetail, setupTaskDetailModalHooks, } from "./TaskDetailModal.test-helpers"; import { fetchSettings } from "../../api"; import { TaskDetailModal } from "../TaskDetailModal"; setupTaskDetailModalHooks(); describe("TaskDetailModal create-PR wiring", () => { beforeEach(() => { vi.clearAllMocks(); prPanelState.latestPrInfo = undefined; prPanelState.latestAutoMerge = undefined; prPanelState.latestIsManualPrFlow = undefined; prCreateModalState.latestProps = null; taskReviewTabState.latestProps = null; }); it("opens PrCreateModal from PrPanel and updates prInfo on create", async () => { const addToast = vi.fn(); const onTaskUpdated = vi.fn(); const task = makeTask({ id: "FN-5020", prInfo: undefined, column: "in-review" }); render( , ); fireEvent.click(screen.getByRole("button", { name: "Pull Request" })); await waitFor(() => { expect(screen.getByRole("button", { name: "Create PR" })).toBeInTheDocument(); }); expect(screen.queryByTestId("pr-create-modal-stub")).toBeNull(); fireEvent.click(screen.getByRole("button", { name: "Create PR" })); expect(screen.getByTestId("pr-create-modal-stub")).toBeInTheDocument(); expect(prCreateModalState.latestProps?.open).toBe(true); expect(prCreateModalState.latestProps?.taskId).toBe("FN-5020"); expect(prCreateModalState.latestProps?.projectId).toBe("project-123"); expect(prCreateModalState.latestProps?.defaultBaseBranch).toBeUndefined(); expect(prCreateModalState.latestProps?.addToast).toBe(addToast); fireEvent.click(screen.getByRole("button", { name: "Stub create" })); expect(screen.queryByTestId("pr-create-modal-stub")).toBeNull(); expect(prCreateModalState.latestProps?.open).toBe(false); expect(screen.getByTestId("pr-panel-pr-number")).toHaveTextContent("321"); expect(prPanelState.latestPrInfo?.number).toBe(321); expect(onTaskUpdated).toHaveBeenCalledWith(expect.objectContaining({ id: "FN-5020", prInfo: expect.objectContaining({ number: 321 }), })); }); it("passes effective auto-merge on to PrPanel for per-task override-on with project default off", async () => { (fetchSettings as ReturnType).mockResolvedValue({ modelPresets: [], autoSelectModelPreset: false, defaultPresetBySize: {}, autoMerge: false, }); render( , ); fireEvent.click(screen.getByRole("button", { name: "Pull Request" })); await waitFor(() => expect(prPanelState.latestAutoMerge).toBe(true)); }); it("passes effective auto-merge off to PrPanel for per-task override-off with project default on", async () => { (fetchSettings as ReturnType).mockResolvedValue({ modelPresets: [], autoSelectModelPreset: false, defaultPresetBySize: {}, autoMerge: true, }); render( , ); fireEvent.click(screen.getByRole("button", { name: "Pull Request" })); await waitFor(() => expect(prPanelState.latestAutoMerge).toBe(false)); }); it("opens the same PrCreateModal from TaskReviewTab without leaving the Review tab and closes via onClose", async () => { (fetchSettings as ReturnType).mockResolvedValue({ modelPresets: [], autoSelectModelPreset: false, defaultPresetBySize: {}, autoMerge: false, }); const task = makeTask({ id: "FN-5021", prInfo: undefined, column: "in-review" }); render( , ); fireEvent.click(screen.getByRole("button", { name: "Review" })); await waitFor(() => expect(screen.getByTestId("task-review-create-pr")).toBeInTheDocument()); expect(screen.queryByTestId("pr-create-modal-stub")).toBeNull(); fireEvent.click(screen.getByTestId("task-review-create-pr")); expect(await screen.findByTestId("pr-create-modal-stub")).toBeInTheDocument(); expect(prCreateModalState.latestProps?.open).toBe(true); expect(prCreateModalState.latestProps?.taskId).toBe("FN-5021"); expect(prCreateModalState.latestProps?.projectId).toBe("project-123"); expect(prCreateModalState.latestProps?.defaultBaseBranch).toBeUndefined(); expect(taskReviewTabState.latestProps?.prAuthAvailable).toBe(true); expect(taskReviewTabState.latestProps?.autoMergeEnabled).toBe(false); expect(screen.getByTestId("task-review-create-pr")).toBeInTheDocument(); fireEvent.click(screen.getByRole("button", { name: "Stub close" })); expect(screen.queryByTestId("pr-create-modal-stub")).toBeNull(); expect(prCreateModalState.latestProps?.open).toBe(false); }); it("prefers live auto-merge off over a stale fetched snapshot for PR surfaces", async () => { (fetchSettings as ReturnType).mockResolvedValue({ modelPresets: [], autoSelectModelPreset: false, defaultPresetBySize: {}, autoMerge: true, }); render( , ); fireEvent.click(screen.getByRole("button", { name: "Pull Request" })); await waitFor(() => expect(prPanelState.latestAutoMerge).toBe(false)); expect(screen.getByRole("button", { name: "Create PR" })).toBeInTheDocument(); expect(screen.queryByText("Auto-merge will handle this task automatically.")).toBeNull(); fireEvent.click(screen.getByRole("button", { name: "Review" })); await waitFor(() => expect(taskReviewTabState.latestProps?.autoMergeEnabled).toBe(false)); expect(screen.getByTestId("task-review-create-pr")).toBeInTheDocument(); }); it("prefers live auto-merge on over a stale fetched snapshot for PR surfaces", async () => { (fetchSettings as ReturnType).mockResolvedValue({ modelPresets: [], autoSelectModelPreset: false, defaultPresetBySize: {}, autoMerge: false, }); render( , ); fireEvent.click(screen.getByRole("button", { name: "Pull Request" })); await waitFor(() => expect(prPanelState.latestAutoMerge).toBe(true)); expect(screen.getByText("Auto-merge will handle this task automatically.")).toBeInTheDocument(); expect(screen.queryByRole("button", { name: "Create PR" })).toBeNull(); fireEvent.click(screen.getByRole("button", { name: "Review" })); await waitFor(() => expect(taskReviewTabState.latestProps?.autoMergeEnabled).toBe(true)); expect(screen.queryByTestId("task-review-create-pr")).toBeNull(); }); it.each([ { taskAutoMerge: undefined, liveAutoMerge: false, expectedEffective: false }, { taskAutoMerge: undefined, liveAutoMerge: true, expectedEffective: true }, { taskAutoMerge: true, liveAutoMerge: false, expectedEffective: true }, { taskAutoMerge: true, liveAutoMerge: true, expectedEffective: true }, { taskAutoMerge: false, liveAutoMerge: false, expectedEffective: false }, { taskAutoMerge: false, liveAutoMerge: true, expectedEffective: false }, ])( "resolves effective auto-merge for task override $taskAutoMerge with live global $liveAutoMerge", async ({ taskAutoMerge, liveAutoMerge, expectedEffective }) => { (fetchSettings as ReturnType).mockResolvedValue({ modelPresets: [], autoSelectModelPreset: false, defaultPresetBySize: {}, autoMerge: !liveAutoMerge, }); render( , ); fireEvent.click(screen.getByRole("button", { name: "Pull Request" })); await waitFor(() => expect(prPanelState.latestAutoMerge).toBe(expectedEffective)); if (expectedEffective) { expect(screen.getByText("Auto-merge will handle this task automatically.")).toBeInTheDocument(); } else { expect(screen.queryByText("Auto-merge will handle this task automatically.")).toBeNull(); expect(screen.getByRole("button", { name: "Create PR" })).toBeInTheDocument(); } fireEvent.click(screen.getByRole("button", { name: "Review" })); await waitFor(() => expect(taskReviewTabState.latestProps?.autoMergeEnabled).toBe(liveAutoMerge)); if (expectedEffective) { expect(screen.queryByTestId("task-review-create-pr")).toBeNull(); } else { expect(screen.getByTestId("task-review-create-pr")).toBeInTheDocument(); } }, ); it("keeps manual PR flow driven by live global auto-merge rather than effective override", async () => { (fetchSettings as ReturnType).mockResolvedValue({ modelPresets: [], autoSelectModelPreset: false, defaultPresetBySize: {}, autoMerge: true, mergeStrategy: "pull-request", }); render( , ); fireEvent.click(screen.getByRole("button", { name: "Pull Request" })); await waitFor(() => expect(prPanelState.latestAutoMerge).toBe(true)); expect(prPanelState.latestIsManualPrFlow).toBe(true); expect(screen.getByText("Auto-merge will handle this task automatically.")).toBeInTheDocument(); }); });