import { describe, expect, it, vi } from "vitest"; import { screen } from "@testing-library/react"; import { renderWithProviders } from "test-utils"; import { createRoutesStub } from "react-router"; import { ExpandableMessage } from "#/components/features/chat/expandable-message"; import OpenHands from "#/api/open-hands"; vi.mock("react-i18next", async () => { const actual = await vi.importActual("react-i18next"); return { ...actual, useTranslation: () => ({ t: (key: string) => key, i18n: { changeLanguage: () => new Promise(() => {}), language: "en", exists: () => true, }, }), }; }); describe("ExpandableMessage", () => { it("should render with neutral border for non-action messages", () => { renderWithProviders(); const element = screen.getAllByText("Hello")[0]; const container = element.closest( "div.flex.gap-2.items-center.justify-start", ); expect(container).toHaveClass("border-neutral-300"); expect(screen.queryByTestId("status-icon")).not.toBeInTheDocument(); }); it("should render with neutral border for error messages", () => { renderWithProviders( , ); const element = screen.getAllByText("Error occurred")[0]; const container = element.closest( "div.flex.gap-2.items-center.justify-start", ); expect(container).toHaveClass("border-danger"); expect(screen.queryByTestId("status-icon")).not.toBeInTheDocument(); }); it("should render with success icon for successful action messages", () => { renderWithProviders( , ); const element = screen.getByText("OBSERVATION_MESSAGE$RUN"); const container = element.closest( "div.flex.gap-2.items-center.justify-start", ); expect(container).toHaveClass("border-neutral-300"); const icon = screen.getByTestId("status-icon"); expect(icon).toHaveClass("fill-success"); }); it("should render with error icon for failed action messages", () => { renderWithProviders( , ); const element = screen.getByText("OBSERVATION_MESSAGE$RUN"); const container = element.closest( "div.flex.gap-2.items-center.justify-start", ); expect(container).toHaveClass("border-neutral-300"); const icon = screen.getByTestId("status-icon"); expect(icon).toHaveClass("fill-danger"); }); it("should render with neutral border and no icon for action messages without success prop", () => { renderWithProviders( , ); const element = screen.getByText("OBSERVATION_MESSAGE$RUN"); const container = element.closest( "div.flex.gap-2.items-center.justify-start", ); expect(container).toHaveClass("border-neutral-300"); expect(screen.queryByTestId("status-icon")).not.toBeInTheDocument(); }); it("should render with neutral border and no icon for action messages with undefined success (timeout case)", () => { renderWithProviders( , ); const element = screen.getByText("OBSERVATION_MESSAGE$RUN"); const container = element.closest( "div.flex.gap-2.items-center.justify-start", ); expect(container).toHaveClass("border-neutral-300"); expect(screen.queryByTestId("status-icon")).not.toBeInTheDocument(); }); it("should render the out of credits message when the user is out of credits", async () => { const getConfigSpy = vi.spyOn(OpenHands, "getConfig"); // @ts-expect-error - We only care about the APP_MODE and FEATURE_FLAGS fields getConfigSpy.mockResolvedValue({ APP_MODE: "saas", FEATURE_FLAGS: { ENABLE_BILLING: true, HIDE_LLM_SETTINGS: false, }, }); const RouterStub = createRoutesStub([ { Component: () => ( ), path: "/", }, ]); renderWithProviders(); await screen.findByTestId("out-of-credits"); }); });