/** * @file Chat.test.tsx * @description Tests for Chat component error handling in imperative handle methods */ import { createRef } from "react"; import { render } from "@testing-library/react"; import { describe, it, expect, beforeEach, vi, afterEach, type MockedFunction } from "vitest"; import { Chat, type ChatRef } from "./Chat"; import * as useChatModule from "@/hooks/useChat"; import * as useWebSocketModule from "@/hooks/useWebSocket"; import * as authModule from "@/lib/auth/auth-context"; // Mock scrollIntoView (not available in JSDOM) Element.prototype.scrollIntoView = vi.fn(); // Mock dependencies vi.mock("@/lib/auth/auth-context", () => ({ useAuth: vi.fn(), })); vi.mock("@/hooks/useChat", () => ({ useChat: vi.fn(), })); vi.mock("@/hooks/useWebSocket", () => ({ useWebSocket: vi.fn(), })); vi.mock("./MessageList", () => ({ MessageList: (): React.ReactElement =>
, })); vi.mock("./ChatInput", () => ({ ChatInput: ({ onSend, }: { onSend: (content: string) => Promise; disabled: boolean; inputRef: React.RefObject; }): React.ReactElement => ( ), })); const mockUseAuth = authModule.useAuth as MockedFunction; const mockUseChat = useChatModule.useChat as MockedFunction; const mockUseWebSocket = useWebSocketModule.useWebSocket as MockedFunction< typeof useWebSocketModule.useWebSocket >; function createMockUseChatReturn( overrides: Partial = {} ): useChatModule.UseChatReturn { return { messages: [ { id: "welcome", role: "assistant", content: "Hello!", createdAt: new Date().toISOString(), }, ], isLoading: false, error: null, conversationId: null, conversationTitle: null, sendMessage: vi.fn().mockResolvedValue(undefined), loadConversation: vi.fn().mockResolvedValue(undefined), startNewConversation: vi.fn(), setMessages: vi.fn(), clearError: vi.fn(), ...overrides, }; } describe("Chat", () => { let consoleSpy: ReturnType; beforeEach(() => { vi.clearAllMocks(); consoleSpy = vi.spyOn(console, "error").mockImplementation(() => undefined); mockUseAuth.mockReturnValue({ user: { id: "user-1", name: "Test User", email: "test@test.com" }, isAuthenticated: true, isLoading: false, login: vi.fn(), logout: vi.fn(), } as unknown as ReturnType); mockUseWebSocket.mockReturnValue({ isConnected: true, socket: null, connectionError: null, }); }); afterEach(() => { consoleSpy.mockRestore(); }); describe("loadConversation via ref", () => { it("should successfully load a conversation", async () => { const mockLoadConversation = vi.fn().mockResolvedValue(undefined); mockUseChat.mockReturnValue( createMockUseChatReturn({ loadConversation: mockLoadConversation }) ); const ref = createRef(); render(); await ref.current?.loadConversation("conv-123"); expect(mockLoadConversation).toHaveBeenCalledWith("conv-123"); }); it("should log error context and re-throw on network failure", async () => { const networkError = new Error("Network request failed"); const mockLoadConversation = vi.fn().mockRejectedValue(networkError); mockUseChat.mockReturnValue( createMockUseChatReturn({ loadConversation: mockLoadConversation }) ); const ref = createRef(); render(); await expect(ref.current?.loadConversation("conv-123")).rejects.toThrow( "Network request failed" ); expect(consoleSpy).toHaveBeenCalledWith( "Failed to load conversation", expect.objectContaining({ error: networkError, conversationId: "conv-123", }) ); }); it("should log error context and re-throw on API error (500)", async () => { const apiError = new Error("Internal Server Error"); const mockLoadConversation = vi.fn().mockRejectedValue(apiError); mockUseChat.mockReturnValue( createMockUseChatReturn({ loadConversation: mockLoadConversation }) ); const ref = createRef(); render(); await expect(ref.current?.loadConversation("conv-456")).rejects.toThrow( "Internal Server Error" ); expect(consoleSpy).toHaveBeenCalledWith( "Failed to load conversation", expect.objectContaining({ conversationId: "conv-456", }) ); }); it("should re-throw error for caller to handle", async () => { const mockLoadConversation = vi.fn().mockRejectedValue(new Error("Auth failed")); mockUseChat.mockReturnValue( createMockUseChatReturn({ loadConversation: mockLoadConversation }) ); const ref = createRef(); render(); // Verify the error propagates to the caller await expect(ref.current?.loadConversation("conv-789")).rejects.toThrow("Auth failed"); }); }); describe("sendMessage delegation", () => { it("should delegate to useChat.sendMessage without extra error handling", async () => { const mockSendMessage = vi.fn().mockResolvedValue(undefined); mockUseChat.mockReturnValue(createMockUseChatReturn({ sendMessage: mockSendMessage })); const ref = createRef(); const { getByTestId } = render(); const sendButton = getByTestId("chat-input"); sendButton.click(); await vi.waitFor(() => { expect(mockSendMessage).toHaveBeenCalledWith("test message"); }); }); }); });