mirror of
https://github.com/OpenHands/OpenHands.git
synced 2026-03-22 05:37:20 +08:00
Co-authored-by: mkdev11 <MkDev11@users.noreply.github.com> Co-authored-by: hieptl <hieptl.developer@gmail.com>
382 lines
9.8 KiB
TypeScript
382 lines
9.8 KiB
TypeScript
import { render, screen } from "@testing-library/react";
|
|
import userEvent from "@testing-library/user-event";
|
|
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
import { OpenRepositoryModal } from "#/components/features/chat/open-repository-modal";
|
|
|
|
// Mock react-i18next
|
|
vi.mock("react-i18next", () => ({
|
|
useTranslation: () => ({
|
|
t: (key: string) => key,
|
|
}),
|
|
}));
|
|
|
|
// Mock useUserProviders - default to single provider (no dropdown shown)
|
|
const mockProviders = vi.hoisted(() => ({
|
|
current: ["github"] as string[],
|
|
}));
|
|
|
|
vi.mock("#/hooks/use-user-providers", () => ({
|
|
useUserProviders: () => ({
|
|
providers: mockProviders.current,
|
|
isLoadingSettings: false,
|
|
}),
|
|
}));
|
|
|
|
// Mock GitProviderDropdown
|
|
vi.mock(
|
|
"#/components/features/home/git-provider-dropdown/git-provider-dropdown",
|
|
() => ({
|
|
GitProviderDropdown: ({
|
|
providers,
|
|
onChange,
|
|
}: {
|
|
providers: string[];
|
|
onChange: (provider: string | null) => void;
|
|
}) => (
|
|
<div data-testid="git-provider-dropdown">
|
|
{providers.map((p: string) => (
|
|
<button
|
|
key={p}
|
|
data-testid={`provider-${p}`}
|
|
onClick={() => onChange(p)}
|
|
>
|
|
{p}
|
|
</button>
|
|
))}
|
|
</div>
|
|
),
|
|
}),
|
|
);
|
|
|
|
// Mock GitRepoDropdown
|
|
vi.mock(
|
|
"#/components/features/home/git-repo-dropdown/git-repo-dropdown",
|
|
() => ({
|
|
GitRepoDropdown: ({
|
|
onChange,
|
|
}: {
|
|
onChange: (repo?: {
|
|
id: number;
|
|
full_name: string;
|
|
git_provider: string;
|
|
main_branch: string;
|
|
}) => void;
|
|
}) => (
|
|
<button
|
|
data-testid="git-repo-dropdown"
|
|
onClick={() =>
|
|
onChange({
|
|
id: 1,
|
|
full_name: "owner/repo",
|
|
git_provider: "github",
|
|
main_branch: "main",
|
|
})
|
|
}
|
|
>
|
|
Mock Repo Dropdown
|
|
</button>
|
|
),
|
|
}),
|
|
);
|
|
|
|
// Mock GitBranchDropdown
|
|
vi.mock(
|
|
"#/components/features/home/git-branch-dropdown/git-branch-dropdown",
|
|
() => ({
|
|
GitBranchDropdown: ({
|
|
onBranchSelect,
|
|
disabled,
|
|
}: {
|
|
onBranchSelect: (branch: { name: string } | null) => void;
|
|
disabled: boolean;
|
|
}) => (
|
|
<button
|
|
data-testid="git-branch-dropdown"
|
|
disabled={disabled}
|
|
onClick={() => onBranchSelect({ name: "main" })}
|
|
>
|
|
Mock Branch Dropdown
|
|
</button>
|
|
),
|
|
}),
|
|
);
|
|
|
|
// Mock RepoForkedIcon
|
|
vi.mock("#/icons/repo-forked.svg?react", () => ({
|
|
default: () => <div data-testid="repo-forked-icon" />,
|
|
}));
|
|
|
|
describe("OpenRepositoryModal", () => {
|
|
const mockOnClose = vi.fn();
|
|
const mockOnLaunch = vi.fn();
|
|
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
mockProviders.current = ["github"];
|
|
});
|
|
|
|
it("should not render when isOpen is false", () => {
|
|
render(
|
|
<OpenRepositoryModal
|
|
isOpen={false}
|
|
onClose={mockOnClose}
|
|
onLaunch={mockOnLaunch}
|
|
/>,
|
|
);
|
|
|
|
expect(
|
|
screen.queryByText("CONVERSATION$OPEN_REPOSITORY"),
|
|
).not.toBeInTheDocument();
|
|
});
|
|
|
|
it("should render modal with title and description when open", () => {
|
|
render(
|
|
<OpenRepositoryModal
|
|
isOpen={true}
|
|
onClose={mockOnClose}
|
|
onLaunch={mockOnLaunch}
|
|
/>,
|
|
);
|
|
|
|
expect(
|
|
screen.getByText("CONVERSATION$OPEN_REPOSITORY"),
|
|
).toBeInTheDocument();
|
|
expect(
|
|
screen.getByText("CONVERSATION$SELECT_OR_INSERT_LINK"),
|
|
).toBeInTheDocument();
|
|
expect(screen.getByTestId("repo-forked-icon")).toBeInTheDocument();
|
|
});
|
|
|
|
it("should render Launch and Cancel buttons", () => {
|
|
render(
|
|
<OpenRepositoryModal
|
|
isOpen={true}
|
|
onClose={mockOnClose}
|
|
onLaunch={mockOnLaunch}
|
|
/>,
|
|
);
|
|
|
|
expect(screen.getByText("BUTTON$LAUNCH")).toBeInTheDocument();
|
|
expect(screen.getByText("BUTTON$CANCEL")).toBeInTheDocument();
|
|
});
|
|
|
|
it("should disable Launch button when no repository or branch is selected", () => {
|
|
render(
|
|
<OpenRepositoryModal
|
|
isOpen={true}
|
|
onClose={mockOnClose}
|
|
onLaunch={mockOnLaunch}
|
|
/>,
|
|
);
|
|
|
|
const launchButton = screen.getByText("BUTTON$LAUNCH").closest("button");
|
|
expect(launchButton).toBeDisabled();
|
|
});
|
|
|
|
it("should call onClose and reset state when Cancel is clicked", async () => {
|
|
const user = userEvent.setup();
|
|
|
|
render(
|
|
<OpenRepositoryModal
|
|
isOpen={true}
|
|
onClose={mockOnClose}
|
|
onLaunch={mockOnLaunch}
|
|
/>,
|
|
);
|
|
|
|
await user.click(screen.getByText("BUTTON$CANCEL"));
|
|
expect(mockOnClose).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
it("should enable Launch button after selecting repository and branch", async () => {
|
|
const user = userEvent.setup();
|
|
|
|
render(
|
|
<OpenRepositoryModal
|
|
isOpen={true}
|
|
onClose={mockOnClose}
|
|
onLaunch={mockOnLaunch}
|
|
/>,
|
|
);
|
|
|
|
// Select a repository
|
|
await user.click(screen.getByTestId("git-repo-dropdown"));
|
|
|
|
// Select a branch
|
|
await user.click(screen.getByTestId("git-branch-dropdown"));
|
|
|
|
const launchButton = screen.getByText("BUTTON$LAUNCH").closest("button");
|
|
expect(launchButton).not.toBeDisabled();
|
|
});
|
|
|
|
it("should call onLaunch with selected repository and branch, then close", async () => {
|
|
const user = userEvent.setup();
|
|
|
|
render(
|
|
<OpenRepositoryModal
|
|
isOpen={true}
|
|
onClose={mockOnClose}
|
|
onLaunch={mockOnLaunch}
|
|
/>,
|
|
);
|
|
|
|
// Select repository and branch
|
|
await user.click(screen.getByTestId("git-repo-dropdown"));
|
|
await user.click(screen.getByTestId("git-branch-dropdown"));
|
|
|
|
// Click Launch
|
|
await user.click(screen.getByText("BUTTON$LAUNCH"));
|
|
|
|
expect(mockOnLaunch).toHaveBeenCalledWith(
|
|
{
|
|
id: 1,
|
|
full_name: "owner/repo",
|
|
git_provider: "github",
|
|
main_branch: "main",
|
|
},
|
|
{ name: "main" },
|
|
);
|
|
expect(mockOnClose).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
it("should not call onLaunch when Launch is clicked without selections", async () => {
|
|
const user = userEvent.setup();
|
|
|
|
render(
|
|
<OpenRepositoryModal
|
|
isOpen={true}
|
|
onClose={mockOnClose}
|
|
onLaunch={mockOnLaunch}
|
|
/>,
|
|
);
|
|
|
|
// Force click the launch button even though it's disabled
|
|
const launchButton = screen.getByText("BUTTON$LAUNCH").closest("button")!;
|
|
await user.click(launchButton);
|
|
|
|
expect(mockOnLaunch).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("should reset branch selection when repository changes", async () => {
|
|
const user = userEvent.setup();
|
|
|
|
render(
|
|
<OpenRepositoryModal
|
|
isOpen={true}
|
|
onClose={mockOnClose}
|
|
onLaunch={mockOnLaunch}
|
|
/>,
|
|
);
|
|
|
|
// Select repository and branch
|
|
await user.click(screen.getByTestId("git-repo-dropdown"));
|
|
await user.click(screen.getByTestId("git-branch-dropdown"));
|
|
|
|
// Launch button should be enabled
|
|
let launchButton = screen.getByText("BUTTON$LAUNCH").closest("button");
|
|
expect(launchButton).not.toBeDisabled();
|
|
|
|
// Select a new repository (resets branch)
|
|
await user.click(screen.getByTestId("git-repo-dropdown"));
|
|
|
|
// Launch button should be disabled again (branch was reset)
|
|
launchButton = screen.getByText("BUTTON$LAUNCH").closest("button");
|
|
expect(launchButton).toBeDisabled();
|
|
});
|
|
|
|
it("should use small modal width", () => {
|
|
render(
|
|
<OpenRepositoryModal
|
|
isOpen={true}
|
|
onClose={mockOnClose}
|
|
onLaunch={mockOnLaunch}
|
|
/>,
|
|
);
|
|
|
|
// ModalBody with width="small" renders w-[384px]
|
|
const modalBody = screen
|
|
.getByText("CONVERSATION$OPEN_REPOSITORY")
|
|
.closest(".bg-base-secondary");
|
|
expect(modalBody).toHaveClass("w-[384px]");
|
|
});
|
|
|
|
it("should override default gap with !gap-4 for tighter spacing", () => {
|
|
render(
|
|
<OpenRepositoryModal
|
|
isOpen={true}
|
|
onClose={mockOnClose}
|
|
onLaunch={mockOnLaunch}
|
|
/>,
|
|
);
|
|
|
|
const modalBody = screen
|
|
.getByText("CONVERSATION$OPEN_REPOSITORY")
|
|
.closest(".bg-base-secondary");
|
|
expect(modalBody).toHaveClass("!gap-4");
|
|
});
|
|
|
|
describe("provider switching", () => {
|
|
it("should not show provider dropdown when only one provider exists", () => {
|
|
mockProviders.current = ["github"];
|
|
|
|
render(
|
|
<OpenRepositoryModal
|
|
isOpen={true}
|
|
onClose={mockOnClose}
|
|
onLaunch={mockOnLaunch}
|
|
/>,
|
|
);
|
|
|
|
expect(
|
|
screen.queryByTestId("git-provider-dropdown"),
|
|
).not.toBeInTheDocument();
|
|
});
|
|
|
|
it("should show provider dropdown when multiple providers exist", () => {
|
|
mockProviders.current = ["github", "gitlab"];
|
|
|
|
render(
|
|
<OpenRepositoryModal
|
|
isOpen={true}
|
|
onClose={mockOnClose}
|
|
onLaunch={mockOnLaunch}
|
|
/>,
|
|
);
|
|
|
|
expect(
|
|
screen.getByTestId("git-provider-dropdown"),
|
|
).toBeInTheDocument();
|
|
expect(screen.getByTestId("provider-github")).toBeInTheDocument();
|
|
expect(screen.getByTestId("provider-gitlab")).toBeInTheDocument();
|
|
});
|
|
|
|
it("should reset repository and branch when provider changes", async () => {
|
|
mockProviders.current = ["github", "gitlab"];
|
|
const user = userEvent.setup();
|
|
|
|
render(
|
|
<OpenRepositoryModal
|
|
isOpen={true}
|
|
onClose={mockOnClose}
|
|
onLaunch={mockOnLaunch}
|
|
/>,
|
|
);
|
|
|
|
// Select repo and branch
|
|
await user.click(screen.getByTestId("git-repo-dropdown"));
|
|
await user.click(screen.getByTestId("git-branch-dropdown"));
|
|
|
|
// Launch should be enabled
|
|
let launchButton = screen.getByText("BUTTON$LAUNCH").closest("button");
|
|
expect(launchButton).not.toBeDisabled();
|
|
|
|
// Switch provider — should reset selections
|
|
await user.click(screen.getByTestId("provider-gitlab"));
|
|
|
|
// Launch should be disabled (repo and branch reset)
|
|
launchButton = screen.getByText("BUTTON$LAUNCH").closest("button");
|
|
expect(launchButton).toBeDisabled();
|
|
});
|
|
});
|
|
});
|