use super::*;
use crate::nav::convs::expand::{ancestors, forest_rows, parent_of, step, visible};
use std::collections::HashSet;
fn open(ids: &[&str]) -> HashSet<String> {
ids.iter().map(|s| (*s).to_string()).collect()
}
fn family() -> [Agent; 5] {
[
agent("r-0", AgentState::Quiescent, 10),
agent("r-0-a-1", AgentState::Quiescent, 11),
agent("r-0-b-1", AgentState::Quiescent, 12),
agent("r-0-b-1-x-2", AgentState::Quiescent, 13),
agent("s-0", AgentState::Quiescent, 90),
]
}
fn ids(rows: &[crate::nav::convs::ConvRow]) -> Vec<&str> {
rows.iter().map(|r| r.root_id.as_str()).collect()
}
fn rows_with(agents: &[Agent], expanded: &HashSet<String>) -> Vec<crate::nav::convs::ConvRow> {
visible(&forest(agents), expanded)
}
fn forest(agents: &[Agent]) -> Vec<crate::nav::convs::ConvRow> {
forest_rows(agents, "/ws", &unseen, 100, &plain, &[])
}
#[test]
fn the_answer_is_the_whole_forest_and_a_fold_selects_out_of_it() {
let agents = family();
let all = forest(&agents);
assert_eq!(
ids(&all),
["s-0", "r-0", "r-0-a-1", "r-0-b-1", "r-0-b-1-x-2"],
"every member, in paint order, with no set consulted"
);
assert_eq!(
ids(&visible(&all, &HashSet::new())),
["s-0", "r-0"],
"no fold selects the root subset"
);
let open_all = open(&["r-0", "r-0-b-1"]);
assert_eq!(
visible(&all, &open_all),
all,
"everything open is everything"
);
for set in [HashSet::new(), open(&["r-0"]), open_all] {
for row in visible(&all, &set) {
let answered = all.iter().find(|r| r.root_id == row.root_id);
assert_eq!(answered, Some(&row), "a fold changes no row it keeps");
}
}
}
#[test]
fn nothing_expanded_is_one_row_per_root() {
let agents = family();
let rows = rows_with(&agents, &HashSet::new());
assert_eq!(ids(&rows), ["s-0", "r-0"], "recency order, roots only");
assert_eq!(rows[1].members, 4, "a collapsed row speaks for its subtree");
assert_eq!(rows[1].direct, 2);
assert_eq!(rows[1].total(), 3, "total is every agent under it");
assert!(rows.iter().all(|r| r.depth == 0));
}
#[test]
fn expanding_a_root_reveals_exactly_its_direct_children_in_descent_order() {
let agents = family();
let rows = rows_with(&agents, &open(&["r-0"]));
assert_eq!(
ids(&rows),
["s-0", "r-0", "r-0-a-1", "r-0-b-1"],
"the grandchild stays hidden under its collapsed parent"
);
assert_eq!(rows[2].depth, 1);
assert_eq!(rows[3].depth, 1);
assert_eq!(rows[3].members, 2);
assert_eq!(rows[3].direct, 1);
assert_eq!(rows[2].members, 1, "a leaf is its own subtree");
assert_eq!(rows[2].direct, 0);
assert!(!rows[2].has_children());
}
#[test]
fn expansion_recurses_and_direct_and_total_part_ways() {
let agents = family();
let rows = rows_with(&agents, &open(&["r-0", "r-0-b-1"]));
assert_eq!(
ids(&rows),
["s-0", "r-0", "r-0-a-1", "r-0-b-1", "r-0-b-1-x-2"]
);
assert_eq!(rows[4].depth, 2, "a generation deeper indents again");
assert_eq!((rows[1].direct, rows[1].total()), (2, 3));
}
#[test]
fn expanding_a_row_never_moves_its_conversation_in_the_list() {
let agents = family();
let rows = rows_with(&agents, &open(&["r-0", "r-0-b-1"]));
assert_eq!(
rows[0].root_id, "s-0",
"the recent conversation still leads"
);
let noise = rows_with(&agents, &open(&["ghost", "s-0", "r-0-a-1"]));
assert_eq!(ids(&noise), ["s-0", "r-0"]);
}
#[test]
fn the_toggle_round_trips_the_list_through_the_shared_disclosure_set() {
let agents = family();
let collapsed = rows_with(&agents, &HashSet::new());
let mut set = HashSet::new();
let toggle = |set: &mut HashSet<String>, id: &str| {
if !set.remove(id) {
set.insert(id.to_owned());
}
};
toggle(&mut set, "r-0");
let open_rows = rows_with(&agents, &set);
assert_eq!(ids(&open_rows), ["s-0", "r-0", "r-0-a-1", "r-0-b-1"]);
toggle(&mut set, "r-0");
assert!(set.is_empty(), "the second flip takes the id back out");
assert_eq!(rows_with(&agents, &set), collapsed);
}
#[test]
fn the_strict_descent_rule_decides_membership_not_a_prefix() {
let agents = [
agent("r-0", AgentState::Quiescent, 10),
agent("r-0-orphanmaker", AgentState::Quiescent, 20),
agent("r-0-gone-1-kid-2", AgentState::Quiescent, 30),
];
let rows = rows_with(&agents, &open(&["r-0"]));
assert_eq!(
ids(&rows),
["r-0-gone-1-kid-2", "r-0-orphanmaker", "r-0"],
"three roots by recency; expanding r-0 reveals nothing"
);
assert!(rows.iter().all(|r| r.depth == 0 && r.direct == 0));
}
#[test]
fn an_empty_workspace_has_no_visible_rows() {
assert!(rows_with(&[], &open(&["r-0"])).is_empty());
}
#[test]
fn the_walk_steps_the_visible_rows_and_wraps() {
let agents = family();
let rows = rows_with(&agents, &open(&["r-0"]));
assert_eq!(step(&rows, Some("s-0"), 1).as_deref(), Some("r-0"));
assert_eq!(step(&rows, Some("r-0"), 1).as_deref(), Some("r-0-a-1"));
assert_eq!(step(&rows, Some("r-0-a-1"), -1).as_deref(), Some("r-0"));
assert_eq!(
step(&rows, Some("r-0-b-1"), 1).as_deref(),
Some("s-0"),
"wraps"
);
assert_eq!(step(&rows, Some("s-0"), -1).as_deref(), Some("r-0-b-1"));
assert_eq!(step(&rows, None, 1).as_deref(), Some("s-0"));
assert_eq!(step(&rows, Some("ghost"), -1).as_deref(), Some("r-0-b-1"));
assert_eq!(step(&[], Some("s-0"), 1), None);
}
#[test]
fn the_walk_skips_a_collapsed_subtree_whole() {
let agents = family();
let collapsed = rows_with(&agents, &HashSet::new());
assert_eq!(
step(&collapsed, Some("s-0"), 1).as_deref(),
Some("r-0"),
"with nothing expanded the walk is the root list"
);
let partway = rows_with(&agents, &open(&["r-0"]));
assert_eq!(
step(&partway, Some("r-0-b-1-x-2"), 1).as_deref(),
Some("s-0")
);
}
#[test]
fn parent_of_pages_up_one_level_off_the_painted_depths() {
let agents = family();
let rows = rows_with(&agents, &open(&["r-0", "r-0-b-1"]));
assert_eq!(parent_of(&rows, "r-0-b-1-x-2").as_deref(), Some("r-0-b-1"));
assert_eq!(parent_of(&rows, "r-0-b-1").as_deref(), Some("r-0"));
assert_eq!(parent_of(&rows, "r-0-a-1").as_deref(), Some("r-0"));
assert_eq!(parent_of(&rows, "r-0"), None, "a root has nothing above it");
assert_eq!(parent_of(&rows, "s-0"), None);
assert_eq!(parent_of(&rows, "ghost"), None);
}
#[test]
fn ancestors_are_the_chain_a_jump_reveals() {
let agents = family();
assert_eq!(
ancestors(&agents, "r-0-b-1-x-2"),
["r-0", "r-0-b-1"],
"outermost first — expanding all of them makes the target visible"
);
assert_eq!(ancestors(&agents, "r-0-a-1"), ["r-0"]);
assert!(
ancestors(&agents, "r-0").is_empty(),
"a root is already visible"
);
assert!(ancestors(&agents, "ghost").is_empty());
let revealed = rows_with(&agents, &open(&["r-0", "r-0-b-1"]));
assert!(revealed.iter().any(|r| r.root_id == "r-0-b-1-x-2"));
}