use crate::tui::app::answer_placement::{
answer_already_on_screen, answer_extends_row, last_text_precedes_tool_group,
};
use crate::tui::app::{DisplayMessage, ToolCallEntry, ToolCallGroup};
use uuid::Uuid;
fn msg(role: &str, content: &str) -> DisplayMessage {
DisplayMessage {
id: Uuid::new_v4(),
role: role.to_string(),
content: content.to_string(),
timestamp: chrono::Utc::now(),
token_count: None,
cost: None,
approval: None,
approve_menu: None,
details: None,
expanded: false,
expanded_full: false,
tool_group: None,
duration_secs: None,
}
}
fn group_msg() -> DisplayMessage {
let mut m = msg("tool_group", "1 tool call");
m.tool_group = Some(ToolCallGroup {
calls: vec![ToolCallEntry {
task_id: None,
description: "suggest options".to_string(),
success: true,
details: None,
completed: true,
tool_input: serde_json::Value::Null,
}],
expanded: false,
});
m
}
#[test]
fn text_then_tool_group_is_stale_so_answer_must_append() {
let msgs = vec![
msg("user", "hi"),
msg("assistant", "Let me check"),
group_msg(),
];
assert!(last_text_precedes_tool_group(&msgs));
}
#[test]
fn text_after_the_last_tool_group_may_be_overwritten() {
let msgs = vec![
msg("user", "hi"),
group_msg(),
msg("assistant", "Here is the answer"),
];
assert!(!last_text_precedes_tool_group(&msgs));
}
#[test]
fn no_tool_group_is_never_stale() {
let msgs = vec![msg("user", "hi"), msg("assistant", "Hello")];
assert!(!last_text_precedes_tool_group(&msgs));
}
#[test]
fn no_assistant_row_is_never_stale() {
let msgs = vec![msg("user", "hi"), group_msg()];
assert!(!last_text_precedes_tool_group(&msgs));
}
#[test]
fn text_above_the_last_tool_group_that_the_response_repeats_is_already_on_screen() {
let answer = "Nothing was installed, built, or run.";
let msgs = vec![
msg("user", "did you run it"),
msg("assistant", answer),
group_msg(),
];
assert!(answer_already_on_screen(&msgs, &format!("{answer}\n")));
}
#[test]
fn identical_text_from_an_earlier_turn_is_a_new_answer() {
let answer = "Nothing was installed, built, or run.";
let msgs = vec![
msg("user", "did you run it"),
msg("assistant", answer),
msg("user", "again"),
group_msg(),
];
assert!(!answer_already_on_screen(&msgs, answer));
}
#[test]
fn different_text_after_tool_groups_is_not_already_on_screen() {
let msgs = vec![
msg("user", "check it"),
msg("assistant", "Let me check"),
group_msg(),
];
assert!(!answer_already_on_screen(&msgs, "Here is the answer"));
}
#[test]
fn no_assistant_row_is_never_already_on_screen() {
let msgs = vec![msg("user", "hi"), group_msg()];
assert!(!answer_already_on_screen(&msgs, "hello"));
}
#[test]
fn a_reasoning_only_row_between_text_and_tools_does_not_defeat_the_dedupe() {
let answer = "Straight answer, with receipts.";
let mut thinking = msg("assistant", "");
thinking.details = Some("weighing the two candidate causes".to_string());
let msgs = vec![
msg("user", "why did it vanish"),
msg("assistant", answer),
thinking,
group_msg(),
];
assert!(answer_already_on_screen(&msgs, answer));
}
#[test]
fn a_report_is_never_replaced_by_a_later_shorter_line() {
let report = "Done. One gate, one commit.\n\n| Check | Result |\n|---|---|\n| fmt | 0 |\n\nUnpushed on your word.";
let closing = "Checking the receipts...";
assert!(!answer_extends_row(report, closing));
}
#[test]
fn the_same_text_and_an_extension_may_replace_the_row() {
assert!(answer_extends_row(
"Here is the answer",
"Here is the answer"
));
assert!(answer_extends_row("Here is", "Here is the answer"));
assert!(!answer_extends_row("", "Here is the answer"));
assert!(!answer_extends_row("Here is the answer", ""));
}