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