diff options
| author | Ogulcan Celik <ogulcancelik@gmail.com> | 2026-04-25 04:16:00 +0300 |
|---|---|---|
| committer | Ogulcan Celik <ogulcancelik@gmail.com> | 2026-04-25 04:16:44 +0300 |
| commit | ddb3e04f92ebc924dd8355df6510781caf498a02 (patch) | |
| tree | da79533c2375bd76417b8237f5daf0dc78cd0511 /src | |
| parent | fba05502cdf235eedc057c3857fdc95e8fbd781d (diff) | |
test: add libghostty upgrade contract coverage
Diffstat (limited to 'src')
| -rw-r--r-- | src/ghostty/mod.rs | 78 | ||||
| -rw-r--r-- | src/pane/terminal.rs | 37 |
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(); |
