mod common;
use std::process::{Command, Stdio};
use common::{ENTER, ESC, Session, TempProject};
fn screen_rows(session: &Session) -> String {
let screen = session.screen();
screen
.rows(0, screen.size().1)
.map(|row| row.trim_end().to_owned())
.collect::<Vec<_>>()
.join("\n")
}
#[track_caller]
fn wait_screen(session: &Session, expected: &str) {
let deadline = std::time::Instant::now() + std::time::Duration::from_secs(30);
loop {
let actual = screen_rows(session);
if actual == expected {
return;
}
assert!(
std::time::Instant::now() < deadline,
"screen did not settle:\nexpected:\n{expected}\nactual:\n{actual}"
);
std::thread::sleep(std::time::Duration::from_millis(20));
}
}
#[test]
fn the_browser_navigates_to_a_detail_page_and_back_out() {
let project = TempProject::new("info-browse");
let mut session = Session::start(project.path(), &["info"]);
session.wait_for("rproj catalog");
session.send("Generated");
session.wait_for("Generated files");
session.send(ENTER);
session.wait_for("Type filter");
session.send("wally.toml");
session.wait_for("wally.toml");
session.send(ENTER);
session.wait_for("when this project uses Wally");
let screen = session.text();
assert!(screen.contains("wally.toml"), "{screen}");
assert!(screen.contains("Wally manifest"), "{screen}");
session.send(ESC);
session.wait_for("Sections");
session.send(ESC);
let outcome = session.finish();
assert_eq!(
outcome.code, 0,
"leaving the browser is a clean exit:\n{}",
outcome.text
);
}
#[test]
fn catalog_resize_recovery_preserves_filtered_details_and_scrolling() {
let project = TempProject::new("info-resize");
let mut session = Session::start_with_env(project.path(), &["info"], &[("RPROJ_NO_LOG", "1")]);
session.wait_for("rproj catalog");
let checkpoint = session.output_checkpoint();
session.resize(30, 120);
session.wait_for_output_since(checkpoint, "Type filter");
session.send("reactRoblox");
session.wait_for("Filter: reactRoblox");
session.wait_for("React's Roblox renderer");
session.send("\x1b[1;5F"); session.wait_for("Call root:unmount()");
session.wait_for("needed.");
let scrolled = screen_rows(&session);
assert!(!scrolled.contains("React's Roblox renderer"), "{scrolled}");
for (rows, cols) in [(24, 80), (16, 60), (10, 40)] {
let checkpoint = session.output_checkpoint();
session.resize(rows, cols);
session.wait_for_output_since(
checkpoint,
if cols < 60 {
"Resize to at least 60 x 16."
} else {
"Filter: reactRoblox"
},
);
}
session.send("?");
session.wait_for("Catalog help");
session.send(ESC);
session.wait_for("Resize to at least 60 x 16.");
session.resize(30, 120);
wait_screen(&session, &scrolled);
assert_eq!(screen_rows(&session), scrolled);
session.send("\x1b[1;5H"); session.wait_for("React's Roblox renderer");
session.send("\x1b[1;5F");
wait_screen(&session, &scrolled);
session.send(ESC);
let outcome = session.finish();
assert_eq!(outcome.code, 0, "{}", outcome.text);
}
#[test]
fn without_a_terminal_it_lists_instead_of_prompting() {
let output = Command::new(env!("CARGO_BIN_EXE_rproj"))
.arg("info")
.stdin(Stdio::null())
.output()
.expect("run rproj info");
assert_eq!(output.status.code(), Some(0));
let text = String::from_utf8_lossy(&output.stdout);
for expected in ["WALLY PACKAGES", "TOOLS", "GENERATED FILES", "TOPICS"] {
assert!(text.contains(expected), "expected {expected:?} in:\n{text}");
}
assert!(!text.contains("PLACE TEMPLATE"));
assert!(!text.contains("\x1b["));
assert!(
!text.contains("What do you want to look up?"),
"must not try to prompt:\n{text}"
);
}
#[test]
fn a_named_lookup_prints_one_entry_and_exits() {
let output = Command::new(env!("CARGO_BIN_EXE_rproj"))
.args(["info", "rojo"])
.stdin(Stdio::null())
.output()
.expect("run rproj info rojo");
assert_eq!(output.status.code(), Some(0));
let text = String::from_utf8_lossy(&output.stdout);
assert!(text.starts_with("rojo\n----"), "{text}");
assert!(
text.contains("rojo serve"),
"the commands are the point:\n{text}"
);
assert!(
!text.contains("WALLY PACKAGES"),
"one entry, not the catalog:\n{text}"
);
}