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 = { 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( , ); expect(screen.getByText("Running")).toBeInTheDocument(); // Test STOPPED status rerender(); expect(screen.getByText("Server Stopped")).toBeInTheDocument(); // Test STARTING status (shows "Running" due to agent state being RUNNING) rerender(); expect(screen.getByText("Running")).toBeInTheDocument(); // Test null status (shows "Running" due to agent state being RUNNING) rerender(); 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(); 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(); 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(); 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(); 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(); 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(); 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(); 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(); 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( , ); expect(screen.getByTestId("stop-server-button")).toBeInTheDocument(); expect(screen.getByText("Stop Runtime")).toBeInTheDocument(); }); it("should render start server button when status is STOPPED", () => { renderWithProviders( , ); 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( , ); expect(screen.queryByTestId("stop-server-button")).not.toBeInTheDocument(); }); it("should not render start server button when onStartServer is not provided", () => { renderWithProviders( , ); 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( , ); 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( , ); 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( , ); expect(screen.getByTestId("stop-server-button")).toHaveTextContent( "Stop Runtime", ); }); it("should render correct text content for start server button", () => { renderWithProviders( , ); expect(screen.getByTestId("start-server-button")).toHaveTextContent( "Start Runtime", ); }); it("should call onClose when context menu is closed", () => { const onClose = vi.fn(); renderWithProviders( , ); // 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( , ); expect(screen.queryByTestId("stop-server-button")).not.toBeInTheDocument(); expect(screen.queryByTestId("start-server-button")).not.toBeInTheDocument(); }); });