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();
});
});
});