Files
kandev/apps/web/components/gitlab/mr-merge-button.test.tsx

115 lines
3.2 KiB
TypeScript

import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { cleanup, fireEvent, render, screen } from "@testing-library/react";
import { ToastProvider } from "@/components/toast-provider";
import type { TaskMR } from "@/lib/types/gitlab";
const mergeMR = vi.hoisted(() => vi.fn());
vi.mock("@/components/state-provider", () => ({
useAppStore: (selector: (state: { workspaces: { activeId: string } }) => unknown) =>
selector({ workspaces: { activeId: "ws-1" } }),
}));
vi.mock("@/lib/api/domains/gitlab-api", () => ({ mergeMR }));
import { MRMergeButton } from "./mr-merge-button";
const MERGE_BUTTON_TESTID = "mr-merge-button";
function makeMR(overrides: Partial<TaskMR> = {}): TaskMR {
return {
id: "id",
task_id: "task-1",
host: "https://gitlab.com",
project_path: "group/project",
mr_iid: 7,
mr_url: "",
mr_title: "Test MR",
head_branch: "feature",
base_branch: "main",
author_username: "alice",
state: "open",
approval_state: "",
pipeline_state: "success",
merge_status: "can_be_merged",
draft: false,
approval_count: 0,
required_approvals: 0,
pipeline_jobs_total: 0,
pipeline_jobs_pass: 0,
reviewer_count: 0,
unapproved_reviewers: 0,
unresolved_discussions: 0,
created_at: "",
updated_at: "",
...overrides,
} as TaskMR;
}
function readyMR(overrides: Partial<TaskMR> = {}): TaskMR {
return makeMR({
state: "open",
draft: false,
pipeline_state: "success",
unresolved_discussions: 0,
detailed_merge_status: "mergeable",
...overrides,
});
}
function renderButton(mr: TaskMR) {
return render(
<ToastProvider>
<MRMergeButton mr={mr} />
</ToastProvider>,
);
}
beforeEach(() => {
mergeMR.mockReset();
});
afterEach(() => cleanup());
describe("MRMergeButton", () => {
it("renders nothing when the MR is not ready to merge", () => {
renderButton(makeMR({ pipeline_state: "failed" }));
expect(screen.queryByTestId(MERGE_BUTTON_TESTID)).toBeNull();
});
it("renders the Merge action when the MR is ready", () => {
renderButton(readyMR());
expect(screen.getByTestId(MERGE_BUTTON_TESTID).textContent).toContain("Merge");
});
it("calls mergeMR with the MR's identity on click", async () => {
mergeMR.mockResolvedValue({});
const mr = readyMR({ project_path: "group/project", mr_iid: 42, host: "https://gitlab.com" });
renderButton(mr);
fireEvent.click(screen.getByTestId(MERGE_BUTTON_TESTID));
await vi.waitFor(() => expect(mergeMR).toHaveBeenCalledTimes(1));
expect(mergeMR).toHaveBeenCalledWith({
workspaceId: "ws-1",
project: "group/project",
iid: 42,
host: "https://gitlab.com",
squash: false,
});
});
it("disables the button and shows an in-flight label while merging", async () => {
let resolveMerge: (() => void) | undefined;
mergeMR.mockReturnValue(
new Promise<void>((resolve) => {
resolveMerge = resolve;
}),
);
renderButton(readyMR());
const button = screen.getByTestId(MERGE_BUTTON_TESTID);
fireEvent.click(button);
await vi.waitFor(() => expect(button.textContent).toContain("Merging"));
expect((button as HTMLButtonElement).disabled).toBe(true);
resolveMerge?.();
});
});