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"; // Mock the useTerminal hook vi.mock("../../hooks/useTerminal", () => ({ useTerminal: vi.fn(), })); const mockUseTerminal = vi.mocked(useTerminalModule.useTerminal); describe("TerminalModal", () => { const mockOnClose = vi.fn(); const mockExecuteCommand = vi.fn(); const mockClearHistory = vi.fn(); const mockSetInput = vi.fn(); const mockKillCurrentCommand = vi.fn(); const mockNavigateHistory = vi.fn(); const createMockTerminalState = (overrides = {}) => ({ history: [], input: "", isRunning: false, currentSessionId: null, currentDirectory: "~/project", executeCommand: mockExecuteCommand, clearHistory: mockClearHistory, setInput: mockSetInput, killCurrentCommand: mockKillCurrentCommand, navigateHistory: mockNavigateHistory, ...overrides, }); beforeEach(() => { mockOnClose.mockClear(); mockExecuteCommand.mockClear(); mockClearHistory.mockClear(); mockSetInput.mockClear(); mockKillCurrentCommand.mockClear(); mockNavigateHistory.mockClear(); mockUseTerminal.mockReturnValue(createMockTerminalState()); }); afterEach(() => { vi.clearAllMocks(); }); it("does not render when closed", () => { const { container } = render( ); expect(container.firstChild).toBeNull(); }); it("renders when open", () => { render(); expect(screen.getByTestId("terminal-modal")).toBeTruthy(); expect(screen.getByTestId("terminal-content")).toBeTruthy(); expect(screen.getByTestId("terminal-input")).toBeTruthy(); }); it("shows welcome message when history is empty", () => { render(); expect(screen.getByTestId("terminal-welcome")).toBeTruthy(); expect(screen.getByText("Interactive Terminal")).toBeTruthy(); }); it("displays command history", () => { mockUseTerminal.mockReturnValue( createMockTerminalState({ history: [ { command: "ls -la", output: "file1\nfile2", exitCode: 0, isRunning: false, timestamp: new Date() }, ], }) ); render(); expect(screen.getByTestId("terminal-output")).toBeTruthy(); expect(screen.getByText("ls -la")).toBeTruthy(); expect(screen.getByTestId("terminal-output-0").textContent).toContain("file1"); }); it("calls onClose when clicking overlay", () => { render(); fireEvent.click(screen.getByTestId("terminal-modal-overlay")); expect(mockOnClose).toHaveBeenCalled(); }); it("calls onClose when clicking close button", () => { render(); fireEvent.click(screen.getByTestId("terminal-close-btn")); expect(mockOnClose).toHaveBeenCalled(); }); it("closes on escape key", () => { render(); fireEvent.keyDown(document, { key: "Escape" }); expect(mockOnClose).toHaveBeenCalled(); }); it("updates input value on type", () => { render(); const input = screen.getByTestId("terminal-input"); fireEvent.change(input, { target: { value: "ls" } }); expect(mockSetInput).toHaveBeenCalledWith("ls"); }); it("executes command on Enter key", async () => { mockUseTerminal.mockReturnValue( createMockTerminalState({ input: "ls -la", }) ); render(); const input = screen.getByTestId("terminal-input"); fireEvent.keyDown(input, { key: "Enter" }); await waitFor(() => { expect(mockExecuteCommand).toHaveBeenCalledWith("ls -la"); }); }); it("does not execute empty command on Enter", () => { render(); const input = screen.getByTestId("terminal-input"); fireEvent.keyDown(input, { key: "Enter" }); expect(mockExecuteCommand).not.toHaveBeenCalled(); }); it("navigates history on up arrow", () => { mockUseTerminal.mockReturnValue( createMockTerminalState({ input: "current", }) ); mockNavigateHistory.mockReturnValue("previous"); render(); const input = screen.getByTestId("terminal-input"); fireEvent.keyDown(input, { key: "ArrowUp" }); expect(mockNavigateHistory).toHaveBeenCalledWith("up", "current"); expect(mockSetInput).toHaveBeenCalledWith("previous"); }); it("navigates history on down arrow", () => { mockUseTerminal.mockReturnValue( createMockTerminalState({ input: "current", }) ); mockNavigateHistory.mockReturnValue("next"); render(); const input = screen.getByTestId("terminal-input"); fireEvent.keyDown(input, { key: "ArrowDown" }); expect(mockNavigateHistory).toHaveBeenCalledWith("down", "current"); expect(mockSetInput).toHaveBeenCalledWith("next"); }); it("clears history on Ctrl+L", () => { render(); const input = screen.getByTestId("terminal-input"); fireEvent.keyDown(input, { key: "l", ctrlKey: true }); expect(mockClearHistory).toHaveBeenCalled(); }); it("kills running command on Ctrl+C when running", async () => { mockUseTerminal.mockReturnValue( createMockTerminalState({ isRunning: true, }) ); render(); const input = screen.getByTestId("terminal-input"); fireEvent.keyDown(input, { key: "c", ctrlKey: true }); await waitFor(() => { expect(mockKillCurrentCommand).toHaveBeenCalled(); }); }); it("shows kill button when command is running", () => { mockUseTerminal.mockReturnValue( createMockTerminalState({ isRunning: true, }) ); render(); expect(screen.getByTestId("terminal-kill-btn")).toBeTruthy(); }); it("hides kill button when not running", () => { mockUseTerminal.mockReturnValue( createMockTerminalState({ isRunning: false, }) ); render(); expect(screen.queryByTestId("terminal-kill-btn")).toBeNull(); }); it("disables input when command is running", () => { mockUseTerminal.mockReturnValue( createMockTerminalState({ isRunning: true, }) ); render(); expect(screen.getByTestId("terminal-input")).toBeDisabled(); }); it("shows running indicator for running commands", () => { mockUseTerminal.mockReturnValue( createMockTerminalState({ history: [ { command: "sleep 10", output: "", exitCode: null, isRunning: true, timestamp: new Date() }, ], isRunning: true, }) ); render(); const entry = screen.getByTestId("terminal-entry-0"); expect(entry.textContent).toContain("●"); }); it("shows exit code for completed commands", () => { mockUseTerminal.mockReturnValue( createMockTerminalState({ history: [ { command: "ls", output: "", exitCode: 0, isRunning: false, timestamp: new Date() }, ], }) ); render(); expect(screen.getByTestId("terminal-exit-0")).toBeTruthy(); }); it("shows error exit code for failed commands", () => { mockUseTerminal.mockReturnValue( createMockTerminalState({ history: [ { command: "false", output: "", exitCode: 1, isRunning: false, timestamp: new Date() }, ], }) ); render(); const exitCode = screen.getByTestId("terminal-exit-0"); expect(exitCode.textContent).toContain("1"); }); it("disables clear button when history is empty", () => { render(); expect(screen.getByTestId("terminal-clear-btn")).toBeDisabled(); }); it("shows current directory in status bar", () => { mockUseTerminal.mockReturnValue( createMockTerminalState({ currentDirectory: "/home/user/project", }) ); render(); expect(screen.getByText("/home/user/project")).toBeTruthy(); }); it("executes initial command on mount when provided", async () => { mockUseTerminal.mockReturnValue( createMockTerminalState({ history: [], isRunning: false, }) ); render(); await waitFor(() => { expect(mockExecuteCommand).toHaveBeenCalledWith("npm install"); }); }); });