use super::super::*;
use super::{Harness, agent};
use crate::cli_outbound::Binary;
use crate::git_tree::{AgentState, GitTree};
use std::path::{Path, PathBuf};
use std::time::Duration;
fn agent_count(model: &AppModel, ws: &Path) -> usize {
model.tree(ws).map_or(0, |t| t.agents.len())
}
fn settle(model: &mut AppModel, clock: &crate::test_support::FakeClock) {
clock.advance(Duration::from_millis(10));
model.tick(); clock.advance(Duration::from_millis(150));
model.tick(); }
#[test]
fn a_dirty_workspace_root_re_derives_its_snapshot() {
let h = Harness::new();
let (clock, mut model) = h.model();
assert_eq!(agent_count(&model, &h.ws), 1);
h.build_more("c-2", "world"); assert_eq!(agent_count(&model, &h.ws), 1, "stale until re-derived");
model.dirty_handle().mark_all([h.ws.clone()]);
clock.advance(Duration::from_millis(10));
assert!(!model.tick(), "debounced within the window");
clock.advance(Duration::from_millis(150));
assert!(model.tick(), "window elapsed → snapshot replaced");
assert_eq!(agent_count(&model, &h.ws), 2);
}
#[test]
fn an_unchanged_re_derivation_replaces_nothing() {
let h = Harness::new();
let (clock, mut model) = h.model();
model.dirty_handle().mark_all([h.ws.clone()]);
clock.advance(Duration::from_millis(10));
model.tick();
clock.advance(Duration::from_millis(150));
assert!(
!model.tick(),
"an unchanged tree suppresses the replacement"
);
}
#[test]
fn a_failed_re_read_keeps_the_last_snapshot() {
let h = Harness::new();
let (clock, mut model) = h.model();
let bogus = PathBuf::from("/nonexistent/workspace");
model.dirty_handle().mark_all([bogus.clone()]);
settle(&mut model, &clock);
assert!(model.tree(&bogus).is_none(), "a failed read stores nothing");
assert_eq!(agent_count(&model, &h.ws), 1, "the good snapshot is intact");
}
#[test]
fn the_full_sweep_re_derives_every_workspace() {
let h = Harness::new();
let (clock, mut model) = h.model();
h.build_more("c-2", "yo");
clock.advance(Duration::from_secs(15)); model.tick();
clock.advance(Duration::from_millis(150)); model.tick();
assert_eq!(agent_count(&model, &h.ws), 2, "the 15 s sweep re-derived");
}
#[test]
fn the_cheap_sweep_re_probes_and_invalidates_only_live_workspaces() {
let h = Harness::new();
let (clock, mut model) = h.model();
clock.advance(super::super::CHEAP_SWEEP);
assert!(!model.tick(), "a quiescent workspace: no targeted re-probe");
model.trees.insert(
h.ws.clone(),
GitTree {
commits: vec![],
agents: vec![agent("c-1", AgentState::Live)],
},
);
clock.advance(super::super::CHEAP_SWEEP);
model.tick(); clock.advance(Duration::from_millis(150));
assert!(model.tick(), "a live agent: the cheap sweep re-derives");
assert_eq!(agent_count(&model, &h.ws), 1, "re-read the real fixture");
}
#[test]
fn an_enumeration_root_event_reconciles_the_workspace_set() {
let mut h = Harness::new();
let (clock, mut model) = h.model();
assert_eq!(model.workspaces().len(), 1);
let ws2 = h.add_workspace("ws2", "d-1");
model.dirty_handle().mark_all([h.roots.workspaces()]);
model.tick(); assert_eq!(
model.workspaces().len(),
2,
"the new workspace is enumerated"
);
assert_eq!(model.workspace_stats(&ws2), (0, 0, false));
clock.advance(Duration::from_millis(150));
model.tick();
assert_eq!(agent_count(&model, &ws2), 1);
}
#[test]
fn a_vanished_workspace_is_pruned_on_reconcile() {
let mut h = Harness::new();
let ws2 = h.add_workspace("ws2", "d-1");
let (_c, mut model) = h.model();
assert_eq!(
model.workspaces().len(),
2,
"both enumerated at construction"
);
assert_eq!(agent_count(&model, &ws2), 1);
std::fs::remove_file(&ws2).unwrap();
model.dirty_handle().mark_all([h.roots.workspaces()]);
model.tick();
assert_eq!(model.workspaces().len(), 1, "the removed workspace is gone");
assert!(model.tree(&ws2).is_none(), "its snapshot is pruned");
}
#[test]
fn every_enum_root_and_the_state_root_are_routed() {
let h = Harness::new();
let (_c, mut model) = h.model();
model.dirty_handle().mark_all([
h.roots.yog_state.clone(),
h.roots.names(),
h.roots.workspaces(),
h.roots.replays(),
h.ws.clone(),
]);
assert!(
!model.tick(),
"no snapshot delta this tick (workspace only debounced)"
);
assert_eq!(model.workspaces().len(), 1, "reconcile kept the set");
}
#[test]
fn adopting_our_own_ui_json_echo_is_suppressed() {
let h = Harness::new();
let (_c, mut model) = h.model();
let key = crate::nav::ws_key(&h.ws);
model.toggle_pin(&key);
model.flush_ui().unwrap();
model.dirty_handle().mark_all([h.roots.yog_state.clone()]);
model.tick(); let bar = model.tab_bar();
assert!(
bar.tabs.first().is_some_and(|t| t.pinned),
"the pin survives the echo: {bar:?}"
);
}
#[test]
fn toolchain_probes_at_startup_and_refreshes_on_demand() {
let h = Harness::new();
let (_c, mut model) = h.model();
for tool in [Binary::Bl, Binary::Lernie, Binary::Bz] {
assert!(model.toolchain().permits(tool), "startup permits {tool:?}");
}
model.refresh_toolchain();
assert!(model.toolchain().permits(Binary::Bz));
}