import { render, screen } from "@testing-library/react"; import { describe, it, expect, vi, beforeEach } from "vitest"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import { MemoryRouter, Route, Routes } from "react-router"; import { I18nextProvider, initReactI18next } from "react-i18next"; import i18n from "i18next"; import { createUserMessageEvent } from "test-utils"; import { useEventStore } from "#/stores/use-event-store"; import { useConversationWebSocket } from "#/contexts/conversation-websocket-context"; import { ArchivedConversationView } from "#/components/features/conversation/archived-conversation-view"; // Initialize i18n for tests i18n.use(initReactI18next).init({ lng: "en", fallbackLng: "en", ns: ["translation"], defaultNS: "translation", resources: { en: { translation: { CONVERSATION$ARCHIVED_READ_ONLY: "This conversation is archived and read-only", CONVERSATION$NO_HISTORY_AVAILABLE: "No history available", }, }, }, interpolation: { escapeValue: false, }, }); // Module-level mocks vi.mock("#/contexts/conversation-websocket-context"); vi.mock("#/hooks/use-agent-state", () => ({ useAgentState: () => ({ curAgentState: "STOPPED", isArchived: true }), })); vi.mock("#/hooks/query/use-active-conversation", () => ({ useActiveConversation: () => ({ data: { sandbox_status: "MISSING" }, }), })); vi.mock("#/hooks/query/use-task-polling", () => ({ useTaskPolling: () => ({ isTask: false, taskStatus: null, }), })); vi.mock("#/hooks/mutation/use-unified-stop-conversation", () => ({ useUnifiedPauseConversationSandbox: () => ({ mutate: vi.fn(), }), })); vi.mock("#/hooks/mutation/use-unified-start-conversation", () => ({ useUnifiedResumeConversationSandbox: () => ({ mutate: vi.fn(), }), })); 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(), }), })); // Mock the V1 Messages component to simplify testing vi.mock("#/components/v1/chat", () => ({ Messages: ({ messages }: { messages: unknown[] }) => (
{messages.map((_, index) => (
))}
), shouldRenderEvent: () => true, hasUserEvent: () => false, getRenderedV1Events: (events: unknown[]) => events, })); const createQueryClient = () => new QueryClient({ defaultOptions: { queries: { retry: false } }, }); const renderWithProviders = (ui: React.ReactElement) => { const queryClient = createQueryClient(); return render( , ); }; describe("ArchivedConversationView", () => { beforeEach(() => { // Reset event store before each test useEventStore.setState({ events: [], uiEvents: [], }); // Default: websocket context with no loading vi.mocked(useConversationWebSocket).mockReturnValue({ isLoadingHistory: false, connectionState: "OPEN", sendMessage: vi.fn(), }); }); describe("loading state", () => { it("shows loading spinner when loading history and no events exist", () => { vi.mocked(useConversationWebSocket).mockReturnValue({ isLoadingHistory: true, connectionState: "OPEN", sendMessage: vi.fn(), }); useEventStore.setState({ events: [], uiEvents: [], }); renderWithProviders(); expect(screen.getByTestId("loading-spinner")).toBeInTheDocument(); }); it("does not show loading spinner when events exist", () => { vi.mocked(useConversationWebSocket).mockReturnValue({ isLoadingHistory: true, connectionState: "OPEN", sendMessage: vi.fn(), }); const userEvent = createUserMessageEvent("evt-1"); useEventStore.setState({ events: [userEvent], uiEvents: [userEvent], }); renderWithProviders(); expect(screen.queryByTestId("loading-spinner")).not.toBeInTheDocument(); }); }); describe("archived banner", () => { it("renders the archived banner", () => { renderWithProviders(); expect(screen.getByTestId("archived-banner")).toBeInTheDocument(); // The i18n key is rendered as-is when translations aren't fully loaded expect( screen.getByText(/ARCHIVED_READ_ONLY|This conversation is archived/i), ).toBeInTheDocument(); }); }); describe("messages display", () => { it("shows empty state message when no events exist and not loading", () => { vi.mocked(useConversationWebSocket).mockReturnValue({ isLoadingHistory: false, connectionState: "OPEN", sendMessage: vi.fn(), }); useEventStore.setState({ events: [], uiEvents: [], }); renderWithProviders(); // The i18n key is rendered as-is when translations aren't fully loaded expect( screen.getByText(/NO_HISTORY_AVAILABLE|No history available/i), ).toBeInTheDocument(); }); it("renders V1 messages when events exist", () => { const userEvent = createUserMessageEvent("evt-1"); useEventStore.setState({ events: [userEvent], uiEvents: [userEvent], }); renderWithProviders(); expect(screen.getByTestId("v1-messages")).toBeInTheDocument(); expect(screen.getAllByTestId("v1-message-item")).toHaveLength(1); }); it("renders multiple messages correctly", () => { const events = [ createUserMessageEvent("evt-1"), createUserMessageEvent("evt-2"), createUserMessageEvent("evt-3"), ]; useEventStore.setState({ events, uiEvents: events, }); renderWithProviders(); expect(screen.getByTestId("v1-messages")).toBeInTheDocument(); expect(screen.getAllByTestId("v1-message-item")).toHaveLength(3); }); }); describe("layout structure", () => { it("does not show loading spinner when conversation is loaded", () => { const userEvent = createUserMessageEvent("evt-1"); useEventStore.setState({ events: [userEvent], uiEvents: [userEvent], }); renderWithProviders(); // Should not have any loading spinner in the main view expect(screen.queryByTestId("loading-spinner")).not.toBeInTheDocument(); }); it("renders the complete layout with header and banner", () => { const userEvent = createUserMessageEvent("evt-1"); useEventStore.setState({ events: [userEvent], uiEvents: [userEvent], }); renderWithProviders(); // Should have archived banner expect(screen.getByTestId("archived-banner")).toBeInTheDocument(); // Should have messages expect(screen.getByTestId("v1-messages")).toBeInTheDocument(); }); }); });