use std::path::{Path, PathBuf};
use std::time::Duration;
use std::{env, fs};
use termlens::{Key, Terminal};
const TIMEOUT: Duration = Duration::from_secs(10);
fn fixture_run() -> PathBuf {
Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures/run-flip")
}
fn golden_path(name: &str) -> PathBuf {
Path::new(env!("CARGO_MANIFEST_DIR"))
.join("tests/golden")
.join(name)
}
fn normalize(frame: &str) -> String {
frame
.lines()
.map(str::trim_end)
.collect::<Vec<_>>()
.join("\n")
}
fn assert_golden(name: &str, screen: &str, context: &str) {
let path = golden_path(name);
let actual = normalize(screen);
if env::var_os("LAUNCHBOUND_BLESS").is_some() {
fs::create_dir_all(path.parent().unwrap()).unwrap();
fs::write(&path, format!("{actual}\n")).unwrap();
}
let expected = fs::read_to_string(&path)
.unwrap_or_else(|_| panic!("missing golden {name}; bless with LAUNCHBOUND_BLESS=1"));
assert_eq!(
normalize(&expected),
actual,
"{context}: frame differs from golden {name}\n--- rendered ---\n{screen}"
);
}
fn spawn(size: (u16, u16)) -> Terminal {
let mut t = Terminal::builder()
.size(size.0, size.1)
.env_clear()
.timeout(TIMEOUT)
.arg(fixture_run())
.spawn(env!("CARGO_BIN_EXE_launchbound-tui"))
.expect("failed to spawn the TUI in a PTY");
t.wait_until(|s| s.to_string().contains("launchbound"))
.expect("first frame");
t
}
fn quit(mut t: Terminal, context: &str) {
t.send(Key::Char('q')).expect("send q");
let status = t.wait_exit().expect("TUI did not exit after q");
assert!(status.success(), "{context}: exited with {status:?}");
}
fn ready(screen: &termlens::Screen) -> bool {
screen.to_string().contains("q quit")
}
#[test]
fn overview_at_80x24() {
let mut t = spawn((80, 24));
let frame = t.wait_frame(ready).expect("the first complete frame");
assert_golden("overview-80x24.txt", &frame.to_string(), "overview");
quit(t, "overview");
}
#[test]
fn resize_relayouts_the_frame() {
let mut t = spawn((80, 24));
t.wait_frame(|s| {
let frame = s.to_string();
frame.contains("q quit") && !frame.contains("[0.0398, 0.0402]")
})
.expect("the 80-column frame");
t.resize(110, 32).expect("resize");
let frame = t
.wait_frame(|s| s.to_string().contains("[0.0398, 0.0402]"))
.expect("the relaid-out frame");
assert_golden("overview-110x32.txt", &frame.to_string(), "resized");
quit(t, "resized");
}
#[test]
fn ranking_scrolls_a_long_candidate_list() {
let mut t = spawn((80, 24));
t.wait_frame(ready).expect("the first complete frame");
t.send(Key::Char('2')).expect("send 2");
t.wait_frame(|s| s.to_string().contains("ranking ("))
.expect("ranking view");
for _ in 0..5 {
t.send(Key::Char('j')).expect("send j");
}
let frame = t
.wait_frame(|s| {
let frame = s.to_string();
frame.contains("c1-0000000000000004") && !frame.contains("c1-0000000000000003")
})
.expect("scroll applied");
assert_golden("ranking-scrolled-80x24.txt", &frame.to_string(), "ranking");
quit(t, "ranking");
}
#[test]
fn rejection_view_names_rules_and_spans() {
let mut t = spawn((80, 24));
t.wait_frame(ready).expect("the first complete frame");
t.send(Key::Char('3')).expect("send 3");
let screen = t
.wait_frame(|s| s.to_string().contains("all refused configurations:"))
.expect("rejections view")
.to_string();
assert!(screen.contains("RC001"), "rule id visible");
assert!(screen.contains("src/lib.rs:33:13"), "span visible");
assert_golden("rejections-80x24.txt", &screen, "rejections");
quit(t, "rejections");
}
#[test]
fn progress_view_shows_measured_of_planned() {
let mut t = spawn((80, 24));
t.wait_frame(ready).expect("the first complete frame");
t.send(Key::Char('4')).expect("send 4");
let screen = t
.wait_frame(|s| s.to_string().contains("measured 11 of"))
.expect("progress view")
.to_string();
assert!(screen.contains("measured 11 of"), "progress counter");
assert_golden("progress-80x24.txt", &screen, "progress");
quit(t, "progress");
}
#[test]
fn stress_100_runs_at_80x24() {
for run in 0..100 {
let mut t = spawn((80, 24));
let frame = t
.wait_frame(ready)
.unwrap_or_else(|e| panic!("run {run}: waiting for the first frame: {e}"));
assert_golden(
"overview-80x24.txt",
&frame.to_string(),
&format!("run {run}"),
);
quit(t, &format!("run {run}"));
}
}