use super::{Alert, Announced, announce, of_queue};
use crate::attention::AttentionKind;
use crate::boundary::answer::queue::QueueRow;
use crate::git_tree::AgentState;
fn row(ws: &str, display: &str, signals: Vec<AttentionKind>) -> QueueRow {
QueueRow {
workspace: ws.to_string(),
agent: "20260802T120000Z-root".to_string(),
display: display.to_string(),
state: AgentState::Quiescent,
uncertain: false,
signals,
preview: "please ping".to_string(),
age_secs: 42,
pending: 0,
held: None,
failure: None,
flag: None,
}
}
fn alert(summary: &str, body: &str) -> Alert {
Alert {
summary: summary.to_string(),
body: body.to_string(),
}
}
#[test]
fn a_queue_row_names_its_workspace_its_conversation_and_its_rules() {
let alerts = of_queue(&[
row("cobalt", "ochre-tern", vec![AttentionKind::Stopped]),
row(
"slate",
"jade-vole",
vec![AttentionKind::Notify, AttentionKind::Mail],
),
]);
assert_eq!(
alerts,
vec![
alert("cobalt · ochre-tern", "came to rest — your turn"),
alert(
"slate · jade-vole",
"raised a notify mark; has mail queued and no driver taking it"
),
]
);
}
#[test]
fn a_signal_less_row_says_nothing_and_a_named_row_says_its_two_words() {
assert!(of_queue(&[row("cobalt", "ochre-tern", Vec::new())]).is_empty());
let alerts = of_queue(&[row("cobalt", "ochre-tern", vec![AttentionKind::Held])]);
assert_eq!(
alerts,
vec![alert(
"cobalt · ochre-tern",
"parked a tool invocation for your answer"
)]
);
}
#[test]
fn the_first_fold_is_the_baseline_and_announces_nothing() {
let mut announced = Announced::default();
let waiting = vec![alert("cobalt · ochre-tern", "came to rest — your turn")];
assert!(announced.arrivals(waiting.clone()).is_empty());
assert!(announced.arrivals(waiting).is_empty());
}
#[test]
fn only_a_sentence_the_operator_has_not_heard_is_an_arrival() {
let rest = alert("cobalt · ochre-tern", "came to rest — your turn");
let mail = alert(
"cobalt · jade-vole",
"has mail queued and no driver taking it",
);
let both = alert(
"cobalt · ochre-tern",
"came to rest — your turn; exhausted its budget",
);
let mut announced = Announced::default();
assert!(announced.arrivals(vec![rest.clone()]).is_empty());
assert_eq!(
announced.arrivals(vec![rest.clone(), mail.clone()]),
vec![mail.clone()]
);
assert_eq!(
announced.arrivals(vec![both.clone(), mail.clone()]),
vec![both]
);
assert!(announced.arrivals(vec![mail.clone()]).is_empty());
assert_eq!(announced.arrivals(vec![rest.clone(), mail]), vec![rest]);
}
#[test]
fn a_discarded_arrival_is_not_re_announced_when_the_window_is_buried_again() {
let rest = alert("cobalt · ochre-tern", "came to rest — your turn");
let mut announced = Announced::default();
announced.arrivals(Vec::new());
assert_eq!(announced.arrivals(vec![rest.clone()]), vec![rest.clone()]);
assert!(announced.arrivals(vec![rest]).is_empty());
}
#[test]
fn every_signal_words_itself_distinctly() {
let all = [
AttentionKind::Notify,
AttentionKind::Stopped,
AttentionKind::Budget,
AttentionKind::Conflicted,
AttentionKind::Mail,
AttentionKind::Held,
];
let said: std::collections::BTreeSet<&str> = all.iter().map(|k| k.says()).collect();
assert_eq!(said.len(), all.len(), "six rules, six sentences");
assert!(said.iter().all(|s| !s.is_empty()));
}
#[test]
fn focus_and_the_knob_each_silence_the_desktop_without_saving_the_news_up() {
let waiting = [row("cobalt", "ochre-tern", vec![AttentionKind::Stopped])];
let arrival = alert("cobalt · ochre-tern", "came to rest — your turn");
let mut armed = Announced::default();
assert!(announce(&mut armed, &[], false, true).is_empty());
assert_eq!(announce(&mut armed, &waiting, false, true), vec![arrival]);
assert!(announce(&mut armed, &waiting, false, true).is_empty());
let mut looking = Announced::default();
assert!(announce(&mut looking, &[], true, true).is_empty());
assert!(announce(&mut looking, &waiting, true, true).is_empty());
assert!(announce(&mut looking, &waiting, false, true).is_empty());
let mut off = Announced::default();
assert!(announce(&mut off, &[], false, false).is_empty());
assert!(announce(&mut off, &waiting, false, false).is_empty());
assert!(announce(&mut off, &waiting, false, true).is_empty());
}