/** * @file TerminalPanel.test.tsx * @description Unit tests for the TerminalPanel component */ import { describe, it, expect, vi, beforeEach } from "vitest"; import { render, screen, fireEvent } from "@testing-library/react"; import type { ReactElement } from "react"; // ========================================== // Mocks // ========================================== // Mock XTerminal to avoid xterm.js DOM dependencies in panel tests vi.mock("./XTerminal", () => ({ XTerminal: vi.fn(({ token, isVisible }: { token: string; isVisible: boolean }) => (
)), })); import { TerminalPanel } from "./TerminalPanel"; // ========================================== // Tests // ========================================== describe("TerminalPanel", () => { const onClose = vi.fn(); const onTabChange = vi.fn(); beforeEach(() => { vi.clearAllMocks(); }); // ========================================== // Rendering // ========================================== describe("rendering", () => { it("renders the terminal panel", () => { render(() as ReactElement); expect(screen.getByRole("region", { name: "Terminal panel" })).toBeInTheDocument(); }); it("renders with height 280 when open", () => { render(() as ReactElement); const panel = screen.getByRole("region", { name: "Terminal panel" }); expect(panel).toHaveStyle({ height: "280px" }); }); it("renders with height 0 when closed", () => { const { container } = render( () as ReactElement ); const panel = container.querySelector('[role="region"][aria-label="Terminal panel"]'); expect(panel).toHaveStyle({ height: "0px" }); }); it("passes isVisible=true to XTerminal when open", () => { render(() as ReactElement); const xterm = screen.getByTestId("mock-xterminal"); expect(xterm).toHaveAttribute("data-visible", "true"); }); it("passes isVisible=false to XTerminal when closed", () => { const { container } = render( () as ReactElement ); // Use container query since the element is inside an aria-hidden region const xterm = container.querySelector('[data-testid="mock-xterminal"]'); expect(xterm).toHaveAttribute("data-visible", "false"); }); it("passes token to XTerminal", () => { render( () as ReactElement ); const xterm = screen.getByTestId("mock-xterminal"); expect(xterm).toHaveAttribute("data-token", "my-auth-token"); }); }); // ========================================== // Tab bar // ========================================== describe("tab bar", () => { it("renders default tabs when none provided", () => { render(() as ReactElement); expect(screen.getByRole("tab", { name: "main" })).toBeInTheDocument(); }); it("renders custom tabs", () => { const tabs = [ { id: "tab1", label: "Terminal 1" }, { id: "tab2", label: "Terminal 2" }, ]; render( ( ) as ReactElement ); expect(screen.getByRole("tab", { name: "Terminal 1" })).toBeInTheDocument(); expect(screen.getByRole("tab", { name: "Terminal 2" })).toBeInTheDocument(); }); it("marks the active tab as selected", () => { const tabs = [ { id: "tab1", label: "Tab 1" }, { id: "tab2", label: "Tab 2" }, ]; render( ( ) as ReactElement ); const tab2 = screen.getByRole("tab", { name: "Tab 2" }); expect(tab2).toHaveAttribute("aria-selected", "true"); }); it("calls onTabChange when a tab is clicked", () => { const tabs = [ { id: "tab1", label: "Tab 1" }, { id: "tab2", label: "Tab 2" }, ]; render( ( ) as ReactElement ); fireEvent.click(screen.getByRole("tab", { name: "Tab 2" })); expect(onTabChange).toHaveBeenCalledWith("tab2"); }); }); // ========================================== // Close button // ========================================== describe("close button", () => { it("renders the close button", () => { render(() as ReactElement); expect(screen.getByRole("button", { name: "Close terminal" })).toBeInTheDocument(); }); it("calls onClose when close button is clicked", () => { render(() as ReactElement); fireEvent.click(screen.getByRole("button", { name: "Close terminal" })); expect(onClose).toHaveBeenCalledTimes(1); }); }); // ========================================== // Accessibility // ========================================== describe("accessibility", () => { it("has aria-hidden=true when closed", () => { const { container } = render( () as ReactElement ); // When aria-hidden=true, testing-library role queries ignore the element's aria-label. // Use a direct DOM query to verify the attribute. const panel = container.querySelector('[role="region"][aria-label="Terminal panel"]'); expect(panel).toHaveAttribute("aria-hidden", "true"); }); it("has aria-hidden=false when open", () => { render(() as ReactElement); const panel = screen.getByRole("region", { name: "Terminal panel" }); expect(panel).toHaveAttribute("aria-hidden", "false"); }); it("has tablist role on the tab bar", () => { render(() as ReactElement); expect(screen.getByRole("tablist")).toBeInTheDocument(); }); }); });