import { render, screen, waitFor } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; import { describe, expect, it, vi, beforeEach, afterEach } from "vitest"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import { ConversationTabTitle } from "#/components/features/conversation/conversation-tabs/conversation-tab-title"; import GitService from "#/api/git-service/git-service.api"; import V1GitService from "#/api/git-service/v1-git-service.api"; import { useConversationStore } from "#/stores/conversation-store"; import { useAgentStore } from "#/stores/agent-store"; import { useOptimisticUserMessageStore } from "#/stores/optimistic-user-message-store"; import { AgentState } from "#/types/agent-state"; import { createChatMessage } from "#/services/chat-service"; // Mock the services that the hook depends on vi.mock("#/api/git-service/git-service.api"); vi.mock("#/api/git-service/v1-git-service.api"); // Mock i18n vi.mock("react-i18next", async (importOriginal) => { const actual = await importOriginal(); return { ...actual, useTranslation: () => ({ t: (key: string) => key, }), }; }); // Mock services for Build button const mockSend = vi.fn(); vi.mock("#/hooks/use-send-message", () => ({ useSendMessage: vi.fn(() => ({ send: mockSend, })), })); vi.mock("#/services/chat-service", () => ({ createChatMessage: vi.fn((content, imageUrls, fileUrls, timestamp) => ({ action: "message", args: { content, image_urls: imageUrls, file_urls: fileUrls, timestamp }, })), })); // Mock the hooks that useUnifiedGetGitChanges depends on vi.mock("#/hooks/use-conversation-id", () => ({ useConversationId: () => ({ conversationId: "test-conversation-id", }), })); vi.mock("#/hooks/query/use-active-conversation", () => ({ useActiveConversation: () => ({ data: { conversation_version: "V0", url: null, session_api_key: null, selected_repository: null, }, }), })); vi.mock("#/hooks/use-runtime-is-ready", () => ({ useRuntimeIsReady: () => true, })); vi.mock("#/utils/get-git-path", () => ({ getGitPath: () => "/workspace", })); describe("ConversationTabTitle", () => { let queryClient: QueryClient; beforeEach(() => { queryClient = new QueryClient({ defaultOptions: { queries: { retry: false, }, }, }); // Mock GitService methods vi.mocked(GitService.getGitChanges).mockResolvedValue([]); vi.mocked(V1GitService.getGitChanges).mockResolvedValue([]); // Reset stores for Build button tests useConversationStore.setState({ planContent: null, conversationMode: "plan", }); useAgentStore.setState({ curAgentState: AgentState.AWAITING_USER_INPUT, }); useOptimisticUserMessageStore.setState({ optimisticUserMessage: null, }); }); afterEach(() => { vi.clearAllMocks(); queryClient.clear(); localStorage.clear(); }); const renderWithProviders = (ui: React.ReactElement) => { return render( {ui}, ); }; describe("Rendering", () => { it("should render the title", () => { // Arrange const title = "Test Title"; // Act renderWithProviders( , ); // Assert expect(screen.getByText(title)).toBeInTheDocument(); }); it("should show refresh button when conversationKey is 'editor'", () => { // Arrange const title = "Changes"; // Act renderWithProviders( , ); // Assert const refreshButton = screen.getByRole("button"); expect(refreshButton).toBeInTheDocument(); }); it("should not show refresh button when conversationKey is not 'editor'", () => { // Arrange const title = "Browser"; // Act renderWithProviders( , ); // Assert expect(screen.queryByRole("button")).not.toBeInTheDocument(); }); }); describe("User Interactions", () => { it("should call refetch and trigger GitService.getGitChanges when refresh button is clicked", async () => { // Arrange const user = userEvent.setup(); const title = "Changes"; const mockGitChanges: Array<{ path: string; status: "M" | "A" | "D" | "R" | "U"; }> = [ { path: "file1.ts", status: "M" }, { path: "file2.ts", status: "A" }, ]; vi.mocked(GitService.getGitChanges).mockResolvedValue(mockGitChanges); renderWithProviders( , ); const refreshButton = screen.getByRole("button"); // Wait for initial query to complete await waitFor(() => { expect(GitService.getGitChanges).toHaveBeenCalled(); }); // Clear the mock to track refetch calls vi.mocked(GitService.getGitChanges).mockClear(); // Act await user.click(refreshButton); // Assert - refetch should trigger another service call await waitFor(() => { expect(GitService.getGitChanges).toHaveBeenCalledWith( "test-conversation-id", ); }); }); }); describe("Build Button", () => { it("should show Build button when conversationKey is 'planner' and planContent exists", () => { // Arrange useConversationStore.setState({ planContent: "# Plan content" }); // Act renderWithProviders( , ); // Assert const buildButton = screen.getByTestId("planner-tab-build-button"); expect(buildButton).toBeInTheDocument(); }); it("should not show Build button when conversationKey is not 'planner'", () => { // Arrange useConversationStore.setState({ planContent: "# Plan content" }); // Act renderWithProviders( , ); // Assert expect( screen.queryByTestId("planner-tab-build-button"), ).not.toBeInTheDocument(); }); it("should disable Build button when no planContent exists", () => { // Arrange useConversationStore.setState({ planContent: null }); useAgentStore.setState({ curAgentState: AgentState.AWAITING_USER_INPUT }); // Act renderWithProviders( , ); // Assert const buildButton = screen.getByTestId("planner-tab-build-button"); expect(buildButton).toBeDisabled(); }); it("should disable Build button when agent is running", () => { // Arrange useConversationStore.setState({ planContent: "# Plan content" }); useAgentStore.setState({ curAgentState: AgentState.RUNNING }); // Act renderWithProviders( , ); // Assert const buildButton = screen.getByTestId("planner-tab-build-button"); expect(buildButton).toBeDisabled(); }); it("should switch to code mode and send message when Build button is clicked", async () => { // Arrange const user = userEvent.setup(); useConversationStore.setState({ planContent: "# Plan content", conversationMode: "plan", }); useAgentStore.setState({ curAgentState: AgentState.AWAITING_USER_INPUT }); renderWithProviders( , ); const buildButton = screen.getByTestId("planner-tab-build-button"); // Act await user.click(buildButton); // Assert expect(useConversationStore.getState().conversationMode).toBe("code"); expect(createChatMessage).toHaveBeenCalledWith( "Execute the plan based on the .agents_tmp/PLAN.md file.", [], [], expect.any(String), ); expect(mockSend).toHaveBeenCalled(); }); }); });