diff options
| author | Gabriel Schneider <[email protected]> | 2026-06-17 21:40:03 -0300 |
|---|---|---|
| committer | Gabriel Schneider <[email protected]> | 2026-06-17 23:44:24 -0300 |
| commit | 15b9bef58c80e918352567e909a88084ca3134ef (patch) | |
| tree | b3d791abf5888dc94a6f4ba280119b7d92a9afd2 /tests.zig | |
| parent | ffed14b9857b53a2aae4a0c39dee47949f1331f2 (diff) | |
| download | pardes-vibes-ghostty.tar.gz pardes-vibes-ghostty.zip | |
vibe fast vibes-ghostty
Diffstat (limited to 'tests.zig')
| -rw-r--r-- | tests.zig | 140 |
1 files changed, 140 insertions, 0 deletions
diff --git a/tests.zig b/tests.zig new file mode 100644 index 00000000..6fa43f7a --- /dev/null +++ b/tests.zig @@ -0,0 +1,140 @@ +//! Deterministic tests for the terminal-pane pipeline, exercised without a real +//! tty/vaxis: pty -> ghostty-vt parsing -> RenderState grid, plus the input +//! encoders used to send keys/mouse back to the child. + +const std = @import("std"); +const posix = std.posix; +const testing = std.testing; +const ghostty_vt = @import("ghostty-vt"); + +extern "c" fn forkpty(amaster: *c_int, name: ?[*:0]u8, termp: ?*const anyopaque, winp: ?*const posix.winsize) c_int; +extern "c" fn execvp(file: [*:0]const u8, argv: [*:null]const ?[*:0]const u8) c_int; +extern "c" fn _exit(status: c_int) noreturn; + +/// Read all of a cell row's codepoints into `out`, returning the slice used. +fn rowText(rs: *const ghostty_vt.RenderState, y: usize, out: []u8) []const u8 { + const cells = rs.row_data.items(.cells)[y]; + var n: usize = 0; + var x: usize = 0; + while (x < rs.cols and n < out.len) : (x += 1) { + const cp = cells.get(x).raw.codepoint(); + out[n] = if (cp == 0 or cp > 0x7f) ' ' else @intCast(cp); + n += 1; + } + // trim trailing spaces for convenience + while (n > 0 and out[n - 1] == ' ') n -= 1; + return out[0..n]; +} + +test "pty -> ghostty: child output lands in the grid with color" { + const alloc = testing.allocator; + + var t: ghostty_vt.Terminal = try .init(alloc, .{ .cols = 40, .rows = 5 }); + defer t.deinit(alloc); + + var stream = t.vtStream(); + defer stream.deinit(); + + // A one-shot child that prints bold-green "GREEN" then a newline. + const argv = [_:null]?[*:0]const u8{ + "/bin/sh", "-c", "printf '\\033[1;32mGREEN\\033[0m\\n'", null, + }; + var master: c_int = undefined; + const ws = posix.winsize{ .row = 5, .col = 40, .xpixel = 0, .ypixel = 0 }; + const pid = forkpty(&master, null, null, &ws); + if (pid == 0) { + _ = execvp(argv[0].?, &argv); + _exit(127); + } + defer posix.kill(pid, posix.SIG.HUP) catch {}; + + // Drain the child until EOF, feeding bytes into ghostty as they arrive + // (exactly what the app's reader task does). + var buf: [1024]u8 = undefined; + while (true) { + const n = posix.read(master, &buf) catch break; + if (n == 0) break; + stream.nextSlice(buf[0..n]); + } + + var rs: ghostty_vt.RenderState = .empty; + defer rs.deinit(alloc); + try rs.update(alloc, &t); + + var text_buf: [40]u8 = undefined; + try testing.expectEqualStrings("GREEN", rowText(&rs, 0, &text_buf)); + + // The 'G' cell should be bold with a green (palette index 2) foreground. + const g = rs.row_data.items(.cells)[0].get(0); + try testing.expect(g.raw.hasStyling()); + try testing.expect(g.style.flags.bold); + try testing.expectEqual(ghostty_vt.Style.Color{ .palette = 2 }, g.style.fg_color); +} + +test "encodeKey: ctrl+c -> 0x03" { + // Mirrors how main.zig builds a KeyEvent from a vaxis ctrl+c. + const ev: ghostty_vt.input.KeyEvent = .{ + .action = .press, + .key = .key_c, + .mods = .{ .ctrl = true }, + .unshifted_codepoint = 'c', + }; + + var buf: [16]u8 = undefined; + var w = std.Io.Writer.fixed(&buf); + try ghostty_vt.input.encodeKey(&w, ev, .{}); + try testing.expectEqualStrings("\x03", w.buffered()); +} + +test "encodeKey: plain 'a' -> a" { + const ev: ghostty_vt.input.KeyEvent = .{ + .action = .press, + .key = .key_a, + .unshifted_codepoint = 'a', + .utf8 = "a", + }; + + var buf: [16]u8 = undefined; + var w = std.Io.Writer.fixed(&buf); + try ghostty_vt.input.encodeKey(&w, ev, .{}); + try testing.expectEqualStrings("a", w.buffered()); +} + +test "encodeKey: up arrow -> CSI A" { + const ev: ghostty_vt.input.KeyEvent = .{ .action = .press, .key = .arrow_up }; + var buf: [16]u8 = undefined; + var w = std.Io.Writer.fixed(&buf); + try ghostty_vt.input.encodeKey(&w, ev, .{}); + try testing.expectEqualStrings("\x1b[A", w.buffered()); +} + +test "encodeMouse: SGR left press at cell (0,0)" { + const alloc = testing.allocator; + var t: ghostty_vt.Terminal = try .init(alloc, .{ .cols = 40, .rows = 5 }); + defer t.deinit(alloc); + + // Enable normal mouse tracking (1000) in SGR mode (1006), as a child app + // would, so fromTerminal picks up the right reporting mode/format. + var stream = t.vtStream(); + defer stream.deinit(); + stream.nextSlice("\x1b[?1000h\x1b[?1006h"); + + const Size = @FieldType(ghostty_vt.input.MouseEncodeOptions, "size"); + var size: Size = std.mem.zeroes(Size); + size.screen = .{ .width = t.cols, .height = t.rows }; + size.cell = .{ .width = 1, .height = 1 }; + + var opts = ghostty_vt.input.MouseEncodeOptions.fromTerminal(&t, size); + opts.any_button_pressed = true; + + const ev: ghostty_vt.input.MouseEncodeEvent = .{ + .action = .press, + .button = .left, + .pos = .{ .x = 0, .y = 0 }, + }; + + var buf: [32]u8 = undefined; + var w = std.Io.Writer.fixed(&buf); + try ghostty_vt.input.encodeMouse(&w, ev, opts); + try testing.expectEqualStrings("\x1b[<0;1;1M", w.buffered()); +} |
