use super::Harness;
use crate::keymap::InspectorTab;
use crate::nav::ws_key;
use crate::ui_state::SeenKind;
#[test]
fn strip_total_counts_attention_bearing_agents() {
let h = Harness::new();
let (_c, model) = h.model();
assert_eq!(model.strip_total(), 1, "the one unseen stop");
}
#[test]
fn focused_agent_resolves_the_selected_row_else_none() {
let h = Harness::new();
let (_c, mut model) = h.model();
model.focus_workspace(&h.ws);
assert!(model.focused_agent().is_none());
model.focus_agent(&h.ws, "c-1");
assert_eq!(
model.focused_agent().map(|a| a.agent_id.as_str()),
Some("c-1")
);
model.focus_agent(&h.ws, "ghost");
assert!(model.focused_agent().is_none());
model.focus_agent(std::path::Path::new("/no/such/ws"), "x");
assert!(model.focused_agent().is_none());
}
#[test]
fn the_tab_bar_and_conversation_list_carry_the_derived_facts() {
let h = Harness::new();
let (_c, mut model) = h.model();
let bar = model.tab_bar();
assert!(bar.tabs.is_empty(), "no named workspaces, no wall tabs");
assert_eq!(bar.overflow.len(), 1);
let entry = &bar.overflow[0];
assert_eq!(entry.name, "ws");
assert_eq!(entry.attention, 1);
assert!(!entry.replay);
model.focus_workspace(&h.ws);
let rows = model.conversations(10);
assert_eq!(rows.len(), 1);
assert_eq!(rows[0].root_id, "c-1");
assert_eq!(rows[0].attention, 1);
assert_eq!(rows[0].members, 1);
model.focus_workspace(std::path::Path::new("/no/such"));
assert!(model.conversations(10).is_empty(), "unfetched ws: no rows");
}
#[test]
fn focus_workspace_selects_no_agent_and_records_nothing() {
let h = Harness::new();
let (_c, mut model) = h.model();
model.focus_workspace(&h.ws);
assert_eq!(model.focused_workspace(), Some(h.ws.as_path()));
assert!(model.focus().agent.is_none());
assert_eq!(model.strip_total(), 1, "focusing a workspace acks nothing");
}
#[test]
fn focus_agent_acknowledges_the_stop_watermark() {
let h = Harness::new();
let (_c, mut model) = h.model();
let tip = model.tree(&h.ws).unwrap().agents[0].tip_oid.clone();
let key = ws_key(&h.ws);
assert!(!model.is_seen(SeenKind::Stopped, &key, "c-1", &tip));
model.focus_agent(&h.ws, "c-1");
assert_eq!(model.focus().agent.as_deref(), Some("c-1"));
assert!(
model.is_seen(SeenKind::Stopped, &key, "c-1", &tip),
"recorded on focus"
);
assert_eq!(
model.strip_total(),
0,
"the acked stop no longer draws attention"
);
}
#[test]
fn focus_agent_on_a_missing_agent_is_a_no_op() {
let h = Harness::new();
let (_c, mut model) = h.model();
model.focus_agent(&h.ws, "nope");
assert_eq!(
model.strip_total(),
1,
"no watermark recorded for a phantom"
);
}
#[test]
fn jump_to_next_attention_focuses_and_acknowledges_it() {
let h = Harness::new();
let (_c, mut model) = h.model();
model.jump_next_attention();
assert_eq!(model.focus().agent.as_deref(), Some("c-1"));
assert_eq!(model.strip_total(), 0, "jumping acknowledges");
model.jump_next_attention();
assert_eq!(model.focus().agent.as_deref(), Some("c-1"));
}
#[test]
fn roster_step_moves_focus_through_agents_and_acknowledges() {
let h = Harness::new();
let (_c, mut model) = h.model();
assert_eq!(model.strip_total(), 1);
model.roster_step(1);
assert_eq!(model.focus().agent.as_deref(), Some("c-1"));
assert_eq!(
model.strip_total(),
0,
"stepping onto the agent acknowledges"
);
model.roster_step(-1);
assert_eq!(model.focus().agent.as_deref(), Some("c-1"));
}
#[test]
fn inspector_tab_selection_is_sticky_across_focus_changes() {
let h = Harness::new();
let (_c, mut model) = h.model();
assert_eq!(
model.inspector_tab(),
InspectorTab::Transcript,
"Transcript is the default tab"
);
model.select_tab(InspectorTab::Steps);
assert_eq!(model.inspector_tab(), InspectorTab::Steps);
model.focus_workspace(&h.ws);
assert_eq!(model.inspector_tab(), InspectorTab::Steps);
model.focus_agent(&h.ws, "c-1");
assert_eq!(
model.inspector_tab(),
InspectorTab::Steps,
"sticky through the acknowledgement gesture"
);
}
#[test]
fn toggle_pin_hoists_and_unhoists_the_workspace_tab() {
let h = Harness::new();
let (_c, mut model) = h.model();
let key = ws_key(&h.ws);
assert!(model.tab_bar().tabs.is_empty());
model.toggle_pin(&key);
let bar = model.tab_bar();
assert_eq!(bar.tabs.len(), 1);
assert!(bar.tabs[0].pinned);
assert!(bar.overflow.is_empty(), "hoisted out of the menu");
model.toggle_pin(&key);
assert!(model.tab_bar().tabs.is_empty(), "unpinned again");
assert_eq!(model.tab_bar().overflow.len(), 1);
}
#[test]
fn set_collapsed_overrides_a_section() {
let h = Harness::new();
let (_c, mut model) = h.model();
assert!(!model.is_collapsed("balls"));
model.set_collapsed("balls", true);
assert!(model.is_collapsed("balls"));
model.set_collapsed("balls", false);
assert!(!model.is_collapsed("balls"));
}
#[test]
fn an_auth_failed_conversation_stirs_the_strip_and_flags_login() {
let h = Harness::new();
h.write_response(
"c-1",
b"{\"type\":\"error\",\"kind\":\"auth\",\"message\":\"no credential for this provider: run `bz --login --provider <id>`\",\"provider_detail\":null}\n{\"type\":\"end\"}\n",
);
let (_c, mut model) = h.model();
assert_eq!(
model.strip_total(),
1,
"the dead conversation stirs (§6 rule 2)"
);
model.focus_workspace(&h.ws);
let rows = model.conversations(10);
assert_eq!(rows[0].state, crate::git_tree::AgentState::Stopped);
assert_eq!(rows[0].attention, 1);
assert!(
crate::login::auth::latest_step_auth_failed(
&h.ws,
"c-1",
crate::git_tree::AgentState::Stopped
),
"the center's Login banner keys on the same response"
);
model.focus_agent(&h.ws, "c-1");
assert_eq!(model.strip_total(), 0, "acknowledged");
let rows = model.conversations(10);
assert_eq!(rows[0].state, crate::git_tree::AgentState::Stopped);
assert!(crate::login::auth::latest_step_auth_failed(
&h.ws,
"c-1",
crate::git_tree::AgentState::Stopped
));
}
#[test]
fn conversation_members_returns_the_focused_conversations_subtree() {
let h = Harness::new();
let (_c, mut model) = h.model();
assert!(model.conversation_members().is_empty(), "no agent focused");
model.focus_agent(&h.ws, "c-1");
assert_eq!(model.conversation_members().len(), 1, "the lone root");
model.focus_agent(&h.ws, "ghost");
assert!(
model.conversation_members().is_empty(),
"an id absent from the tree has no conversation root"
);
model.focus_agent(std::path::Path::new("/no/such"), "c-1");
assert!(model.conversation_members().is_empty(), "no snapshot");
}
#[test]
fn flush_ui_writes_only_when_dirty() {
let h = Harness::new();
let (_c, mut model) = h.model();
model.toggle_pin("k");
assert!(model.flush_ui().unwrap(), "a pending change is written");
assert!(!model.flush_ui().unwrap(), "nothing pending → no write");
}
#[test]
fn m2_two_instances_converge_on_seen_through_ui_json() {
let h = Harness::new();
let (_ca, mut a) = h.model();
let (_cb, mut b) = h.model();
assert_eq!(a.strip_total(), 1, "both see the unseen stop");
assert_eq!(b.strip_total(), 1);
a.focus_agent(&h.ws, "c-1");
a.flush_ui().unwrap();
assert_eq!(a.strip_total(), 0, "A's own view clears");
assert_eq!(b.strip_total(), 1, "B has not adopted yet");
b.dirty_handle().mark_all([h.roots.yog_state.clone()]);
b.tick();
assert_eq!(b.strip_total(), 0, "B converged on the acknowledgement");
let tip = b.tree(&h.ws).unwrap().agents[0].tip_oid.clone();
let key = ws_key(&h.ws);
assert!(
b.is_seen(SeenKind::Stopped, &key, "c-1", &tip),
"the concrete watermark converged, not just the count",
);
}