import { screen } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; import { afterEach, beforeAll, describe, expect, it, vi } from "vitest"; import { MemoryRouter } from "react-router"; import { InteractiveChatBox } from "#/components/features/chat/interactive-chat-box"; import { renderWithProviders } from "../../test-utils"; import { AgentState } from "#/types/agent-state"; import { useAgentState } from "#/hooks/use-agent-state"; import { useConversationStore } from "#/state/conversation-store"; // Mock the agent state hook vi.mock("#/hooks/use-agent-state", () => ({ useAgentState: vi.fn(), })); // Mock the conversation store vi.mock("#/state/conversation-store", () => ({ useConversationStore: vi.fn(), })); // Mock React Router hooks vi.mock("react-router", async () => { const actual = await vi.importActual("react-router"); return { ...actual, useNavigate: () => vi.fn(), useParams: () => ({ conversationId: "test-conversation-id" }), }; }); // Mock the useActiveConversation hook vi.mock("#/hooks/query/use-active-conversation", () => ({ useActiveConversation: () => ({ data: { status: null }, isFetched: true, refetch: vi.fn(), }), })); // Mock other hooks that might be used by the component vi.mock("#/hooks/use-user-providers", () => ({ useUserProviders: () => ({ providers: [], }), })); vi.mock("#/hooks/use-conversation-name-context-menu", () => ({ useConversationNameContextMenu: () => ({ isOpen: false, contextMenuRef: { current: null }, handleContextMenu: vi.fn(), handleClose: vi.fn(), handleRename: vi.fn(), handleDelete: vi.fn(), }), })); describe("InteractiveChatBox", () => { const onSubmitMock = vi.fn(); // Helper function to mock stores const mockStores = (agentState: AgentState = AgentState.INIT) => { vi.mocked(useAgentState).mockReturnValue({ curAgentState: agentState, }); vi.mocked(useConversationStore).mockReturnValue({ images: [], files: [], addImages: vi.fn(), addFiles: vi.fn(), clearAllFiles: vi.fn(), addFileLoading: vi.fn(), removeFileLoading: vi.fn(), addImageLoading: vi.fn(), removeImageLoading: vi.fn(), submittedMessage: null, setShouldHideSuggestions: vi.fn(), setSubmittedMessage: vi.fn(), isRightPanelShown: true, selectedTab: "editor" as const, loadingFiles: [], loadingImages: [], messageToSend: null, shouldShownAgentLoading: false, shouldHideSuggestions: false, hasRightPanelToggled: true, setIsRightPanelShown: vi.fn(), setSelectedTab: vi.fn(), setShouldShownAgentLoading: vi.fn(), removeImage: vi.fn(), removeFile: vi.fn(), clearImages: vi.fn(), clearFiles: vi.fn(), clearAllLoading: vi.fn(), setMessageToSend: vi.fn(), resetConversationState: vi.fn(), setHasRightPanelToggled: vi.fn(), }); }; // Helper function to render with Router context const renderInteractiveChatBox = (props: any, options: any = {}) => renderWithProviders( , options, ); beforeAll(() => { global.URL.createObjectURL = vi .fn() .mockReturnValue("blob:http://example.com"); }); afterEach(() => { vi.clearAllMocks(); }); it("should render", () => { mockStores(AgentState.INIT); renderInteractiveChatBox({ onSubmit: onSubmitMock, }); const chatBox = screen.getByTestId("interactive-chat-box"); expect(chatBox).toBeInTheDocument(); }); it("should set custom values", async () => { const user = userEvent.setup(); mockStores(AgentState.AWAITING_USER_INPUT); renderInteractiveChatBox({ onSubmit: onSubmitMock, }); const textbox = screen.getByTestId("chat-input"); // Simulate user typing to populate the input await user.type(textbox, "Hello, world!"); expect(textbox).toHaveTextContent("Hello, world!"); }); it("should display the image previews when images are uploaded", async () => { const user = userEvent.setup(); mockStores(AgentState.INIT); renderInteractiveChatBox({ onSubmit: onSubmitMock, }); // Create a larger file to ensure it passes validation const fileContent = new Array(1024).fill("a").join(""); // 1KB file const file = new File([fileContent], "chucknorris.png", { type: "image/png", }); // Click on the paperclip icon to trigger file selection const paperclipIcon = screen.getByTestId("paperclip-icon"); await user.click(paperclipIcon); // Now trigger the file input change event directly const input = screen.getByTestId("upload-image-input"); await user.upload(input, file); // For now, just verify the file input is accessible expect(input).toBeInTheDocument(); }); it("should remove the image preview when the close button is clicked", async () => { const user = userEvent.setup(); mockStores(AgentState.INIT); renderInteractiveChatBox({ onSubmit: onSubmitMock, }); const fileContent = new Array(1024).fill("a").join(""); // 1KB file const file = new File([fileContent], "chucknorris.png", { type: "image/png", }); // Click on the paperclip icon to trigger file selection const paperclipIcon = screen.getByTestId("paperclip-icon"); await user.click(paperclipIcon); const input = screen.getByTestId("upload-image-input"); await user.upload(input, file); // For now, just verify the file input is accessible expect(input).toBeInTheDocument(); }); it("should call onSubmit with the message and images", async () => { const user = userEvent.setup(); mockStores(AgentState.INIT); renderInteractiveChatBox({ onSubmit: onSubmitMock, }); const textarea = screen.getByTestId("chat-input"); // Type the message and ensure it's properly set await user.type(textarea, "Hello, world!"); // Set innerText directly as the component reads this property textarea.innerText = "Hello, world!"; // Verify the text is in the input before submitting expect(textarea).toHaveTextContent("Hello, world!"); // Click the submit button instead of pressing Enter for more reliable testing const submitButton = screen.getByTestId("submit-button"); // Verify the button is enabled before clicking expect(submitButton).not.toBeDisabled(); await user.click(submitButton); expect(onSubmitMock).toHaveBeenCalledWith("Hello, world!", [], []); }); it("should disable the submit button when agent is loading", async () => { const user = userEvent.setup(); mockStores(AgentState.LOADING); renderInteractiveChatBox({ onSubmit: onSubmitMock, }); const button = screen.getByTestId("submit-button"); expect(button).toBeDisabled(); await user.click(button); expect(onSubmitMock).not.toHaveBeenCalled(); }); it("should handle image upload and message submission correctly", async () => { const user = userEvent.setup(); const onSubmit = vi.fn(); mockStores(AgentState.AWAITING_USER_INPUT); const { rerender } = renderInteractiveChatBox({ onSubmit, }); // Verify text input has the initial value const textarea = screen.getByTestId("chat-input"); expect(textarea).toHaveTextContent(""); // Set innerText directly as the component reads this property textarea.innerText = "test message"; // Submit the message const submitButton = screen.getByTestId("submit-button"); await user.click(submitButton); // Verify onSubmit was called with the message expect(onSubmit).toHaveBeenCalledWith("test message", [], []); // Simulate parent component updating the value prop rerender( , ); // Verify the text input was cleared expect(screen.getByTestId("chat-input")).toHaveTextContent(""); }); });