vt: add tests for write_pty and bell effect callbacks

Test that the write_pty callback receives correct DECRQM response
data and userdata, that queries are silently ignored without a
callback, and that setting null clears the callback. Test that
the bell callback fires on single and multiple BEL characters
with correct userdata, and that BEL without a callback is safe.
This commit is contained in:
Mitchell Hashimoto
2026-03-24 06:58:17 -07:00
parent b49e9f37ff
commit c13a9bb49c

View File

@@ -864,6 +864,157 @@ test "grid_ref null terminal" {
}, &out_ref));
}
test "set write_pty callback" {
var t: Terminal = null;
try testing.expectEqual(Result.success, new(
&lib_alloc.test_allocator,
&t,
.{
.cols = 80,
.rows = 24,
.max_scrollback = 0,
},
));
defer free(t);
const S = struct {
var last_data: ?[]u8 = null;
var last_userdata: ?*anyopaque = null;
fn deinit() void {
if (last_data) |d| testing.allocator.free(d);
last_data = null;
last_userdata = null;
}
fn writePty(_: Terminal, ud: ?*anyopaque, ptr: [*]const u8, len: usize) callconv(.c) void {
if (last_data) |d| testing.allocator.free(d);
last_data = testing.allocator.dupe(u8, ptr[0..len]) catch @panic("OOM");
last_userdata = ud;
}
};
defer S.deinit();
// Set userdata and write_pty callback
var sentinel: u8 = 42;
const ud: ?*anyopaque = @ptrCast(&sentinel);
set(t, .userdata, @ptrCast(&ud));
const cb: ?Effects.WritePtyFn = &S.writePty;
set(t, .write_pty, @ptrCast(&cb));
// DECRQM for wraparound mode (mode 7, set by default) should trigger write_pty
vt_write(t, "\x1B[?7$p", 6);
try testing.expect(S.last_data != null);
try testing.expectEqualStrings("\x1B[?7;1$y", S.last_data.?);
try testing.expectEqual(@as(?*anyopaque, @ptrCast(&sentinel)), S.last_userdata);
}
test "set write_pty without callback ignores queries" {
var t: Terminal = null;
try testing.expectEqual(Result.success, new(
&lib_alloc.test_allocator,
&t,
.{
.cols = 80,
.rows = 24,
.max_scrollback = 0,
},
));
defer free(t);
// Without setting a callback, DECRQM should be silently ignored (no crash)
vt_write(t, "\x1B[?7$p", 6);
}
test "set write_pty null clears callback" {
var t: Terminal = null;
try testing.expectEqual(Result.success, new(
&lib_alloc.test_allocator,
&t,
.{
.cols = 80,
.rows = 24,
.max_scrollback = 0,
},
));
defer free(t);
const S = struct {
var called: bool = false;
fn writePty(_: Terminal, _: ?*anyopaque, _: [*]const u8, _: usize) callconv(.c) void {
called = true;
}
};
S.called = false;
// Set then clear the callback
const cb: ?Effects.WritePtyFn = &S.writePty;
set(t, .write_pty, @ptrCast(&cb));
set(t, .write_pty, null);
vt_write(t, "\x1B[?7$p", 6);
try testing.expect(!S.called);
}
test "set bell callback" {
var t: Terminal = null;
try testing.expectEqual(Result.success, new(
&lib_alloc.test_allocator,
&t,
.{
.cols = 80,
.rows = 24,
.max_scrollback = 0,
},
));
defer free(t);
const S = struct {
var bell_count: usize = 0;
var last_userdata: ?*anyopaque = null;
fn bell(_: Terminal, ud: ?*anyopaque) callconv(.c) void {
bell_count += 1;
last_userdata = ud;
}
};
S.bell_count = 0;
S.last_userdata = null;
// Set userdata and bell callback
var sentinel: u8 = 99;
const ud: ?*anyopaque = @ptrCast(&sentinel);
set(t, .userdata, @ptrCast(&ud));
const cb: ?Effects.BellFn = &S.bell;
set(t, .bell, @ptrCast(&cb));
// Single BEL
vt_write(t, "\x07", 1);
try testing.expectEqual(@as(usize, 1), S.bell_count);
try testing.expectEqual(@as(?*anyopaque, @ptrCast(&sentinel)), S.last_userdata);
// Multiple BELs
vt_write(t, "\x07\x07", 2);
try testing.expectEqual(@as(usize, 3), S.bell_count);
}
test "bell without callback is silent" {
var t: Terminal = null;
try testing.expectEqual(Result.success, new(
&lib_alloc.test_allocator,
&t,
.{
.cols = 80,
.rows = 24,
.max_scrollback = 0,
},
));
defer free(t);
// BEL without a callback should not crash
vt_write(t, "\x07", 1);
}
test "grid_ref out of bounds" {
var t: Terminal = null;
try testing.expectEqual(Result.success, new(