summaryrefslogtreecommitdiff
path: root/tests.zig
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-06-17 21:40:03 -0300
committerGabriel Schneider <[email protected]>2026-06-17 23:44:24 -0300
commit15b9bef58c80e918352567e909a88084ca3134ef (patch)
treeb3d791abf5888dc94a6f4ba280119b7d92a9afd2 /tests.zig
parentffed14b9857b53a2aae4a0c39dee47949f1331f2 (diff)
downloadpardes-vibes-ghostty.tar.gz
pardes-vibes-ghostty.zip
vibe fast vibes-ghostty
Diffstat (limited to 'tests.zig')
-rw-r--r--tests.zig140
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());
+}