ChatInterface.test.tsx 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148
  1. import { screen, act } from "@testing-library/react";
  2. import { describe, expect, it, vi } from "vitest";
  3. import userEvent from "@testing-library/user-event";
  4. import { renderWithProviders } from "test-utils";
  5. import { createMemoryRouter, RouterProvider } from "react-router-dom";
  6. import { addAssistantMessage } from "#/state/chatSlice";
  7. import AgentState from "#/types/AgentState";
  8. import ChatInterface from "#/components/chat/ChatInterface";
  9. const router = createMemoryRouter([
  10. {
  11. path: "/",
  12. element: <ChatInterface />,
  13. },
  14. ]);
  15. /// <reference types="vitest" />
  16. interface CustomMatchers<R = unknown> {
  17. toMatchMessageEvent(expected: string): R;
  18. }
  19. declare module "vitest" {
  20. interface Assertion<T> extends CustomMatchers<T> {}
  21. // @ts-expect-error - recursively references itself
  22. interface AsymmetricMatchersContaining extends CustomMatchers {}
  23. }
  24. // This is for the scrollview ref in Chat.tsx
  25. // TODO: Move this into test setup
  26. HTMLElement.prototype.scrollTo = vi.fn().mockImplementation(() => {});
  27. const TEST_TIMESTAMP = new Date().toISOString();
  28. describe.skip("ChatInterface", () => {
  29. // TODO: replace below with e.g. fake timers
  30. // https://vitest.dev/guide/mocking#timers
  31. // https://vitest.dev/api/vi.html#vi-usefaketimers
  32. // Custom matcher for testing message events
  33. expect.extend({
  34. toMatchMessageEvent(received, expected) {
  35. const receivedObj = JSON.parse(received);
  36. const expectedObj = JSON.parse(expected);
  37. // Compare everything except the timestamp
  38. const { timestamp: receivedTimestamp, ...receivedRest } =
  39. receivedObj.args;
  40. const { timestamp: expectedTimestamp, ...expectedRest } =
  41. expectedObj.args;
  42. const pass =
  43. this.equals(receivedRest, expectedRest) &&
  44. typeof receivedTimestamp === "string";
  45. return {
  46. pass,
  47. message: () =>
  48. pass
  49. ? `expected ${received} not to match the structure of ${expected} (ignoring exact timestamp)`
  50. : `expected ${received} to match the structure of ${expected} (ignoring exact timestamp)`,
  51. };
  52. },
  53. });
  54. it("should render empty message list and input", () => {
  55. renderWithProviders(<ChatInterface />);
  56. expect(screen.queryAllByTestId("article")).toHaveLength(0);
  57. });
  58. it("should render user and assistant messages", () => {
  59. const { store } = renderWithProviders(<RouterProvider router={router} />, {
  60. preloadedState: {
  61. chat: {
  62. messages: [
  63. {
  64. sender: "user",
  65. content: "Hello",
  66. imageUrls: [],
  67. timestamp: TEST_TIMESTAMP,
  68. },
  69. ],
  70. },
  71. },
  72. });
  73. expect(screen.getAllByTestId("article")).toHaveLength(1);
  74. expect(screen.getByText("Hello")).toBeInTheDocument();
  75. act(() => {
  76. // simulate assistant response
  77. store.dispatch(addAssistantMessage("Hello to you!"));
  78. });
  79. expect(screen.getAllByTestId("article")).toHaveLength(2);
  80. expect(screen.getByText("Hello to you!")).toBeInTheDocument();
  81. });
  82. it("should send the user message as an event to the Session when the agent state is INIT", async () => {
  83. const user = userEvent.setup();
  84. renderWithProviders(<RouterProvider router={router} />, {
  85. preloadedState: {
  86. agent: {
  87. curAgentState: AgentState.INIT,
  88. },
  89. },
  90. });
  91. const input = screen.getByRole("textbox");
  92. await user.type(input, "my message");
  93. await user.keyboard("{Enter}");
  94. });
  95. it("should send the user message as an event to the Session when the agent state is AWAITING_USER_INPUT", async () => {
  96. const user = userEvent.setup();
  97. renderWithProviders(<RouterProvider router={router} />, {
  98. preloadedState: {
  99. agent: {
  100. curAgentState: AgentState.AWAITING_USER_INPUT,
  101. },
  102. },
  103. });
  104. const input = screen.getByRole("textbox");
  105. await user.type(input, "my message");
  106. await user.keyboard("{Enter}");
  107. });
  108. it("should disable the user input if agent is not initialized", async () => {
  109. const user = userEvent.setup();
  110. renderWithProviders(<RouterProvider router={router} />, {
  111. preloadedState: {
  112. agent: {
  113. curAgentState: AgentState.LOADING,
  114. },
  115. },
  116. });
  117. const input = screen.getByRole("textbox");
  118. await user.type(input, "my message");
  119. await user.keyboard("{Enter}");
  120. const submitButton = screen.getByLabelText(
  121. "CHAT_INTERFACE$TOOLTIP_SEND_MESSAGE",
  122. );
  123. expect(submitButton).toBeDisabled();
  124. });
  125. it.todo("test scroll-related behaviour");
  126. });