use super::*;
#[test]
fn list_files_card_projects_running_modes_and_empty_success() {
let files = list_files_card_state(
"list-running-files",
ActivityStatus::Running,
serde_json::json!({"path":"src"}),
serde_json::json!({}),
"",
);
let card = &project_cards(&files)[0];
assert_eq!(card.title, "List Files");
assert_eq!(card.body, "src");
assert!(card.metadata.is_empty());
let mixed = list_files_card_state(
"list-running-mixed",
ActivityStatus::Running,
serde_json::json!({"path":"src","include_directories":true}),
serde_json::json!({}),
"",
);
assert_eq!(project_cards(&mixed)[0].body, "src\ninclude directories");
let empty = list_files_card_state(
"list-empty",
ActivityStatus::Success,
serde_json::json!({"path":"."}),
serde_json::json!({"files":0,"directories":0}),
"",
);
let card = &project_cards(&empty)[0];
assert_eq!(card.title, "List Files • 0 files");
assert_eq!(card.body, ".\n0 entries");
let unknown_empty = list_files_card_state(
"list-unknown-empty",
ActivityStatus::Success,
serde_json::json!({"path":"unknown"}),
serde_json::json!({}),
"",
);
let card = &project_cards(&unknown_empty)[0];
assert_eq!(card.title, "List Files");
assert_eq!(card.body, "unknown");
let unknown_nonempty = list_files_card_state(
"list-unknown-nonempty",
ActivityStatus::Success,
serde_json::json!({"path":"unknown"}),
serde_json::json!({}),
"visible.txt",
);
let card = &project_cards(&unknown_nonempty)[0];
assert_eq!(card.title, "List Files");
assert_eq!(card.body, "unknown\nvisible.txt");
}
#[test]
fn list_files_card_uses_paths_and_metadata_contract() {
let success = list_files_card_state(
"list-success",
ActivityStatus::Success,
serde_json::json!({"path":"request","include_directories":false}),
serde_json::json!({"path":"resolved","files":2,"directories":1}),
"a.txt\nb.txt\ndir",
);
let card = &project_cards(&success)[0];
assert_eq!(card.title, "List Files • 3 entries");
assert_eq!(card.body, "resolved\na.txt\nb.txt\ndir");
assert!(card.metadata.is_empty());
let metadata_mixed = list_files_card_state(
"list-metadata-mixed",
ActivityStatus::Success,
serde_json::json!({"path":"request"}),
serde_json::json!({"path":"resolved","files":2,"directories":3}),
"file\ndir",
);
assert_eq!(
project_cards(&metadata_mixed)[0].title,
"List Files • 5 entries"
);
let fallback = list_files_card_state(
"list-fallback",
ActivityStatus::Success,
serde_json::json!({"path":"request"}),
serde_json::json!({"files":1,"directories":0}),
"file",
);
let card = &project_cards(&fallback)[0];
assert_eq!(card.title, "List Files • 1 file");
assert_eq!(card.body, "request\nfile");
let default_path = list_files_card_state(
"list-default-path",
ActivityStatus::Success,
serde_json::json!({}),
serde_json::json!({"files":1,"directories":0}),
"file",
);
assert_eq!(project_cards(&default_path)[0].body, ".\nfile");
}
#[test]
fn list_files_card_bounds_preview_truncation_and_untrusted_text() {
let output_secret = "list-files-output-secret";
let path_secret = "list-files-path-secret";
let long_path = format!(
"界/token={path_secret}/{}\u{1b}[31m",
"very-long-path/".repeat(30)
);
let state = list_files_card_state(
"list-bounded",
ActivityStatus::Success,
serde_json::json!({"path":long_path,"include_directories":true}),
serde_json::json!({"files":998,"directories":2,"truncated":true}),
&format!("one\nauthorization: Bearer {output_secret}\u{1b}[31m\nthree\nfour\nfive"),
);
let card = &project_cards(&state)[0];
assert_eq!(card.title, "List Files • 1000 entries returned");
assert!(card.body.contains("… preview limited to 4 lines"));
assert!(card.body.contains("… output capped at 1000 entries"));
assert!(card.body.contains("<redacted>"));
assert!(!card.body.contains("five"));
assert!(!card.body.contains(output_secret));
assert!(!card.body.contains(path_secret));
assert!(!card.body.contains('\u{1b}'));
assert!(
card.body
.lines()
.all(|line| { UnicodeWidthStr::width(line) <= PREVIEW_LINE_MAX_DISPLAY_WIDTH })
);
let unknown_count = list_files_card_state(
"list-truncated-unknown-count",
ActivityStatus::Success,
serde_json::json!({"path":"."}),
serde_json::json!({"truncated":true}),
"visible.txt",
);
let card = &project_cards(&unknown_count)[0];
assert_eq!(card.title, "List Files • entries returned");
assert!(card.body.ends_with("… output capped at 1000 entries"));
}
#[test]
fn list_files_card_failed_and_canceled_paths_are_safe_and_recover() {
let failed = list_files_card_state(
"list-failed",
ActivityStatus::Failed,
serde_json::json!({"path":"params-path"}),
serde_json::json!({"path":"metadata-path"}),
"authorization: Bearer list-files-secret\u{1b}[2J\nsecond line",
);
let card = &project_cards(&failed)[0];
assert_eq!(card.title, "List Files");
assert_eq!(
card.body, "params-path\nauthorization: <redacted> <redacted>",
"body={:?}",
card.body
);
for (status, output) in [
(ActivityStatus::Canceled, "partial output"),
(ActivityStatus::Failed, "prompt canceled"),
] {
let state = list_files_card_state(
&format!("list-canceled-{status:?}"),
status,
serde_json::json!({"path":"cancel-path"}),
serde_json::json!({"outcome":"canceled"}),
output,
);
let card = &project_cards(&state)[0];
assert_eq!(card.status, TranscriptCardStatus::Canceled);
assert_eq!(card.title, "List Files • canceled");
assert_eq!(card.body, "cancel-path\nCanceled before completion");
}
}
#[test]
fn list_files_card_visual_copy_is_bounded_at_tiny_widths() {
let state = list_files_card_state(
"list-visual",
ActivityStatus::Success,
serde_json::json!({"path":"visual"}),
serde_json::json!({"files":1}),
"entry",
);
for width in [1, 2, 3, 12, CARD_LAYOUT_MIN_WIDTH, 80] {
let lines = visual_lines(&state, width);
assert!(
lines.iter().all(|line| {
UnicodeWidthStr::width(line_text(&line.line).as_str()) == usize::from(width)
}),
"width {width}"
);
let copied = visual_text_for_width(&state, width);
assert!(copied.contains("List Files"), "width {width}: {copied:?}");
assert!(copied.contains("visual"), "width {width}: {copied:?}");
assert!(copied.contains("entry"), "width {width}: {copied:?}");
}
let unicode = list_files_card_state(
"list-visual-unicode",
ActivityStatus::Success,
serde_json::json!({"path":"visual/界"}),
serde_json::json!({"files":1}),
"entry-界",
);
let copied = visual_text_for_width(&unicode, 12);
assert!(copied.contains("visual/界"));
assert!(copied.contains("entry-界"));
}