use super::mux::MuxAttachPending;
use super::mux::tests::{socket_path, spawn_daemon};
use super::mux_test_seams::{attach_until_installed, poll_until, wait_until};
use crate::app::window_state::WindowState;
use par_term_emu_core_rust::mux::MuxClient;
const ROWS: usize = 400;
fn window_state_with_scrollback(lines: usize) -> WindowState {
let runtime = std::sync::Arc::new(
tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.expect("build test runtime"),
);
let mut config = crate::config::Config::default();
config.scrollback.scrollback_lines = lines;
WindowState::new(config, runtime)
}
fn seed_history(path: &std::path::Path, session: &str) {
let mut first = MuxClient::connect(path).expect("first client");
first
.send(&format!("new-session -s {session}"))
.expect("new-session");
let command = format!("seq -f ROW%g 1 {ROWS} | tr A-Z a-z");
first
.send(&format!("send-keys -t %0 \"{command}\" Enter"))
.expect("send-keys");
let mut history = String::new();
let printed = poll_until(|| {
history = first
.send("capture-pane -t %0 -p -S -2000")
.expect("capture-pane")
.join("\n");
history.lines().any(|l| l.trim() == format!("row{ROWS}"))
});
assert!(printed, "the pane never printed its rows: {history:?}");
}
fn reattach(ws: &mut WindowState, path: &std::path::Path, session: &str) {
attach_until_installed(
ws,
|| {
let core_client = MuxClient::connect(path).expect("reattach client");
let (tx, rx) = std::sync::mpsc::channel();
tx.send(Ok(core_client)).unwrap();
drop(tx);
MuxAttachPending {
name: session.to_string(),
rx,
}
},
path,
);
assert!(
ws.tmux_state.mux_screen_seeds.contains_key(&0),
"the attach collected a seed for %0"
);
wait_until("the %0 seed is delivered", || {
if let Some(t) = ws.tmux_state.transport.as_ref() {
let _ = t.send_command_no_wait("refresh-client -t %0 -C 80x24");
}
ws.check_mux_notifications();
!ws.tmux_state.mux_screen_seeds.contains_key(&0)
});
}
fn mirror_scrollback(ws: &WindowState) -> (usize, Vec<String>) {
let (tab_id, native) = ws.tmux_state.tmux_pane_owner(0).expect("%0 is mapped");
let tab = ws.tab_manager.get_tab(tab_id).expect("owning tab");
let pane = tab
.pane_manager()
.and_then(|pm| pm.get_pane(native))
.expect("mirror pane");
let term = pane.terminal.try_read().expect("mirror terminal");
(term.scrollback_len(), term.scrollback())
}
#[test]
fn reattach_shows_scrollback_up_to_the_configured_limit() {
let path = socket_path("m10-scrollback");
spawn_daemon(&path);
seed_history(&path, "m10");
const LIMIT: usize = 100;
let mut capped = window_state_with_scrollback(LIMIT);
reattach(&mut capped, &path, "m10");
let (len, lines) = mirror_scrollback(&capped);
assert_eq!(
len, LIMIT,
"the mirror holds exactly the configured limit of history"
);
assert!(
!lines.iter().any(|l| l.trim() == "row1"),
"the oldest line is dropped at the limit"
);
assert!(
lines
.iter()
.any(|l| l.trim() == format!("row{}", ROWS - 30)),
"recent history survives the cap: {:?}",
&lines[lines.len().saturating_sub(5)..]
);
assert!(capped.execute_keybinding_action("clear_scrollback"));
assert_eq!(
mirror_scrollback(&capped).0,
0,
"the focused pane's mirror scrollback is cleared"
);
assert!(capped.detach_mux_session(), "detach the capped client");
let mut full = window_state_with_scrollback(10_000);
reattach(&mut full, &path, "m10");
let (len, lines) = mirror_scrollback(&full);
assert!(
len >= ROWS - 30,
"a limit above the history receives all of it: {len} lines"
);
assert!(
lines.iter().any(|l| l.trim() == "row1"),
"the first history line reaches the mirror"
);
let _ = std::fs::remove_file(&path);
}