import { describe, expect, it, vi } from "vitest"; import { render, screen, waitFor } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; import { makeTask, mockConfirm, noop, noopDelete, noopMerge, noopMove, noopOpenDetail, setupTaskDetailModalHooks } from "./TaskDetailModal.test-helpers"; import { TaskDetailModal } from "../TaskDetailModal"; setupTaskDetailModalHooks(); const projectIssue = { kind: "project_issue" as const, url: "https://gitlab.com/acme/app/-/issues/42", instanceUrl: "https://gitlab.com", host: "gitlab.com", iid: 42, projectPath: "acme/app", title: "Project issue", state: "opened", createdAt: "2026-07-02T00:00:00.000Z", lastSyncedAt: "2026-07-02T00:01:00.000Z", }; function renderModal(task = makeTask({ column: "todo", gitlabTracking: { item: projectIssue } }), onTaskUpdated = vi.fn()) { return render( , ); } describe("TaskDetailModal GitLab tracking", () => { it("renders linked project issue metadata with provider-correct labels and actions", async () => { const user = userEvent.setup(); renderModal(); expect(screen.getByText("GitLab tracking")).toBeInTheDocument(); expect(screen.getByText("Linked")).toBeInTheDocument(); expect(screen.getByRole("link", { name: "Project issue #42" })).toHaveAttribute("href", projectIssue.url); await user.click(screen.getByRole("button", { name: "Expand GitLab tracking details" })); expect(screen.getByText("Kind")).toBeInTheDocument(); expect(screen.getByText("gitlab.com")).toBeInTheDocument(); expect(screen.getByRole("link", { name: "Open linked GitLab item" })).toHaveAttribute("href", projectIssue.url); expect(screen.getByRole("button", { name: "Unlink GitLab item" })).toBeInTheDocument(); expect(screen.queryByText("GitHub tracking")).not.toBeInTheDocument(); }); it("renders group issues, merge requests, stale state, and GitHub coexistence", async () => { const user = userEvent.setup(); const staleGroupIssue = { kind: "group_issue" as const, url: "https://git.example.test/groups/platform/-/issues/9", instanceUrl: "https://git.example.test", host: "git.example.test", iid: 9, groupPath: "platform", title: "Group issue", state: "opened", createdAt: "2026-07-02T00:00:00.000Z", staleAt: "2026-07-02T01:00:00.000Z", staleReason: "GitLab sync failed", }; const task = makeTask({ column: "todo", gitlabTracking: { item: staleGroupIssue }, githubTracking: { enabled: true, repoOverride: "runfusion/fusion" }, }); const { rerender } = renderModal(task); expect(screen.getByText("Stale")).toBeInTheDocument(); expect(screen.getByRole("link", { name: "Group issue #9" })).toBeInTheDocument(); expect(screen.getByText("GitHub tracking")).toBeInTheDocument(); await user.click(screen.getByRole("button", { name: "Expand GitLab tracking details" })); expect(screen.getByText("GitLab sync failed")).toBeInTheDocument(); rerender( , ); expect(screen.getByRole("link", { name: "Merge request !5" })).toBeInTheDocument(); }); it("unlinks after confirmation and does not render empty GitLab shells", async () => { const user = userEvent.setup(); const onTaskUpdated = vi.fn(); const { updateTask } = await import("../../api"); vi.mocked(updateTask).mockResolvedValueOnce(makeTask({ column: "todo", gitlabTracking: { unlinkedAt: "2026-07-02T00:00:00.000Z" } }) as any); mockConfirm.mockResolvedValueOnce(false).mockResolvedValueOnce(true); renderModal(undefined, onTaskUpdated); await user.click(screen.getByRole("button", { name: "Expand GitLab tracking details" })); await user.click(screen.getByRole("button", { name: "Unlink GitLab item" })); expect(updateTask).not.toHaveBeenCalled(); await user.click(screen.getByRole("button", { name: "Unlink GitLab item" })); await waitFor(() => expect(updateTask).toHaveBeenCalledWith("FN-099", { gitlabTracking: { item: null } }, undefined)); expect(onTaskUpdated).toHaveBeenCalled(); }); it("omits GitLab tracking section when metadata is empty", () => { renderModal(makeTask({ column: "todo", gitlabTracking: undefined })); expect(screen.queryByTestId("detail-gitlab-tracking-section")).not.toBeInTheDocument(); expect(screen.queryByLabelText(/GitLab/i)).not.toBeInTheDocument(); }); });