Co-authored-by: Jason Woltje <[email protected]> Co-committed-by: Jason Woltje <[email protected]>
196 lines
6.7 KiB
TypeScript
196 lines
6.7 KiB
TypeScript
/**
|
|
* @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 }) => (
|
|
<div
|
|
data-testid="mock-xterminal"
|
|
data-token={token}
|
|
data-visible={isVisible ? "true" : "false"}
|
|
/>
|
|
)),
|
|
}));
|
|
|
|
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((<TerminalPanel open={true} onClose={onClose} token="test-token" />) as ReactElement);
|
|
expect(screen.getByRole("region", { name: "Terminal panel" })).toBeInTheDocument();
|
|
});
|
|
|
|
it("renders with height 280 when open", () => {
|
|
render((<TerminalPanel open={true} onClose={onClose} token="test-token" />) 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(
|
|
(<TerminalPanel open={false} onClose={onClose} token="test-token" />) 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((<TerminalPanel open={true} onClose={onClose} token="test-token" />) 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(
|
|
(<TerminalPanel open={false} onClose={onClose} token="test-token" />) 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(
|
|
(<TerminalPanel open={true} onClose={onClose} token="my-auth-token" />) 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((<TerminalPanel open={true} onClose={onClose} token="test-token" />) 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(
|
|
(
|
|
<TerminalPanel open={true} onClose={onClose} tabs={tabs} token="test-token" />
|
|
) 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(
|
|
(
|
|
<TerminalPanel
|
|
open={true}
|
|
onClose={onClose}
|
|
tabs={tabs}
|
|
activeTab="tab2"
|
|
token="test-token"
|
|
/>
|
|
) 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(
|
|
(
|
|
<TerminalPanel
|
|
open={true}
|
|
onClose={onClose}
|
|
tabs={tabs}
|
|
onTabChange={onTabChange}
|
|
token="test-token"
|
|
/>
|
|
) 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((<TerminalPanel open={true} onClose={onClose} token="test-token" />) as ReactElement);
|
|
expect(screen.getByRole("button", { name: "Close terminal" })).toBeInTheDocument();
|
|
});
|
|
|
|
it("calls onClose when close button is clicked", () => {
|
|
render((<TerminalPanel open={true} onClose={onClose} token="test-token" />) 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(
|
|
(<TerminalPanel open={false} onClose={onClose} token="test-token" />) 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((<TerminalPanel open={true} onClose={onClose} token="test-token" />) 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((<TerminalPanel open={true} onClose={onClose} token="test-token" />) as ReactElement);
|
|
expect(screen.getByRole("tablist")).toBeInTheDocument();
|
|
});
|
|
});
|
|
});
|