/** * KHAELOR * File: tests/tui/input.test.ts * Description: Key decoder tests — control keys, escape sequences, bracketed paste across chunks, reply filtering. * * Author: Simon-Pierre Boucher * Contact: contact@spboucher.ai */ import { describe, expect, it } from "vitest"; import { KeyDecoder } from "../../src/tui/renderer/input.js"; function decode(...chunks: string[]) { const d = new KeyDecoder(); return chunks.flatMap((c) => d.push(c)); } describe("KeyDecoder", () => { it("decodes printable characters including unicode", () => { expect(decode("aĆ©šŸŽ‰")).toEqual([ { type: "char", ch: "a" }, { type: "char", ch: "Ć©" }, { type: "char", ch: "šŸŽ‰" }, ]); }); it("decodes enter, tab, backspace, ctrl keys", () => { expect(decode("\r")).toEqual([{ type: "enter" }]); expect(decode("\t")).toEqual([{ type: "tab" }]); expect(decode("\x7f")).toEqual([{ type: "backspace" }]); expect(decode("\x01")).toEqual([{ type: "ctrl", ch: "a" }]); expect(decode("\x0b")).toEqual([{ type: "ctrl", ch: "k" }]); expect(decode("\x1f")).toEqual([{ type: "ctrl", ch: "_" }]); expect(decode("\n")).toEqual([{ type: "ctrl", ch: "j" }]); }); it("decodes arrows with alt/ctrl modifiers", () => { expect(decode("\x1b[A")).toEqual([{ type: "arrow", key: "up", alt: false, ctrl: false }]); expect(decode("\x1b[1;5C")).toEqual([{ type: "arrow", key: "right", alt: false, ctrl: true }]); expect(decode("\x1b[1;3D")).toEqual([{ type: "arrow", key: "left", alt: true, ctrl: false }]); }); it("decodes home/end/delete variants", () => { expect(decode("\x1b[H")).toEqual([{ type: "home" }]); expect(decode("\x1b[4~")).toEqual([{ type: "end" }]); expect(decode("\x1b[3~")).toEqual([{ type: "delete" }]); expect(decode("\x1bOF")).toEqual([{ type: "end" }]); }); it("decodes alt-letter and alt-backspace", () => { expect(decode("\x1bb")).toEqual([{ type: "alt", ch: "b" }]); expect(decode("\x1b\x7f")).toEqual([{ type: "alt-backspace" }]); }); it("decodes kitty shift+enter", () => { expect(decode("\x1b[13;2u")).toEqual([{ type: "shift-enter" }]); }); it("treats a chunk ending on lone ESC as the Esc key", () => { expect(decode("\x1b")).toEqual([{ type: "esc" }]); }); it("reassembles a bracketed paste split across chunks", () => { expect(decode("\x1b[200~hello\nwor", "ld\x1b[201~")).toEqual([ { type: "paste", text: "hello\nworld" }, ]); }); it("handles a paste-end marker torn mid-sequence", () => { expect(decode("\x1b[200~abc\x1b[20", "1~x")).toEqual([ { type: "paste", text: "abc" }, { type: "char", ch: "x" }, ]); }); it("silently drops terminal query replies (DECRQM, OSC 11)", () => { expect(decode("\x1b[?2026;1$y")).toEqual([]); expect(decode("\x1b]11;rgb:1c1c/1c1c/1c1c\x07")).toEqual([]); }); it("keeps a torn CSI across chunks and completes it", () => { expect(decode("\x1b[1;", "5C")).toEqual([ { type: "arrow", key: "right", alt: false, ctrl: true }, ]); }); });