aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorOgulcan Celik <ogulcancelik@gmail.com>2026-04-25 04:16:00 +0300
committerOgulcan Celik <ogulcancelik@gmail.com>2026-04-25 04:16:44 +0300
commitddb3e04f92ebc924dd8355df6510781caf498a02 (patch)
treeda79533c2375bd76417b8237f5daf0dc78cd0511 /src
parentfba05502cdf235eedc057c3857fdc95e8fbd781d (diff)
test: add libghostty upgrade contract coverage
Diffstat (limited to 'src')
-rw-r--r--src/ghostty/mod.rs78
-rw-r--r--src/pane/terminal.rs37
2 files changed, 115 insertions, 0 deletions
diff --git a/src/ghostty/mod.rs b/src/ghostty/mod.rs
index 75e600b8..99923789 100644
--- a/src/ghostty/mod.rs
+++ b/src/ghostty/mod.rs
@@ -1163,6 +1163,51 @@ mod tests {
}
}
+ fn build_info_bool(data: ffi::GhosttyBuildInfo) -> bool {
+ let mut out = false;
+ unsafe {
+ ffi::ghostty_build_info(data, (&mut out as *mut bool).cast())
+ .into_result()
+ .unwrap();
+ }
+ out
+ }
+
+ fn build_info_optimize() -> ffi::GhosttyOptimizeMode {
+ let mut out = ffi::GhosttyOptimizeMode_GHOSTTY_OPTIMIZE_DEBUG;
+ unsafe {
+ ffi::ghostty_build_info(
+ ffi::GhosttyBuildInfo_GHOSTTY_BUILD_INFO_OPTIMIZE,
+ (&mut out as *mut ffi::GhosttyOptimizeMode).cast(),
+ )
+ .into_result()
+ .unwrap();
+ }
+ out
+ }
+
+ #[test]
+ fn build_info_contract_matches_expected_vendored_features() {
+ let _simd = build_info_bool(ffi::GhosttyBuildInfo_GHOSTTY_BUILD_INFO_SIMD);
+ let _tmux_control_mode =
+ build_info_bool(ffi::GhosttyBuildInfo_GHOSTTY_BUILD_INFO_TMUX_CONTROL_MODE);
+ let kitty_graphics =
+ build_info_bool(ffi::GhosttyBuildInfo_GHOSTTY_BUILD_INFO_KITTY_GRAPHICS);
+ assert!(
+ !kitty_graphics,
+ "current herdr vendor must keep Kitty graphics disabled until image handling is explicit"
+ );
+
+ let optimize = build_info_optimize();
+ assert!(matches!(
+ optimize,
+ ffi::GhosttyOptimizeMode_GHOSTTY_OPTIMIZE_DEBUG
+ | ffi::GhosttyOptimizeMode_GHOSTTY_OPTIMIZE_RELEASE_SAFE
+ | ffi::GhosttyOptimizeMode_GHOSTTY_OPTIMIZE_RELEASE_SMALL
+ | ffi::GhosttyOptimizeMode_GHOSTTY_OPTIMIZE_RELEASE_FAST
+ ));
+ }
+
#[test]
fn focus_encoding_matches_expected_sequences() {
assert_eq!(encode_focus(FocusEvent::Gained).unwrap(), b"\x1b[I");
@@ -1282,6 +1327,39 @@ mod tests {
}
#[test]
+ fn active_screen_and_cursor_visibility_contract() {
+ let mut terminal = Terminal::new(12, 3, 0).unwrap();
+ let mut render_state = RenderState::new().unwrap();
+
+ terminal.write(b"primary");
+ assert_eq!(terminal.active_screen().unwrap(), ActiveScreen::Primary);
+ assert_eq!(
+ terminal.read_text_viewport((0, 0), (6, 0), false).unwrap(),
+ "primary"
+ );
+
+ render_state.update(&terminal).unwrap();
+ assert_eq!(render_state.cursor_visible().unwrap(), true);
+ terminal.write(b"\x1b[?25l");
+ render_state.update(&terminal).unwrap();
+ assert_eq!(render_state.cursor_visible().unwrap(), false);
+
+ terminal.write(b"\x1b[?1049h\x1b[HALT");
+ assert_eq!(terminal.active_screen().unwrap(), ActiveScreen::Alternate);
+ assert_eq!(
+ terminal.read_text_viewport((0, 0), (2, 0), false).unwrap(),
+ "ALT"
+ );
+
+ terminal.write(b"\x1b[?1049l");
+ assert_eq!(terminal.active_screen().unwrap(), ActiveScreen::Primary);
+ assert_eq!(
+ terminal.read_text_viewport((0, 0), (6, 0), false).unwrap(),
+ "primary"
+ );
+ }
+
+ #[test]
fn terminal_and_render_state_smoke_test() {
let mut terminal = Terminal::new(8, 3, 100).unwrap();
assert_eq!(terminal.cols().unwrap(), 8);
diff --git a/src/pane/terminal.rs b/src/pane/terminal.rs
index c688dcc9..c8f29b4c 100644
--- a/src/pane/terminal.rs
+++ b/src/pane/terminal.rs
@@ -972,6 +972,13 @@ mod tests {
}
}
+ fn write_wrapped_contract_lines(terminal: &mut crate::ghostty::Terminal, count: usize) {
+ for i in 0..count {
+ terminal.write(format!("WRAP-{i:03}-abcdefghijklmnopqrstuvwxyz\r\n").as_bytes());
+ }
+ terminal.write(b"END");
+ }
+
#[test]
fn host_terminal_theme_restore_probe_skips_when_no_transient_override() {
let (tx, _rx) = mpsc::channel(4);
@@ -1363,6 +1370,36 @@ mod tests {
}
#[test]
+ fn resize_reflow_keeps_scrolled_viewport_and_bottom_detection_sane() {
+ let (tx, _rx) = mpsc::channel(4);
+ let mut terminal = crate::ghostty::Terminal::new(12, 4, 10_000).unwrap();
+ write_wrapped_contract_lines(&mut terminal, 40);
+ let pane = GhosttyPaneTerminal::new(terminal, tx).unwrap();
+
+ let bottom_snapshot = pane.detection_text();
+ assert!(bottom_snapshot.contains("END"));
+
+ let initial = pane.scroll_metrics().expect("initial scroll metrics");
+ assert!(initial.max_offset_from_bottom > 0);
+ pane.set_scroll_offset_from_bottom(initial.max_offset_from_bottom / 2);
+ assert!(!pane.visible_text().trim().is_empty());
+
+ for (rows, cols) in [(4, 10), (4, 7), (6, 18), (3, 9), (5, 12)] {
+ pane.resize(rows, cols);
+
+ let metrics = pane.scroll_metrics().expect("scroll metrics after resize");
+ assert_eq!(metrics.viewport_rows, rows as usize);
+ assert!(metrics.offset_from_bottom <= metrics.max_offset_from_bottom);
+ assert!(metrics.max_offset_from_bottom > 0);
+ assert!(!pane.visible_text().trim().is_empty());
+ assert!(
+ pane.detection_text().contains("END"),
+ "bottom detection should remain independent from the scrolled viewport after resize"
+ );
+ }
+ }
+
+ #[test]
fn synchronized_output_suppresses_intermediate_render_requests_until_batch_ends() {
let (tx, _rx) = mpsc::channel(4);
let terminal = crate::ghostty::Terminal::new(80, 24, 0).unwrap();