Open History to see the last commit
1import { describe, expect, it } from "vite-plus/test";
2
3import wasmDataUrl from "./vendor/ghostty-vt.wasm?inline";
4import writePtyWasmDataUrl from "./vendor/ghostty-write-pty.wasm?inline";
5import pinnedVersion from "../../../../../native/libghostty-vt/VERSION?raw";
6
7type WasmFunction = (...args: number[]) => number;
8
9function decodeWasmDataUrl(dataUrl: string): Uint8Array {
10 const encoded = dataUrl.split(",", 2)[1];
11 if (!encoded) throw new Error("The vendored Ghostty WASM data URL is invalid");
12 return Uint8Array.from(atob(encoded), (character) => character.charCodeAt(0));
13}
14
15describe("vendored libghostty-vt WebAssembly", () => {
16 it("stays pinned to the canonical revision and size budget", async () => {
17 const wasm = decodeWasmDataUrl(wasmDataUrl);
18 expect(wasm.byteLength).toBeLessThan(750_000);
19
20 const result = await WebAssembly.instantiate(wasm.buffer as ArrayBuffer, {
21 env: { log: () => {} },
22 });
23 const instance = result instanceof WebAssembly.Instance ? result : result.instance;
24 const memory = instance.exports.memory as WebAssembly.Memory;
25 const call = (name: string, ...args: number[]) =>
26 (instance.exports[name] as WasmFunction)(...args);
27 const out = call("ghostty_wasm_alloc_u8_array", 8);
28 expect(call("ghostty_build_info", 10, out)).toBe(0);
29 const view = new DataView(memory.buffer, out, 8);
30 const embeddedRevision = new TextDecoder().decode(
31 new Uint8Array(memory.buffer, view.getUint32(0, true), view.getUint32(4, true)),
32 );
33 call("ghostty_wasm_free_u8_array", out, 8);
34 expect(embeddedRevision).toBe(pinnedVersion.trim());
35 });
36
37 it("routes terminal-generated replies through the shared callback table", async () => {
38 const mainResult = await WebAssembly.instantiate(
39 decodeWasmDataUrl(wasmDataUrl).buffer as ArrayBuffer,
40 { env: { log: () => {} } },
41 );
42 const main = mainResult instanceof WebAssembly.Instance ? mainResult : mainResult.instance;
43 const memory = main.exports.memory as WebAssembly.Memory;
44 let reply = "";
45 const trampolineResult = await WebAssembly.instantiate(
46 decodeWasmDataUrl(writePtyWasmDataUrl).buffer as ArrayBuffer,
47 {
48 env: {
49 t3_write_pty: (_terminal: number, _userdata: number, pointer: number, length: number) => {
50 reply += new TextDecoder().decode(new Uint8Array(memory.buffer, pointer, length));
51 },
52 },
53 },
54 );
55 const trampoline =
56 trampolineResult instanceof WebAssembly.Instance
57 ? trampolineResult
58 : trampolineResult.instance;
59 const table = main.exports.__indirect_function_table as WebAssembly.Table;
60 const callbackIndex = table.length;
61 table.grow(1, trampoline.exports.ghostty_write_pty as CallableFunction);
62 const call = (name: string, ...args: number[]) => (main.exports[name] as WasmFunction)(...args);
63 const options = call("ghostty_wasm_alloc_u8_array", 8);
64 const optionsView = new DataView(memory.buffer, options, 8);
65 optionsView.setUint16(0, 80, true);
66 optionsView.setUint16(2, 24, true);
67 const terminalSlot = call("ghostty_wasm_alloc_opaque");
68 expect(call("ghostty_terminal_new", 0, terminalSlot, options)).toBe(0);
69 const terminal = new DataView(memory.buffer).getUint32(terminalSlot, true);
70 call("ghostty_terminal_set", terminal, 0, 1);
71 call("ghostty_terminal_set", terminal, 1, callbackIndex);
72 const query = new TextEncoder().encode("\u001b[5n");
73 const queryPointer = call("ghostty_wasm_alloc_u8_array", query.length);
74 new Uint8Array(memory.buffer, queryPointer, query.length).set(query);
75 call("ghostty_terminal_vt_write", terminal, queryPointer, query.length);
76
77 expect(reply).toBe("\u001b[0n");
78 reply = "";
79 call("ghostty_terminal_set", terminal, 1, 0);
80 call("ghostty_terminal_set", terminal, 0, 0);
81 call("ghostty_terminal_vt_write", terminal, queryPointer, query.length);
82 expect(reply).toBe("");
83 call("ghostty_terminal_set", terminal, 0, 1);
84 call("ghostty_terminal_set", terminal, 1, callbackIndex);
85 call("ghostty_terminal_vt_write", terminal, queryPointer, query.length);
86 expect(reply).toBe("\u001b[0n");
87 call("ghostty_wasm_free_u8_array", queryPointer, query.length);
88 call("ghostty_terminal_free", terminal);
89 call("ghostty_wasm_free_opaque", terminalSlot);
90 call("ghostty_wasm_free_u8_array", options, 8);
91 });
92});