From 15539d3dec40bcc40e6f4bb0eaa4f073ad26b74f Mon Sep 17 00:00:00 2001 From: Can Celik Date: Sat, 15 Aug 2026 15:09:53 +0300 Subject: feat: make headless terminal size configurable (#2829) refs #2828 --- tests/api_ping.rs | 2 +- tests/detach_reattach.rs | 145 +++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 141 insertions(+), 6 deletions(-) (limited to 'tests') diff --git a/tests/api_ping.rs b/tests/api_ping.rs index f4f92da6..bfecc27a 100644 --- a/tests/api_ping.rs +++ b/tests/api_ping.rs @@ -502,7 +502,7 @@ fn workspace_list_and_create_round_trip() { let recent = send_request( &socket_path, &format!( - r#"{{"id":"req_11","method":"pane.read","params":{{"pane_id":"{}","source":"recent","lines":20}}}}"#, + r#"{{"id":"req_11","method":"pane.read","params":{{"pane_id":"{}","source":"recent","lines":50}}}}"#, pane_id ), ); diff --git a/tests/detach_reattach.rs b/tests/detach_reattach.rs index 3abe1bec..4461a17b 100644 --- a/tests/detach_reattach.rs +++ b/tests/detach_reattach.rs @@ -19,6 +19,19 @@ use support::{ wait_for_disconnect, wait_for_message_variant, wait_for_socket, wait_until, CURRENT_PROTOCOL, }; +const CUSTOM_HEADLESS_SIZE_CONFIG: &str = r#"onboarding = false + +[server] +headless_cols = 132 +headless_rows = 41 + +[ui] +sidebar_start_collapsed = true +sidebar_collapsed_mode = "hidden" +hide_tab_bar_when_single_tab = true +pane_scrollbars = false +"#; + fn unique_test_dir() -> PathBuf { let nanos = SystemTime::now() .duration_since(UNIX_EPOCH) @@ -70,19 +83,31 @@ fn test_lock() -> MutexGuard<'static, ()> { } fn spawn_server( + config_home: &PathBuf, + runtime_dir: &PathBuf, + api_socket_path: &PathBuf, + client_socket_path: &PathBuf, +) -> SpawnedHerdr { + spawn_server_with_config( + config_home, + runtime_dir, + api_socket_path, + client_socket_path, + "onboarding = false\n", + ) +} + +fn spawn_server_with_config( config_home: &PathBuf, runtime_dir: &PathBuf, api_socket_path: &PathBuf, _client_socket_path: &PathBuf, + config: &str, ) -> SpawnedHerdr { fs::create_dir_all(config_home.join("herdr")).unwrap(); fs::create_dir_all(runtime_dir).unwrap(); register_runtime_dir(runtime_dir); - fs::write( - config_home.join("herdr/config.toml"), - "onboarding = false\n", - ) - .unwrap(); + fs::write(config_home.join("herdr/config.toml"), config).unwrap(); let pair = native_pty_system() .openpty(PtySize { @@ -99,6 +124,7 @@ fn spawn_server( cmd.env("XDG_RUNTIME_DIR", runtime_dir); cmd.env("HERDR_SOCKET_PATH", api_socket_path); cmd.env_remove("HERDR_CLIENT_SOCKET_PATH"); + cmd.env("HERDR_CONFIG_PATH", config_home.join("herdr/config.toml")); cmd.env("SHELL", "/bin/sh"); cmd.env_remove("HERDR_ENV"); @@ -638,6 +664,115 @@ fn server_persists_after_client_connection_drop() { cleanup_spawned_herdr(spawned, base); } +#[test] +fn pane_created_without_client_uses_configured_headless_size() { + let _lock = test_lock(); + let base = unique_test_dir(); + let config_home = base.join("config"); + let runtime_dir = base.join("runtime"); + let api_socket = runtime_dir.join("herdr.sock"); + let client_socket = runtime_dir.join("herdr-client.sock"); + + let spawned = spawn_server_with_config( + &config_home, + &runtime_dir, + &api_socket, + &client_socket, + CUSTOM_HEADLESS_SIZE_CONFIG, + ); + wait_for_socket(&api_socket, Duration::from_secs(10)); + + let create = workspace_create(&api_socket, "headless-size"); + let pane_id = create["result"]["root_pane"]["pane_id"] + .as_str() + .expect("root pane id") + .to_string(); + + // A second request cannot run until the full render triggered by workspace + // creation has applied the virtual headless geometry. + assert!(ping_socket(&api_socket).contains("pong")); + let size = read_pane_tty_size_after_marker( + &api_socket, + &pane_id, + "HEADLESS_SIZE", + Duration::from_secs(5), + ); + + assert_eq!(size, (41, 132)); + + cleanup_spawned_herdr(spawned, base); +} + +#[test] +fn pane_created_after_detach_uses_configured_headless_size() { + let _lock = test_lock(); + let base = unique_test_dir(); + let config_home = base.join("config"); + let runtime_dir = base.join("runtime"); + let api_socket = runtime_dir.join("herdr.sock"); + let client_socket = runtime_dir.join("herdr-client.sock"); + + let spawned = spawn_server_with_config( + &config_home, + &runtime_dir, + &api_socket, + &client_socket, + CUSTOM_HEADLESS_SIZE_CONFIG, + ); + wait_for_socket(&api_socket, Duration::from_secs(10)); + wait_for_socket(&client_socket, Duration::from_secs(10)); + + let mut stream = UnixStream::connect(&client_socket).expect("client should connect"); + let (version, error) = + client_handshake(&mut stream, CURRENT_PROTOCOL, 160, 50).expect("handshake should succeed"); + assert_eq!(version, CURRENT_PROTOCOL); + assert!(error.is_none(), "{error:?}"); + drain_messages(&mut stream); + + let first = workspace_create(&api_socket, "attached-size"); + let first_pane_id = first["result"]["root_pane"]["pane_id"] + .as_str() + .expect("first root pane id") + .to_string(); + let attached_size = read_pane_tty_size_after_marker( + &api_socket, + &first_pane_id, + "ATTACHED_SIZE", + Duration::from_secs(5), + ); + assert_eq!(attached_size, (50, 160)); + + send_detach(&mut stream).expect("send detach"); + assert!( + wait_for_disconnect(&mut stream, Duration::from_secs(2)).expect("wait for detach"), + "detached client connection should close" + ); + drop(stream); + + let second = workspace_create(&api_socket, "headless-size"); + let second_pane_id = second["result"]["root_pane"]["pane_id"] + .as_str() + .expect("second root pane id") + .to_string(); + let headless_size = read_pane_tty_size_after_marker( + &api_socket, + &second_pane_id, + "HEADLESS_SIZE_AFTER_DETACH", + Duration::from_secs(5), + ); + let preserved_size = read_pane_tty_size_after_marker( + &api_socket, + &first_pane_id, + "PRESERVED_SIZE_AFTER_DETACH", + Duration::from_secs(5), + ); + + assert_eq!(headless_size, (41, 132)); + assert_eq!(preserved_size, attached_size); + + cleanup_spawned_herdr(spawned, base); +} + #[test] fn detached_output_preserves_last_attached_pty_size() { let _lock = test_lock(); -- cgit v1.2.3