Files
fusion/packages/dashboard/app/components/__tests__/TerminalModal.test.tsx

176 lines
5.1 KiB
TypeScript

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(<TerminalModal isOpen={true} onClose={mockOnClose} />);
await waitFor(() => {
expect(screen.getByTestId("terminal-modal")).toBeTruthy();
});
});
it("does not render when closed", () => {
const { container } = render(<TerminalModal isOpen={false} onClose={mockOnClose} />);
expect(container.firstChild).toBeNull();
});
it("creates terminal session on open", async () => {
render(<TerminalModal isOpen={true} onClose={mockOnClose} />);
await waitFor(() => {
expect(mockCreateTerminalSession).toHaveBeenCalled();
});
});
it("shows loading state while creating session", async () => {
mockCreateTerminalSession.mockImplementation(() => new Promise(() => {}));
render(<TerminalModal isOpen={true} onClose={mockOnClose} />);
expect(screen.getByTestId("terminal-loading")).toBeTruthy();
});
it("shows error when session creation fails", async () => {
mockCreateTerminalSession.mockRejectedValue(new Error("Failed to create session"));
render(<TerminalModal isOpen={true} onClose={mockOnClose} />);
await waitFor(() => {
expect(screen.getByTestId("terminal-error")).toBeTruthy();
});
});
it("closes modal on close button click", async () => {
render(<TerminalModal isOpen={true} onClose={mockOnClose} />);
await waitFor(() => {
const closeBtn = screen.getByTestId("terminal-close-btn");
fireEvent.click(closeBtn);
});
expect(mockOnClose).toHaveBeenCalled();
});
it("closes modal on escape key", async () => {
render(<TerminalModal isOpen={true} onClose={mockOnClose} />);
fireEvent.keyDown(document, { key: "Escape" });
expect(mockOnClose).toHaveBeenCalled();
});
it("closes modal on overlay click", async () => {
render(<TerminalModal isOpen={true} onClose={mockOnClose} />);
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(<TerminalModal isOpen={true} onClose={mockOnClose} />);
await waitFor(() => {
expect(mockCreateTerminalSession).toHaveBeenCalled();
});
rerender(<TerminalModal isOpen={false} onClose={mockOnClose} />);
await waitFor(() => {
expect(mockKillPtyTerminalSession).toHaveBeenCalledWith("test-session-123");
});
});
it("shows reconnect button when disconnected", async () => {
mockUseTerminal.mockReturnValue(
createMockTerminalState({
connectionStatus: "disconnected",
})
);
render(<TerminalModal isOpen={true} onClose={mockOnClose} />);
await waitFor(() => {
expect(screen.getByTestId("terminal-reconnect-btn")).toBeTruthy();
});
});
it("reconnects when reconnect button clicked", async () => {
mockUseTerminal.mockReturnValue(
createMockTerminalState({
connectionStatus: "disconnected",
})
);
render(<TerminalModal isOpen={true} onClose={mockOnClose} />);
await waitFor(() => {
const reconnectBtn = screen.getByTestId("terminal-reconnect-btn");
fireEvent.click(reconnectBtn);
});
expect(mockReconnect).toHaveBeenCalled();
});
it("WebSocket connects on mount with sessionId", async () => {
render(<TerminalModal isOpen={true} onClose={mockOnClose} />);
await waitFor(() => {
expect(mockUseTerminal).toHaveBeenCalledWith("test-session-123");
});
});
});