mirror of
https://github.com/OpenHands/OpenHands.git
synced 2025-12-26 05:48:36 +08:00
411 lines
12 KiB
TypeScript
411 lines
12 KiB
TypeScript
import { screen } from "@testing-library/react";
|
|
import userEvent from "@testing-library/user-event";
|
|
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
import { renderWithProviders } from "test-utils";
|
|
import { ServerStatus } from "#/components/features/controls/server-status";
|
|
import { ServerStatusContextMenu } from "#/components/features/controls/server-status-context-menu";
|
|
import { ConversationStatus } from "#/types/conversation-status";
|
|
import { AgentState } from "#/types/agent-state";
|
|
import { useAgentStore } from "#/stores/agent-store";
|
|
|
|
// Mock the agent store
|
|
vi.mock("#/stores/agent-store", () => ({
|
|
useAgentStore: vi.fn(),
|
|
}));
|
|
|
|
// Mock the custom hooks
|
|
const mockStartConversationMutate = vi.fn();
|
|
const mockStopConversationMutate = vi.fn();
|
|
|
|
vi.mock("#/hooks/mutation/use-start-conversation", () => ({
|
|
useStartConversation: () => ({
|
|
mutate: mockStartConversationMutate,
|
|
}),
|
|
}));
|
|
|
|
vi.mock("#/hooks/mutation/use-stop-conversation", () => ({
|
|
useStopConversation: () => ({
|
|
mutate: mockStopConversationMutate,
|
|
}),
|
|
}));
|
|
|
|
vi.mock("#/hooks/use-conversation-id", () => ({
|
|
useConversationId: () => ({
|
|
conversationId: "test-conversation-id",
|
|
}),
|
|
}));
|
|
|
|
vi.mock("#/hooks/use-user-providers", () => ({
|
|
useUserProviders: () => ({
|
|
providers: [],
|
|
}),
|
|
}));
|
|
|
|
// Mock react-i18next
|
|
vi.mock("react-i18next", async () => {
|
|
const actual = await vi.importActual("react-i18next");
|
|
return {
|
|
...actual,
|
|
useTranslation: () => ({
|
|
t: (key: string) => {
|
|
const translations: Record<string, string> = {
|
|
COMMON$RUNNING: "Running",
|
|
COMMON$SERVER_STOPPED: "Server Stopped",
|
|
COMMON$ERROR: "Error",
|
|
COMMON$STARTING: "Starting",
|
|
COMMON$STOP_RUNTIME: "Stop Runtime",
|
|
COMMON$START_RUNTIME: "Start Runtime",
|
|
};
|
|
return translations[key] || key;
|
|
},
|
|
i18n: {
|
|
changeLanguage: () => new Promise(() => {}),
|
|
},
|
|
}),
|
|
};
|
|
});
|
|
|
|
describe("ServerStatus", () => {
|
|
// Helper function to mock agent store with specific state
|
|
const mockAgentStore = (agentState: AgentState) => {
|
|
vi.mocked(useAgentStore).mockReturnValue({
|
|
curAgentState: agentState,
|
|
setCurrentAgentState: vi.fn(),
|
|
reset: vi.fn(),
|
|
});
|
|
};
|
|
|
|
afterEach(() => {
|
|
vi.clearAllMocks();
|
|
});
|
|
|
|
it("should render server status with different conversation statuses", () => {
|
|
// Mock agent store to return RUNNING state
|
|
mockAgentStore(AgentState.RUNNING);
|
|
|
|
// Test RUNNING status
|
|
const { rerender } = renderWithProviders(
|
|
<ServerStatus conversationStatus="RUNNING" />,
|
|
);
|
|
expect(screen.getByText("Running")).toBeInTheDocument();
|
|
|
|
// Test STOPPED status
|
|
rerender(<ServerStatus conversationStatus="STOPPED" />);
|
|
expect(screen.getByText("Server Stopped")).toBeInTheDocument();
|
|
|
|
// Test STARTING status (shows "Running" due to agent state being RUNNING)
|
|
rerender(<ServerStatus conversationStatus="STARTING" />);
|
|
expect(screen.getByText("Running")).toBeInTheDocument();
|
|
|
|
// Test null status (shows "Running" due to agent state being RUNNING)
|
|
rerender(<ServerStatus conversationStatus={null} />);
|
|
expect(screen.getByText("Running")).toBeInTheDocument();
|
|
});
|
|
|
|
it("should show context menu when clicked with RUNNING status", async () => {
|
|
const user = userEvent.setup();
|
|
|
|
// Mock agent store to return RUNNING state
|
|
mockAgentStore(AgentState.RUNNING);
|
|
|
|
renderWithProviders(<ServerStatus conversationStatus="RUNNING" />);
|
|
|
|
const statusContainer = screen.getByText("Running").closest("div");
|
|
expect(statusContainer).toBeInTheDocument();
|
|
|
|
await user.click(statusContainer!);
|
|
|
|
// Context menu should appear
|
|
expect(
|
|
screen.getByTestId("server-status-context-menu"),
|
|
).toBeInTheDocument();
|
|
expect(screen.getByTestId("stop-server-button")).toBeInTheDocument();
|
|
});
|
|
|
|
it("should show context menu when clicked with STOPPED status", async () => {
|
|
const user = userEvent.setup();
|
|
|
|
// Mock agent store to return STOPPED state
|
|
mockAgentStore(AgentState.STOPPED);
|
|
|
|
renderWithProviders(<ServerStatus conversationStatus="STOPPED" />);
|
|
|
|
const statusContainer = screen.getByText("Server Stopped").closest("div");
|
|
expect(statusContainer).toBeInTheDocument();
|
|
|
|
await user.click(statusContainer!);
|
|
|
|
// Context menu should appear
|
|
expect(
|
|
screen.getByTestId("server-status-context-menu"),
|
|
).toBeInTheDocument();
|
|
expect(screen.getByTestId("start-server-button")).toBeInTheDocument();
|
|
});
|
|
|
|
it("should not show context menu when clicked with other statuses", async () => {
|
|
const user = userEvent.setup();
|
|
|
|
// Mock agent store to return RUNNING state
|
|
mockAgentStore(AgentState.RUNNING);
|
|
|
|
renderWithProviders(<ServerStatus conversationStatus="STARTING" />);
|
|
|
|
const statusContainer = screen.getByText("Running").closest("div");
|
|
expect(statusContainer).toBeInTheDocument();
|
|
|
|
await user.click(statusContainer!);
|
|
|
|
// Context menu should not appear
|
|
expect(
|
|
screen.queryByTestId("server-status-context-menu"),
|
|
).not.toBeInTheDocument();
|
|
});
|
|
|
|
it("should call stop conversation mutation when stop server is clicked", async () => {
|
|
const user = userEvent.setup();
|
|
|
|
// Clear previous calls
|
|
mockStopConversationMutate.mockClear();
|
|
|
|
// Mock agent store to return RUNNING state
|
|
mockAgentStore(AgentState.RUNNING);
|
|
|
|
renderWithProviders(<ServerStatus conversationStatus="RUNNING" />);
|
|
|
|
const statusContainer = screen.getByText("Running").closest("div");
|
|
await user.click(statusContainer!);
|
|
|
|
const stopButton = screen.getByTestId("stop-server-button");
|
|
await user.click(stopButton);
|
|
|
|
expect(mockStopConversationMutate).toHaveBeenCalledWith({
|
|
conversationId: "test-conversation-id",
|
|
});
|
|
});
|
|
|
|
it("should call start conversation mutation when start server is clicked", async () => {
|
|
const user = userEvent.setup();
|
|
|
|
// Clear previous calls
|
|
mockStartConversationMutate.mockClear();
|
|
|
|
// Mock agent store to return STOPPED state
|
|
mockAgentStore(AgentState.STOPPED);
|
|
|
|
renderWithProviders(<ServerStatus conversationStatus="STOPPED" />);
|
|
|
|
const statusContainer = screen.getByText("Server Stopped").closest("div");
|
|
await user.click(statusContainer!);
|
|
|
|
const startButton = screen.getByTestId("start-server-button");
|
|
await user.click(startButton);
|
|
|
|
expect(mockStartConversationMutate).toHaveBeenCalledWith({
|
|
conversationId: "test-conversation-id",
|
|
providers: [],
|
|
});
|
|
});
|
|
|
|
it("should close context menu after stop server action", async () => {
|
|
const user = userEvent.setup();
|
|
|
|
// Mock agent store to return RUNNING state
|
|
mockAgentStore(AgentState.RUNNING);
|
|
|
|
renderWithProviders(<ServerStatus conversationStatus="RUNNING" />);
|
|
|
|
const statusContainer = screen.getByText("Running").closest("div");
|
|
await user.click(statusContainer!);
|
|
|
|
const stopButton = screen.getByTestId("stop-server-button");
|
|
await user.click(stopButton);
|
|
|
|
// Context menu should be closed (handled by the component)
|
|
expect(mockStopConversationMutate).toHaveBeenCalledWith({
|
|
conversationId: "test-conversation-id",
|
|
});
|
|
});
|
|
|
|
it("should close context menu after start server action", async () => {
|
|
const user = userEvent.setup();
|
|
|
|
// Mock agent store to return STOPPED state
|
|
mockAgentStore(AgentState.STOPPED);
|
|
|
|
renderWithProviders(<ServerStatus conversationStatus="STOPPED" />);
|
|
|
|
const statusContainer = screen.getByText("Server Stopped").closest("div");
|
|
await user.click(statusContainer!);
|
|
|
|
const startButton = screen.getByTestId("start-server-button");
|
|
await user.click(startButton);
|
|
|
|
// Context menu should be closed
|
|
expect(
|
|
screen.queryByTestId("server-status-context-menu"),
|
|
).not.toBeInTheDocument();
|
|
});
|
|
|
|
it("should handle null conversation status", () => {
|
|
// Mock agent store to return RUNNING state
|
|
mockAgentStore(AgentState.RUNNING);
|
|
|
|
renderWithProviders(<ServerStatus conversationStatus={null} />);
|
|
|
|
const statusText = screen.getByText("Running");
|
|
expect(statusText).toBeInTheDocument();
|
|
});
|
|
});
|
|
|
|
describe("ServerStatusContextMenu", () => {
|
|
const defaultProps = {
|
|
onClose: vi.fn(),
|
|
conversationStatus: "RUNNING" as ConversationStatus,
|
|
};
|
|
|
|
afterEach(() => {
|
|
vi.clearAllMocks();
|
|
});
|
|
|
|
it("should render stop server button when status is RUNNING", () => {
|
|
renderWithProviders(
|
|
<ServerStatusContextMenu
|
|
{...defaultProps}
|
|
conversationStatus="RUNNING"
|
|
onStopServer={vi.fn()}
|
|
/>,
|
|
);
|
|
|
|
expect(screen.getByTestId("stop-server-button")).toBeInTheDocument();
|
|
expect(screen.getByText("Stop Runtime")).toBeInTheDocument();
|
|
});
|
|
|
|
it("should render start server button when status is STOPPED", () => {
|
|
renderWithProviders(
|
|
<ServerStatusContextMenu
|
|
{...defaultProps}
|
|
conversationStatus="STOPPED"
|
|
onStartServer={vi.fn()}
|
|
/>,
|
|
);
|
|
|
|
expect(screen.getByTestId("start-server-button")).toBeInTheDocument();
|
|
expect(screen.getByText("Start Runtime")).toBeInTheDocument();
|
|
});
|
|
|
|
it("should not render stop server button when onStopServer is not provided", () => {
|
|
renderWithProviders(
|
|
<ServerStatusContextMenu
|
|
{...defaultProps}
|
|
conversationStatus="RUNNING"
|
|
/>,
|
|
);
|
|
|
|
expect(screen.queryByTestId("stop-server-button")).not.toBeInTheDocument();
|
|
});
|
|
|
|
it("should not render start server button when onStartServer is not provided", () => {
|
|
renderWithProviders(
|
|
<ServerStatusContextMenu
|
|
{...defaultProps}
|
|
conversationStatus="STOPPED"
|
|
/>,
|
|
);
|
|
|
|
expect(screen.queryByTestId("start-server-button")).not.toBeInTheDocument();
|
|
});
|
|
|
|
it("should call onStopServer when stop button is clicked", async () => {
|
|
const user = userEvent.setup();
|
|
const onStopServer = vi.fn();
|
|
|
|
renderWithProviders(
|
|
<ServerStatusContextMenu
|
|
{...defaultProps}
|
|
conversationStatus="RUNNING"
|
|
onStopServer={onStopServer}
|
|
/>,
|
|
);
|
|
|
|
const stopButton = screen.getByTestId("stop-server-button");
|
|
await user.click(stopButton);
|
|
|
|
expect(onStopServer).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
it("should call onStartServer when start button is clicked", async () => {
|
|
const user = userEvent.setup();
|
|
const onStartServer = vi.fn();
|
|
|
|
renderWithProviders(
|
|
<ServerStatusContextMenu
|
|
{...defaultProps}
|
|
conversationStatus="STOPPED"
|
|
onStartServer={onStartServer}
|
|
/>,
|
|
);
|
|
|
|
const startButton = screen.getByTestId("start-server-button");
|
|
await user.click(startButton);
|
|
|
|
expect(onStartServer).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
it("should render correct text content for stop server button", () => {
|
|
renderWithProviders(
|
|
<ServerStatusContextMenu
|
|
{...defaultProps}
|
|
conversationStatus="RUNNING"
|
|
onStopServer={vi.fn()}
|
|
/>,
|
|
);
|
|
|
|
expect(screen.getByTestId("stop-server-button")).toHaveTextContent(
|
|
"Stop Runtime",
|
|
);
|
|
});
|
|
|
|
it("should render correct text content for start server button", () => {
|
|
renderWithProviders(
|
|
<ServerStatusContextMenu
|
|
{...defaultProps}
|
|
conversationStatus="STOPPED"
|
|
onStartServer={vi.fn()}
|
|
/>,
|
|
);
|
|
|
|
expect(screen.getByTestId("start-server-button")).toHaveTextContent(
|
|
"Start Runtime",
|
|
);
|
|
});
|
|
|
|
it("should call onClose when context menu is closed", () => {
|
|
const onClose = vi.fn();
|
|
|
|
renderWithProviders(
|
|
<ServerStatusContextMenu
|
|
{...defaultProps}
|
|
onClose={onClose}
|
|
conversationStatus="RUNNING"
|
|
onStopServer={vi.fn()}
|
|
/>,
|
|
);
|
|
|
|
// The onClose is typically called by the parent component when clicking outside
|
|
// This test verifies the prop is properly passed
|
|
expect(onClose).toBeDefined();
|
|
});
|
|
|
|
it("should not render any buttons for other conversation statuses", () => {
|
|
renderWithProviders(
|
|
<ServerStatusContextMenu
|
|
{...defaultProps}
|
|
conversationStatus="STARTING"
|
|
/>,
|
|
);
|
|
|
|
expect(screen.queryByTestId("stop-server-button")).not.toBeInTheDocument();
|
|
expect(screen.queryByTestId("start-server-button")).not.toBeInTheDocument();
|
|
});
|
|
});
|