use super::*;
use crate::ui::progress::Kind;
fn row(state: State) -> Row {
Row {
kind: Kind::Container,
name: "proj-web-1".to_string(),
state,
started: None,
elapsed: None,
}
}
fn plain(line: &str) -> String {
let mut out = String::new();
let mut chars = line.chars();
while let Some(c) = chars.next() {
if c == '\u{1b}' {
for c in chars.by_ref() {
if c == 'm' {
break;
}
}
} else {
out.push(c);
}
}
out
}
#[test]
fn a_line_never_exceeds_the_terminal_width() {
for width in [10, 20, 40, 80] {
let r = Row {
name: "a-very-long-container-name-that-will-not-fit-anywhere".to_string(),
..row(State::Working("Creating".into()))
};
let line = plain(&render(&r, 40, 0, Instant::now(), width));
assert!(
line.chars().count() <= width,
"width {width}: got {} chars: {line:?}",
line.chars().count()
);
}
}
#[test]
fn width_zero_leaves_the_line_intact() {
let line = plain(&render(&row(State::Pending), 20, 0, Instant::now(), 0));
assert!(line.contains("proj-web-1"), "{line:?}");
}
#[test]
fn each_state_gets_its_own_marker() {
let now = Instant::now();
let done = plain(&render(&row(State::Done("Created".into())), 20, 0, now, 80));
let working = plain(&render(
&row(State::Working("Creating".into())),
20,
0,
now,
80,
));
let pending = plain(&render(&row(State::Pending), 20, 0, now, 80));
assert!(done.contains(DONE_MARK), "{done:?}");
assert!(working.contains(SPINNER[0]), "{working:?}");
assert!(pending.contains(PENDING_MARK), "{pending:?}");
}
#[test]
fn the_working_marker_advances_with_the_frame() {
let now = Instant::now();
let r = row(State::Working("Pulling".into()));
let a = plain(&render(&r, 20, 0, now, 80));
let b = plain(&render(&r, 20, 1, now, 80));
assert_ne!(a, b);
}
#[test]
fn the_frame_index_wraps() {
let now = Instant::now();
let r = row(State::Working("Pulling".into()));
let wrapped = plain(&render(&r, 20, SPINNER.len() * 7 + 3, now, 80));
let direct = plain(&render(&r, 20, 3, now, 80));
assert_eq!(wrapped, direct);
}
#[test]
fn a_pending_row_shows_no_time() {
let line = plain(&render(&row(State::Pending), 20, 0, Instant::now(), 80));
assert!(!line.contains('s'), "{line:?}");
assert!(line.contains("Pending"), "{line:?}");
}
#[test]
fn elapsed_switches_units_at_a_minute() {
assert_eq!(format_elapsed(Duration::from_millis(100)), "0.1s");
assert_eq!(format_elapsed(Duration::from_millis(12_900)), "12.9s");
assert_eq!(format_elapsed(Duration::from_secs(63)), "1m03s");
assert_eq!(format_elapsed(Duration::from_secs(600)), "10m00s");
}
#[test]
fn truncation_never_cuts_through_an_escape() {
let r = Row {
name: "x".repeat(60),
..row(State::Working("Creating".into()))
};
let line = render(&r, 60, 0, Instant::now(), 24);
let escapes = line.matches('\u{1b}').count();
let resets = line.matches("\u{1b}[0m").count();
assert_eq!(
escapes,
resets * 2,
"every opening escape needs its reset: {line:?}"
);
}
#[test]
fn a_name_cannot_drive_the_terminal() {
let r = Row {
name: "evil\u{1b}[31m\u{7}name".to_string(),
..row(State::Pending)
};
let line = render(&r, 30, 0, Instant::now(), 80);
assert!(!line.contains('\u{7}'), "{line:?}");
assert!(line.contains("name"), "{line:?}");
}
#[test]
fn the_summary_counts_done_over_total() {
assert_eq!(summary(3, 6), "[+] Running 3/6");
}