mirror of
https://github.com/OpenHands/OpenHands.git
synced 2025-12-26 05:48:36 +08:00
2511 lines
83 KiB
TypeScript
2511 lines
83 KiB
TypeScript
import { screen, waitFor } from "@testing-library/react";
|
|
import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
|
import { QueryClientConfig } from "@tanstack/react-query";
|
|
import userEvent from "@testing-library/user-event";
|
|
import { createRoutesStub } from "react-router";
|
|
import React from "react";
|
|
import { renderWithProviders } from "test-utils";
|
|
import MicroagentManagement from "#/routes/microagent-management";
|
|
import { MicroagentManagementMain } from "#/components/features/microagent-management/microagent-management-main";
|
|
import ConversationService from "#/api/conversation-service/conversation-service.api";
|
|
import GitService from "#/api/git-service/git-service.api";
|
|
import { GitRepository } from "#/types/git";
|
|
import { RepositoryMicroagent } from "#/types/microagent-management";
|
|
import { Conversation } from "#/api/open-hands.types";
|
|
import { useMicroagentManagementStore } from "#/stores/microagent-management-store";
|
|
|
|
// Mock hooks
|
|
const mockUseUserProviders = vi.fn();
|
|
const mockUseGitRepositories = vi.fn();
|
|
const mockUseConfig = vi.fn();
|
|
const mockUseRepositoryMicroagents = vi.fn();
|
|
const mockUseMicroagentManagementConversations = vi.fn();
|
|
const mockUseSearchRepositories = vi.fn();
|
|
const mockUseCreateConversationAndSubscribeMultiple = vi.fn();
|
|
|
|
vi.mock("#/hooks/use-user-providers", () => ({
|
|
useUserProviders: () => mockUseUserProviders(),
|
|
}));
|
|
|
|
vi.mock("#/hooks/query/use-git-repositories", () => ({
|
|
useGitRepositories: () => mockUseGitRepositories(),
|
|
}));
|
|
|
|
vi.mock("#/hooks/query/use-config", () => ({
|
|
useConfig: () => mockUseConfig(),
|
|
}));
|
|
|
|
vi.mock("#/hooks/query/use-repository-microagents", () => ({
|
|
useRepositoryMicroagents: () => mockUseRepositoryMicroagents(),
|
|
}));
|
|
|
|
vi.mock("#/hooks/query/use-microagent-management-conversations", () => ({
|
|
useMicroagentManagementConversations: () =>
|
|
mockUseMicroagentManagementConversations(),
|
|
}));
|
|
|
|
vi.mock("#/hooks/query/use-search-repositories", () => ({
|
|
useSearchRepositories: () => mockUseSearchRepositories(),
|
|
}));
|
|
|
|
vi.mock("#/hooks/use-tracking", () => ({
|
|
useTracking: () => ({
|
|
trackEvent: vi.fn(),
|
|
}),
|
|
}));
|
|
|
|
vi.mock("#/hooks/use-create-conversation-and-subscribe-multiple", () => ({
|
|
useCreateConversationAndSubscribeMultiple: () =>
|
|
mockUseCreateConversationAndSubscribeMultiple(),
|
|
}));
|
|
|
|
describe("MicroagentManagement", () => {
|
|
const RouterStub = createRoutesStub([
|
|
{
|
|
Component: MicroagentManagement,
|
|
path: "/",
|
|
},
|
|
]);
|
|
|
|
const renderMicroagentManagement = (config?: QueryClientConfig) =>
|
|
renderWithProviders(<RouterStub />);
|
|
|
|
// Common test data
|
|
const testRepository = {
|
|
id: "1",
|
|
full_name: "user/test-repo",
|
|
git_provider: "github" as const,
|
|
is_public: true,
|
|
owner_type: "user" as const,
|
|
pushed_at: "2021-10-01T12:00:00Z",
|
|
};
|
|
|
|
// Helper function to render with custom Zustand store state
|
|
const renderWithCustomStore = (storeOverrides: Partial<any>) => {
|
|
useMicroagentManagementStore.setState(storeOverrides);
|
|
return renderWithProviders(<RouterStub />);
|
|
};
|
|
|
|
// Helper function to render with update modal visible
|
|
const renderWithUpdateModal = (additionalState: Partial<any> = {}) => {
|
|
return renderWithCustomStore({
|
|
updateMicroagentModalVisible: true,
|
|
selectedRepository: testRepository,
|
|
...additionalState,
|
|
});
|
|
};
|
|
|
|
// Helper function to render with selected microagent
|
|
const renderWithSelectedMicroagent = (
|
|
microagent: any,
|
|
additionalState: Partial<any> = {},
|
|
) => {
|
|
return renderWithCustomStore({
|
|
selectedRepository: testRepository,
|
|
selectedMicroagentItem: {
|
|
microagent,
|
|
conversation: null,
|
|
},
|
|
...additionalState,
|
|
});
|
|
};
|
|
|
|
beforeAll(() => {
|
|
vi.mock("react-router", async (importOriginal) => ({
|
|
...(await importOriginal<typeof import("react-router")>()),
|
|
Link: ({ children }: React.PropsWithChildren) => children,
|
|
useNavigate: vi.fn(() => vi.fn()),
|
|
useLocation: vi.fn(() => ({ pathname: "/microagent-management" })),
|
|
useParams: vi.fn(() => ({ conversationId: "2" })),
|
|
}));
|
|
});
|
|
|
|
const mockRepositories: GitRepository[] = [
|
|
{
|
|
id: "1",
|
|
full_name: "user/repo1",
|
|
git_provider: "github",
|
|
is_public: true,
|
|
owner_type: "user",
|
|
pushed_at: "2021-10-01T12:00:00Z",
|
|
},
|
|
{
|
|
id: "2",
|
|
full_name: "user/repo2/.openhands",
|
|
git_provider: "github",
|
|
is_public: true,
|
|
owner_type: "user",
|
|
pushed_at: "2021-10-02T12:00:00Z",
|
|
},
|
|
{
|
|
id: "3",
|
|
full_name: "org/repo3/.openhands",
|
|
git_provider: "github",
|
|
is_public: true,
|
|
owner_type: "organization",
|
|
pushed_at: "2021-10-03T12:00:00Z",
|
|
},
|
|
{
|
|
id: "4",
|
|
full_name: "user/repo4",
|
|
git_provider: "github",
|
|
is_public: true,
|
|
owner_type: "user",
|
|
pushed_at: "2021-10-04T12:00:00Z",
|
|
},
|
|
{
|
|
id: "5",
|
|
full_name: "user/TestRepository",
|
|
git_provider: "github",
|
|
is_public: true,
|
|
owner_type: "user",
|
|
pushed_at: "2021-10-05T12:00:00Z",
|
|
},
|
|
{
|
|
id: "6",
|
|
full_name: "org/AnotherRepo",
|
|
git_provider: "github",
|
|
is_public: true,
|
|
owner_type: "organization",
|
|
pushed_at: "2021-10-06T12:00:00Z",
|
|
},
|
|
{
|
|
id: "7",
|
|
full_name: "user/gitlab-repo/openhands-config",
|
|
git_provider: "gitlab",
|
|
is_public: true,
|
|
owner_type: "user",
|
|
pushed_at: "2021-10-07T12:00:00Z",
|
|
},
|
|
{
|
|
id: "8",
|
|
full_name: "org/gitlab-org-repo/openhands-config",
|
|
git_provider: "gitlab",
|
|
is_public: true,
|
|
owner_type: "organization",
|
|
pushed_at: "2021-10-08T12:00:00Z",
|
|
},
|
|
];
|
|
|
|
// Helper function to filter repositories with OpenHands suffixes
|
|
const getRepositoriesWithOpenHandsSuffix = (
|
|
repositories: GitRepository[],
|
|
) => {
|
|
return repositories.filter(
|
|
(repo) =>
|
|
repo.full_name.endsWith("/.openhands") ||
|
|
repo.full_name.endsWith("/openhands-config"),
|
|
);
|
|
};
|
|
|
|
// Helper functions for mocking search repositories
|
|
const mockSearchRepositoriesWithData = (data: GitRepository[]) => {
|
|
mockUseSearchRepositories.mockReturnValue({
|
|
data,
|
|
isLoading: false,
|
|
isError: false,
|
|
});
|
|
};
|
|
|
|
const mockSearchRepositoriesEmpty = () => {
|
|
mockUseSearchRepositories.mockReturnValue({
|
|
data: [],
|
|
isLoading: false,
|
|
isError: false,
|
|
});
|
|
};
|
|
|
|
const mockMicroagents: RepositoryMicroagent[] = [
|
|
{
|
|
name: "test-microagent-1",
|
|
created_at: "2021-10-01T12:00:00Z",
|
|
git_provider: "github",
|
|
path: ".openhands/microagents/test-microagent-1",
|
|
},
|
|
{
|
|
name: "test-microagent-2",
|
|
created_at: "2021-10-02T12:00:00Z",
|
|
git_provider: "github",
|
|
path: ".openhands/microagents/test-microagent-2",
|
|
},
|
|
];
|
|
|
|
const mockConversations: Conversation[] = [
|
|
{
|
|
conversation_id: "conv-1",
|
|
title: "Test Conversation 1",
|
|
selected_repository: "user/repo2/.openhands",
|
|
selected_branch: "main",
|
|
git_provider: "github",
|
|
last_updated_at: "2021-10-01T12:00:00Z",
|
|
created_at: "2021-10-01T12:00:00Z",
|
|
status: "RUNNING",
|
|
runtime_status: null,
|
|
trigger: "microagent_management",
|
|
url: null,
|
|
session_api_key: null,
|
|
},
|
|
{
|
|
conversation_id: "conv-2",
|
|
title: "Test Conversation 2",
|
|
selected_repository: "user/repo2/.openhands",
|
|
selected_branch: "main",
|
|
git_provider: "github",
|
|
last_updated_at: "2021-10-02T12:00:00Z",
|
|
created_at: "2021-10-02T12:00:00Z",
|
|
status: "STOPPED",
|
|
runtime_status: null,
|
|
trigger: "microagent_management",
|
|
url: null,
|
|
session_api_key: null,
|
|
},
|
|
];
|
|
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
vi.restoreAllMocks();
|
|
|
|
// Reset Zustand store to default state
|
|
useMicroagentManagementStore.setState({
|
|
// Modal visibility states
|
|
addMicroagentModalVisible: false,
|
|
updateMicroagentModalVisible: false,
|
|
learnThisRepoModalVisible: false,
|
|
|
|
// Repository states
|
|
selectedRepository: null,
|
|
personalRepositories: [],
|
|
organizationRepositories: [],
|
|
repositories: [],
|
|
|
|
// Microagent states
|
|
selectedMicroagentItem: null,
|
|
});
|
|
|
|
// Setup default hook mocks
|
|
mockUseUserProviders.mockReturnValue({
|
|
providers: ["github"],
|
|
});
|
|
|
|
mockUseGitRepositories.mockReturnValue({
|
|
data: {
|
|
pages: [
|
|
{
|
|
data: mockRepositories,
|
|
nextPage: null,
|
|
},
|
|
],
|
|
},
|
|
isLoading: false,
|
|
isError: false,
|
|
hasNextPage: false,
|
|
isFetchingNextPage: false,
|
|
onLoadMore: vi.fn(),
|
|
});
|
|
|
|
mockUseConfig.mockReturnValue({
|
|
data: {
|
|
APP_MODE: "oss",
|
|
},
|
|
});
|
|
|
|
mockUseRepositoryMicroagents.mockReturnValue({
|
|
data: mockMicroagents,
|
|
isLoading: false,
|
|
isError: false,
|
|
});
|
|
|
|
mockUseMicroagentManagementConversations.mockReturnValue({
|
|
data: mockConversations,
|
|
isLoading: false,
|
|
isError: false,
|
|
});
|
|
|
|
mockUseCreateConversationAndSubscribeMultiple.mockReturnValue({
|
|
createConversationAndSubscribe: vi.fn(({ onSuccessCallback }) => {
|
|
// Immediately call the success callback to close the modal
|
|
if (onSuccessCallback) {
|
|
onSuccessCallback();
|
|
}
|
|
}),
|
|
isPending: false,
|
|
});
|
|
|
|
// Mock the search repositories hook to return repositories with OpenHands suffixes
|
|
const mockSearchResults =
|
|
getRepositoriesWithOpenHandsSuffix(mockRepositories);
|
|
|
|
mockSearchRepositoriesWithData(mockSearchResults);
|
|
|
|
// Setup default mock for retrieveUserGitRepositories
|
|
vi.spyOn(GitService, "retrieveUserGitRepositories").mockResolvedValue({
|
|
data: [...mockRepositories],
|
|
nextPage: null,
|
|
});
|
|
// Setup default mock for getRepositoryMicroagents
|
|
vi.spyOn(GitService, "getRepositoryMicroagents").mockResolvedValue([
|
|
...mockMicroagents,
|
|
]);
|
|
// Setup default mock for searchConversations
|
|
vi.spyOn(ConversationService, "searchConversations").mockResolvedValue([
|
|
...mockConversations,
|
|
]);
|
|
// Setup default mock for getRepositoryMicroagentContent
|
|
vi.spyOn(GitService, "getRepositoryMicroagentContent").mockResolvedValue({
|
|
content: "Original microagent content for testing updates",
|
|
path: ".openhands/microagents/update-test-microagent",
|
|
git_provider: "github",
|
|
triggers: ["test", "update"],
|
|
});
|
|
});
|
|
|
|
it("should render the microagent management page", async () => {
|
|
renderMicroagentManagement();
|
|
|
|
// Check that the main title is rendered
|
|
await screen.findByText("MICROAGENT_MANAGEMENT$DESCRIPTION");
|
|
});
|
|
|
|
it("should display loading state when fetching repositories", async () => {
|
|
// Mock loading state
|
|
mockUseGitRepositories.mockReturnValue({
|
|
data: undefined,
|
|
isLoading: true,
|
|
isError: false,
|
|
hasNextPage: false,
|
|
isFetchingNextPage: false,
|
|
onLoadMore: vi.fn(),
|
|
});
|
|
|
|
renderMicroagentManagement();
|
|
|
|
// Check that loading spinner is displayed
|
|
const loadingSpinner = await screen.findByText("HOME$LOADING_REPOSITORIES");
|
|
expect(loadingSpinner).toBeInTheDocument();
|
|
});
|
|
|
|
it("should handle error when fetching repositories", async () => {
|
|
// Mock error state
|
|
mockUseGitRepositories.mockReturnValue({
|
|
data: undefined,
|
|
isLoading: false,
|
|
isError: true,
|
|
hasNextPage: false,
|
|
isFetchingNextPage: false,
|
|
onLoadMore: vi.fn(),
|
|
});
|
|
|
|
renderMicroagentManagement();
|
|
|
|
// Wait for the error to be handled
|
|
await waitFor(() => {
|
|
expect(mockUseGitRepositories).toHaveBeenCalled();
|
|
});
|
|
});
|
|
|
|
it("should categorize repositories correctly", async () => {
|
|
renderMicroagentManagement();
|
|
|
|
// Wait for repositories to be loaded
|
|
await waitFor(() => {
|
|
expect(mockUseGitRepositories).toHaveBeenCalled();
|
|
});
|
|
|
|
// Check that tabs are rendered
|
|
const personalTab = screen.getByText("COMMON$PERSONAL");
|
|
const repositoriesTab = screen.getByText("COMMON$REPOSITORIES");
|
|
const organizationsTab = screen.getByText("COMMON$ORGANIZATIONS");
|
|
|
|
expect(personalTab).toBeInTheDocument();
|
|
expect(repositoriesTab).toBeInTheDocument();
|
|
expect(organizationsTab).toBeInTheDocument();
|
|
});
|
|
|
|
it("should display repositories in accordion", async () => {
|
|
renderMicroagentManagement();
|
|
|
|
// Wait for repositories to be loaded and rendered
|
|
await waitFor(() => {
|
|
expect(mockUseGitRepositories).toHaveBeenCalled();
|
|
});
|
|
|
|
// Check that repository names are displayed
|
|
const repo1 = screen.getByTestId("repository-name-tooltip");
|
|
expect(repo1).toBeInTheDocument();
|
|
expect(repo1).toHaveTextContent("user/repo2/.openhands");
|
|
});
|
|
|
|
it("should expand repository accordion and show microagents", async () => {
|
|
const user = userEvent.setup();
|
|
renderMicroagentManagement();
|
|
|
|
// Wait for repositories to be loaded
|
|
await waitFor(() => {
|
|
expect(mockUseGitRepositories).toHaveBeenCalled();
|
|
});
|
|
|
|
// Find and click on the first repository accordion
|
|
const repoAccordion = screen.getByTestId("repository-name-tooltip");
|
|
await user.click(repoAccordion);
|
|
|
|
// Wait for microagents to be fetched
|
|
await waitFor(() => {
|
|
expect(mockUseRepositoryMicroagents).toHaveBeenCalled();
|
|
});
|
|
|
|
// Check that microagents are displayed
|
|
const microagent1 = screen.getByText("test-microagent-1");
|
|
const microagent2 = screen.getByText("test-microagent-2");
|
|
|
|
expect(microagent1).toBeInTheDocument();
|
|
expect(microagent2).toBeInTheDocument();
|
|
});
|
|
|
|
it("should display loading state when fetching microagents", async () => {
|
|
const user = userEvent.setup();
|
|
mockUseRepositoryMicroagents.mockReturnValue({
|
|
data: undefined,
|
|
isLoading: true,
|
|
isError: false,
|
|
});
|
|
|
|
renderMicroagentManagement();
|
|
|
|
// Wait for repositories to be loaded
|
|
await waitFor(() => {
|
|
expect(mockUseGitRepositories).toHaveBeenCalled();
|
|
});
|
|
|
|
// Find and click on the first repository accordion
|
|
const repoAccordion = screen.getByTestId("repository-name-tooltip");
|
|
await user.click(repoAccordion);
|
|
|
|
// Check that loading spinner is displayed
|
|
expect(screen.getByTestId("loading-spinner")).toBeInTheDocument();
|
|
});
|
|
|
|
it("should handle error when fetching microagents", async () => {
|
|
const user = userEvent.setup();
|
|
mockUseRepositoryMicroagents.mockReturnValue({
|
|
data: undefined,
|
|
isLoading: false,
|
|
isError: true,
|
|
});
|
|
|
|
renderMicroagentManagement();
|
|
|
|
// Wait for repositories to be loaded
|
|
await waitFor(() => {
|
|
expect(mockUseGitRepositories).toHaveBeenCalled();
|
|
});
|
|
|
|
// Find and click on the first repository accordion
|
|
const repoAccordion = screen.getByTestId("repository-name-tooltip");
|
|
await user.click(repoAccordion);
|
|
|
|
// Wait for the error to be handled
|
|
await waitFor(() => {
|
|
expect(mockUseRepositoryMicroagents).toHaveBeenCalled();
|
|
});
|
|
});
|
|
|
|
it("should display empty state when no microagents are found", async () => {
|
|
const user = userEvent.setup();
|
|
mockUseRepositoryMicroagents.mockReturnValue({
|
|
data: [],
|
|
isLoading: false,
|
|
isError: false,
|
|
});
|
|
|
|
renderMicroagentManagement();
|
|
|
|
// Wait for repositories to be loaded
|
|
await waitFor(() => {
|
|
expect(mockUseGitRepositories).toHaveBeenCalled();
|
|
});
|
|
|
|
// Find and click on the first repository accordion
|
|
const repoAccordion = screen.getByTestId("repository-name-tooltip");
|
|
await user.click(repoAccordion);
|
|
|
|
// Wait for microagents to be fetched
|
|
await waitFor(() => {
|
|
expect(mockUseRepositoryMicroagents).toHaveBeenCalled();
|
|
});
|
|
|
|
// Check that no microagents are displayed
|
|
expect(screen.queryByText("test-microagent-1")).not.toBeInTheDocument();
|
|
expect(screen.queryByText("test-microagent-2")).not.toBeInTheDocument();
|
|
});
|
|
|
|
it("should display microagent cards with correct information", async () => {
|
|
const user = userEvent.setup();
|
|
renderMicroagentManagement();
|
|
|
|
// Wait for repositories to be loaded
|
|
await waitFor(() => {
|
|
expect(mockUseGitRepositories).toHaveBeenCalled();
|
|
});
|
|
|
|
// Find and click on the first repository accordion
|
|
const repoAccordion = screen.getByTestId("repository-name-tooltip");
|
|
await user.click(repoAccordion);
|
|
|
|
// Wait for microagents to be fetched
|
|
await waitFor(() => {
|
|
expect(mockUseRepositoryMicroagents).toHaveBeenCalled();
|
|
});
|
|
|
|
// Check that microagent cards display correct information
|
|
const microagent1 = screen.getByText("test-microagent-1");
|
|
const microagent2 = screen.getByText("test-microagent-2");
|
|
|
|
expect(microagent1).toBeInTheDocument();
|
|
expect(microagent2).toBeInTheDocument();
|
|
|
|
// Check that microagent file paths are displayed
|
|
const filePath1 = screen.getByText(
|
|
".openhands/microagents/test-microagent-1",
|
|
);
|
|
const filePath2 = screen.getByText(
|
|
".openhands/microagents/test-microagent-2",
|
|
);
|
|
|
|
expect(filePath1).toBeInTheDocument();
|
|
expect(filePath2).toBeInTheDocument();
|
|
});
|
|
|
|
it("should render add microagent button", async () => {
|
|
renderMicroagentManagement();
|
|
|
|
// Wait for repositories to be loaded and processed
|
|
await waitFor(() => {
|
|
expect(mockUseGitRepositories).toHaveBeenCalled();
|
|
});
|
|
|
|
// Wait for repositories to be displayed in the accordion
|
|
await waitFor(() => {
|
|
expect(screen.getByTestId("repository-name-tooltip")).toBeInTheDocument();
|
|
});
|
|
|
|
// Check that add microagent buttons are present
|
|
const addButtons = screen.getAllByTestId("add-microagent-button");
|
|
expect(addButtons.length).toBeGreaterThan(0);
|
|
});
|
|
|
|
it("should open modal when add button is clicked", async () => {
|
|
const user = userEvent.setup();
|
|
renderMicroagentManagement();
|
|
|
|
// Wait for repositories to be loaded and processed
|
|
await waitFor(() => {
|
|
expect(mockUseGitRepositories).toHaveBeenCalled();
|
|
});
|
|
|
|
// Wait for repositories to be displayed in the accordion
|
|
await waitFor(() => {
|
|
expect(screen.getByTestId("repository-name-tooltip")).toBeInTheDocument();
|
|
});
|
|
|
|
// Find and click the first add microagent button
|
|
const addButtons = screen.getAllByTestId("add-microagent-button");
|
|
await user.click(addButtons[0]);
|
|
|
|
// Check that the modal is opened
|
|
await waitFor(() => {
|
|
expect(screen.getByTestId("add-microagent-modal")).toBeInTheDocument();
|
|
});
|
|
});
|
|
|
|
it("should close add microagent modal when cancel is clicked", async () => {
|
|
const user = userEvent.setup();
|
|
renderMicroagentManagement();
|
|
|
|
// Wait for repositories to be loaded
|
|
await waitFor(() => {
|
|
expect(mockUseGitRepositories).toHaveBeenCalled();
|
|
});
|
|
|
|
// Find and click the first add microagent button
|
|
const addButtons = screen.getAllByTestId("add-microagent-button");
|
|
await user.click(addButtons[0]);
|
|
|
|
// Check that the modal is opened
|
|
const closeButton = screen.getByRole("button", { name: "" });
|
|
await user.click(closeButton);
|
|
|
|
// Check that modal is closed
|
|
await waitFor(() => {
|
|
expect(
|
|
screen.queryByTestId("add-microagent-modal"),
|
|
).not.toBeInTheDocument();
|
|
});
|
|
});
|
|
|
|
it("should display empty state when no repositories are found", async () => {
|
|
// Mock empty repositories
|
|
mockUseGitRepositories.mockReturnValue({
|
|
data: {
|
|
pages: [
|
|
{
|
|
data: [],
|
|
nextPage: null,
|
|
},
|
|
],
|
|
},
|
|
isLoading: false,
|
|
isError: false,
|
|
hasNextPage: false,
|
|
isFetchingNextPage: false,
|
|
onLoadMore: vi.fn(),
|
|
});
|
|
|
|
// Mock empty search results
|
|
mockSearchRepositoriesEmpty();
|
|
|
|
renderMicroagentManagement();
|
|
|
|
// Wait for repositories to be loaded
|
|
await waitFor(() => {
|
|
expect(mockUseGitRepositories).toHaveBeenCalled();
|
|
});
|
|
|
|
// Check that empty state messages are displayed
|
|
const personalEmptyState = screen.getByText(
|
|
"MICROAGENT_MANAGEMENT$YOU_DO_NOT_HAVE_USER_LEVEL_MICROAGENTS",
|
|
);
|
|
|
|
expect(personalEmptyState).toBeInTheDocument();
|
|
});
|
|
|
|
it("should handle multiple repository expansions", async () => {
|
|
const user = userEvent.setup();
|
|
renderMicroagentManagement();
|
|
|
|
// Wait for repositories to be loaded
|
|
await waitFor(() => {
|
|
expect(mockUseGitRepositories).toHaveBeenCalled();
|
|
});
|
|
|
|
// Find and click on the first repository accordion
|
|
const repoAccordion1 = screen.getByTestId("repository-name-tooltip");
|
|
await user.click(repoAccordion1);
|
|
|
|
// Wait for microagents to be fetched for first repo
|
|
await waitFor(() => {
|
|
expect(mockUseRepositoryMicroagents).toHaveBeenCalled();
|
|
});
|
|
|
|
// Check that the hook was called
|
|
expect(mockUseRepositoryMicroagents).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
it("should display ready to add microagent message in main area", async () => {
|
|
renderMicroagentManagement();
|
|
|
|
// Check that the main area shows the ready message
|
|
const readyMessage = screen.getByText(
|
|
"MICROAGENT_MANAGEMENT$READY_TO_ADD_MICROAGENT",
|
|
);
|
|
const descriptionMessage = screen.getByText(
|
|
"MICROAGENT_MANAGEMENT$OPENHANDS_CAN_LEARN_ABOUT_REPOSITORIES",
|
|
);
|
|
|
|
expect(readyMessage).toBeInTheDocument();
|
|
expect(descriptionMessage).toBeInTheDocument();
|
|
});
|
|
|
|
// Search functionality tests
|
|
describe("Search functionality", () => {
|
|
it("should render search input field", async () => {
|
|
renderMicroagentManagement();
|
|
|
|
// Wait for repositories to be loaded
|
|
await waitFor(() => {
|
|
expect(mockUseGitRepositories).toHaveBeenCalled();
|
|
});
|
|
|
|
// Check that search input is rendered
|
|
const searchInput = screen.getByRole("textbox", {
|
|
name: "COMMON$SEARCH_REPOSITORIES",
|
|
});
|
|
expect(searchInput).toBeInTheDocument();
|
|
expect(searchInput).toHaveAttribute(
|
|
"placeholder",
|
|
"COMMON$SEARCH_REPOSITORIES...",
|
|
);
|
|
});
|
|
|
|
it("should filter repositories when typing in search input", async () => {
|
|
const user = userEvent.setup();
|
|
renderMicroagentManagement();
|
|
|
|
// Wait for repositories to be loaded
|
|
await waitFor(() => {
|
|
expect(mockUseGitRepositories).toHaveBeenCalled();
|
|
});
|
|
|
|
// Initially only repositories with .openhands should be visible
|
|
expect(screen.getByTestId("repository-name-tooltip")).toBeInTheDocument();
|
|
expect(screen.queryByText("user/repo1")).not.toBeInTheDocument();
|
|
expect(screen.queryByText("user/repo4")).not.toBeInTheDocument();
|
|
|
|
// Type in search input to filter further
|
|
const searchInput = screen.getByRole("textbox", {
|
|
name: "COMMON$SEARCH_REPOSITORIES",
|
|
});
|
|
await user.type(searchInput, "repo2");
|
|
|
|
// Only repo2 should be visible
|
|
expect(screen.getByTestId("repository-name-tooltip")).toBeInTheDocument();
|
|
expect(screen.queryByText("user/repo1")).not.toBeInTheDocument();
|
|
expect(
|
|
screen.queryByText("org/repo3/.openhands"),
|
|
).not.toBeInTheDocument();
|
|
expect(screen.queryByText("user/repo4")).not.toBeInTheDocument();
|
|
expect(screen.queryByText("user/TestRepository")).not.toBeInTheDocument();
|
|
expect(screen.queryByText("org/AnotherRepo")).not.toBeInTheDocument();
|
|
});
|
|
|
|
it("should perform case-insensitive search", async () => {
|
|
const user = userEvent.setup();
|
|
renderMicroagentManagement();
|
|
|
|
// Wait for repositories to be loaded
|
|
await waitFor(() => {
|
|
expect(mockUseGitRepositories).toHaveBeenCalled();
|
|
});
|
|
|
|
// Type in search input with uppercase
|
|
const searchInput = screen.getByRole("textbox", {
|
|
name: "COMMON$SEARCH_REPOSITORIES",
|
|
});
|
|
await user.type(searchInput, "REPO2");
|
|
|
|
// repo2 should be visible (case-insensitive match)
|
|
expect(screen.getByTestId("repository-name-tooltip")).toBeInTheDocument();
|
|
expect(screen.queryByText("user/repo1")).not.toBeInTheDocument();
|
|
expect(
|
|
screen.queryByText("org/repo3/.openhands"),
|
|
).not.toBeInTheDocument();
|
|
});
|
|
|
|
it("should filter repositories by partial matches", async () => {
|
|
const user = userEvent.setup();
|
|
renderMicroagentManagement();
|
|
|
|
// Wait for repositories to be loaded
|
|
await waitFor(() => {
|
|
expect(mockUseGitRepositories).toHaveBeenCalled();
|
|
});
|
|
|
|
// Type in search input with partial match
|
|
const searchInput = screen.getByRole("textbox", {
|
|
name: "COMMON$SEARCH_REPOSITORIES",
|
|
});
|
|
await user.type(searchInput, "repo");
|
|
|
|
// All repositories with "repo" in the name should be visible
|
|
expect(screen.getByTestId("repository-name-tooltip")).toBeInTheDocument();
|
|
expect(screen.queryByText("user/repo1")).not.toBeInTheDocument();
|
|
expect(
|
|
screen.queryByText("org/repo3/.openhands"),
|
|
).not.toBeInTheDocument();
|
|
expect(screen.queryByText("user/repo4")).not.toBeInTheDocument();
|
|
expect(screen.queryByText("user/TestRepository")).not.toBeInTheDocument();
|
|
expect(screen.queryByText("org/AnotherRepo")).not.toBeInTheDocument();
|
|
});
|
|
|
|
it("should show all repositories when search input is cleared", async () => {
|
|
const user = userEvent.setup();
|
|
renderMicroagentManagement();
|
|
|
|
// Wait for repositories to be loaded
|
|
await waitFor(() => {
|
|
expect(mockUseGitRepositories).toHaveBeenCalled();
|
|
});
|
|
|
|
// Type in search input
|
|
const searchInput = screen.getByRole("textbox", {
|
|
name: "COMMON$SEARCH_REPOSITORIES",
|
|
});
|
|
await user.type(searchInput, "repo2");
|
|
|
|
// Only repo2 should be visible
|
|
expect(screen.getByTestId("repository-name-tooltip")).toBeInTheDocument();
|
|
expect(screen.queryByText("user/repo1")).not.toBeInTheDocument();
|
|
|
|
// Clear the search input
|
|
await user.clear(searchInput);
|
|
|
|
// All repositories should be visible again (only those with .openhands)
|
|
expect(screen.getByTestId("repository-name-tooltip")).toBeInTheDocument();
|
|
expect(screen.queryByText("user/repo1")).not.toBeInTheDocument();
|
|
expect(
|
|
screen.queryByText("org/repo3/.openhands"),
|
|
).not.toBeInTheDocument();
|
|
expect(screen.queryByText("user/repo4")).not.toBeInTheDocument();
|
|
expect(screen.queryByText("user/TestRepository")).not.toBeInTheDocument();
|
|
expect(screen.queryByText("org/AnotherRepo")).not.toBeInTheDocument();
|
|
});
|
|
|
|
it("should handle empty search results", async () => {
|
|
const user = userEvent.setup();
|
|
|
|
// Mock empty search results for this test
|
|
mockSearchRepositoriesEmpty();
|
|
|
|
renderMicroagentManagement();
|
|
|
|
// Wait for repositories to be loaded
|
|
await waitFor(() => {
|
|
expect(mockUseGitRepositories).toHaveBeenCalled();
|
|
});
|
|
|
|
// Type in search input with non-existent repository name
|
|
const searchInput = screen.getByRole("textbox", {
|
|
name: "COMMON$SEARCH_REPOSITORIES",
|
|
});
|
|
await user.type(searchInput, "nonexistent");
|
|
|
|
// Wait for debounced search to complete (300ms debounce + buffer)
|
|
await new Promise((resolve) => setTimeout(resolve, 400));
|
|
|
|
// Wait for the search to complete and check that no repositories are visible
|
|
await waitFor(() => {
|
|
expect(screen.queryByText("user/repo1")).not.toBeInTheDocument();
|
|
expect(
|
|
screen.queryByText("user/repo2/.openhands"),
|
|
).not.toBeInTheDocument();
|
|
expect(
|
|
screen.queryByText("org/repo3/.openhands"),
|
|
).not.toBeInTheDocument();
|
|
expect(screen.queryByText("user/repo4")).not.toBeInTheDocument();
|
|
expect(
|
|
screen.queryByText("user/TestRepository"),
|
|
).not.toBeInTheDocument();
|
|
expect(screen.queryByText("org/AnotherRepo")).not.toBeInTheDocument();
|
|
});
|
|
});
|
|
|
|
it("should handle special characters in search", async () => {
|
|
const user = userEvent.setup();
|
|
renderMicroagentManagement();
|
|
|
|
// Wait for repositories to be loaded
|
|
await waitFor(() => {
|
|
expect(mockUseGitRepositories).toHaveBeenCalled();
|
|
});
|
|
|
|
// Type in search input with special characters
|
|
const searchInput = screen.getByRole("textbox", {
|
|
name: "COMMON$SEARCH_REPOSITORIES",
|
|
});
|
|
await user.type(searchInput, ".openhands");
|
|
|
|
// Only repositories with .openhands should be visible
|
|
expect(screen.getByTestId("repository-name-tooltip")).toBeInTheDocument();
|
|
expect(screen.queryByText("user/repo1")).not.toBeInTheDocument();
|
|
expect(screen.queryByText("user/repo4")).not.toBeInTheDocument();
|
|
});
|
|
|
|
it("should maintain accordion functionality with filtered results", async () => {
|
|
const user = userEvent.setup();
|
|
renderMicroagentManagement();
|
|
|
|
// Wait for repositories to be loaded
|
|
await waitFor(() => {
|
|
expect(mockUseGitRepositories).toHaveBeenCalled();
|
|
});
|
|
|
|
// Filter to show only repo2
|
|
const searchInput = screen.getByRole("textbox", {
|
|
name: "COMMON$SEARCH_REPOSITORIES",
|
|
});
|
|
await user.type(searchInput, "repo2");
|
|
|
|
// Click on the filtered repository accordion
|
|
const repoAccordion = screen.getByTestId("repository-name-tooltip");
|
|
await user.click(repoAccordion);
|
|
|
|
// Wait for microagents to be fetched
|
|
await waitFor(() => {
|
|
expect(mockUseRepositoryMicroagents).toHaveBeenCalled();
|
|
});
|
|
|
|
// Check that microagents are displayed
|
|
const microagent1 = screen.getByText("test-microagent-1");
|
|
const microagent2 = screen.getByText("test-microagent-2");
|
|
|
|
expect(microagent1).toBeInTheDocument();
|
|
expect(microagent2).toBeInTheDocument();
|
|
});
|
|
|
|
it("should handle whitespace in search input", async () => {
|
|
const user = userEvent.setup();
|
|
renderMicroagentManagement();
|
|
|
|
// Wait for repositories to be loaded
|
|
await waitFor(() => {
|
|
expect(mockUseGitRepositories).toHaveBeenCalled();
|
|
});
|
|
|
|
// Type in search input with leading/trailing whitespace
|
|
const searchInput = screen.getByRole("textbox", {
|
|
name: "COMMON$SEARCH_REPOSITORIES",
|
|
});
|
|
await user.type(searchInput, " repo2 ");
|
|
|
|
// repo2 should still be visible (whitespace should be trimmed)
|
|
expect(screen.getByTestId("repository-name-tooltip")).toBeInTheDocument();
|
|
expect(screen.queryByText("user/repo1")).not.toBeInTheDocument();
|
|
});
|
|
|
|
it("should update search results in real-time", async () => {
|
|
const user = userEvent.setup();
|
|
renderMicroagentManagement();
|
|
|
|
// Wait for repositories to be loaded
|
|
await waitFor(() => {
|
|
expect(mockUseGitRepositories).toHaveBeenCalled();
|
|
});
|
|
|
|
const searchInput = screen.getByRole("textbox", {
|
|
name: "COMMON$SEARCH_REPOSITORIES",
|
|
});
|
|
|
|
// Type "repo" - should show repo2
|
|
await user.type(searchInput, "repo");
|
|
expect(screen.getByTestId("repository-name-tooltip")).toBeInTheDocument();
|
|
expect(screen.queryByText("user/repo1")).not.toBeInTheDocument();
|
|
|
|
// Add "2" to make it "repo2" - should show only repo2
|
|
await user.type(searchInput, "2");
|
|
expect(screen.getByTestId("repository-name-tooltip")).toBeInTheDocument();
|
|
expect(screen.queryByText("user/repo1")).not.toBeInTheDocument();
|
|
|
|
// Remove "2" to make it "repo" again - should show repo2
|
|
await user.keyboard("{Backspace}");
|
|
expect(screen.getByTestId("repository-name-tooltip")).toBeInTheDocument();
|
|
expect(screen.queryByText("user/repo1")).not.toBeInTheDocument();
|
|
});
|
|
});
|
|
|
|
// Search conversations functionality tests
|
|
describe("Microagent management conversations functionality", () => {
|
|
it("should call useMicroagentManagementConversations API when repository is expanded", async () => {
|
|
const user = userEvent.setup();
|
|
renderMicroagentManagement();
|
|
|
|
// Wait for repositories to be loaded
|
|
await waitFor(() => {
|
|
expect(mockUseGitRepositories).toHaveBeenCalled();
|
|
});
|
|
|
|
// Find and click on the first repository accordion
|
|
const repoAccordion = screen.getByTestId("repository-name-tooltip");
|
|
await user.click(repoAccordion);
|
|
|
|
// Wait for both microagents and conversations to be fetched
|
|
await waitFor(() => {
|
|
expect(mockUseRepositoryMicroagents).toHaveBeenCalled();
|
|
expect(mockUseMicroagentManagementConversations).toHaveBeenCalled();
|
|
});
|
|
});
|
|
|
|
it("should display both microagents and conversations when repository is expanded", async () => {
|
|
const user = userEvent.setup();
|
|
renderMicroagentManagement();
|
|
|
|
// Wait for repositories to be loaded
|
|
await waitFor(() => {
|
|
expect(mockUseGitRepositories).toHaveBeenCalled();
|
|
});
|
|
|
|
// Find and click on the first repository accordion
|
|
const repoAccordion = screen.getByTestId("repository-name-tooltip");
|
|
await user.click(repoAccordion);
|
|
|
|
// Wait for both queries to complete
|
|
await waitFor(() => {
|
|
expect(mockUseRepositoryMicroagents).toHaveBeenCalled();
|
|
expect(mockUseMicroagentManagementConversations).toHaveBeenCalled();
|
|
});
|
|
|
|
// Check that microagents are displayed
|
|
const microagent1 = screen.getByText("test-microagent-1");
|
|
const microagent2 = screen.getByText("test-microagent-2");
|
|
|
|
expect(microagent1).toBeInTheDocument();
|
|
expect(microagent2).toBeInTheDocument();
|
|
|
|
// Check that conversations are displayed
|
|
const conversation1 = screen.getByText("Test Conversation 1");
|
|
const conversation2 = screen.getByText("Test Conversation 2");
|
|
|
|
expect(conversation1).toBeInTheDocument();
|
|
expect(conversation2).toBeInTheDocument();
|
|
});
|
|
|
|
it("should show loading state when both microagents and conversations are loading", async () => {
|
|
const user = userEvent.setup();
|
|
mockUseRepositoryMicroagents.mockReturnValue({
|
|
data: undefined,
|
|
isLoading: true,
|
|
isError: false,
|
|
});
|
|
mockUseMicroagentManagementConversations.mockReturnValue({
|
|
data: undefined,
|
|
isLoading: true,
|
|
isError: false,
|
|
});
|
|
|
|
renderMicroagentManagement();
|
|
|
|
// Wait for repositories to be loaded
|
|
await waitFor(() => {
|
|
expect(mockUseGitRepositories).toHaveBeenCalled();
|
|
});
|
|
|
|
// Find and click on the first repository accordion
|
|
const repoAccordion = screen.getByTestId("repository-name-tooltip");
|
|
await user.click(repoAccordion);
|
|
|
|
// Check that loading spinner is displayed
|
|
expect(screen.getByTestId("loading-spinner")).toBeInTheDocument();
|
|
});
|
|
|
|
it("should hide loading state when both queries complete", async () => {
|
|
const user = userEvent.setup();
|
|
const { container } = renderMicroagentManagement();
|
|
|
|
// Wait for repositories to be loaded
|
|
await waitFor(() => {
|
|
expect(mockUseGitRepositories).toHaveBeenCalled();
|
|
});
|
|
|
|
// Find and click on the first repository accordion
|
|
const repoAccordion = screen.getByTestId("repository-name-tooltip");
|
|
await user.click(repoAccordion);
|
|
|
|
// Wait for both queries to complete
|
|
await waitFor(() => {
|
|
expect(mockUseRepositoryMicroagents).toHaveBeenCalled();
|
|
expect(mockUseMicroagentManagementConversations).toHaveBeenCalled();
|
|
});
|
|
|
|
// Check that loading spinner is not displayed
|
|
expect(
|
|
container.querySelector(".animate-indeterminate-spinner"),
|
|
).toBeNull();
|
|
});
|
|
|
|
it("should display microagent file paths for microagents but not for conversations", async () => {
|
|
const user = userEvent.setup();
|
|
renderMicroagentManagement();
|
|
|
|
// Wait for repositories to be loaded
|
|
await waitFor(() => {
|
|
expect(mockUseGitRepositories).toHaveBeenCalled();
|
|
});
|
|
|
|
// Find and click on the first repository accordion
|
|
const repoAccordion = screen.getByTestId("repository-name-tooltip");
|
|
await user.click(repoAccordion);
|
|
|
|
// Wait for both queries to complete
|
|
await waitFor(() => {
|
|
expect(mockUseRepositoryMicroagents).toHaveBeenCalled();
|
|
expect(mockUseMicroagentManagementConversations).toHaveBeenCalled();
|
|
});
|
|
|
|
// Check that microagent file paths are displayed for microagents
|
|
const microagentFilePath1 = screen.getByText(
|
|
".openhands/microagents/test-microagent-1",
|
|
);
|
|
const microagentFilePath2 = screen.getByText(
|
|
".openhands/microagents/test-microagent-2",
|
|
);
|
|
|
|
expect(microagentFilePath1).toBeInTheDocument();
|
|
expect(microagentFilePath2).toBeInTheDocument();
|
|
|
|
// Check that microagent file paths are NOT displayed for conversations
|
|
expect(
|
|
screen.queryByText(".openhands/microagents/Test Conversation 1"),
|
|
).not.toBeInTheDocument();
|
|
expect(
|
|
screen.queryByText(".openhands/microagents/Test Conversation 2"),
|
|
).not.toBeInTheDocument();
|
|
});
|
|
|
|
it("should show learn this repo component when no microagents and no conversations", async () => {
|
|
const user = userEvent.setup();
|
|
mockUseRepositoryMicroagents.mockReturnValue({
|
|
data: [],
|
|
isLoading: false,
|
|
isError: false,
|
|
});
|
|
mockUseMicroagentManagementConversations.mockReturnValue({
|
|
data: [],
|
|
isLoading: false,
|
|
isError: false,
|
|
});
|
|
|
|
renderMicroagentManagement();
|
|
|
|
// Wait for repositories to be loaded
|
|
await waitFor(() => {
|
|
expect(mockUseGitRepositories).toHaveBeenCalled();
|
|
});
|
|
|
|
// Find and click on the first repository accordion
|
|
const repoAccordion = screen.getByTestId("repository-name-tooltip");
|
|
await user.click(repoAccordion);
|
|
|
|
// Wait for both queries to complete
|
|
await waitFor(() => {
|
|
expect(mockUseRepositoryMicroagents).toHaveBeenCalled();
|
|
expect(mockUseMicroagentManagementConversations).toHaveBeenCalled();
|
|
});
|
|
|
|
// Check that the learn this repo component is displayed
|
|
const learnThisRepo = screen.getByText(
|
|
"MICROAGENT_MANAGEMENT$LEARN_THIS_REPO",
|
|
);
|
|
expect(learnThisRepo).toBeInTheDocument();
|
|
});
|
|
|
|
it("should show learn this repo component when only conversations exist but no microagents", async () => {
|
|
const user = userEvent.setup();
|
|
mockUseRepositoryMicroagents.mockReturnValue({
|
|
data: [],
|
|
isLoading: false,
|
|
isError: false,
|
|
});
|
|
mockUseMicroagentManagementConversations.mockReturnValue({
|
|
data: [...mockConversations],
|
|
isLoading: false,
|
|
isError: false,
|
|
});
|
|
|
|
renderMicroagentManagement();
|
|
|
|
// Wait for repositories to be loaded
|
|
await waitFor(() => {
|
|
expect(mockUseGitRepositories).toHaveBeenCalled();
|
|
});
|
|
|
|
// Find and click on the first repository accordion
|
|
const repoAccordion = screen.getByTestId("repository-name-tooltip");
|
|
await user.click(repoAccordion);
|
|
|
|
// Wait for both queries to complete
|
|
await waitFor(() => {
|
|
expect(mockUseRepositoryMicroagents).toHaveBeenCalled();
|
|
expect(mockUseMicroagentManagementConversations).toHaveBeenCalled();
|
|
});
|
|
|
|
// Check that conversations are displayed
|
|
const conversation1 = screen.getByText("Test Conversation 1");
|
|
const conversation2 = screen.getByText("Test Conversation 2");
|
|
|
|
expect(conversation1).toBeInTheDocument();
|
|
expect(conversation2).toBeInTheDocument();
|
|
|
|
// Check that learn this repo component is NOT displayed (since we have conversations)
|
|
expect(
|
|
screen.queryByText("MICROAGENT_MANAGEMENT$LEARN_THIS_REPO"),
|
|
).not.toBeInTheDocument();
|
|
});
|
|
|
|
it("should show learn this repo component when only microagents exist but no conversations", async () => {
|
|
const user = userEvent.setup();
|
|
mockUseRepositoryMicroagents.mockReturnValue({
|
|
data: [...mockMicroagents],
|
|
isLoading: false,
|
|
isError: false,
|
|
});
|
|
mockUseMicroagentManagementConversations.mockReturnValue({
|
|
data: [],
|
|
isLoading: false,
|
|
isError: false,
|
|
});
|
|
|
|
renderMicroagentManagement();
|
|
|
|
// Wait for repositories to be loaded
|
|
await waitFor(() => {
|
|
expect(mockUseGitRepositories).toHaveBeenCalled();
|
|
});
|
|
|
|
// Find and click on the first repository accordion
|
|
const repoAccordion = screen.getByTestId("repository-name-tooltip");
|
|
await user.click(repoAccordion);
|
|
|
|
// Wait for both queries to complete
|
|
await waitFor(() => {
|
|
expect(mockUseRepositoryMicroagents).toHaveBeenCalled();
|
|
expect(mockUseMicroagentManagementConversations).toHaveBeenCalled();
|
|
});
|
|
|
|
// Check that microagents are displayed
|
|
const microagent1 = screen.getByText("test-microagent-1");
|
|
const microagent2 = screen.getByText("test-microagent-2");
|
|
|
|
expect(microagent1).toBeInTheDocument();
|
|
expect(microagent2).toBeInTheDocument();
|
|
|
|
// Check that learn this repo component is NOT displayed (since we have microagents)
|
|
expect(
|
|
screen.queryByText("MICROAGENT_MANAGEMENT$LEARN_THIS_REPO"),
|
|
).not.toBeInTheDocument();
|
|
});
|
|
|
|
it("should handle error when fetching conversations", async () => {
|
|
const user = userEvent.setup();
|
|
mockUseMicroagentManagementConversations.mockReturnValue({
|
|
data: undefined,
|
|
isLoading: false,
|
|
isError: true,
|
|
});
|
|
|
|
renderMicroagentManagement();
|
|
|
|
// Wait for repositories to be loaded
|
|
await waitFor(() => {
|
|
expect(mockUseGitRepositories).toHaveBeenCalled();
|
|
});
|
|
|
|
// Find and click on the first repository accordion
|
|
const repoAccordion = screen.getByTestId("repository-name-tooltip");
|
|
await user.click(repoAccordion);
|
|
|
|
// Wait for the error to be handled
|
|
await waitFor(() => {
|
|
expect(mockUseMicroagentManagementConversations).toHaveBeenCalled();
|
|
});
|
|
|
|
// Check that the learn this repo component is displayed (since conversations failed)
|
|
await waitFor(() => {
|
|
expect(
|
|
screen.getByText("MICROAGENT_MANAGEMENT$LEARN_THIS_REPO"),
|
|
).toBeInTheDocument();
|
|
});
|
|
|
|
// Also check that the microagents query was called successfully
|
|
expect(mockUseRepositoryMicroagents).toHaveBeenCalled();
|
|
});
|
|
|
|
it("should handle error when fetching microagents but conversations succeed", async () => {
|
|
const user = userEvent.setup();
|
|
mockUseRepositoryMicroagents.mockReturnValue({
|
|
data: undefined,
|
|
isLoading: false,
|
|
isError: true,
|
|
});
|
|
|
|
renderMicroagentManagement();
|
|
|
|
// Wait for repositories to be loaded
|
|
await waitFor(() => {
|
|
expect(mockUseGitRepositories).toHaveBeenCalled();
|
|
});
|
|
|
|
// Find and click on the first repository accordion
|
|
const repoAccordion = screen.getByTestId("repository-name-tooltip");
|
|
await user.click(repoAccordion);
|
|
|
|
// Wait for the error to be handled
|
|
await waitFor(() => {
|
|
expect(mockUseRepositoryMicroagents).toHaveBeenCalled();
|
|
});
|
|
|
|
// Check that the learn this repo component is displayed (since microagents failed)
|
|
const learnThisRepo = screen.getByText(
|
|
"MICROAGENT_MANAGEMENT$LEARN_THIS_REPO",
|
|
);
|
|
expect(learnThisRepo).toBeInTheDocument();
|
|
});
|
|
|
|
it("should call useMicroagentManagementConversations with correct parameters", async () => {
|
|
const user = userEvent.setup();
|
|
renderMicroagentManagement();
|
|
|
|
// Wait for repositories to be loaded
|
|
await waitFor(() => {
|
|
expect(mockUseGitRepositories).toHaveBeenCalled();
|
|
});
|
|
|
|
// Find and click on the first repository accordion
|
|
const repoAccordion = screen.getByTestId("repository-name-tooltip");
|
|
await user.click(repoAccordion);
|
|
|
|
// Wait for useMicroagentManagementConversations to be called
|
|
await waitFor(() => {
|
|
expect(mockUseMicroagentManagementConversations).toHaveBeenCalled();
|
|
});
|
|
});
|
|
|
|
it("should display conversations with correct information", async () => {
|
|
const user = userEvent.setup();
|
|
renderMicroagentManagement();
|
|
|
|
// Wait for repositories to be loaded
|
|
await waitFor(() => {
|
|
expect(mockUseGitRepositories).toHaveBeenCalled();
|
|
});
|
|
|
|
// Find and click on the first repository accordion
|
|
const repoAccordion = screen.getByTestId("repository-name-tooltip");
|
|
await user.click(repoAccordion);
|
|
|
|
// Wait for both queries to complete
|
|
await waitFor(() => {
|
|
expect(mockUseRepositoryMicroagents).toHaveBeenCalled();
|
|
expect(mockUseMicroagentManagementConversations).toHaveBeenCalled();
|
|
});
|
|
|
|
// Check that conversations display correct information
|
|
const conversation1 = screen.getByText("Test Conversation 1");
|
|
const conversation2 = screen.getByText("Test Conversation 2");
|
|
|
|
expect(conversation1).toBeInTheDocument();
|
|
expect(conversation2).toBeInTheDocument();
|
|
});
|
|
|
|
it("should handle multiple repository expansions with conversations", async () => {
|
|
const user = userEvent.setup();
|
|
renderMicroagentManagement();
|
|
|
|
// Wait for repositories to be loaded
|
|
await waitFor(() => {
|
|
expect(mockUseGitRepositories).toHaveBeenCalled();
|
|
});
|
|
|
|
// Find and click on the first repository accordion
|
|
const repoAccordion1 = screen.getByTestId("repository-name-tooltip");
|
|
await user.click(repoAccordion1);
|
|
|
|
// Wait for both queries to be called for first repo
|
|
await waitFor(() => {
|
|
expect(mockUseRepositoryMicroagents).toHaveBeenCalled();
|
|
expect(mockUseMicroagentManagementConversations).toHaveBeenCalled();
|
|
});
|
|
|
|
// Check that both microagents and conversations are displayed
|
|
expect(screen.getByText("test-microagent-1")).toBeInTheDocument();
|
|
expect(screen.getByText("test-microagent-2")).toBeInTheDocument();
|
|
expect(screen.getByText("Test Conversation 1")).toBeInTheDocument();
|
|
expect(screen.getByText("Test Conversation 2")).toBeInTheDocument();
|
|
});
|
|
});
|
|
|
|
// Add microagent integration tests
|
|
describe("Add microagent functionality", () => {
|
|
beforeEach(() => {
|
|
vi.spyOn(GitService, "getRepositoryBranches").mockResolvedValue({
|
|
branches: [{ name: "main", commit_sha: "abc123", protected: false }],
|
|
has_next_page: false,
|
|
current_page: 1,
|
|
per_page: 30,
|
|
total_count: [{ name: "main", commit_sha: "abc123", protected: false }]
|
|
.length,
|
|
});
|
|
});
|
|
|
|
it("should render add microagent button", async () => {
|
|
renderMicroagentManagement();
|
|
|
|
// Wait for repositories to be loaded and processed
|
|
await waitFor(() => {
|
|
expect(mockUseGitRepositories).toHaveBeenCalled();
|
|
});
|
|
|
|
// Wait for repositories to be displayed in the accordion
|
|
await waitFor(() => {
|
|
expect(
|
|
screen.getByTestId("repository-name-tooltip"),
|
|
).toBeInTheDocument();
|
|
});
|
|
|
|
// Check that add microagent buttons are present
|
|
const addButtons = screen.getAllByTestId("add-microagent-button");
|
|
expect(addButtons.length).toBeGreaterThan(0);
|
|
});
|
|
|
|
it("should open modal when add button is clicked", async () => {
|
|
const user = userEvent.setup();
|
|
renderMicroagentManagement();
|
|
|
|
// Wait for repositories to be loaded and processed
|
|
await waitFor(() => {
|
|
expect(mockUseGitRepositories).toHaveBeenCalled();
|
|
});
|
|
|
|
// Wait for repositories to be displayed in the accordion
|
|
await waitFor(() => {
|
|
expect(
|
|
screen.getByTestId("repository-name-tooltip"),
|
|
).toBeInTheDocument();
|
|
});
|
|
|
|
// Find and click the first add microagent button
|
|
const addButtons = screen.getAllByTestId("add-microagent-button");
|
|
await user.click(addButtons[0]);
|
|
|
|
// Check that the modal is opened
|
|
await waitFor(() => {
|
|
expect(screen.getByTestId("add-microagent-modal")).toBeInTheDocument();
|
|
});
|
|
});
|
|
|
|
it("should render modal when Zustand state is set to visible", async () => {
|
|
// Render with modal already visible in Zustand state
|
|
renderWithCustomStore({
|
|
addMicroagentModalVisible: true,
|
|
});
|
|
|
|
// Check that modal is rendered
|
|
expect(screen.getByTestId("add-microagent-modal")).toBeInTheDocument();
|
|
expect(screen.getByTestId("query-input")).toBeInTheDocument();
|
|
expect(screen.getByTestId("cancel-button")).toBeInTheDocument();
|
|
expect(screen.getByTestId("confirm-button")).toBeInTheDocument();
|
|
});
|
|
|
|
it("should display form fields in the modal", async () => {
|
|
const user = userEvent.setup();
|
|
renderMicroagentManagement();
|
|
|
|
// Wait for repositories to be loaded and processed
|
|
await waitFor(() => {
|
|
expect(mockUseGitRepositories).toHaveBeenCalled();
|
|
});
|
|
|
|
// Wait for repositories to be displayed in the accordion
|
|
await waitFor(() => {
|
|
expect(
|
|
screen.getByTestId("repository-name-tooltip"),
|
|
).toBeInTheDocument();
|
|
});
|
|
|
|
// Find and click the first add microagent button
|
|
const addButtons = screen.getAllByTestId("add-microagent-button");
|
|
await user.click(addButtons[0]);
|
|
|
|
// Wait for modal to be rendered and check form fields
|
|
await waitFor(() => {
|
|
expect(screen.getByTestId("add-microagent-modal")).toBeInTheDocument();
|
|
});
|
|
|
|
// Check that form fields are present
|
|
expect(screen.getByTestId("query-input")).toBeInTheDocument();
|
|
expect(screen.getByTestId("cancel-button")).toBeInTheDocument();
|
|
expect(screen.getByTestId("confirm-button")).toBeInTheDocument();
|
|
});
|
|
|
|
it("should disable confirm button when query is empty", async () => {
|
|
const user = userEvent.setup();
|
|
renderMicroagentManagement();
|
|
|
|
// Wait for repositories to be loaded and processed
|
|
await waitFor(() => {
|
|
expect(mockUseGitRepositories).toHaveBeenCalled();
|
|
});
|
|
|
|
// Wait for repositories to be displayed in the accordion
|
|
await waitFor(() => {
|
|
expect(
|
|
screen.getByTestId("repository-name-tooltip"),
|
|
).toBeInTheDocument();
|
|
});
|
|
|
|
// Find and click the first add microagent button
|
|
const addButtons = screen.getAllByTestId("add-microagent-button");
|
|
await user.click(addButtons[0]);
|
|
|
|
// Wait for modal to be rendered
|
|
await waitFor(() => {
|
|
expect(screen.getByTestId("add-microagent-modal")).toBeInTheDocument();
|
|
});
|
|
|
|
// Check that confirm button is disabled when query is empty
|
|
const confirmButton = screen.getByTestId("confirm-button");
|
|
expect(confirmButton).toBeDisabled();
|
|
});
|
|
|
|
it("should close modal when cancel button is clicked", async () => {
|
|
const user = userEvent.setup();
|
|
renderMicroagentManagement();
|
|
|
|
// Wait for repositories to be loaded and processed
|
|
await waitFor(() => {
|
|
expect(mockUseGitRepositories).toHaveBeenCalled();
|
|
});
|
|
|
|
// Wait for repositories to be displayed in the accordion
|
|
await waitFor(() => {
|
|
expect(
|
|
screen.getByTestId("repository-name-tooltip"),
|
|
).toBeInTheDocument();
|
|
});
|
|
|
|
// Find and click the first add microagent button
|
|
const addButtons = screen.getAllByTestId("add-microagent-button");
|
|
await user.click(addButtons[0]);
|
|
|
|
// Check that the modal is opened
|
|
await waitFor(() => {
|
|
expect(screen.getByTestId("add-microagent-modal")).toBeInTheDocument();
|
|
});
|
|
|
|
// Click the close button (X icon) - use the first one which should be the modal close button
|
|
const closeButtons = screen.getAllByRole("button", { name: "" });
|
|
const modalCloseButton = closeButtons.find(
|
|
(button) => button.querySelector('svg[height="24"]') !== null,
|
|
);
|
|
await user.click(modalCloseButton!);
|
|
|
|
// Check that modal is closed
|
|
await waitFor(() => {
|
|
expect(
|
|
screen.queryByTestId("add-microagent-modal"),
|
|
).not.toBeInTheDocument();
|
|
});
|
|
});
|
|
|
|
it("should enable confirm button when query is entered", async () => {
|
|
const user = userEvent.setup();
|
|
renderMicroagentManagement();
|
|
|
|
// Wait for repositories to be loaded and processed
|
|
await waitFor(() => {
|
|
expect(mockUseGitRepositories).toHaveBeenCalled();
|
|
});
|
|
|
|
// Wait for repositories to be displayed in the accordion
|
|
await waitFor(() => {
|
|
expect(
|
|
screen.getByTestId("repository-name-tooltip"),
|
|
).toBeInTheDocument();
|
|
});
|
|
|
|
// Find and click the first add microagent button
|
|
const addButtons = screen.getAllByTestId("add-microagent-button");
|
|
await user.click(addButtons[0]);
|
|
|
|
// Wait for modal to be rendered and branch to be selected
|
|
await waitFor(() => {
|
|
expect(screen.getByTestId("add-microagent-modal")).toBeInTheDocument();
|
|
});
|
|
// Enter query text
|
|
const queryInput = screen.getByTestId("query-input");
|
|
await user.type(queryInput, "Test query");
|
|
// Wait for the confirm button to be enabled after entering query and branch selection
|
|
await waitFor(() => {
|
|
const confirmButton = screen.getByTestId("confirm-button");
|
|
expect(confirmButton).not.toBeDisabled();
|
|
});
|
|
});
|
|
|
|
it("should prevent form submission when query is empty", async () => {
|
|
const user = userEvent.setup();
|
|
renderMicroagentManagement();
|
|
|
|
// Wait for repositories to be loaded and processed
|
|
await waitFor(() => {
|
|
expect(mockUseGitRepositories).toHaveBeenCalled();
|
|
});
|
|
|
|
// Wait for repositories to be displayed in the accordion
|
|
await waitFor(() => {
|
|
expect(
|
|
screen.getByTestId("repository-name-tooltip"),
|
|
).toBeInTheDocument();
|
|
});
|
|
|
|
// Find and click the first add microagent button
|
|
const addButtons = screen.getAllByTestId("add-microagent-button");
|
|
await user.click(addButtons[0]);
|
|
|
|
// Wait for modal to be rendered
|
|
await waitFor(() => {
|
|
expect(screen.getByTestId("add-microagent-modal")).toBeInTheDocument();
|
|
});
|
|
|
|
// Try to submit form with empty query
|
|
const confirmButton = screen.getByTestId("confirm-button");
|
|
await user.click(confirmButton);
|
|
|
|
// Check that modal is still open (form submission prevented)
|
|
expect(screen.getByTestId("add-microagent-modal")).toBeInTheDocument();
|
|
});
|
|
|
|
it("should trim whitespace from query before submission", async () => {
|
|
const user = userEvent.setup();
|
|
renderMicroagentManagement();
|
|
|
|
// Wait for repositories to be loaded and processed
|
|
await waitFor(() => {
|
|
expect(mockUseGitRepositories).toHaveBeenCalled();
|
|
});
|
|
|
|
// Wait for repositories to be displayed in the accordion
|
|
await waitFor(() => {
|
|
expect(
|
|
screen.getByTestId("repository-name-tooltip"),
|
|
).toBeInTheDocument();
|
|
});
|
|
|
|
// Find and click the first add microagent button
|
|
const addButtons = screen.getAllByTestId("add-microagent-button");
|
|
await user.click(addButtons[0]);
|
|
|
|
// Wait for modal to be rendered and branch to be selected
|
|
await waitFor(() => {
|
|
expect(screen.getByTestId("add-microagent-modal")).toBeInTheDocument();
|
|
});
|
|
// Enter query with whitespace
|
|
const queryInput = screen.getByTestId("query-input");
|
|
await user.type(queryInput, " Test query with whitespace ");
|
|
// Wait for the confirm button to be enabled after entering query and branch selection
|
|
await waitFor(() => {
|
|
const confirmButton = screen.getByTestId("confirm-button");
|
|
expect(confirmButton).not.toBeDisabled();
|
|
});
|
|
});
|
|
});
|
|
|
|
// MicroagentManagementMain component tests
|
|
describe("MicroagentManagementMain", () => {
|
|
const mockRepositoryMicroagent: RepositoryMicroagent = {
|
|
name: "test-microagent",
|
|
created_at: "2021-10-01T12:00:00Z",
|
|
git_provider: "github",
|
|
path: ".openhands/microagents/test-microagent",
|
|
};
|
|
|
|
const mockConversationWithPr: Conversation = {
|
|
conversation_id: "conv-with-pr",
|
|
title: "Test Conversation with PR",
|
|
selected_repository: "user/repo2/.openhands",
|
|
selected_branch: "main",
|
|
git_provider: "github",
|
|
last_updated_at: "2021-10-01T12:00:00Z",
|
|
created_at: "2021-10-01T12:00:00Z",
|
|
status: "RUNNING",
|
|
runtime_status: "STATUS$READY",
|
|
trigger: "microagent_management",
|
|
url: null,
|
|
session_api_key: null,
|
|
pr_number: [123],
|
|
};
|
|
|
|
const mockConversationWithoutPr: Conversation = {
|
|
conversation_id: "conv-without-pr",
|
|
title: "Test Conversation without PR",
|
|
selected_repository: "user/repo2/.openhands",
|
|
selected_branch: "main",
|
|
git_provider: "github",
|
|
last_updated_at: "2021-10-01T12:00:00Z",
|
|
created_at: "2021-10-01T12:00:00Z",
|
|
status: "RUNNING",
|
|
runtime_status: "STATUS$READY",
|
|
trigger: "microagent_management",
|
|
url: null,
|
|
session_api_key: null,
|
|
pr_number: [],
|
|
};
|
|
|
|
const mockConversationWithNullPr: Conversation = {
|
|
conversation_id: "conv-null-pr",
|
|
title: "Test Conversation with null PR",
|
|
selected_repository: "user/repo2/.openhands",
|
|
selected_branch: "main",
|
|
git_provider: "github",
|
|
last_updated_at: "2021-10-01T12:00:00Z",
|
|
created_at: "2021-10-01T12:00:00Z",
|
|
status: "RUNNING",
|
|
runtime_status: null,
|
|
trigger: "microagent_management",
|
|
url: null,
|
|
session_api_key: null,
|
|
pr_number: null,
|
|
};
|
|
|
|
const renderMicroagentManagementMain = (selectedMicroagentItem: any) => {
|
|
// Set the store with the selected microagent item and a repository
|
|
useMicroagentManagementStore.setState({
|
|
selectedMicroagentItem,
|
|
selectedRepository: testRepository,
|
|
});
|
|
|
|
return renderWithProviders(<MicroagentManagementMain />);
|
|
};
|
|
|
|
it("should render MicroagentManagementDefault when no microagent or conversation is selected", async () => {
|
|
renderMicroagentManagementMain(null);
|
|
|
|
// Check that the default component is rendered
|
|
await screen.findByText("MICROAGENT_MANAGEMENT$READY_TO_ADD_MICROAGENT");
|
|
expect(
|
|
screen.getByText(
|
|
"MICROAGENT_MANAGEMENT$OPENHANDS_CAN_LEARN_ABOUT_REPOSITORIES",
|
|
),
|
|
).toBeInTheDocument();
|
|
});
|
|
|
|
it("should render MicroagentManagementDefault when selectedMicroagentItem is empty object", async () => {
|
|
renderMicroagentManagementMain({});
|
|
|
|
// Check that the default component is rendered
|
|
await screen.findByText("MICROAGENT_MANAGEMENT$READY_TO_ADD_MICROAGENT");
|
|
expect(
|
|
screen.getByText(
|
|
"MICROAGENT_MANAGEMENT$OPENHANDS_CAN_LEARN_ABOUT_REPOSITORIES",
|
|
),
|
|
).toBeInTheDocument();
|
|
});
|
|
|
|
it("should render MicroagentManagementViewMicroagent when microagent is selected", async () => {
|
|
renderMicroagentManagementMain({
|
|
microagent: mockRepositoryMicroagent,
|
|
conversation: null,
|
|
});
|
|
|
|
// Check that the microagent view component is rendered
|
|
await screen.findByText("test-microagent");
|
|
expect(
|
|
screen.getByText(".openhands/microagents/test-microagent"),
|
|
).toBeInTheDocument();
|
|
});
|
|
|
|
it("should render MicroagentManagementOpeningPr when conversation is selected with empty pr_number array", async () => {
|
|
renderMicroagentManagementMain({
|
|
microagent: null,
|
|
conversation: mockConversationWithoutPr,
|
|
});
|
|
|
|
// Check that the opening PR component is rendered
|
|
await screen.findByText(
|
|
(content) => content === "COMMON$WORKING_ON_IT!",
|
|
{ exact: false },
|
|
);
|
|
expect(
|
|
screen.getByText("MICROAGENT_MANAGEMENT$WE_ARE_WORKING_ON_IT"),
|
|
).toBeInTheDocument();
|
|
expect(screen.getAllByTestId("view-conversation-button")).toHaveLength(1);
|
|
});
|
|
|
|
it("should render MicroagentManagementOpeningPr when conversation is selected with null pr_number", async () => {
|
|
const conversationWithNullPr = {
|
|
...mockConversationWithoutPr,
|
|
pr_number: null,
|
|
};
|
|
renderMicroagentManagementMain({
|
|
microagent: null,
|
|
conversation: conversationWithNullPr,
|
|
});
|
|
|
|
// Check that the opening PR component is rendered
|
|
await screen.findByText(
|
|
(content) => content === "COMMON$WORKING_ON_IT!",
|
|
{ exact: false },
|
|
);
|
|
expect(
|
|
screen.getByText("MICROAGENT_MANAGEMENT$WE_ARE_WORKING_ON_IT"),
|
|
).toBeInTheDocument();
|
|
expect(screen.getAllByTestId("view-conversation-button")).toHaveLength(1);
|
|
});
|
|
|
|
it("should render MicroagentManagementReviewPr when conversation is selected with non-empty pr_number array", async () => {
|
|
renderMicroagentManagementMain({
|
|
microagent: null,
|
|
conversation: mockConversationWithPr,
|
|
});
|
|
|
|
// Check that the review PR component is rendered
|
|
await screen.findByText("MICROAGENT_MANAGEMENT$YOUR_MICROAGENT_IS_READY");
|
|
expect(screen.getAllByTestId("view-conversation-button")).toHaveLength(2);
|
|
});
|
|
|
|
it("should prioritize microagent over conversation when both are present", async () => {
|
|
renderMicroagentManagementMain({
|
|
microagent: mockRepositoryMicroagent,
|
|
conversation: mockConversationWithPr,
|
|
});
|
|
|
|
// Should render the microagent view, not the conversation view
|
|
await screen.findByText("test-microagent");
|
|
expect(
|
|
screen.getByText(".openhands/microagents/test-microagent"),
|
|
).toBeInTheDocument();
|
|
|
|
// Should NOT render the review PR component
|
|
expect(
|
|
screen.queryByText("MICROAGENT_MANAGEMENT$YOUR_MICROAGENT_IS_READY"),
|
|
).not.toBeInTheDocument();
|
|
});
|
|
|
|
it("should handle conversation with undefined pr_number", async () => {
|
|
const conversationWithUndefinedPr = {
|
|
...mockConversationWithoutPr,
|
|
};
|
|
delete conversationWithUndefinedPr.pr_number;
|
|
|
|
renderMicroagentManagementMain({
|
|
microagent: null,
|
|
conversation: conversationWithUndefinedPr,
|
|
});
|
|
|
|
// Should render the opening PR component (treats undefined as empty array)
|
|
await screen.findByText(
|
|
(content) => content === "COMMON$WORKING_ON_IT!",
|
|
{ exact: false },
|
|
);
|
|
expect(
|
|
screen.getByText("MICROAGENT_MANAGEMENT$WE_ARE_WORKING_ON_IT"),
|
|
).toBeInTheDocument();
|
|
});
|
|
|
|
it("should handle conversation with multiple PR numbers", async () => {
|
|
const conversationWithMultiplePrs = {
|
|
...mockConversationWithPr,
|
|
pr_number: [123, 456, 789],
|
|
};
|
|
|
|
renderMicroagentManagementMain({
|
|
microagent: null,
|
|
conversation: conversationWithMultiplePrs,
|
|
});
|
|
|
|
// Should render the review PR component (non-empty array)
|
|
await screen.findByText("MICROAGENT_MANAGEMENT$YOUR_MICROAGENT_IS_READY");
|
|
expect(screen.getAllByTestId("view-conversation-button")).toHaveLength(2);
|
|
});
|
|
|
|
it("should handle conversation with empty string pr_number", async () => {
|
|
const conversationWithEmptyStringPr = {
|
|
...mockConversationWithoutPr,
|
|
pr_number: "",
|
|
};
|
|
|
|
renderMicroagentManagementMain({
|
|
microagent: null,
|
|
conversation: conversationWithEmptyStringPr,
|
|
});
|
|
|
|
// Should render the opening PR component (treats empty string as empty array)
|
|
await screen.findByText(
|
|
(content) => content === "COMMON$WORKING_ON_IT!",
|
|
{ exact: false },
|
|
);
|
|
expect(
|
|
screen.getByText("MICROAGENT_MANAGEMENT$WE_ARE_WORKING_ON_IT"),
|
|
).toBeInTheDocument();
|
|
});
|
|
|
|
it("should handle conversation with zero pr_number", async () => {
|
|
const conversationWithZeroPr = {
|
|
...mockConversationWithoutPr,
|
|
pr_number: 0,
|
|
};
|
|
|
|
renderMicroagentManagementMain({
|
|
microagent: null,
|
|
conversation: conversationWithZeroPr,
|
|
});
|
|
|
|
// Should render the opening PR component (treats 0 as falsy)
|
|
await screen.findByText(
|
|
(content) => content === "COMMON$WORKING_ON_IT!",
|
|
{ exact: false },
|
|
);
|
|
expect(
|
|
screen.getByText("MICROAGENT_MANAGEMENT$WE_ARE_WORKING_ON_IT"),
|
|
).toBeInTheDocument();
|
|
});
|
|
|
|
it("should handle conversation with single PR number as array", async () => {
|
|
const conversationWithSinglePr = {
|
|
...mockConversationWithPr,
|
|
pr_number: [42],
|
|
};
|
|
|
|
renderMicroagentManagementMain({
|
|
microagent: null,
|
|
conversation: conversationWithSinglePr,
|
|
});
|
|
|
|
// Should render the review PR component (non-empty array)
|
|
await screen.findByText("MICROAGENT_MANAGEMENT$YOUR_MICROAGENT_IS_READY");
|
|
expect(screen.getAllByTestId("view-conversation-button")).toHaveLength(2);
|
|
});
|
|
|
|
it("should handle edge case with null selectedMicroagentItem", async () => {
|
|
renderMicroagentManagementMain(null);
|
|
|
|
// Should render the default component
|
|
await screen.findByText("MICROAGENT_MANAGEMENT$READY_TO_ADD_MICROAGENT");
|
|
expect(
|
|
screen.getByText(
|
|
"MICROAGENT_MANAGEMENT$OPENHANDS_CAN_LEARN_ABOUT_REPOSITORIES",
|
|
),
|
|
).toBeInTheDocument();
|
|
});
|
|
|
|
it("should handle edge case with undefined selectedMicroagentItem", async () => {
|
|
renderMicroagentManagementMain(undefined);
|
|
|
|
// Should render the default component
|
|
await screen.findByText("MICROAGENT_MANAGEMENT$READY_TO_ADD_MICROAGENT");
|
|
expect(
|
|
screen.getByText(
|
|
"MICROAGENT_MANAGEMENT$OPENHANDS_CAN_LEARN_ABOUT_REPOSITORIES",
|
|
),
|
|
).toBeInTheDocument();
|
|
});
|
|
|
|
it("should handle conversation with missing pr_number property", async () => {
|
|
const conversationWithoutPrNumber = {
|
|
conversation_id: "conv-no-pr-number",
|
|
title: "Test Conversation without PR number property",
|
|
selected_repository: "user/repo2/.openhands",
|
|
selected_branch: "main",
|
|
git_provider: "github",
|
|
last_updated_at: "2021-10-01T12:00:00Z",
|
|
created_at: "2021-10-01T12:00:00Z",
|
|
status: "RUNNING",
|
|
runtime_status: "STATUS$READY",
|
|
trigger: "microagent_management",
|
|
url: null,
|
|
session_api_key: null,
|
|
// pr_number property is missing
|
|
};
|
|
|
|
renderMicroagentManagementMain({
|
|
microagent: null,
|
|
conversation: conversationWithoutPrNumber,
|
|
});
|
|
|
|
// Should render the opening PR component (undefined pr_number defaults to empty array)
|
|
await screen.findByText(
|
|
(content) => content === "COMMON$WORKING_ON_IT!",
|
|
{ exact: false },
|
|
);
|
|
expect(
|
|
screen.getByText("MICROAGENT_MANAGEMENT$WE_ARE_WORKING_ON_IT"),
|
|
).toBeInTheDocument();
|
|
});
|
|
|
|
it("should handle microagent with all required properties", async () => {
|
|
const completeMicroagent: RepositoryMicroagent = {
|
|
name: "complete-microagent",
|
|
created_at: "2021-10-01T12:00:00Z",
|
|
git_provider: "github",
|
|
path: ".openhands/microagents/complete-microagent",
|
|
};
|
|
|
|
renderMicroagentManagementMain({
|
|
microagent: completeMicroagent,
|
|
conversation: null,
|
|
});
|
|
|
|
// Check that the microagent view component is rendered with complete data
|
|
await screen.findByText("complete-microagent");
|
|
expect(
|
|
screen.getByText(".openhands/microagents/complete-microagent"),
|
|
).toBeInTheDocument();
|
|
});
|
|
|
|
it("should handle conversation with all required properties", async () => {
|
|
const completeConversation: Conversation = {
|
|
conversation_id: "complete-conversation",
|
|
title: "Complete Conversation",
|
|
selected_repository: "user/complete-repo/.openhands",
|
|
selected_branch: "main",
|
|
git_provider: "github",
|
|
last_updated_at: "2021-10-01T12:00:00Z",
|
|
created_at: "2021-10-01T12:00:00Z",
|
|
status: "RUNNING",
|
|
runtime_status: "STATUS$READY",
|
|
trigger: "microagent_management",
|
|
url: "https://example.com",
|
|
session_api_key: "test-api-key",
|
|
pr_number: [999],
|
|
};
|
|
|
|
renderMicroagentManagementMain({
|
|
microagent: null,
|
|
conversation: completeConversation,
|
|
});
|
|
|
|
// Check that the review PR component is rendered with complete data
|
|
await screen.findByText("MICROAGENT_MANAGEMENT$YOUR_MICROAGENT_IS_READY");
|
|
expect(screen.getAllByTestId("view-conversation-button")).toHaveLength(2);
|
|
});
|
|
});
|
|
|
|
// Update microagent functionality tests
|
|
describe("Update microagent functionality", () => {
|
|
const mockMicroagentForUpdate: RepositoryMicroagent = {
|
|
name: "update-test-microagent",
|
|
created_at: "2021-10-01T12:00:00Z",
|
|
git_provider: "github",
|
|
path: ".openhands/microagents/update-test-microagent",
|
|
};
|
|
|
|
beforeEach(() => {
|
|
vi.spyOn(GitService, "getRepositoryBranches").mockResolvedValue({
|
|
branches: [{ name: "main", commit_sha: "abc123", protected: false }],
|
|
has_next_page: false,
|
|
current_page: 1,
|
|
per_page: 30,
|
|
total_count: [{ name: "main", commit_sha: "abc123", protected: false }]
|
|
.length,
|
|
});
|
|
});
|
|
|
|
it("should render update microagent modal when updateMicroagentModalVisible is true", async () => {
|
|
// Render with update modal visible in Zustand state
|
|
renderWithUpdateModal();
|
|
|
|
// Check that update modal is rendered
|
|
expect(screen.getByTestId("add-microagent-modal")).toBeInTheDocument();
|
|
expect(screen.getByTestId("query-input")).toBeInTheDocument();
|
|
expect(screen.getByTestId("cancel-button")).toBeInTheDocument();
|
|
expect(screen.getByTestId("confirm-button")).toBeInTheDocument();
|
|
});
|
|
|
|
it("should display update microagent title when isUpdate is true", async () => {
|
|
// Render with update modal visible and selected microagent
|
|
renderWithUpdateModal();
|
|
|
|
// Check that the update title is displayed
|
|
expect(
|
|
screen.getByText("MICROAGENT_MANAGEMENT$UPDATE_MICROAGENT"),
|
|
).toBeInTheDocument();
|
|
});
|
|
|
|
it("should populate form fields with existing microagent data when updating", async () => {
|
|
// Render with update modal visible and selected microagent
|
|
renderWithUpdateModal({
|
|
selectedMicroagentItem: {
|
|
microagent: mockMicroagentForUpdate,
|
|
conversation: null,
|
|
},
|
|
});
|
|
|
|
// Wait for the content to be loaded and form fields to be populated
|
|
await waitFor(() => {
|
|
const queryInput = screen.getByTestId("query-input");
|
|
expect(queryInput).toHaveValue(
|
|
"Original microagent content for testing updates",
|
|
);
|
|
});
|
|
});
|
|
|
|
it("should handle update microagent form submission", async () => {
|
|
const user = userEvent.setup();
|
|
|
|
// Render with update modal visible and selected microagent
|
|
renderWithUpdateModal();
|
|
|
|
// Wait for modal to be rendered
|
|
await waitFor(() => {
|
|
expect(screen.getByTestId("add-microagent-modal")).toBeInTheDocument();
|
|
});
|
|
|
|
// Modify the content
|
|
const queryInput = screen.getByTestId("query-input");
|
|
await user.clear(queryInput);
|
|
await user.type(queryInput, "Updated microagent content");
|
|
|
|
// Submit the form
|
|
const confirmButton = screen.getByTestId("confirm-button");
|
|
await user.click(confirmButton);
|
|
|
|
// Wait for the modal to be removed after form submission
|
|
await waitFor(() => {
|
|
expect(
|
|
screen.queryByTestId("add-microagent-modal"),
|
|
).not.toBeInTheDocument();
|
|
});
|
|
});
|
|
|
|
it("should close update modal when cancel button is clicked", async () => {
|
|
const user = userEvent.setup();
|
|
|
|
// Render with update modal visible
|
|
renderWithUpdateModal();
|
|
|
|
// Wait for modal to be rendered
|
|
await waitFor(() => {
|
|
expect(screen.getByTestId("add-microagent-modal")).toBeInTheDocument();
|
|
});
|
|
|
|
// Click the cancel button
|
|
const cancelButton = screen.getByTestId("cancel-button");
|
|
await user.click(cancelButton);
|
|
|
|
// Check that modal is closed
|
|
await waitFor(() => {
|
|
expect(
|
|
screen.queryByTestId("add-microagent-modal"),
|
|
).not.toBeInTheDocument();
|
|
});
|
|
});
|
|
|
|
it("should close update modal when close button (X) is clicked", async () => {
|
|
const user = userEvent.setup();
|
|
|
|
// Render with update modal visible
|
|
renderWithUpdateModal();
|
|
|
|
// Wait for modal to be rendered
|
|
await waitFor(() => {
|
|
expect(screen.getByTestId("add-microagent-modal")).toBeInTheDocument();
|
|
});
|
|
|
|
// Click the close button (X icon) - use the first one which should be the modal close button
|
|
const closeButtons = screen.getAllByRole("button", { name: "" });
|
|
const modalCloseButton = closeButtons.find(
|
|
(button) =>
|
|
button.querySelector('svg[height="24"]') !== null &&
|
|
!button.hasAttribute("data-testid"),
|
|
);
|
|
await user.click(modalCloseButton!);
|
|
|
|
// Check that modal is closed
|
|
await waitFor(() => {
|
|
expect(
|
|
screen.queryByTestId("add-microagent-modal"),
|
|
).not.toBeInTheDocument();
|
|
});
|
|
});
|
|
|
|
it("should handle update modal with empty microagent data", async () => {
|
|
// Render with update modal visible but no microagent data
|
|
renderWithUpdateModal();
|
|
|
|
// Check that update modal is still rendered
|
|
expect(screen.getByTestId("add-microagent-modal")).toBeInTheDocument();
|
|
expect(
|
|
screen.getByText("MICROAGENT_MANAGEMENT$UPDATE_MICROAGENT"),
|
|
).toBeInTheDocument();
|
|
});
|
|
|
|
it("should handle update modal with microagent that has no content", async () => {
|
|
const user = userEvent.setup();
|
|
|
|
// Mock the content API to return empty content for this test
|
|
vi.spyOn(GitService, "getRepositoryMicroagentContent").mockResolvedValue({
|
|
content: "",
|
|
path: ".openhands/microagents/update-test-microagent",
|
|
git_provider: "github",
|
|
triggers: [],
|
|
});
|
|
|
|
// Render with update modal visible and microagent
|
|
renderWithUpdateModal();
|
|
|
|
// Wait for the content to be loaded and check that the form field is empty
|
|
await waitFor(() => {
|
|
const queryInput = screen.getByTestId("query-input");
|
|
expect(queryInput).toHaveValue("");
|
|
});
|
|
});
|
|
|
|
it("should handle update modal with microagent that has no triggers", async () => {
|
|
const user = userEvent.setup();
|
|
|
|
// Mock the content API to return content without triggers for this test
|
|
vi.spyOn(GitService, "getRepositoryMicroagentContent").mockResolvedValue({
|
|
content: "Original microagent content for testing updates",
|
|
path: ".openhands/microagents/update-test-microagent",
|
|
git_provider: "github",
|
|
triggers: [],
|
|
});
|
|
|
|
// Render with update modal visible and microagent
|
|
renderWithUpdateModal();
|
|
|
|
// Check that the modal is rendered correctly
|
|
expect(screen.getByTestId("add-microagent-modal")).toBeInTheDocument();
|
|
expect(
|
|
screen.getByText("MICROAGENT_MANAGEMENT$UPDATE_MICROAGENT"),
|
|
).toBeInTheDocument();
|
|
});
|
|
});
|
|
|
|
// Learn this repo functionality tests
|
|
describe("Learn this repo functionality", () => {
|
|
it("should display learn this repo trigger when no microagents exist", async () => {
|
|
const user = userEvent.setup();
|
|
|
|
// Setup mocks before rendering
|
|
mockUseRepositoryMicroagents.mockReturnValue({
|
|
data: [],
|
|
isLoading: false,
|
|
isError: false,
|
|
});
|
|
mockUseMicroagentManagementConversations.mockReturnValue({
|
|
data: [],
|
|
isLoading: false,
|
|
isError: false,
|
|
});
|
|
|
|
renderMicroagentManagement();
|
|
|
|
// Wait for repositories to be loaded
|
|
await waitFor(() => {
|
|
expect(mockUseGitRepositories).toHaveBeenCalled();
|
|
});
|
|
|
|
// Find and click on the first repository accordion to expand it
|
|
const repoAccordion = screen.getByTestId("repository-name-tooltip");
|
|
await user.click(repoAccordion);
|
|
|
|
// Wait for microagents and conversations to be fetched
|
|
await waitFor(() => {
|
|
expect(mockUseRepositoryMicroagents).toHaveBeenCalled();
|
|
expect(mockUseMicroagentManagementConversations).toHaveBeenCalled();
|
|
});
|
|
|
|
// Verify the learn this repo trigger is displayed when no microagents exist
|
|
await waitFor(() => {
|
|
expect(
|
|
screen.getByTestId("learn-this-repo-trigger"),
|
|
).toBeInTheDocument();
|
|
});
|
|
|
|
// Verify trigger has correct text content
|
|
expect(screen.getByTestId("learn-this-repo-trigger")).toHaveTextContent(
|
|
"MICROAGENT_MANAGEMENT$LEARN_THIS_REPO",
|
|
);
|
|
});
|
|
|
|
it("should trigger learn this repo modal opening when trigger is clicked", async () => {
|
|
const user = userEvent.setup();
|
|
|
|
// Setup mocks
|
|
mockUseRepositoryMicroagents.mockReturnValue({
|
|
data: [],
|
|
isLoading: false,
|
|
isError: false,
|
|
});
|
|
mockUseMicroagentManagementConversations.mockReturnValue({
|
|
data: [],
|
|
isLoading: false,
|
|
isError: false,
|
|
});
|
|
|
|
renderMicroagentManagement();
|
|
|
|
// Wait for repositories and expand accordion
|
|
await waitFor(() => {
|
|
expect(mockUseGitRepositories).toHaveBeenCalled();
|
|
});
|
|
|
|
const repoAccordion = screen.getByTestId("repository-name-tooltip");
|
|
await user.click(repoAccordion);
|
|
|
|
await waitFor(() => {
|
|
expect(
|
|
screen.getByTestId("learn-this-repo-trigger"),
|
|
).toBeInTheDocument();
|
|
});
|
|
|
|
// Verify the trigger is clickable and has correct behavior
|
|
const learnThisRepoTrigger = screen.getByTestId(
|
|
"learn-this-repo-trigger",
|
|
);
|
|
|
|
// Verify the trigger has the expected text content
|
|
expect(learnThisRepoTrigger).toHaveTextContent(
|
|
"MICROAGENT_MANAGEMENT$LEARN_THIS_REPO",
|
|
);
|
|
|
|
// Click the trigger should not throw an error
|
|
await user.click(learnThisRepoTrigger);
|
|
|
|
// The trigger should still be present after click (testing that click is handled gracefully)
|
|
expect(learnThisRepoTrigger).toBeInTheDocument();
|
|
});
|
|
|
|
it("should show learn this repo trigger only when no microagents or conversations exist", async () => {
|
|
const user = userEvent.setup();
|
|
|
|
// Setup mocks with existing microagents (should NOT show trigger)
|
|
mockUseRepositoryMicroagents.mockReturnValue({
|
|
data: [
|
|
{
|
|
name: "test-microagent",
|
|
created_at: "2021-10-01",
|
|
git_provider: "github",
|
|
path: ".openhands/microagents/test",
|
|
},
|
|
],
|
|
isLoading: false,
|
|
isError: false,
|
|
});
|
|
mockUseMicroagentManagementConversations.mockReturnValue({
|
|
data: [],
|
|
isLoading: false,
|
|
isError: false,
|
|
});
|
|
|
|
renderMicroagentManagement();
|
|
|
|
await waitFor(() => {
|
|
expect(mockUseGitRepositories).toHaveBeenCalled();
|
|
});
|
|
|
|
const repoAccordion = screen.getByTestId("repository-name-tooltip");
|
|
await user.click(repoAccordion);
|
|
|
|
await waitFor(() => {
|
|
expect(mockUseRepositoryMicroagents).toHaveBeenCalled();
|
|
expect(mockUseMicroagentManagementConversations).toHaveBeenCalled();
|
|
});
|
|
|
|
// Should NOT show the learn this repo trigger when microagents exist
|
|
expect(
|
|
screen.queryByTestId("learn-this-repo-trigger"),
|
|
).not.toBeInTheDocument();
|
|
});
|
|
});
|
|
|
|
// Learn something new button functionality tests
|
|
describe("Learn something new button functionality", () => {
|
|
const mockMicroagentForLearn: RepositoryMicroagent = {
|
|
name: "learn-test-microagent",
|
|
created_at: "2021-10-01T12:00:00Z",
|
|
git_provider: "github",
|
|
path: ".openhands/microagents/learn-test-microagent",
|
|
};
|
|
|
|
it("should render learn something new button in microagent view", async () => {
|
|
// Render with selected microagent
|
|
renderWithSelectedMicroagent(mockMicroagentForLearn);
|
|
|
|
// Check that the learn something new button is displayed
|
|
expect(
|
|
screen.getByText("COMMON$LEARN_SOMETHING_NEW"),
|
|
).toBeInTheDocument();
|
|
});
|
|
|
|
it("should open update modal when learn something new button is clicked", async () => {
|
|
const user = userEvent.setup();
|
|
|
|
// Render with selected microagent
|
|
renderWithSelectedMicroagent(mockMicroagentForLearn);
|
|
|
|
// Find and click the learn something new button
|
|
const learnButton = screen.getByText("COMMON$LEARN_SOMETHING_NEW");
|
|
await user.click(learnButton);
|
|
|
|
// Check that the update modal is opened
|
|
await waitFor(() => {
|
|
expect(screen.getByTestId("add-microagent-modal")).toBeInTheDocument();
|
|
});
|
|
|
|
// Check that the update title is displayed
|
|
expect(
|
|
screen.getByText("MICROAGENT_MANAGEMENT$UPDATE_MICROAGENT"),
|
|
).toBeInTheDocument();
|
|
});
|
|
|
|
it("should populate form fields with current microagent data when learn button is clicked", async () => {
|
|
const user = userEvent.setup();
|
|
|
|
// Mock the content API to return the expected content for this test
|
|
vi.spyOn(GitService, "getRepositoryMicroagentContent").mockResolvedValue({
|
|
content: "Test microagent content for learn functionality",
|
|
path: ".openhands/microagents/learn-test-microagent",
|
|
git_provider: "github",
|
|
triggers: ["learn", "test"],
|
|
});
|
|
|
|
// Render with selected microagent
|
|
renderWithSelectedMicroagent(mockMicroagentForLearn);
|
|
|
|
// Find and click the learn something new button
|
|
const learnButton = screen.getByText("COMMON$LEARN_SOMETHING_NEW");
|
|
await user.click(learnButton);
|
|
|
|
// Wait for modal to be rendered
|
|
await waitFor(() => {
|
|
expect(screen.getByTestId("add-microagent-modal")).toBeInTheDocument();
|
|
});
|
|
|
|
// Wait for the content to be loaded and form to be populated
|
|
await waitFor(() => {
|
|
const queryInput = screen.getByTestId("query-input");
|
|
expect(queryInput).toHaveValue(
|
|
"Test microagent content for learn functionality",
|
|
);
|
|
});
|
|
});
|
|
|
|
it("should handle learn button click with microagent that has no content", async () => {
|
|
const user = userEvent.setup();
|
|
|
|
// Mock the content API to return empty content for this test
|
|
vi.spyOn(GitService, "getRepositoryMicroagentContent").mockResolvedValue({
|
|
content: "",
|
|
path: ".openhands/microagents/learn-test-microagent",
|
|
git_provider: "github",
|
|
triggers: [],
|
|
});
|
|
|
|
// Render with selected microagent
|
|
renderWithSelectedMicroagent(mockMicroagentForLearn);
|
|
|
|
// Find and click the learn something new button
|
|
const learnButton = screen.getByText("COMMON$LEARN_SOMETHING_NEW");
|
|
await user.click(learnButton);
|
|
|
|
// Wait for modal to be rendered
|
|
await waitFor(() => {
|
|
expect(screen.getByTestId("add-microagent-modal")).toBeInTheDocument();
|
|
});
|
|
|
|
// Wait for the content to be loaded and check that the form field is empty
|
|
await waitFor(() => {
|
|
const queryInput = screen.getByTestId("query-input");
|
|
expect(queryInput).toHaveValue("");
|
|
});
|
|
});
|
|
|
|
it("should handle learn button click with microagent that has no triggers", async () => {
|
|
const user = userEvent.setup();
|
|
|
|
// Mock the content API to return content without triggers for this test
|
|
vi.spyOn(GitService, "getRepositoryMicroagentContent").mockResolvedValue({
|
|
content: "Test microagent content for learn functionality",
|
|
path: ".openhands/microagents/learn-test-microagent",
|
|
git_provider: "github",
|
|
triggers: [],
|
|
});
|
|
|
|
// Render with selected microagent
|
|
renderWithSelectedMicroagent(mockMicroagentForLearn);
|
|
|
|
// Find and click the learn something new button
|
|
const learnButton = screen.getByText("COMMON$LEARN_SOMETHING_NEW");
|
|
await user.click(learnButton);
|
|
|
|
// Wait for modal to be rendered
|
|
await waitFor(() => {
|
|
expect(screen.getByTestId("add-microagent-modal")).toBeInTheDocument();
|
|
});
|
|
|
|
// Check that the update modal is opened correctly
|
|
expect(
|
|
screen.getByText("MICROAGENT_MANAGEMENT$UPDATE_MICROAGENT"),
|
|
).toBeInTheDocument();
|
|
});
|
|
});
|
|
});
|