use super::*;
#[test]
fn one_row_per_root_with_subtree_member_counts() {
let agents = [
agent("r1-0", AgentState::Quiescent, 10),
agent("r1-0-c-1", AgentState::Quiescent, 20),
agent("r2-0", AgentState::Quiescent, 30),
];
let rows = build(&agents, "/ws", &unseen, 100, &plain, &[]);
assert_eq!(rows.len(), 2);
assert_eq!(rows[0].root_id, "r2-0");
assert!(!rows[0].has_children());
assert_eq!(rows[1].root_id, "r1-0");
assert_eq!(rows[1].members, 2);
assert!(rows[1].has_children());
assert_eq!(rows[1].age_secs, 80, "age from the subtree's latest action");
}
#[test]
fn sort_is_last_action_descending_and_nothing_else() {
let mut idle_new = agent("idle-new", AgentState::Quiescent, 90);
idle_new.tip_oid = "seen-tip".into();
let mut idle_old = agent("idle-old", AgentState::Quiescent, 10);
idle_old.tip_oid = "seen-tip".into();
let agents = [
idle_new,
idle_old,
agent("running", AgentState::Live, 5),
agent("dead", AgentState::Stopped, 1),
];
let seen = |k, _: &str, _: &str, o: &str| k == SeenKind::Stopped && o == "seen-tip";
let rows = build(&agents, "/ws", &seen, 100, &plain, &[]);
let ids: Vec<&str> = rows.iter().map(|r| r.root_id.as_str()).collect();
assert_eq!(ids, ["idle-new", "idle-old", "running", "dead"]);
assert_eq!(rows[0].attention, 0);
assert_eq!(rows[3].attention, 1, "the flag still renders, it just sank");
assert_eq!(rows[2].state, AgentState::Live);
}
#[test]
fn recency_is_the_last_action_of_any_kind_not_the_last_commit() {
let mut streaming = agent("streaming", AgentState::InFlight, 10);
streaming.last_action_unix = 95;
let fresher_tip = agent("idle", AgentState::Quiescent, 40);
let rows = build(&[fresher_tip, streaming], "/ws", &unseen, 100, &plain, &[]);
let ids: Vec<&str> = rows.iter().map(|r| r.root_id.as_str()).collect();
assert_eq!(ids, ["streaming", "idle"], "last action, not last commit");
assert_eq!(rows[0].age_secs, 5, "the age label reads the same one fact");
assert_eq!(rows[1].age_secs, 60);
}
#[test]
fn equal_last_action_breaks_on_root_id_ascending() {
let agents = [
agent("b-0", AgentState::Quiescent, 7),
agent("a-0", AgentState::Quiescent, 7),
agent("c-0", AgentState::Quiescent, 7),
];
let rows = build(&agents, "/ws", &unseen, 10, &plain, &[]);
let ids: Vec<&str> = rows.iter().map(|r| r.root_id.as_str()).collect();
assert_eq!(ids, ["a-0", "b-0", "c-0"]);
}
#[test]
fn subtree_state_aggregates_in_flight_over_live_over_root() {
let streaming = [
agent("r-0", AgentState::Quiescent, 1),
agent("r-0-c-1", AgentState::InFlight, 2),
];
let row = &build(&streaming, "/ws", &unseen, 10, &plain, &[])[0];
assert_eq!(row.state, AgentState::InFlight);
assert_eq!(row.flight, Some(crate::nav::convs::Flight::Inference));
let live = [
agent("r-0", AgentState::Quiescent, 1),
agent("r-0-c-1", AgentState::Live, 2),
];
let row = &build(&live, "/ws", &unseen, 10, &plain, &[])[0];
assert_eq!(row.state, AgentState::Live);
assert_eq!(row.flight, Some(crate::nav::convs::Flight::Subagents));
let mut root = agent("r-0", AgentState::Stopped, 1);
root.state_uncertain = true;
let settled = [root, agent("r-0-c-1", AgentState::Quiescent, 2)];
let row = &build(&settled, "/ws", &unseen, 10, &plain, &[])[0];
assert_eq!(row.state, AgentState::Stopped);
assert!(row.uncertain);
}
#[test]
fn deciding_agents_uncertainty_is_carried() {
let mut child = agent("r-0-c-1", AgentState::InFlight, 2);
child.state_uncertain = true;
let agents = [agent("r-0", AgentState::Quiescent, 1), child];
assert!(build(&agents, "/ws", &unseen, 10, &plain, &[])[0].uncertain);
}
#[test]
fn preview_is_the_roots_first_line_capped_with_streaming_fallback() {
let mut a = agent("r-0", AgentState::Quiescent, 1);
a.preview = Some("first line\nsecond line".into());
assert_eq!(
build(&[a], "/ws", &unseen, 10, &plain, &[])[0].preview,
"first line"
);
let mut b = agent("r-0", AgentState::InFlight, 1);
b.stream.text = Some("streamed so far".into());
assert_eq!(
build(&[b], "/ws", &unseen, 10, &plain, &[])[0].preview,
"streamed so far",
"streaming text backfills a missing request preview"
);
let mut c = agent("r-0", AgentState::Quiescent, 1);
c.preview = Some("x".repeat(200));
assert_eq!(
build(&[c], "/ws", &unseen, 10, &plain, &[])[0]
.preview
.chars()
.count(),
80,
"capped at the preview cap"
);
let d = agent("r-0", AgentState::Quiescent, 1);
assert_eq!(build(&[d], "/ws", &unseen, 10, &plain, &[])[0].preview, "");
}
#[test]
fn the_ball_badge_is_the_roots_goal_stamp_coloured_by_the_join() {
use crate::projects::join::JoinState;
let resolve = |id: &str| ConvBall {
id: id.to_owned(),
state: Some(JoinState::Bound),
title: Some("Do the thing".to_owned()),
badge: None,
};
let mut root = agent("r-0", AgentState::Quiescent, 2);
root.goal_ball = Some("b1".to_owned());
let child = agent("r-0-c-1", AgentState::Quiescent, 1);
let rows = build(&[root, child], "/ws", &unseen, 10, &resolve, &[]);
let ball = rows[0].ball.as_ref().expect("the root's stamped ball");
assert_eq!(ball.id, "b1");
assert_eq!(ball.state, Some(JoinState::Bound));
assert_eq!(ball.title.as_deref(), Some("Do the thing"));
let bare = agent("bare-0", AgentState::Quiescent, 1);
assert!(
build(&[bare], "/ws", &unseen, 10, &plain, &[])[0]
.ball
.is_none()
);
}
#[test]
fn age_clamps_clock_skew_to_zero() {
let a = agent("r-0", AgentState::Quiescent, 100);
assert_eq!(build(&[a], "/ws", &unseen, 50, &plain, &[])[0].age_secs, 0);
}
#[test]
fn age_label_buckets() {
assert_eq!(age_label(-5), "0s");
assert_eq!(age_label(42), "42s");
assert_eq!(age_label(420), "7m");
assert_eq!(age_label(7200), "2h");
assert_eq!(age_label(200_000), "2d");
}