Files
multica/packages/views/issues/components/pull-request-list.test.tsx
Bohan Jiang 8d18d3a9ec Revert "MUL-5180: fix(github): surface CI status on PR cards (#5811)" (#5855)
This reverts commit 139cc89200.

Co-authored-by: Bohan-J <bohan@devv.ai>
Co-authored-by: multica-agent <github@multica.ai>
2026-07-24 02:52:22 +08:00

212 lines
7.4 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { describe, expect, it, vi } from "vitest";
import { render, screen } from "@testing-library/react";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { I18nProvider } from "@multica/core/i18n/react";
import type { GitHubPullRequest } from "@multica/core/types";
import enCommon from "../../locales/en/common.json";
import enIssues from "../../locales/en/issues.json";
const TEST_RESOURCES = { en: { common: enCommon, issues: enIssues } };
vi.mock("@multica/core/github/queries", async () => {
const actual = await vi.importActual<typeof import("@multica/core/github/queries")>(
"@multica/core/github/queries",
);
return {
...actual,
issuePullRequestsOptions: (issueId: string) => ({
queryKey: ["github", "pull-requests", issueId],
queryFn: async () => ({ pull_requests: mockPRs }),
enabled: !!issueId,
}),
};
});
import { PullRequestList } from "./pull-request-list";
let mockPRs: GitHubPullRequest[] = [];
function makePR(overrides: Partial<GitHubPullRequest> = {}): GitHubPullRequest {
return {
id: "pr-1",
workspace_id: "ws-1",
repo_owner: "acme",
repo_name: "widget",
number: 1,
title: "Test PR",
state: "open",
html_url: "https://example.test/pr/1",
branch: "feat/x",
author_login: "octocat",
author_avatar_url: null,
merged_at: null,
closed_at: null,
pr_created_at: "2026-01-01T00:00:00Z",
pr_updated_at: "2026-01-01T00:00:00Z",
mergeable_state: null,
checks_conclusion: null,
checks_passed: 0,
checks_failed: 0,
checks_pending: 0,
additions: 0,
deletions: 0,
changed_files: 0,
...overrides,
};
}
function renderList() {
const qc = new QueryClient({ defaultOptions: { queries: { retry: false } } });
return render(
<QueryClientProvider client={qc}>
<I18nProvider resources={TEST_RESOURCES} locale="en">
<PullRequestList issueId="issue-1" />
</I18nProvider>
</QueryClientProvider>,
);
}
async function waitForRender() {
return screen.findAllByRole("link");
}
describe("PullRequestList sidebar rows", () => {
it("uses the sidebar list-row surface instead of a card surface", async () => {
mockPRs = [makePR({ title: "Visual row" })];
renderList();
await waitForRender();
const row = screen.getByTestId("pull-request-row");
expect(row).toHaveClass("rounded-md", "-mx-2", "hover:bg-accent/50");
expect(row).not.toHaveClass("rounded-lg", "border", "bg-card");
});
it("renders All-checks-passed status when only passed counts are non-zero", async () => {
mockPRs = [makePR({ checks_passed: 3 })];
renderList();
await waitForRender();
expect(screen.getByText("All checks passed")).toBeInTheDocument();
});
it("renders Some-checks-failed when any failed count is non-zero", async () => {
mockPRs = [makePR({ checks_failed: 1, checks_passed: 5 })];
renderList();
await waitForRender();
expect(screen.getByText("Some checks failed")).toBeInTheDocument();
});
it("renders pending status when only pending suites remain", async () => {
mockPRs = [makePR({ checks_pending: 2, checks_passed: 1 })];
renderList();
await waitForRender();
expect(screen.getByText("Some checks haven't completed yet")).toBeInTheDocument();
});
it("renders conflicts status when mergeable_state=dirty", async () => {
mockPRs = [makePR({ mergeable_state: "dirty" })];
renderList();
await waitForRender();
expect(screen.getByText("Has merge conflicts")).toBeInTheDocument();
});
it("renders Ready-to-merge when mergeable=clean and no suites observed", async () => {
mockPRs = [makePR({ mergeable_state: "clean" })];
renderList();
await waitForRender();
expect(screen.getByText("Ready to merge")).toBeInTheDocument();
});
it("renders Merged status for merged PRs, suppressing conflict/check text", async () => {
mockPRs = [
makePR({
state: "merged",
mergeable_state: "dirty",
checks_conclusion: "failed",
checks_failed: 5,
}),
];
renderList();
await waitForRender();
expect(screen.getByText("Merged")).toBeInTheDocument();
expect(screen.queryByText("Has merge conflicts")).not.toBeInTheDocument();
expect(screen.queryByText("Some checks failed")).not.toBeInTheDocument();
expect(screen.queryByText("Conflicts")).not.toBeInTheDocument();
expect(screen.queryByText("Checks failed")).not.toBeInTheDocument();
});
it("renders Closed-without-merging status for closed PRs, suppressing conflict/check badges", async () => {
mockPRs = [
makePR({
state: "closed",
mergeable_state: "clean",
checks_conclusion: "passed",
checks_passed: 3,
}),
];
renderList();
await waitForRender();
expect(screen.getByText("Closed without merging")).toBeInTheDocument();
expect(screen.queryByText("Ready to merge")).not.toBeInTheDocument();
expect(screen.queryByText("All checks passed")).not.toBeInTheDocument();
expect(screen.queryByText("No conflicts")).not.toBeInTheDocument();
expect(screen.queryByText("Checks passed")).not.toBeInTheDocument();
});
it("hides stats row when all stats are 0 (legacy backend)", async () => {
mockPRs = [makePR()];
renderList();
await waitForRender();
expect(screen.queryByText(/files?$/)).not.toBeInTheDocument();
expect(screen.queryByText(/^\+0/)).not.toBeInTheDocument();
});
it("shows stats row with additions / deletions / file count when present", async () => {
mockPRs = [makePR({ additions: 437, deletions: 6, changed_files: 6 })];
renderList();
await waitForRender();
expect(screen.getByText("+437")).toBeInTheDocument();
expect(screen.getByText("6")).toBeInTheDocument();
expect(screen.getByText("6 files")).toBeInTheDocument();
});
it("uses singular file copy when changed_files=1", async () => {
mockPRs = [makePR({ additions: 1, changed_files: 1 })];
renderList();
await waitForRender();
expect(screen.getByText("1 file")).toBeInTheDocument();
});
it("collapses extra PR rows past the visible limit behind Show more toggle", async () => {
mockPRs = [
makePR({ id: "a", number: 1, title: "PR-A" }),
makePR({ id: "b", number: 2, title: "PR-B" }),
makePR({ id: "c", number: 3, title: "PR-C" }),
makePR({ id: "d", number: 4, title: "PR-D" }),
makePR({ id: "e", number: 5, title: "PR-E" }),
];
renderList();
await waitForRender();
expect(screen.getByText("PR-A")).toBeInTheDocument();
expect(screen.getByText("PR-B")).toBeInTheDocument();
expect(screen.getByText("PR-C")).toBeInTheDocument();
expect(screen.queryByText("PR-D")).not.toBeInTheDocument();
expect(screen.queryByText("PR-E")).not.toBeInTheDocument();
expect(screen.getByText("Show 2 more")).toBeInTheDocument();
});
it("collapses to 3 rows + hidden tail when count == threshold", async () => {
mockPRs = [
makePR({ id: "a", number: 1, title: "PR-A" }),
makePR({ id: "b", number: 2, title: "PR-B" }),
makePR({ id: "c", number: 3, title: "PR-C" }),
makePR({ id: "d", number: 4, title: "PR-D" }),
];
renderList();
await waitForRender();
expect(screen.getByText("PR-A")).toBeInTheDocument();
expect(screen.getByText("PR-B")).toBeInTheDocument();
expect(screen.getByText("PR-C")).toBeInTheDocument();
expect(screen.queryByText("PR-D")).not.toBeInTheDocument();
expect(screen.getByText("Show 1 more")).toBeInTheDocument();
});
});