use super::*;
#[test]
fn find_card_projects_lifecycle_counts_kinds_and_alias() {
let running = find_card_state(
"find-running",
"fffind",
ActivityStatus::Running,
serde_json::json!({"query":"card"}),
serde_json::json!({}),
"",
);
let card = &project_cards(&running)[0];
assert_eq!(card.role, TranscriptCardRole::Find);
assert_eq!(card.title, "Find");
assert_eq!(card.body, "\"card\"\nfiles in .");
let one = find_card_state(
"find-one",
"find",
ActivityStatus::Success,
serde_json::json!({"query":" card ","path":"src","kind":"mixed"}),
serde_json::json!({"query":"card","matches_returned":1,"total_matched":1,"truncated":false,"index_ready":true}),
"src/tui/transcript_cards/",
);
let card = &project_cards(&one)[0];
assert_eq!(card.title, "Find • 1 path");
assert_eq!(
card.body,
"\"card\"\nfiles and directories in src\nsrc/tui/transcript_cards/"
);
let zero = find_card_state(
"find-zero",
"find",
ActivityStatus::Success,
serde_json::json!({"query":"missing"}),
serde_json::json!({"matches_returned":0,"total_matched":0,"truncated":false,"index_ready":true}),
"",
);
assert_eq!(project_cards(&zero)[0].title, "Find • 0 paths");
let secret = ["find", "failure", "secret"].join("-");
let failed = find_card_state(
"find-failed",
"find",
ActivityStatus::Failed,
serde_json::json!({"query":"card"}),
serde_json::json!({}),
&format!("authorization: Bearer {secret}\u{1b}[2J\ntrace omitted"),
);
let card = &project_cards(&failed)[0];
assert_eq!(card.title, "Find");
assert!(card.body.contains("<redacted>"), "{}", card.body);
assert!(!card.body.contains(&secret));
assert!(!card.body.contains("trace omitted"));
assert!(!card.body.contains('\u{1b}'));
let canceled = find_card_state(
"find-canceled",
"find",
ActivityStatus::Canceled,
serde_json::json!({"query":"card","kind":"directories"}),
serde_json::json!({}),
"partial output",
);
let card = &project_cards(&canceled)[0];
assert_eq!(card.title, "Find • canceled");
assert_eq!(card.body, "\"card\"\ndirectories in .\ncanceled");
}
#[test]
fn find_card_projects_bounded_preview_and_pagination() {
let state = find_card_state(
"find-page",
"find",
ActivityStatus::Success,
serde_json::json!({"query":"card", "path":"src", "kind":"directories", "offset":2, "limit":5}),
serde_json::json!({"matches_returned":5,"total_matched":8,"truncated":true,"index_ready":false}),
"src/a/\nsrc/b/\nsrc/c/\nsrc/d/\nsrc/e/",
);
let card = &project_cards(&state)[0];
assert_eq!(card.title, "Find • 5 paths returned");
assert_eq!(
card.body,
"\"card\"\ndirectories in src\noffset 2 · limit 5\nsrc/a/\nsrc/b/\nsrc/c/\nsrc/d/\n… preview limited to 4 paths\n… showing 3–7 of 8 paths\n… index not ready; results may be incomplete"
);
assert!(!card.body.contains("src/e/"));
let unknown_total = find_card_state(
"find-truncated",
"find",
ActivityStatus::Success,
serde_json::json!({"query":"card"}),
serde_json::json!({"matches_returned":1,"truncated":true}),
"src/card.rs",
);
assert!(
project_cards(&unknown_total)[0]
.body
.ends_with("… more paths may exist")
);
let beyond_end = find_card_state(
"find-beyond-end",
"find",
ActivityStatus::Success,
serde_json::json!({"query":"card","offset":usize::MAX,"limit":2}),
serde_json::json!({"matches_returned":0,"total_matched":3,"truncated":false}),
"",
);
assert!(
project_cards(&beyond_end)[0]
.body
.ends_with("… showing 0 of 3 paths")
);
}
#[test]
fn find_card_sanitizes_and_preserves_copy_at_tiny_widths() {
let query_secret = ["find", "query", "secret"].join("-");
let output_secret = ["find", "output", "secret"].join("-");
let query = format!("quote\\\" slash\\\\ token={query_secret}\u{1b}[31m\nforged");
let output = format!(" dir/\tname/ authorization: Bearer {output_secret}\u{1b}[2J");
let state = find_card_state(
"find-safe",
"find",
ActivityStatus::Success,
serde_json::json!({
"query":query,
"path":" root\tname\u{1b}]0;hidden\u{7} ",
"kind":"mixed"
}),
serde_json::json!({"matches_returned":1,"total_matched":1,"truncated":false,"index_ready":true}),
&output,
);
let card = &project_cards(&state)[0];
assert_eq!(card.title, "Find • 1 path");
let expected_query =
serde_json::to_string(&crate::tui::transcript::sanitize_preview(&query)).unwrap();
assert!(card.body.starts_with(&expected_query), "{}", card.body);
assert!(card.body.contains("files and directories in root\\tname"));
assert!(card.body.contains(" dir/\\tname/"));
assert!(card.body.contains("<redacted>"));
assert!(!card.body.contains(&query_secret));
assert!(!card.body.contains(&output_secret));
assert!(!card.body.contains("hidden"));
assert!(!card.body.contains('\u{1b}'));
let visual = state.transcript_visual_text();
for envelope in ["Tool:", "STATUS:", "QUERY:", "PATH:", "OUTPUT:"] {
assert!(!visual.contains(envelope), "{visual}");
}
for width in [1, 2, 3, 12, CARD_LAYOUT_MIN_WIDTH, 80] {
let lines = visual_lines(&state, width);
assert!(!lines.is_empty());
assert!(lines.iter().all(|line| {
UnicodeWidthStr::width(line_text(&line.line).as_str()) == usize::from(width)
}));
let copied = visual_text_for_width(&state, width);
assert!(copied.contains("Find"), "width {width}: {copied:?}");
assert!(copied.contains("files and directories"));
assert!(!copied.contains(&query_secret));
assert!(!copied.contains(&output_secret));
assert!(!copied.contains("hidden"));
}
}
#[test]
fn noncanonical_ast_grep_name_keeps_generic_card() {
let state = linked_tool_state(
"ast-grep-generic",
"ast-grep",
"ast-grep",
ActivityStatus::Success,
Vec::new(),
"src/lib.rs:1:match",
);
let card = &project_cards(&state)[0];
assert_eq!(card.role, TranscriptCardRole::Tool);
assert!(state.transcript_visual_text().contains("✓ Tool • ast-grep"));
}