nandi/oripublic Fork 0
00224b167266e7d496672adfe1e60fcffeab7e63
Commits
Clone
git clone https://git.rickub.com/nandi/ori.git
git clone ssh://git@rickub.com/nandi/ori.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

forked from bots-garden/ori

TerminalPane.test.tsx · 134 lines · 3.5 KBTypeScript Blame HistoryRaw
✨ Workspace panel, selectors, previews, desktop app, sandbox template, resizable file tree, light/dark theme 76d62ac k33g yesterday1import { render, waitFor } from "@testing-library/react";
2import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
3import { TerminalPane, terminalWsUrl } from "./TerminalPane";
4
5const { terminalInstances, TerminalMock, FitAddonMock } = vi.hoisted(() => {
6 class TerminalStub {
7 cols = 80;
8 rows = 24;
9 opened: HTMLElement | null = null;
10 written: unknown[] = [];
11 disposed = false;
12 dataHandler: ((data: string) => void) | null = null;
13
14 loadAddon() {}
15 open(element: HTMLElement) {
16 this.opened = element;
17 }
18 write(data: unknown) {
19 this.written.push(data);
20 }
21 onData(handler: (data: string) => void) {
22 this.dataHandler = handler;
23 return { dispose: () => {} };
24 }
25 onResize() {
26 return { dispose: () => {} };
27 }
28 dispose() {
29 this.disposed = true;
30 }
31 }
32 const instances: TerminalStub[] = [];
33 return {
34 terminalInstances: instances,
35 TerminalMock: class extends TerminalStub {
36 constructor() {
37 super();
38 instances.push(this);
39 }
40 },
41 FitAddonMock: class {
42 fit() {}
43 },
44 };
45});
46
47vi.mock("@xterm/xterm", () => ({ Terminal: TerminalMock }));
48vi.mock("@xterm/addon-fit", () => ({ FitAddon: FitAddonMock }));
49vi.mock("@xterm/xterm/css/xterm.css", () => ({}));
50
51class FakeWebSocket {
52 static OPEN = 1;
53 static instances: FakeWebSocket[] = [];
54 url: string;
55 readyState = FakeWebSocket.OPEN;
56 binaryType = "";
57 sent: string[] = [];
58 onmessage: ((event: { data: ArrayBuffer }) => void) | null = null;
59 onopen: (() => void) | null = null;
60 onclose: (() => void) | null = null;
61
62 constructor(url: string) {
63 this.url = url;
64 FakeWebSocket.instances.push(this);
65 }
66 send(data: string) {
67 this.sent.push(data);
68 }
69 close() {}
70}
71
72describe("TerminalPane", () => {
73 beforeEach(() => {
74 terminalInstances.length = 0;
75 FakeWebSocket.instances.length = 0;
76 vi.stubGlobal("WebSocket", FakeWebSocket);
77 vi.stubGlobal(
78 "ResizeObserver",
79 class {
80 observe() {}
81 disconnect() {}
82 },
83 );
84 });
85
86 afterEach(() => {
87 vi.unstubAllGlobals();
88 });
89
90 it("opens xterm in the pane and connects to /ws/terminal", async () => {
91 render(<TerminalPane />);
92
93 await waitFor(() => expect(terminalInstances).toHaveLength(1));
94 expect(terminalInstances[0].opened).not.toBeNull();
95
96 await waitFor(() => expect(FakeWebSocket.instances).toHaveLength(1));
97 expect(FakeWebSocket.instances[0].url).toBe(terminalWsUrl());
98 expect(FakeWebSocket.instances[0].binaryType).toBe("arraybuffer");
99 });
100
101 it("announces its size on open and forwards keystrokes as input frames", async () => {
102 render(<TerminalPane />);
103 await waitFor(() => expect(FakeWebSocket.instances).toHaveLength(1));
104 const socket = FakeWebSocket.instances[0];
105
106 socket.onopen?.();
107 expect(socket.sent[0]).toBe(
108 JSON.stringify({ type: "resize", cols: 80, rows: 24 }),
109 );
110
111 terminalInstances[0].dataHandler?.("ls\r");
112 expect(socket.sent[1]).toBe(
113 JSON.stringify({ type: "input", data: "ls\r" }),
114 );
115 });
116
117 it("writes incoming binary frames to the terminal", async () => {
118 render(<TerminalPane />);
119 await waitFor(() => expect(FakeWebSocket.instances).toHaveLength(1));
120
121 FakeWebSocket.instances[0].onmessage?.({
122 data: new TextEncoder().encode("hello").buffer as ArrayBuffer,
123 });
124 expect(terminalInstances[0].written).toHaveLength(1);
125 });
126
127 it("disposes the terminal on unmount", async () => {
128 const { unmount } = render(<TerminalPane />);
129 await waitFor(() => expect(terminalInstances).toHaveLength(1));
130
131 unmount();
132 await waitFor(() => expect(terminalInstances[0].disposed).toBe(true));
133 });
134});