use super::*;
#[test]
fn generic_tool_card_keeps_existing_preview_behavior() {
let state = linked_tool_state(
"call_dynamic",
"dynamic_tool",
"dynamic_tool inspect",
ActivityStatus::Success,
vec![("path".to_string(), "src/lib.rs".to_string())],
"STATUS: success\nPATH: src/lib.rs\nOUTPUT:\ncontents",
);
let cards = project_cards(&state);
assert_eq!(cards[0].role, TranscriptCardRole::Tool);
assert_eq!(cards[0].title, "dynamic_tool");
assert!(cards[0].body.contains("STATUS: success"));
assert!(cards[0].body.contains("PATH: src/lib.rs"));
let visual = state.transcript_visual_text();
assert!(
visual.contains("✓ Tool • dynamic_tool\n STATUS: success"),
"{visual}"
);
assert!(visual.contains("PATH: src/lib.rs"), "{visual}");
}
#[test]
fn typed_tool_summary_controls_card_even_when_body_looks_like_another_tool() {
use crate::tui::transcript::{TranscriptEntry, TranscriptToolData, TranscriptToolIdentity};
let mut state = MissionControlState::default();
state.transcript.push_back(TranscriptEntry::tool(
"✗ subagents • misleading display text".to_string(),
TranscriptToolData {
identity: TranscriptToolIdentity::Summary(crate::output::ToolDisplaySummary {
tool_name: "dynamic_tool".to_string(),
status: crate::output::ToolStatus::Success,
unicode_mark: "✓",
ascii_mark: "+",
label: "inspect".to_string(),
metadata: Vec::new(),
}),
children: Vec::new(),
},
));
let cards = project_cards(&state);
assert_eq!(cards[0].role, TranscriptCardRole::Tool);
assert_eq!(cards[0].title, "dynamic_tool");
assert_eq!(cards[0].status, TranscriptCardStatus::Success);
assert!(cards[0].body.contains("misleading display text"));
}
#[test]
fn bash_tool_card_header_and_body_use_dedicated_projection() {
let state = bash_card_state(
"call_bash_header",
"bash",
ActivityStatus::Success,
serde_json::json!({"command":"cargo check"}),
serde_json::json!({"exit_code":0,"timed_out":false,"stdout":"ok","stderr":""}),
"stdout:\nok\nstderr:\n",
);
let wide_lines = visual_lines(&state, CARD_LAYOUT_MIN_WIDTH);
let wide_header = line_text(&wide_lines[1].line);
assert!(wide_header.starts_with(" ✓ Bash • exit 0"), "{wide_header}");
assert!(!wide_header.contains("cargo check"), "{wide_header}");
let visual = state.transcript_visual_text();
assert_eq!(visual, "✓ Bash • exit 0\n cargo check\n 1 line");
}
#[test]
fn subagents_card_header_uses_dispatch_name_without_task_metadata() {
let state = linked_tool_state(
"call_parallel",
"subagents",
"subagents • 2 tasks",
ActivityStatus::Running,
vec![("tasks".to_string(), "2".to_string())],
"STATUS: running\nTASK_COUNT: 2\nTASK_IDS: g1, g2\nsummary: total=2 completed=0 failed=0",
);
let cards = project_cards(&state);
assert_eq!(cards[0].role, TranscriptCardRole::SubagentBatch);
assert!(cards[0].metadata.is_empty());
let subagent_card = cards[0]
.subagent_card
.as_ref()
.expect("subagent batch carries subagent card summary");
assert_eq!(subagent_card.total, 2);
assert_eq!(subagent_card.done, 0);
assert_eq!(subagent_card.failed, 0);
let wide_lines = visual_lines(&state, CARD_LAYOUT_MIN_WIDTH);
let wide_header = line_text(&wide_lines[1].line);
assert!(wide_header.contains("Subagents"), "{wide_header}");
assert!(wide_header.contains("0/2 settled"), "{wide_header}");
assert!(!wide_header.contains("TASK_COUNT"), "{wide_header}");
assert!(!wide_header.contains("Tool:"), "{wide_header}");
let rendered = wide_lines
.iter()
.map(|line| line_text(&line.line))
.collect::<Vec<_>>()
.join("\n");
assert!(!rendered.contains("[view-activity]"), "{rendered}");
assert!(!rendered.contains("TASK_COUNT"), "{rendered}");
assert!(!rendered.contains("TASK_IDS"), "{rendered}");
}
#[test]
fn tool_and_subagent_cards_have_no_activity_affordance() {
let mut state = MissionControlState::default();
state
.transcript
.push_back("tool: ✓ read • done".to_string());
state
.transcript
.push_back("tool: ⟳ subagents • running".to_string());
state.transcript.link_activity(
0,
TranscriptActivityLink {
activity_id: ActivityId::new("call_read"),
tool_name: "read".to_string(),
},
);
state.transcript.link_activity(
1,
TranscriptActivityLink {
activity_id: ActivityId::new("call_parallel"),
tool_name: "subagents".to_string(),
},
);
state.invalidate_transcript_cache();
for width in [20, CARD_LAYOUT_MIN_WIDTH, 80] {
let rendered = visual_lines(&state, width)
.iter()
.map(|line| line_text(&line.line))
.collect::<Vec<_>>()
.join("\n");
assert!(
!rendered.contains("[view-activity]"),
"width {width}: {rendered}"
);
assert!(
!rendered.contains("[activity]"),
"width {width}: {rendered}"
);
}
}
#[test]
fn visual_text_excludes_card_decoration_and_keeps_labels() {
let mut state = MissionControlState::default();
state.apply_output_event(&OutputEvent::UserPrompt {
text: "hello".into(),
});
state.apply_output_event(&OutputEvent::AssistantComplete {
text: "**world**".into(),
});
let text = visual_text(&state);
assert!(text.contains("User: hello"));
assert!(text.contains("Agent\n world"));
assert!(!text.contains('┌'));
assert!(!text.contains('│'));
}
#[test]
fn tui_perf_projected_card_counter_only_updates_when_enabled() {
let mut disabled = MissionControlState::default();
disabled.transcript.push_back("assistant: off".to_string());
let _ = project_cards(&disabled);
assert_eq!(disabled.perf.latest_snapshot(), None);
let mut enabled = MissionControlState {
perf: crate::tui::perf::TuiPerfCounters::with_enabled(true),
..Default::default()
};
enabled.transcript.push_back("assistant: on".to_string());
enabled.perf.reset_projected_cards();
let _ = project_cards(&enabled);
enabled.perf.record_draw_us(1);
let snapshot = enabled.perf.latest_snapshot().unwrap();
assert_eq!(snapshot.projected_cards, 1);
}