#![cfg(target_os = "linux")]
use std::io::Read;
use std::time::Duration;
use portable_pty::{CommandBuilder, PtySize, native_pty_system};
fn strip_ansi(input: &str) -> String {
let mut out = String::with_capacity(input.len());
let mut chars = input.chars().peekable();
while let Some(c) = chars.next() {
if c == '\x1b' {
while let Some(&next) = chars.peek() {
chars.next();
if next.is_ascii_alphabetic() {
break;
}
}
} else {
out.push(c);
}
}
out
}
#[test]
fn pty_smoke_renders_welcome_then_quits_cleanly() {
let bin = env!("CARGO_BIN_EXE_navi");
let pty_system = native_pty_system();
let pair = pty_system
.openpty(PtySize {
rows: 24,
cols: 80,
pixel_width: 0,
pixel_height: 0,
})
.expect("openpty");
let mut cmd = CommandBuilder::new(bin);
let tmp = tempfile::tempdir().expect("tempdir");
cmd.env("XDG_DATA_HOME", tmp.path());
cmd.env("NAVI_SMOKE_TEST", "1");
let mut child = pair.slave.spawn_command(cmd).expect("spawn navi");
drop(pair.slave);
let mut reader = pair.master.try_clone_reader().expect("clone reader");
let mut writer = pair.master.take_writer().expect("take writer");
let mut buf: Vec<u8> = Vec::with_capacity(16 * 1024);
let read_handle = std::thread::spawn(move || {
let mut local = vec![0u8; 4096];
loop {
match reader.read(&mut local) {
Ok(0) => break,
Ok(n) => {
buf.extend_from_slice(&local[..n]);
}
Err(_) => break,
}
}
buf
});
std::thread::sleep(Duration::from_secs(2));
use std::io::Write;
writer.write_all(b"\x03").expect("write ctrl+c");
drop(writer);
let _ = child.wait();
let output = read_handle.join().expect("read thread");
let text = strip_ansi(&String::from_utf8_lossy(&output));
assert!(
text.contains("commands") && text.contains("models") && text.contains("send"),
"expected NAVI welcome controls in PTY output, got:\n{text}"
);
}