1import { describe, expect, it } from "vite-plus/test";
2
3import {
4 applyCtrlModifier,
5 chunkTerminalWrite,
6 encodeTerminalPaste,
7 hostPlatformFromOs,
8 resolveModifiedTerminalInput,
9 TERMINAL_WRITE_MAX_LENGTH,
10} from "./terminalInput";
11
12const byte = (code: number) => String.fromCharCode(code);
13const ESC = byte(0x1b);
14const CTRL_C = byte(0x03);
15const CTRL_V = byte(0x16);
16
17describe("applyCtrlModifier", () => {
18 it("maps letters to control bytes regardless of case", () => {
19 expect(applyCtrlModifier("c")).toBe(CTRL_C);
20 expect(applyCtrlModifier("C")).toBe(CTRL_C);
21 expect(applyCtrlModifier("z")).toBe(byte(0x1a));
22 });
23
24 it("maps the punctuation control keys and leaves the rest untouched", () => {
25 expect(applyCtrlModifier("[")).toBe(ESC);
26 expect(applyCtrlModifier("?")).toBe(byte(0x7f));
27 expect(applyCtrlModifier("1")).toBe("1");
28 expect(applyCtrlModifier("")).toBe("");
29 });
30});
31
32describe("resolveModifiedTerminalInput", () => {
33 it("pastes on ctrl+v for windows, linux, and unknown hosts", () => {
34 for (const hostPlatform of ["windows", "linux", "unknown"] as const) {
35 expect(resolveModifiedTerminalInput({ data: "v", modifier: "ctrl", hostPlatform })).toEqual({
36 kind: "paste",
37 });
38 expect(resolveModifiedTerminalInput({ data: "V", modifier: "ctrl", hostPlatform })).toEqual({
39 kind: "paste",
40 });
41 }
42 });
43
44 it("keeps alt+v as a meta chord on non-mac hosts", () => {
45 expect(
46 resolveModifiedTerminalInput({ data: "v", modifier: "meta", hostPlatform: "windows" }),
47 ).toEqual({ kind: "write", data: `${ESC}v` });
48 });
49
50 it("pastes on cmd+v and forwards raw ctrl+v on mac hosts", () => {
51 expect(
52 resolveModifiedTerminalInput({ data: "v", modifier: "meta", hostPlatform: "mac" }),
53 ).toEqual({ kind: "paste" });
54 expect(
55 resolveModifiedTerminalInput({ data: "v", modifier: "ctrl", hostPlatform: "mac" }),
56 ).toEqual({ kind: "write", data: CTRL_V });
57 });
58
59 it("still encodes every other modified key", () => {
60 expect(
61 resolveModifiedTerminalInput({ data: "c", modifier: "ctrl", hostPlatform: "windows" }),
62 ).toEqual({ kind: "write", data: CTRL_C });
63 expect(
64 resolveModifiedTerminalInput({ data: "[A", modifier: "meta", hostPlatform: "linux" }),
65 ).toEqual({ kind: "write", data: `${ESC}[A` });
66 });
67});
68
69describe("encodeTerminalPaste", () => {
70 it("passes single-line text through unchanged", () => {
71 expect(encodeTerminalPaste("git switch -c fix/paste")).toBe("git switch -c fix/paste");
72 expect(encodeTerminalPaste("")).toBe("");
73 });
74
75 it("turns LF and CRLF line breaks into a single carriage return each", () => {
76 expect(encodeTerminalPaste("one\ntwo\r\nthree\n")).toBe("one\rtwo\rthree\r");
77 });
78
79 it("replaces unsafe control bytes with spaces but keeps tabs", () => {
80 expect(encodeTerminalPaste(`a${byte(0)}b${ESC}c${byte(0x7f)}d\te`)).toBe("a b c d\te");
81 });
82
83 it("never lets a bracketed-paste end marker reach the shell", () => {
84 expect(encodeTerminalPaste(`safe${ESC}[201~; rm -rf /\n`)).toBe("safe [201~; rm -rf /\r");
85 });
86});
87
88describe("chunkTerminalWrite", () => {
89 it("leaves writes within the wire limit whole", () => {
90 expect(chunkTerminalWrite("")).toEqual([]);
91 expect(chunkTerminalWrite("ls")).toEqual(["ls"]);
92 expect(chunkTerminalWrite("x".repeat(TERMINAL_WRITE_MAX_LENGTH))).toHaveLength(1);
93 });
94
95 it("splits oversized writes so every chunk fits the contract", () => {
96 const chunks = chunkTerminalWrite("y".repeat(TERMINAL_WRITE_MAX_LENGTH * 2 + 5));
97 expect(chunks.map((chunk) => chunk.length)).toEqual([
98 TERMINAL_WRITE_MAX_LENGTH,
99 TERMINAL_WRITE_MAX_LENGTH,
100 5,
101 ]);
102 expect(chunks.join("")).toHaveLength(TERMINAL_WRITE_MAX_LENGTH * 2 + 5);
103 });
104
105 it("does not cut a surrogate pair in half at the boundary", () => {
106 const data = `${"z".repeat(TERMINAL_WRITE_MAX_LENGTH - 1)}😀tail`;
107 const chunks = chunkTerminalWrite(data);
108 expect(chunks[0]).toHaveLength(TERMINAL_WRITE_MAX_LENGTH - 1);
109 expect(chunks[1]).toBe("😀tail");
110 expect(chunks.join("")).toBe(data);
111 });
112});
113
114describe("hostPlatformFromOs", () => {
115 it("maps the descriptor os onto the toolbar layout", () => {
116 expect(hostPlatformFromOs("darwin")).toBe("mac");
117 expect(hostPlatformFromOs("windows")).toBe("windows");
118 expect(hostPlatformFromOs("linux")).toBe("linux");
119 });
120
121 it("defers to the caller when the os is unknown or not loaded yet", () => {
122 expect(hostPlatformFromOs("unknown")).toBeNull();
123 expect(hostPlatformFromOs(null)).toBeNull();
124 });
125});