import { fireEvent, render, screen } from "@testing-library/react"; import { beforeEach, describe, expect, it, vi } from "vitest"; import { GitToolsSubmenu } from "./git-tools-submenu"; const mocks = vi.hoisted(() => ({ setMessageToSend: vi.fn(), useActiveConversation: vi.fn(), useTranslation: vi.fn(() => ({ t: (key: string) => key })), getGitPullPrompt: vi.fn(() => "pull prompt"), getGitPushPrompt: vi.fn(() => "push prompt"), getCreatePRPrompt: vi.fn(() => "pr prompt"), getCreateNewBranchPrompt: vi.fn(() => "branch prompt"), })); vi.mock("react-i18next", () => ({ useTranslation: mocks.useTranslation, })); vi.mock("#/stores/conversation-store", () => ({ useConversationStore: () => ({ setMessageToSend: mocks.setMessageToSend }), })); vi.mock("#/hooks/query/use-active-conversation", () => ({ useActiveConversation: mocks.useActiveConversation, })); vi.mock("#/utils/utils", () => ({ getGitPullPrompt: mocks.getGitPullPrompt, getGitPushPrompt: mocks.getGitPushPrompt, getCreatePRPrompt: mocks.getCreatePRPrompt, getCreateNewBranchPrompt: mocks.getCreateNewBranchPrompt, })); vi.mock("#/ui/context-menu", () => ({ ContextMenu: ({ children, testId, className, }: { children: React.ReactNode; testId: string; className: string; }) => (
{children}
), })); vi.mock("../context-menu/context-menu-list-item", () => ({ ContextMenuListItem: ({ children, testId, onClick, className, }: { children: React.ReactNode; testId: string; onClick: () => void; className: string; }) => ( ), })); vi.mock("./tools-context-menu-icon-text", () => ({ ToolsContextMenuIconText: ({ text }: { text: string }) => {text}, })); beforeEach(() => { vi.clearAllMocks(); mocks.useActiveConversation.mockReturnValue({ data: { git_provider: "github" }, }); }); describe("GitToolsSubmenu", () => { it("renders every git action in the submenu", async () => { vi.resetModules(); const { GitToolsSubmenu: ReloadedGitToolsSubmenu } = await import("./git-tools-submenu"); render(); expect(mocks.useTranslation).toHaveBeenCalledWith("openhands"); expect(screen.getByTestId("git-tools-submenu")).toHaveClass("w-max"); expect(screen.getByTestId("git-pull-button")).toHaveTextContent( "COMMON$GIT_PULL", ); expect(screen.getByTestId("git-push-button")).toHaveTextContent( "COMMON$GIT_PUSH", ); expect(screen.getByTestId("create-pr-button")).toHaveTextContent( "COMMON$CREATE_PR", ); expect(screen.getByTestId("create-new-branch-button")).toHaveTextContent( "COMMON$CREATE_NEW_BRANCH", ); for (const button of screen.getAllByRole("button")) { expect(button).toHaveClass("!w-auto", "whitespace-nowrap"); } }); it.each([ ["git-pull-button", mocks.getGitPullPrompt, "pull prompt"], ["git-push-button", mocks.getGitPushPrompt, "push prompt"], ["create-pr-button", mocks.getCreatePRPrompt, "pr prompt"], [ "create-new-branch-button", mocks.getCreateNewBranchPrompt, "branch prompt", ], ])( "builds and queues the %s action before closing", (testId, prompt, text) => { const onClose = vi.fn(); render(); fireEvent.click(screen.getByTestId(testId)); expect(prompt).toHaveBeenCalledTimes(1); expect(mocks.setMessageToSend).toHaveBeenCalledWith(text); expect(mocks.setMessageToSend.mock.invocationCallOrder[0]).toBeLessThan( onClose.mock.invocationCallOrder[0], ); expect(onClose).toHaveBeenCalledTimes(1); }, ); it("passes the active provider to provider-specific prompts", () => { render(); fireEvent.click(screen.getByTestId("git-push-button")); fireEvent.click(screen.getByTestId("create-pr-button")); expect(mocks.getGitPushPrompt).toHaveBeenCalledWith("github"); expect(mocks.getCreatePRPrompt).toHaveBeenCalledWith("github"); }); it("uses the provider-agnostic prompt fallback without an active conversation", () => { mocks.useActiveConversation.mockReturnValue({ data: undefined }); render(); fireEvent.click(screen.getByTestId("git-push-button")); fireEvent.click(screen.getByTestId("create-pr-button")); expect(mocks.getGitPushPrompt).toHaveBeenCalledWith(undefined); expect(mocks.getCreatePRPrompt).toHaveBeenCalledWith(undefined); }); });