import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; import { render, screen, fireEvent, waitFor } from "@testing-library/react"; import { TerminalModal } from "../TerminalModal"; import * as useTerminalModule from "../../hooks/useTerminal"; import * as apiModule from "../../api"; // Mock hooks and API vi.mock("../../hooks/useTerminal", () => ({ useTerminal: vi.fn(), })); vi.mock("../../api", () => ({ createTerminalSession: vi.fn(), killPtyTerminalSession: vi.fn(), })); const mockUseTerminal = vi.mocked(useTerminalModule.useTerminal); const mockCreateTerminalSession = vi.mocked(apiModule.createTerminalSession); const mockKillPtyTerminalSession = vi.mocked(apiModule.killPtyTerminalSession); describe("TerminalModal", () => { const mockOnClose = vi.fn(); const mockSendInput = vi.fn(); const mockResize = vi.fn(); const mockReconnect = vi.fn(); const createMockTerminalState = (overrides = {}) => ({ connectionStatus: "disconnected" as const, sendInput: mockSendInput, resize: mockResize, onData: vi.fn(() => vi.fn()), onExit: vi.fn(() => vi.fn()), onConnect: vi.fn(() => vi.fn()), onScrollback: vi.fn(() => vi.fn()), reconnect: mockReconnect, ...overrides, }); beforeEach(() => { vi.clearAllMocks(); mockCreateTerminalSession.mockResolvedValue({ sessionId: "test-session-123", shell: "/bin/bash", cwd: "/project", }); mockKillPtyTerminalSession.mockResolvedValue({ killed: true }); mockUseTerminal.mockReturnValue(createMockTerminalState()); }); afterEach(() => { vi.restoreAllMocks(); }); it("renders without crashing when open", async () => { render(); await waitFor(() => { expect(screen.getByTestId("terminal-modal")).toBeTruthy(); }); }); it("does not render when closed", () => { const { container } = render(); expect(container.firstChild).toBeNull(); }); it("creates terminal session on open", async () => { render(); await waitFor(() => { expect(mockCreateTerminalSession).toHaveBeenCalled(); }); }); it("shows loading state while creating session", async () => { mockCreateTerminalSession.mockImplementation(() => new Promise(() => {})); render(); expect(screen.getByTestId("terminal-loading")).toBeTruthy(); }); it("shows error when session creation fails", async () => { mockCreateTerminalSession.mockRejectedValue(new Error("Failed to create session")); render(); await waitFor(() => { expect(screen.getByTestId("terminal-error")).toBeTruthy(); }); }); it("closes modal on close button click", async () => { render(); await waitFor(() => { const closeBtn = screen.getByTestId("terminal-close-btn"); fireEvent.click(closeBtn); }); expect(mockOnClose).toHaveBeenCalled(); }); it("closes modal on escape key", async () => { render(); fireEvent.keyDown(document, { key: "Escape" }); expect(mockOnClose).toHaveBeenCalled(); }); it("closes modal on overlay click", async () => { render(); await waitFor(() => { const overlay = screen.getByTestId("terminal-modal-overlay"); fireEvent.click(overlay); }); expect(mockOnClose).toHaveBeenCalled(); }); it("kills session on modal close", async () => { const { rerender } = render(); await waitFor(() => { expect(mockCreateTerminalSession).toHaveBeenCalled(); }); rerender(); await waitFor(() => { expect(mockKillPtyTerminalSession).toHaveBeenCalledWith("test-session-123"); }); }); it("shows reconnect button when disconnected", async () => { mockUseTerminal.mockReturnValue( createMockTerminalState({ connectionStatus: "disconnected", }) ); render(); await waitFor(() => { expect(screen.getByTestId("terminal-reconnect-btn")).toBeTruthy(); }); }); it("reconnects when reconnect button clicked", async () => { mockUseTerminal.mockReturnValue( createMockTerminalState({ connectionStatus: "disconnected", }) ); render(); await waitFor(() => { const reconnectBtn = screen.getByTestId("terminal-reconnect-btn"); fireEvent.click(reconnectBtn); }); expect(mockReconnect).toHaveBeenCalled(); }); it("WebSocket connects on mount with sessionId", async () => { render(); await waitFor(() => { expect(mockUseTerminal).toHaveBeenCalledWith("test-session-123"); }); }); });