OpenHands/frontend/src/components/chat/ChatInterface.test.tsx
Robert Brennan f7e0c6cd06
Separate agent controller and server via EventStream (#1538)
* move towards event stream

* refactor agent state changes

* move agent state logic

* fix callbacks

* break on finish

* closer to working

* change frontend to accomodate new flow

* handle start action

* fix locked stream

* revert message

* logspam

* no async on close

* get rid of agent_task

* fix up closing

* better asyncio handling

* sleep to give back control

* fix key

* logspam

* update frontend agent state actions

* fix pause and cancel

* delint

* fix map

* delint

* wait for agent to finish

* fix unit test

* event stream enums

* fix merge issues

* fix lint

* fix test

* fix test

* add user message action

* add user message action

* fix up user messages

* fix main.py flow

* refactor message waiting

* lint

* fix test

* fix test
2024-05-05 19:20:01 +00:00

142 lines
3.7 KiB
TypeScript

import React from "react";
import { screen } from "@testing-library/react";
import { describe, expect, it } from "vitest";
import { act } from "react-dom/test-utils";
import userEvent from "@testing-library/user-event";
import { renderWithProviders } from "test-utils";
import ChatInterface from "./ChatInterface";
import Socket from "#/services/socket";
import ActionType from "#/types/ActionType";
import { addAssistantMessage } from "#/state/chatSlice";
import AgentState from "#/types/AgentState";
// avoid typing side-effect
vi.mock("#/hooks/useTyping", () => ({
useTyping: vi.fn((text: string) => text),
}));
const socketSpy = vi.spyOn(Socket, "send");
// This is for the scrollview ref in Chat.tsx
// TODO: Move this into test setup
HTMLElement.prototype.scrollIntoView = vi.fn();
const renderChatInterface = () =>
renderWithProviders(<ChatInterface />, {
preloadedState: {
task: {
completed: false,
},
},
});
describe("ChatInterface", () => {
it("should render the messages and input", () => {
renderChatInterface();
expect(screen.queryAllByTestId("message")).toHaveLength(1); // initial welcome message only
});
it("should render the new message the user has typed", async () => {
renderWithProviders(<ChatInterface />, {
preloadedState: {
task: {
completed: false,
},
agent: {
curAgentState: AgentState.INIT,
},
},
});
const input = screen.getByRole("textbox");
act(() => {
userEvent.type(input, "my message{enter}");
});
expect(screen.getByText("my message")).toBeInTheDocument();
});
it("should render user and assistant messages", () => {
const { store } = renderWithProviders(<ChatInterface />, {
preloadedState: {
chat: {
messages: [{ sender: "user", content: "Hello" }],
},
},
});
expect(screen.getAllByTestId("message")).toHaveLength(1);
expect(screen.getByText("Hello")).toBeInTheDocument();
act(() => {
store.dispatch(addAssistantMessage("Hello to you!"));
});
expect(screen.getAllByTestId("message")).toHaveLength(2);
expect(screen.getByText("Hello to you!")).toBeInTheDocument();
});
it("should send the a start event to the Socket", () => {
renderWithProviders(<ChatInterface />, {
preloadedState: {
task: {
completed: false,
},
agent: {
curAgentState: AgentState.INIT,
},
},
});
const input = screen.getByRole("textbox");
act(() => {
userEvent.type(input, "my message{enter}");
});
const event = { action: ActionType.START, args: { task: "my message" } };
expect(socketSpy).toHaveBeenCalledWith(JSON.stringify(event));
});
it("should send the a user message event to the Socket", () => {
renderWithProviders(<ChatInterface />, {
preloadedState: {
task: {
completed: false,
},
agent: {
curAgentState: AgentState.AWAITING_USER_INPUT,
},
},
});
const input = screen.getByRole("textbox");
act(() => {
userEvent.type(input, "my message{enter}");
});
const event = {
action: ActionType.MESSAGE,
args: { content: "my message" },
};
expect(socketSpy).toHaveBeenCalledWith(JSON.stringify(event));
});
it("should disable the user input if agent is not initialized", () => {
renderWithProviders(<ChatInterface />, {
preloadedState: {
task: {
completed: false,
},
agent: {
curAgentState: AgentState.LOADING,
},
},
});
const submitButton = screen.getByLabelText(/send message/i);
expect(submitButton).toBeDisabled();
});
});