mirror of
https://github.com/multica-ai/multica.git
synced 2026-07-28 05:46:58 +02:00
This reverts commit 139cc89200.
Co-authored-by: Bohan-J <bohan@devv.ai>
Co-authored-by: multica-agent <github@multica.ai>
212 lines
7.4 KiB
TypeScript
212 lines
7.4 KiB
TypeScript
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();
|
||
});
|
||
});
|