use super::*;
fn entry(exit: i32, stderr: &str) -> OpEntry {
OpEntry {
ts: "TS".into(),
argv: vec!["bl".into(), "close".into(), "bl-4db6".into()],
cwd: "/proj".into(),
exit,
stdout: "out".into(),
stderr: stderr.into(),
origin: Origin::default(),
client: crate::registry::Client::default(),
}
}
#[test]
fn op_row_carries_the_full_entry_and_joins_argv() {
let row = OpRow::from(&entry(0, ""));
assert_eq!(row.ts, "TS");
assert_eq!(row.argv, "bl close bl-4db6");
assert_eq!(row.cwd, "/proj");
assert_eq!(row.exit, 0);
assert_eq!(row.stdout, "out");
assert_eq!(row.stderr, "");
}
#[test]
fn when_renders_iso8601_and_falls_back_on_a_non_numeric_ts() {
assert_eq!(OpRow::from(&entry(0, "")).when(), "TS");
let stamped = OpEntry {
ts: "1785630266".into(),
..entry(0, "")
};
assert_eq!(OpRow::from(&stamped).when(), "2026-08-02 00:24:26Z");
}
#[test]
fn has_output_reflects_either_stream() {
assert!(OpRow::from(&entry(0, "")).has_output()); let neither = OpEntry {
stdout: String::new(),
..entry(0, "")
};
assert!(!OpRow::from(&neither).has_output());
let only_err = OpEntry {
stdout: String::new(),
..entry(1, "boom")
};
assert!(OpRow::from(&only_err).has_output());
}
#[test]
fn summary_leaves_a_short_argv_unchanged() {
assert_eq!(OpRow::from(&entry(0, "")).summary(), "bl close bl-4db6");
}
#[test]
fn summary_folds_newlines_and_elides_a_long_multiline_goal() {
let goal = format!(
"identity preamble\n{}\nball worktree preamble",
"payload segment ".repeat(30)
);
assert!(goal.chars().count() >= 500, "fixture must exceed 500 chars");
let e = OpEntry {
argv: vec![goal.clone()],
..entry(0, "")
};
let row = OpRow::from(&e);
let summary = row.summary();
assert!(!summary.contains('\n') && !summary.contains('\r'));
assert_eq!(summary.chars().count(), SUMMARY_ARGV_MAX);
assert!(summary.contains('…'), "the cut is marked: {summary}");
assert!(summary.starts_with("identity preamble"), "{summary}");
assert!(summary.ends_with("ball worktree preamble"), "{summary}");
assert_eq!(row.argv, goal);
}
#[test]
fn summary_elision_boundary_is_exact() {
let at_cap = "a".repeat(SUMMARY_ARGV_MAX);
let e = OpEntry {
argv: vec![at_cap.clone()],
..entry(0, "")
};
assert_eq!(OpRow::from(&e).summary(), at_cap);
let over_cap = "a".repeat(SUMMARY_ARGV_MAX + 1);
let e = OpEntry {
argv: vec![over_cap],
..entry(0, "")
};
let summary = OpRow::from(&e).summary();
assert_eq!(summary.chars().count(), SUMMARY_ARGV_MAX);
assert!(summary.contains('…'));
}
#[test]
fn two_ops_differing_only_in_their_tail_do_not_summarize_alike() {
let prefix = "litany prompt --name growing \
/home/u/.cache/yog-drive/quality-20260807T214407Z/data/yog/workspaces/";
let summarize = |leaf: &str| {
let e = OpEntry {
argv: vec![format!("{prefix}{leaf}")],
..entry(0, "")
};
OpRow::from(&e).summary()
};
let home = summarize("home 20260807T214551Z-2a1181a3");
let other = summarize("scratch 20260807T220107Z-c0ffeeba");
assert!(
home.chars().count() == SUMMARY_ARGV_MAX && other.chars().count() == SUMMARY_ARGV_MAX,
"both rows really are over the cap, so this is not a vacuous pass"
);
assert_ne!(home, other, "the rows must stay distinguishable");
assert!(home.ends_with("home 20260807T214551Z-2a1181a3"), "{home}");
assert!(
other.ends_with("scratch 20260807T220107Z-c0ffeeba"),
"{other}"
);
}
#[test]
fn stderr_tail_keeps_only_the_last_lines() {
assert_eq!(stderr_tail(""), "");
assert_eq!(stderr_tail("only\n"), "only");
assert_eq!(stderr_tail("a\nb\nc\nd\ne\n"), "c\nd\ne");
}