use std::time::{Duration, Instant};
use termlens::{Error, Key, Terminal};
mod common;
fn emit(steps: &[&str]) -> termlens::Result<Terminal> {
common::spawn_emit(
Terminal::builder()
.size(40, 6)
.env_clear()
.timeout(Duration::from_secs(10)),
steps,
)
}
#[test]
fn snapshot_after_returns_the_screen_once_it_holds_still() -> termlens::Result<()> {
let mut t = emit(&["first", "--sleep", "2s", " second", "--wait"])?;
let screen = t.snapshot_after(|s| s.contains("first"))?;
assert_eq!(screen.row_text(0).trim_end(), "first", "{screen}");
t.wait_until(|s| s.contains("second"))?;
t.send(Key::Enter)?;
assert!(t.wait_exit()?.success());
Ok(())
}
#[test]
fn snapshot_after_fails_on_the_predicate_before_it_settles() -> termlens::Result<()> {
let mut t = emit(&["other", "--wait"])?;
let err = t
.snapshot_after_for(|s| s.contains("never"), Duration::from_millis(300))
.unwrap_err();
match &err {
Error::Timeout { waiting_for, .. } => assert!(
waiting_for.starts_with("the screen predicate to hold"),
"{waiting_for}"
),
other => panic!("expected a timeout, got {other:?}"),
}
t.send(Key::Enter)?;
assert!(t.wait_exit()?.success());
Ok(())
}
#[test]
fn wait_stable_ignores_output_that_changes_no_cell() -> termlens::Result<()> {
let mut t = emit(&["noisy", "--loop", "--raw", r"\a"])?;
t.wait_until(|s| s.contains("noisy") && s.bells() > 0)?;
let idle = t.wait_idle_for(Duration::from_secs(2), Duration::from_millis(400));
assert!(
matches!(idle, Err(Error::Timeout { .. })),
"silence never comes, so wait_idle must time out: {idle:?}"
);
let start = Instant::now();
let screen = t.wait_stable(Duration::from_millis(100))?;
assert_eq!(screen.row_text(0).trim_end(), "noisy", "{screen}");
assert!(
screen.bells() >= 1,
"the returned screen is a current observation"
);
assert!(
start.elapsed() < Duration::from_secs(5),
"settled long after the picture stopped changing: {:?}",
start.elapsed()
);
Ok(())
}
#[test]
fn wait_stable_times_out_while_the_picture_keeps_changing() -> termlens::Result<()> {
let mut t = emit(&["--seq", "100000000"])?;
t.wait_until(|s| s.text().chars().any(|c| c.is_ascii_digit()))?;
let err = t
.wait_stable_for(Duration::from_secs(2), Duration::from_millis(400))
.unwrap_err();
match err {
Error::Timeout {
waiting_for,
timeout,
..
} => {
assert!(
waiting_for.starts_with("the screen to hold still for 2s"),
"{waiting_for}"
);
assert_eq!(timeout, Duration::from_millis(400));
}
other => panic!("expected a timeout, got {other:?}"),
}
Ok(())
}
#[test]
fn wait_stable_does_not_settle_inside_an_open_synchronized_update() -> termlens::Result<()> {
let mut t = emit(&[
"--raw",
r"\e[?2026hhalf",
"--wait",
"--csi",
"?2026l",
"--wait",
])?;
t.wait_until(|s| s.contains("half"))?;
let err = t
.wait_stable_for(Duration::from_millis(50), Duration::from_millis(400))
.unwrap_err();
match &err {
Error::Timeout { waiting_for, .. } => assert!(
waiting_for.contains("unfinished DEC 2026 synchronized update"),
"{waiting_for}"
),
other => panic!("expected a timeout, got {other:?}"),
}
t.send(Key::Enter)?;
let screen = t.wait_stable(Duration::from_millis(50))?;
assert!(screen.contains("half"), "{screen}");
t.send(Key::Enter)?;
assert!(t.wait_exit()?.success());
Ok(())
}
#[test]
fn a_screen_that_already_holds_still_settles_at_once() -> termlens::Result<()> {
let mut t = emit(&["settled", "--wait"])?;
t.wait_until(|s| s.contains("settled"))?;
std::thread::sleep(Duration::from_millis(1200));
let start = Instant::now();
let screen = t.wait_stable(Duration::from_secs(1))?;
assert!(
start.elapsed() < Duration::from_millis(500),
"a 1s stillness already elapsed was waited out again: {:?}",
start.elapsed()
);
assert!(screen.contains("settled"), "{screen}");
t.send(Key::Enter)?;
assert!(t.wait_exit()?.success());
let screen = t.wait_stable(Duration::from_secs(5))?;
assert!(screen.contains("settled"), "EOF is still: {screen}");
Ok(())
}