Open History to see the last commit
1import { describe, expect, it } from "vite-plus/test";
2
3import {
4 chunkTerminalWrite,
5 encodeTerminalPaste,
6 resolveModifiedTerminalInput,
7 TERMINAL_WRITE_MAX_LENGTH,
8} from "./terminalInput";
9
10const byte = (code: number) => String.fromCharCode(code);
11const ESC = byte(0x1b);
12const CTRL_V = byte(0x16);
13
14describe("resolveModifiedTerminalInput", () => {
15 it.each(["A", "B", "C", "D"])("encodes ctrl+arrow %s with the cursor modifier", (arrow) => {
16 expect(
17 resolveModifiedTerminalInput({
18 data: `${ESC}[${arrow}`,
19 modifier: "ctrl",
20 hostPlatform: "linux",
21 }),
22 ).toEqual({ kind: "write", data: `${ESC}[1;5${arrow}` });
23 });
24
25 it.each(["A", "B", "C", "D"])("encodes meta+arrow %s with the cursor modifier", (arrow) => {
26 expect(
27 resolveModifiedTerminalInput({
28 data: `${ESC}[${arrow}`,
29 modifier: "meta",
30 hostPlatform: "linux",
31 }),
32 ).toEqual({ kind: "write", data: `${ESC}[1;3${arrow}` });
33 });
34
35 it.each(["a", "", ESC, `${ESC}[H`, `${ESC}[1;5D`, `${ESC}[D\n`])(
36 "preserves non-arrow modifier handling for %j",
37 (data) => {
38 expect(
39 resolveModifiedTerminalInput({ data, modifier: "meta", hostPlatform: "linux" }),
40 ).toEqual({
41 kind: "write",
42 data: `${ESC}${data}`,
43 });
44 },
45 );
46
47 it("pastes on ctrl+v for windows, linux, and unknown hosts", () => {
48 for (const hostPlatform of ["windows", "linux", "unknown"] as const) {
49 expect(resolveModifiedTerminalInput({ data: "v", modifier: "ctrl", hostPlatform })).toEqual({
50 kind: "paste",
51 });
52 expect(resolveModifiedTerminalInput({ data: "V", modifier: "ctrl", hostPlatform })).toEqual({
53 kind: "paste",
54 });
55 }
56 });
57
58 it("pastes on cmd+v and forwards raw ctrl+v on mac hosts", () => {
59 expect(
60 resolveModifiedTerminalInput({ data: "v", modifier: "meta", hostPlatform: "mac" }),
61 ).toEqual({ kind: "paste" });
62 expect(
63 resolveModifiedTerminalInput({ data: "v", modifier: "ctrl", hostPlatform: "mac" }),
64 ).toEqual({ kind: "write", data: CTRL_V });
65 });
66});
67
68describe("encodeTerminalPaste", () => {
69 it("turns LF and CRLF line breaks into a single carriage return each", () => {
70 expect(encodeTerminalPaste("one\ntwo\r\nthree\n")).toBe("one\rtwo\rthree\r");
71 });
72
73 it("replaces unsafe control bytes with spaces but keeps tabs", () => {
74 expect(encodeTerminalPaste(`a${byte(0)}b${ESC}c${byte(0x7f)}d\te`)).toBe("a b c d\te");
75 });
76
77 it("never lets a bracketed-paste end marker reach the shell", () => {
78 expect(encodeTerminalPaste(`safe${ESC}[201~; rm -rf /\n`)).toBe("safe [201~; rm -rf /\r");
79 });
80});
81
82describe("chunkTerminalWrite", () => {
83 it("splits oversized writes so every chunk fits the contract", () => {
84 const chunks = chunkTerminalWrite("y".repeat(TERMINAL_WRITE_MAX_LENGTH * 2 + 5));
85 expect(chunks.map((chunk) => chunk.length)).toEqual([
86 TERMINAL_WRITE_MAX_LENGTH,
87 TERMINAL_WRITE_MAX_LENGTH,
88 5,
89 ]);
90 expect(chunks.join("")).toHaveLength(TERMINAL_WRITE_MAX_LENGTH * 2 + 5);
91 });
92
93 it("does not cut a surrogate pair in half at the boundary", () => {
94 const data = `${"z".repeat(TERMINAL_WRITE_MAX_LENGTH - 1)}😀tail`;
95 const chunks = chunkTerminalWrite(data);
96 expect(chunks[0]).toHaveLength(TERMINAL_WRITE_MAX_LENGTH - 1);
97 expect(chunks[1]).toBe("😀tail");
98 expect(chunks.join("")).toBe(data);
99 });
100});