use std::collections::HashSet;
use super::*;
use crate::config_edit::branch::{GoverningConfig, governing_config};
use crate::files_view::{self, FilesView};
use crate::git_tree::tests::fixture::Fixture;
use crate::git_tree::{AgentState, GitTree};
use crate::inboxview::list_inbox;
use crate::steps_view::{self, StepTab};
use crate::transcript;
fn screen() -> egui::RawInput {
egui::RawInput {
screen_rect: Some(egui::Rect::from_min_size(
egui::Pos2::ZERO,
egui::vec2(1024.0, 4096.0),
)),
..Default::default()
}
}
fn collect_text(shape: &egui::Shape, out: &mut String) {
match shape {
egui::Shape::Text(t) => {
out.push_str(t.galley.text());
out.push('\n');
}
egui::Shape::Vec(shapes) => shapes.iter().for_each(|s| collect_text(s, out)),
_ => {}
}
}
fn paint(tab: InspectorTab, data: &TabData) -> String {
let ctx = egui::Context::default();
let mut collapsed = HashSet::new();
let mut file_sel = None;
let out = ctx.run(screen(), |ctx| {
egui::CentralPanel::default().show(ctx, |ui| {
render(ui, tab, data, &mut collapsed, &mut file_sel);
});
});
let mut text = String::new();
for clipped in out.shapes {
collect_text(&clipped.shape, &mut text);
}
text
}
fn populated() -> (Fixture, String, String) {
let fx = Fixture::new();
fx.build_agent("c-1", "hello");
let messages = fx.path.join("agents/c-1/messages");
std::fs::create_dir_all(&messages).unwrap();
std::fs::write(messages.join("001-user.md"), "please ping").unwrap();
std::fs::write(
messages.join("002-opus.json"),
br#"{"content":[{"type":"text","text":"pong reply"}]}"#,
)
.unwrap();
std::fs::write(
messages.join("003-tool.json"),
br#"{"tool_use_id":"toolu_1","content":"tool said hi","is_error":false}"#,
)
.unwrap();
let step = fx.path.join("steps/c-1/001");
std::fs::create_dir_all(step.join("tools/toolu_1")).unwrap();
std::fs::write(
step.join("response.json"),
b"{\"type\":\"usage\",\"input_tokens\":10,\"output_tokens\":5}\n{\"type\":\"end\"}\n",
)
.unwrap();
std::fs::write(step.join("tools/toolu_1/input.json"), br#"{"name":"Read"}"#).unwrap();
std::fs::write(
step.join("tools/toolu_1/output.json"),
br#"{"exit_code":0}"#,
)
.unwrap();
fx.deposit_message(
"c-1",
"user-001.md",
"---\nfrom: user\ndeposited_at: t0\n---\nfollow-up message",
);
let tree = GitTree::from_repo(&fx.path).unwrap();
let agent = tree.agents.iter().find(|a| a.agent_id == "c-1").unwrap();
(fx, agent.agent_id.clone(), agent.tip_oid.clone())
}
#[test]
fn collect_text_descends_shape_vec_and_ignores_non_text() {
use egui::{Color32, FontId, Pos2, Stroke};
let ctx = egui::Context::default();
let mut nested: Option<egui::Shape> = None;
let _ = ctx.run(egui::RawInput::default(), |ctx| {
let galley =
ctx.fonts(|f| f.layout_no_wrap("nested".into(), FontId::default(), Color32::WHITE));
nested = Some(egui::Shape::Vec(vec![egui::Shape::Text(
egui::epaint::TextShape {
pos: Pos2::ZERO,
galley,
underline: Stroke::NONE,
fallback_color: Color32::WHITE,
override_text_color: None,
opacity_factor: 1.0,
angle: 0.0,
},
)]));
});
let mut out = String::new();
collect_text(nested.as_ref().unwrap(), &mut out);
collect_text(&egui::Shape::Noop, &mut out);
assert!(out.contains("nested"));
}
#[test]
fn every_tab_paints_its_signature_from_a_populated_fixture() {
let (fx, agent, tip) = populated();
let ws = &fx.path;
let state = GitTree::from_repo(ws)
.unwrap()
.agents
.iter()
.find(|a| a.agent_id == agent)
.unwrap()
.state;
let in_flight = matches!(state, AgentState::InFlight);
let transcript = transcript::build(ws, &agent, in_flight);
let steps = steps_view::build(ws, &agent, state);
let detail = steps_view::detail(ws, &agent, "001");
let inbox = list_inbox(ws, &agent);
let files = files_view::build(ws, &agent);
let preview = files_view::preview(&files_view::agent_worktree(ws, &agent).join("goal.md"));
let governing = governing_config(ws, &tip).unwrap();
let governing_short = governing.short_oid.clone();
let data = TabData {
transcript,
raw: false,
steps,
step_sel: Some(0),
step_detail: Some(detail),
step_tab: StepTab::Tools,
inbox,
files,
file_preview: Some(preview),
governing: Some(governing),
};
let tx = paint(InspectorTab::Transcript, &data);
assert!(tx.contains("please ping"), "transcript:\n{tx}");
assert!(tx.contains("pong reply"));
assert!(tx.contains("tool said hi"));
let raw = paint(
InspectorTab::Transcript,
&TabData {
raw: true,
..data.clone()
},
);
assert!(raw.contains("001-user.md"), "raw:\n{raw}");
let st = paint(InspectorTab::Steps, &data);
assert!(st.contains("15 tok"), "steps:\n{st}");
assert!(st.contains("toolu_1"));
assert!(st.contains("input"));
assert!(st.contains("output"));
let ib = paint(InspectorTab::Inbox, &data);
assert!(ib.contains("✉ user · t0"), "inbox:\n{ib}");
assert!(ib.contains("follow-up message"));
let fl = paint(InspectorTab::Files, &data);
assert!(fl.contains("goal.md"), "files tree:\n{fl}");
assert!(fl.contains("hello"), "goal.md preview reached paint:\n{fl}");
assert!(!fl.contains("messages"), "root messages/ excluded:\n{fl}");
let cfg = paint(InspectorTab::Config, &data);
assert!(cfg.contains("policy frozen at"), "config:\n{cfg}");
assert!(cfg.contains(&governing_short));
assert!(cfg.contains("version"), "governing files listed:\n{cfg}");
}
fn empty_tab_data(
transcript: Transcript,
steps: StepsView,
inbox: Vec<Deposit>,
files: FilesView,
governing: Option<GoverningConfig>,
) -> TabData {
TabData {
transcript,
raw: false,
steps,
step_sel: None,
step_detail: None,
step_tab: StepTab::Meta,
inbox,
files,
file_preview: None,
governing,
}
}
#[test]
fn config_tab_without_governing_shows_a_note() {
let (transcript, steps, inbox) = (Transcript::default(), StepsView::default(), Vec::new());
let files = FilesView::default();
let data = empty_tab_data(transcript, steps, inbox, files, None);
assert!(
paint(InspectorTab::Config, &data).contains("no governing config"),
"absent governing note missing"
);
}
#[test]
fn config_tab_frozen_past_a_branch_tip_omits_the_tip_line() {
let fx = Fixture::new();
fx.build_agent("c-1", "hi");
let forked_tip = {
let tree = GitTree::from_repo(&fx.path).unwrap();
tree.agents
.iter()
.find(|a| a.agent_id == "c-1")
.unwrap()
.tip_oid
.clone()
};
fx.commit_other("providers.yaml", "advanced\n");
let governing = governing_config(&fx.path, &forked_tip).unwrap();
assert!(
governing.branch_name_if_tip_of_one.is_none(),
"governing should be frozen past the tip"
);
let (transcript, steps, inbox) = (Transcript::default(), StepsView::default(), Vec::new());
let files = FilesView::default();
let data = empty_tab_data(transcript, steps, inbox, files, Some(governing));
let cfg = paint(InspectorTab::Config, &data);
assert!(cfg.contains("policy frozen at"), "config:\n{cfg}");
assert!(
!cfg.contains("tip of config/"),
"no tip line expected:\n{cfg}"
);
}
#[test]
fn config_tab_at_a_branch_tip_shows_the_tip_line() {
let governing = GoverningConfig {
oid: "a".repeat(40),
short_oid: "aaaaaaaa".into(),
branch_name_if_tip_of_one: Some("default".into()),
files: vec!["version".into()],
};
let (transcript, steps, inbox) = (Transcript::default(), StepsView::default(), Vec::new());
let files = FilesView::default();
let data = empty_tab_data(transcript, steps, inbox, files, Some(governing));
let cfg = paint(InspectorTab::Config, &data);
assert!(cfg.contains("tip of config/default"), "config:\n{cfg}");
}