use super::super::transcript::BlockKind;
use super::support::{busy_stream, empty_app, fixed_status, render_buffer};
#[test]
fn every_block_kind_is_distinguishable_without_colour() {
use super::super::transcript::BlockKind;
let mut app = empty_app();
for (kind, text) in [
(BlockKind::User, "the user request"),
(BlockKind::Assistant, "the assistant answer"),
(BlockKind::Tool, "the tool trail"),
(BlockKind::Table, "the result grid"),
(BlockKind::Error, "the failure"),
(BlockKind::System, "the receipt"),
(BlockKind::Thinking, "the reasoning"),
] {
app.transcript.push(kind, text);
}
let buffer = render_buffer(&app, &fixed_status(), 100, 40);
for (marker, what) in [
("YOU", "a user turn"),
("SAYA", "an assistant turn"),
("ACTIVITY", "the tool trail"),
("RESULT", "a result"),
("✗ the failure", "a failure"),
("· the receipt", "system content"),
("≈ the reasoning", "reasoning"),
] {
assert!(
buffer.contains(marker),
"{what} must be identifiable from symbols alone, found no {marker:?}:\n{buffer}"
);
}
}
#[test]
fn the_action_line_names_the_tool_and_its_target() {
let mut app = empty_app();
app.transcript
.push(BlockKind::User, "count the orders by region");
app.request.stream = Some(busy_stream());
app.request.started = Some(std::time::Instant::now());
app.request.activity = Some("bounded_sql_query".into());
app.transcript.buffer_tool_request(
"bounded_sql_query".into(),
serde_json::json!({"sql": "select region, count(*) from orders"}),
None,
);
assert_eq!(
app.transcript.open_tool_count(),
1,
"the fixture must hold one open call or the bar cannot name its target"
);
assert_eq!(
crate::interactive::tui::stream_events::tool_call_detail(
"bounded_sql_query",
&serde_json::json!({"sql": "select region, count(*) from orders"}),
)
.as_deref(),
Some("select region, count(*) from orders"),
"the shared seam must surface the SQL for this fixture"
);
let buffer = render_buffer(&app, &fixed_status(), 100, 10);
assert!(
buffer.contains("running bounded_sql_query: select region, count(*) from orders"),
"the action line must name the tool and its target:\n{buffer}"
);
}
#[test]
fn an_action_with_no_detail_falls_back_to_the_bare_tool_name() {
let mut app = empty_app();
app.transcript.push(BlockKind::User, "what can you see");
app.request.stream = Some(busy_stream());
app.request.started = Some(std::time::Instant::now());
app.request.activity = Some("schema_discovery".into());
app.transcript
.buffer_tool_request("schema_discovery".into(), serde_json::json!({}), None);
let buffer = render_buffer(&app, &fixed_status(), 100, 10);
assert!(
buffer.contains("running schema_discovery"),
"the bare tool name must render when there is no detail:\n{buffer}"
);
assert!(
!buffer.contains("running schema_discovery:"),
"no colon and no invented target when the detail is None:\n{buffer}"
);
}
#[test]
fn a_long_detail_never_pushes_the_cancel_hint_off_the_bar() {
let mut app = empty_app();
app.transcript.push(BlockKind::User, "count everything");
app.request.stream = Some(busy_stream());
app.request.started = Some(std::time::Instant::now());
app.request.activity = Some("bounded_sql_query".into());
app.transcript.buffer_tool_request(
"bounded_sql_query".into(),
serde_json::json!({"sql": "select a_very_long_column_list from some_table ".repeat(20)}),
None,
);
let buffer = render_buffer(&app, &fixed_status(), 100, 10);
assert!(
buffer.contains("Esc to cancel"),
"a long detail must not push the cancel affordance off the bar:\n{buffer}"
);
assert!(
buffer.contains("…"),
"the over-long detail truncates with an ellipsis rather than overflowing:\n{buffer}"
);
}