import { assert, assertEquals } from "@std/assert"; import { existsSync } from "node:fs"; import { Ghostty, type KeyInput } from "./ghostty.ts"; const WASM = new URL("./ghostty-vt.wasm", import.meta.url); const built = existsSync(WASM); const load = async () => await Ghostty.load(await Deno.readFile(WASM)); const encode = (text: string) => new TextEncoder().encode(text); const decode = (bytes: Uint8Array | null) => bytes && new TextDecoder().decode(bytes); const press = (code: string, key: string, mods: Partial = {}): KeyInput => ({ code, key, shiftKey: false, ctrlKey: false, altKey: false, metaKey: false, repeat: false, ...mods, }); Deno.test({ name: "a terminal draws what it is written in runs of one style, and then only what changes", ignore: !built, async fn() { const terminal = (await load()).terminal(20, 4); terminal.write(encode("plain \x1b[1;31mbold red\x1b[0m\r\n\x1b[7minverse\x1b[0m 🦀 x ")); const first = terminal.frame(); assertEquals(first.cols, 20); assertEquals(first.rows, 4); assertEquals(first.changed.size, 4); assertEquals(first.changed.get(0), [ { text: "plain " }, { text: "bold red", bold: true, fg: first.changed.get(0)![1].fg }, ]); const red = first.changed.get(0)![1].fg!; assert(red[0] > red[1] && red[0] > red[2], "red is red"); const [inverse, space, crab, rest] = first.changed.get(1)!; assertEquals(inverse.text, "inverse"); assert(inverse.fg && inverse.bg, "inverse swaps in the defaults"); assertEquals([space.text, crab, rest.text], [" ", { text: "🦀", wide: true }, " x"]); assertEquals(first.changed.get(2), []); assertEquals(first.cursor, { x: 15, y: 1, style: "block" }); assertEquals(terminal.frame().changed.size, 0); terminal.write(encode("\r\nnext")); assertEquals([...terminal.frame().changed.keys()], [1, 2]); terminal.free(); }, }); Deno.test({ name: "a snapshot carries a terminal, scrollback and unfinished sequences included, to another", ignore: !built, async fn() { const ghostty = await load(); const terminal = ghostty.terminal(10, 2); terminal.write(encode("one\r\ntwo\r\nthree\r\n\x1b[3")); const restored = ghostty.restore(terminal.snapshot()); assertEquals(restored.text(), terminal.text()); // the half-written sequence finishes in the copy, as it would have in the original restored.write(encode("1mred")); assertEquals(restored.frame().changed.get(1)!.at(-1)!.text, "red"); restored.scroll(-1); assertEquals(restored.frame().changed.get(0), [{ text: "two" }]); }, }); Deno.test({ name: "keys encode as the program's modes ask, pastes bracket when asked, and queries are answered", ignore: !built, async fn() { const terminal = (await load()).terminal(80, 24); assertEquals(decode(terminal.key(press("KeyA", "a"))), "a"); assertEquals(decode(terminal.key(press("KeyC", "c", { ctrlKey: true }))), "\x03"); assertEquals(decode(terminal.key(press("ArrowUp", "ArrowUp"))), "\x1b[A"); assertEquals(decode(terminal.key(press("Enter", "Enter"))), "\r"); assertEquals(terminal.key(press("ShiftLeft", "Shift", { shiftKey: true })), null); assertEquals(decode(terminal.paste("ls")), "ls"); const answers: string[] = []; terminal.answer((data) => answers.push(decode(data)!)); terminal.write(encode("\x1b[?1h\x1b[?2004h\x1b[c")); assertEquals(decode(terminal.key(press("ArrowUp", "ArrowUp"))), "\x1bOA"); assertEquals(decode(terminal.paste("ls")), "\x1b[200~ls\x1b[201~"); assertEquals(answers.length, 1); assert(answers[0].startsWith("\x1b[?"), "a device attributes report"); }, });