use super::{ConvBall, ConvRow};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ConvGroup {
pub ball: Option<ConvBall>,
pub convs: Vec<ConvRow>,
}
pub fn group_by_ball(rows: Vec<ConvRow>) -> Vec<ConvGroup> {
let mut groups: Vec<ConvGroup> = Vec::new();
let mut unassociated: Vec<ConvRow> = Vec::new();
for row in rows {
let Some(ball) = row.ball.clone() else {
unassociated.push(row);
continue;
};
match groups
.iter_mut()
.find(|g| group_id(g) == Some(ball.id.as_str()))
{
Some(g) => g.convs.push(row),
None => groups.push(ConvGroup {
ball: Some(ball),
convs: vec![row],
}),
}
}
if !unassociated.is_empty() {
groups.push(ConvGroup {
ball: None,
convs: unassociated,
});
}
groups
}
fn group_id(group: &ConvGroup) -> Option<&str> {
group.ball.as_ref().map(|b| b.id.as_str())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::git_tree::AgentState;
fn ball(id: &str) -> ConvBall {
ConvBall {
id: id.to_owned(),
state: None,
title: None,
badge: None,
}
}
fn row(root: &str, ball: Option<&str>) -> ConvRow {
ConvRow {
root_id: root.to_owned(),
state: AgentState::Quiescent,
uncertain: false,
preview: String::new(),
age_secs: 0,
last_active_unix: 1_700_000_042,
flight: None,
attention: 0,
members: 1,
depth: 0,
direct: 0,
stoppable: false,
stop_children: false,
ball: ball.map(self::ball),
name: None,
name_display_only: false,
verdict: None,
tone: crate::nav::convs::Tone::Plain,
failure: None,
}
}
#[test]
fn balls_head_groups_in_first_appearance_order_conversations_beneath() {
let rows = vec![
row("r1", Some("b1")),
row("r2", Some("b2")),
row("r3", Some("b1")),
];
let groups = group_by_ball(rows);
assert_eq!(groups.len(), 2);
assert_eq!(groups[0].ball.as_ref().map(|b| b.id.as_str()), Some("b1"));
let ids: Vec<&str> = groups[0].convs.iter().map(|c| c.root_id.as_str()).collect();
assert_eq!(ids, ["r1", "r3"], "same-ball conversations, input order");
assert_eq!(groups[1].ball.as_ref().map(|b| b.id.as_str()), Some("b2"));
}
#[test]
fn unassociated_conversations_collect_into_one_trailing_group() {
let rows = vec![
row("r1", Some("b1")),
row("bare1", None),
row("bare2", None),
];
let groups = group_by_ball(rows);
assert_eq!(groups.len(), 2);
assert!(groups[0].ball.is_some());
assert!(groups[1].ball.is_none(), "unassociated group is last");
let ids: Vec<&str> = groups[1].convs.iter().map(|c| c.root_id.as_str()).collect();
assert_eq!(ids, ["bare1", "bare2"]);
}
#[test]
fn all_unassociated_is_the_single_none_group_and_empty_input_is_empty() {
let groups = group_by_ball(vec![row("a", None), row("b", None)]);
assert_eq!(groups.len(), 1);
assert!(groups[0].ball.is_none());
assert!(group_by_ball(vec![]).is_empty(), "no rows, no groups");
}
}