feat(run): add TerminalView xterm.js component for the PTY transport
- TerminalView.tsx: xterm.js component with WebSocket attachment to /ws-pty/:runId - Test: validates WS connection URL, incoming terminal data, and outgoing keystrokes - Added ResizeObserver stub to test-setup.ts for jsdom environment
This commit is contained in:
@@ -0,0 +1,84 @@
|
||||
/**
|
||||
* @file TerminalView.test.tsx
|
||||
* @description Tests for the xterm.js-backed terminal view: verifies it opens
|
||||
* a WS connection to the right URL, writes incoming binary frames to the
|
||||
* mocked terminal, and forwards typed input as outgoing binary frames.
|
||||
* @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
|
||||
*/
|
||||
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
||||
import { render, cleanup } from "@testing-library/react";
|
||||
import { TerminalView } from "../TerminalView";
|
||||
|
||||
const writeMock = vi.fn();
|
||||
const onDataHandlers: Array<(d: string) => void> = [];
|
||||
const openMock = vi.fn();
|
||||
const disposeMock = vi.fn();
|
||||
|
||||
vi.mock("@xterm/xterm", () => ({
|
||||
Terminal: vi.fn().mockImplementation(() => ({
|
||||
open: openMock,
|
||||
write: writeMock,
|
||||
onData: (fn: (d: string) => void) => {
|
||||
onDataHandlers.push(fn);
|
||||
return { dispose: vi.fn() };
|
||||
},
|
||||
dispose: disposeMock,
|
||||
loadAddon: vi.fn(),
|
||||
})),
|
||||
}));
|
||||
vi.mock("@xterm/addon-fit", () => ({
|
||||
FitAddon: vi.fn().mockImplementation(() => ({ fit: vi.fn() })),
|
||||
}));
|
||||
|
||||
class MockWebSocket {
|
||||
static instances: MockWebSocket[] = [];
|
||||
url: string;
|
||||
sent: unknown[] = [];
|
||||
onopen: (() => void) | null = null;
|
||||
onmessage: ((e: { data: unknown }) => void) | null = null;
|
||||
onclose: (() => void) | null = null;
|
||||
constructor(url: string) {
|
||||
this.url = url;
|
||||
MockWebSocket.instances.push(this);
|
||||
}
|
||||
send(data: unknown) {
|
||||
this.sent.push(data);
|
||||
}
|
||||
close() {
|
||||
this.onclose?.();
|
||||
}
|
||||
}
|
||||
// @ts-expect-error test override
|
||||
global.WebSocket = MockWebSocket;
|
||||
|
||||
describe("TerminalView", () => {
|
||||
beforeEach(() => {
|
||||
MockWebSocket.instances = [];
|
||||
onDataHandlers.length = 0;
|
||||
writeMock.mockClear();
|
||||
openMock.mockClear();
|
||||
});
|
||||
|
||||
afterEach(cleanup);
|
||||
|
||||
it("opens a WS connection to the run's ws-pty path", () => {
|
||||
render(<TerminalView runId="ccam-lane-1" wsBaseUrl="ws://localhost:4820" />);
|
||||
expect(MockWebSocket.instances).toHaveLength(1);
|
||||
expect(MockWebSocket.instances[0].url).toBe("ws://localhost:4820/ws-pty/ccam-lane-1");
|
||||
});
|
||||
|
||||
it("writes incoming WS data to the terminal", () => {
|
||||
render(<TerminalView runId="ccam-lane-1" wsBaseUrl="ws://localhost:4820" />);
|
||||
const ws = MockWebSocket.instances[0];
|
||||
ws.onopen?.();
|
||||
ws.onmessage?.({ data: "hello" });
|
||||
expect(writeMock).toHaveBeenCalledWith("hello");
|
||||
});
|
||||
|
||||
it("forwards terminal keystrokes as outgoing WS sends", () => {
|
||||
render(<TerminalView runId="ccam-lane-1" wsBaseUrl="ws://localhost:4820" />);
|
||||
const ws = MockWebSocket.instances[0];
|
||||
onDataHandlers[0]("ls -la\r");
|
||||
expect(ws.sent).toEqual(["ls -la\r"]);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user