use super::*;
#[test]
fn grep_card_projects_running_success_zero_and_alias() {
let running = grep_card_state(
"grep-running",
"ffgrep",
ActivityStatus::Running,
serde_json::json!({"patterns":["ToolActivityDetail"],"path":"src/"}),
serde_json::json!({}),
"",
);
let card = &project_cards(&running)[0];
assert_eq!(card.role, TranscriptCardRole::Grep);
assert_eq!(card.title, "Grep");
assert_eq!(card.body, "\"ToolActivityDetail\"\nin src/");
let success = grep_card_state(
"grep-success",
"grep",
ActivityStatus::Success,
serde_json::json!({"patterns":["ToolActivityDetail"],"path":"src/"}),
serde_json::json!({"matches_returned":2,"truncated":false}),
"src/a.rs:1:ToolActivityDetail\nsrc/b.rs:2:ToolActivityDetail",
);
let card = &project_cards(&success)[0];
assert_eq!(card.title, "Grep • 2 matches");
assert_eq!(
card.body,
"\"ToolActivityDetail\"\nin src/\nsrc/a.rs:1:ToolActivityDetail\nsrc/b.rs:2:ToolActivityDetail"
);
let visual = success.transcript_visual_text();
assert!(!visual.contains("Tool:"), "{visual}");
assert!(!visual.contains("STATUS:"), "{visual}");
assert!(!visual.contains("PATTERN:"), "{visual}");
assert!(!visual.contains("OUTPUT:"), "{visual}");
let zero = grep_card_state(
"grep-zero",
"grep",
ActivityStatus::Success,
serde_json::json!({"patterns":["missing"]}),
serde_json::json!({"matches_returned":0,"truncated":false}),
"",
);
let card = &project_cards(&zero)[0];
assert_eq!(card.title, "Grep • 0 matches");
assert_eq!(card.body, "\"missing\"\nin .");
}
#[test]
fn grep_card_displays_multiple_canonical_patterns() {
let state = grep_card_state(
"grep-patterns",
"grep",
ActivityStatus::Running,
serde_json::json!({"patterns":["alpha", "beta"],"path":"src"}),
serde_json::json!({}),
"",
);
let card = &project_cards(&state)[0];
assert_eq!(card.body, "[\"alpha\", \"beta\"]\nin src");
}
#[test]
fn grep_card_projects_failures_timeout_and_truncation_precisely() {
let failed = grep_card_state(
"grep-failed",
"grep",
ActivityStatus::Failed,
serde_json::json!({"patterns":["["],"path":"src"}),
serde_json::json!({}),
"regex parse error: unclosed character class\nbacktrace omitted",
);
let card = &project_cards(&failed)[0];
assert_eq!(card.title, "Grep");
assert_eq!(
card.body,
"\"[\"\nin src\nregex parse error: unclosed character class"
);
let timeout = grep_card_state(
"grep-timeout",
"grep",
ActivityStatus::Failed,
serde_json::json!({"patterns":["needle"],"path":"."}),
serde_json::json!({"timed_out":true}),
"partial output",
);
let card = &project_cards(&timeout)[0];
assert_eq!(card.title, "Grep • timed out");
assert_eq!(card.body, "\"needle\"\nin .\nsearch timed out");
let canceled = grep_card_state(
"grep-canceled",
"grep",
ActivityStatus::Canceled,
serde_json::json!({"patterns":["needle"],"path":"src"}),
serde_json::json!({}),
"partial output",
);
let card = &project_cards(&canceled)[0];
assert_eq!(card.title, "Grep • canceled");
assert_eq!(card.body, "\"needle\"\nin src\ncanceled");
for (reason, message) in [
("matches", "… more matches may exist"),
("file_bytes", "… search incomplete: file size limit"),
(
"read_errors",
"… search incomplete: unreadable files skipped",
),
(
"walk_errors",
"… search incomplete: workspace walk errors or omissions",
),
] {
let truncated = grep_card_state(
"grep-truncated",
"grep",
ActivityStatus::Success,
serde_json::json!({"patterns":["needle"],"path":"src"}),
serde_json::json!({"matches_returned":4,"truncated":true,"limit_reason":reason}),
"a:1:needle\nb:1:needle\nc:1:needle\nd:1:needle\ne:1:needle",
);
let card = &project_cards(&truncated)[0];
assert_eq!(card.title, "Grep • 4 matches returned");
assert!(
card.body.contains("… preview limited to 4 lines"),
"{}",
card.body
);
assert!(card.body.ends_with(message), "{}", card.body);
assert!(!card.body.contains("e:1:needle"), "{}", card.body);
}
}
#[test]
fn grep_card_quotes_pattern_and_reports_independent_truncation_limits() {
let quoted = grep_card_state(
"grep-quoted",
"grep",
ActivityStatus::Running,
serde_json::json!({"patterns":["quote\" and slash\\"],"path":"."}),
serde_json::json!({}),
"",
);
let card = &project_cards("ed)[0];
assert_eq!(card.body, "\"quote\\\" and slash\\\\\"\nin .");
let truncated = grep_card_state(
"grep-multiple-limits",
"grep",
ActivityStatus::Success,
serde_json::json!({"patterns":["needle"],"path":"src"}),
serde_json::json!({
"matches_returned":4,
"truncated":true,
"truncation_reasons":["matches", "file_bytes", "read_errors", "walk_errors"],
"limit_reason":"matches"
}),
"a:1:needle",
);
let card = &project_cards(&truncated)[0];
assert_eq!(card.title, "Grep • 4 matches returned");
assert!(
card.body.ends_with(
"… more matches may exist\n… search incomplete: file size limit\n… search incomplete: unreadable files skipped\n… search incomplete: workspace walk errors or omissions"
)
);
}
#[test]
fn grep_card_sanitizes_pattern_scope_preview_and_handles_tiny_widths() {
let secret = ["pattern", "secret"].join("-");
let bearer = ["output", "secret"].join("-");
let pattern = format!("{}{}\x1b[31m\nforged", "token=", secret);
let output = format!("src/a.rs:1:authorization: {}{}\x1b[2J", "Bearer ", bearer);
let state = grep_card_state(
"grep-safe",
"grep",
ActivityStatus::Success,
serde_json::json!({
"patterns":[pattern],
"path":"src\tsecret\u{1b}]0;hidden\u{7}"
}),
serde_json::json!({"matches_returned":1,"truncated":false}),
&output,
);
let card = &project_cards(&state)[0];
assert_eq!(card.title, "Grep • 1 match");
assert!(card.body.contains("<redacted>"), "{}", card.body);
assert!(card.body.contains("in src\\tsecret"), "{}", card.body);
assert!(!card.body.contains(&secret), "{}", card.body);
assert!(!card.body.contains(&bearer), "{}", card.body);
assert!(!card.body.contains("hidden"), "{}", card.body);
assert!(!card.body.contains('\u{1b}'), "{}", card.body);
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("Grep"), "width {width}: {copied:?}");
assert!(!copied.contains("hidden"), "width {width}: {copied:?}");
}
}