use super::{acked, agent, nothing};
use crate::attention::*;
use crate::git_tree::{Agent, AgentState};
use crate::ui_state::{SeenKind, UiState};
#[test]
fn no_signals_is_no_attention() {
let a = attention(&agent("a"), "ws", ¬hing);
assert!(!a.any());
assert!(a.kinds().is_empty());
assert_eq!(
a,
Attention {
notify: false,
stopped: false,
budget: false,
conflicted: false,
mail: false,
}
);
}
#[test]
fn each_marked_signal_fires_when_unseen() {
type Case = (fn(&mut Agent), AttentionKind);
let cases: &[Case] = &[
(|a| a.notify_oid = Some("n".into()), AttentionKind::Notify),
(|a| a.budget_oid = Some("b".into()), AttentionKind::Budget),
(
|a| a.conflicted_oid = Some("c".into()),
AttentionKind::Conflicted,
),
];
for (mutate, kind) in cases {
let mut ag = agent("a");
mutate(&mut ag);
let att = attention(&ag, "ws", ¬hing);
assert!(att.any(), "{kind:?} should fire");
assert_eq!(att.kinds(), vec![*kind]);
assert!(!att.mail, "a mark never implies mail");
}
}
#[test]
fn stopped_fires_when_unabandoned_and_tip_unseen() {
let mut ag = agent("a");
ag.state = AgentState::Stopped;
let att = attention(&ag, "ws", ¬hing);
assert!(att.stopped);
assert_eq!(att.kinds(), vec![AttentionKind::Stopped]);
}
#[test]
fn abandoned_suppresses_the_stop_signal() {
let mut ag = agent("a");
ag.state = AgentState::Stopped;
ag.abandoned_oid = Some("dead".into());
let att = attention(&ag, "ws", ¬hing);
assert!(
!att.stopped,
"abandoned = will-not-retry suppresses stop (§6)"
);
assert!(!att.any());
}
#[test]
fn acked_oid_clears_but_moved_oid_re_arms() {
let mut ag = agent("a");
ag.notify_oid = Some("v2".into());
assert!(
attention(&ag, "ws", &acked(SeenKind::Notify, "v1")).notify,
"a moved ref re-notifies (§4.1)"
);
assert!(!attention(&ag, "ws", &acked(SeenKind::Notify, "v2")).notify);
}
#[test]
fn stopped_tip_watermark_gates_on_the_branch_tip() {
let mut ag = agent("a");
ag.state = AgentState::Stopped;
assert!(!attention(&ag, "ws", &acked(SeenKind::Stopped, "tip-a")).stopped);
assert!(attention(&ag, "ws", &acked(SeenKind::Stopped, "tip-old")).stopped);
}
#[test]
fn mail_fires_only_on_pending_and_definite_free() {
let cases = [
(AgentState::Quiescent, false, 2, true), (AgentState::Stopped, false, 1, true), (AgentState::Quiescent, true, 2, false), (AgentState::Stopped, true, 3, false), (AgentState::Live, false, 5, false), (AgentState::InFlight, false, 5, false), (AgentState::Quiescent, false, 0, false), ];
for (state, uncertain, pending, expect) in cases {
let mut ag = agent("a");
ag.state = state;
ag.state_uncertain = uncertain;
ag.pending_messages = pending;
ag.abandoned_oid = Some("x".into());
assert_eq!(
attention(&ag, "ws", ¬hing).mail,
expect,
"{state:?} uncertain={uncertain} pending={pending}"
);
}
}
#[test]
fn all_signals_together_list_in_badge_order() {
let mut ag = agent("a");
ag.notify_oid = Some("n".into());
ag.state = AgentState::Stopped;
ag.budget_oid = Some("b".into());
ag.conflicted_oid = Some("c".into());
ag.pending_messages = 1; assert_eq!(
attention(&ag, "ws", ¬hing).kinds(),
vec![
AttentionKind::Notify,
AttentionKind::Stopped,
AttentionKind::Budget,
AttentionKind::Conflicted,
AttentionKind::Mail,
]
);
}
#[test]
fn wires_through_a_real_ui_state() {
let mut ui = UiState::open(std::path::PathBuf::from("/nonexistent/dir/ui.json"));
ui.record_seen(SeenKind::Notify, "ws", "a", "oid1");
let mut ag = agent("a");
ag.notify_oid = Some("oid1".into());
let seen = |k: SeenKind, w: &str, a: &str, o: &str| ui.is_seen(k, w, a, o);
assert!(!attention(&ag, "ws", &seen).notify);
ag.notify_oid = Some("oid2".into());
assert!(attention(&ag, "ws", &seen).notify);
}