use std::path::PathBuf;
use crate::tui::app::App;
use crate::tui::app::dropped_path::{self, Dropped};
struct Fixture {
dir: PathBuf,
path: String,
}
impl Fixture {
fn new(tag: &str, name: &str) -> Self {
let dir = std::env::temp_dir().join(format!("oc-drop-{tag}-{}", std::process::id()));
std::fs::create_dir_all(&dir).expect("temp dir");
let path = dir.join(name);
std::fs::write(&path, b"\x89PNG\r\n\x1a\n").expect("fixture write");
Self {
dir,
path: path.to_string_lossy().to_string(),
}
}
}
impl Drop for Fixture {
fn drop(&mut self) {
let _ = std::fs::remove_dir_all(&self.dir);
}
}
#[test]
fn test_a_spaced_path_followed_by_prose_still_attaches() {
let f = Fixture::new("prose", "Screenshot 2026-09-01 at 18.18.16.png");
let msg = format!("{} can you see this image by any chance", f.path);
let e = App::extract_attachments(&msg);
let (clean, atts) = (e.text, e.attachments);
assert_eq!(
atts.len(),
1,
"#1288: this is the reported case and it attached nothing"
);
assert_eq!(atts[0].path, f.path);
assert!(!atts[0].is_video);
assert!(
clean.contains("can you see this image"),
"the question must survive: {clean:?}"
);
assert!(
!clean.contains(".png"),
"the path must be consumed, not left as prose: {clean:?}"
);
}
#[test]
fn test_the_shell_escaped_form_attaches_too() {
let f = Fixture::new("escaped", "My Shot 3.png");
let msg = format!("{} what is in it", f.path.replace(' ', "\\ "));
let e = App::extract_attachments(&msg);
let (_clean, atts) = (e.text, e.attachments);
assert_eq!(atts.len(), 1, "#1288: escaped drop with prose must attach");
assert_eq!(atts[0].path, f.path);
}
#[test]
fn test_longest_match_wins_over_a_trailing_fragment() {
let f = Fixture::new("longest", "My File.png");
let msg = format!("look at {} please", f.path);
let e = App::extract_attachments(&msg);
let (_clean, atts) = (e.text, e.attachments);
assert_eq!(atts.len(), 1);
assert_eq!(
atts[0].path, f.path,
"must resolve the full path, not the fragment after the space"
);
}
#[test]
fn test_a_path_from_another_machine_is_marked_not_forwarded() {
let msg = "/Users/someone/Downloads/Screenshot 2026-09-01 at 18.18.16.png \
can you see this image";
let e = App::extract_attachments(msg);
let (clean, atts) = (e.text, e.attachments);
assert!(atts.is_empty(), "nothing to attach: {atts:?}");
assert!(
clean.contains("[attachment unavailable:"),
"#1288: the message must say the path is unreachable: {clean:?}"
);
assert!(
!clean.contains("Screenshot 2026-09-01 at 18.18.16.png can you"),
"the raw path must not simply be forwarded as prose: {clean:?}"
);
assert!(
clean.contains("can you see this image"),
"the question still stands: {clean:?}"
);
}
#[test]
fn test_a_space_free_path_and_a_url_still_work() {
let f = Fixture::new("plain", "nospace.png");
let e = App::extract_attachments(&format!("{} describe it", f.path));
let (clean, atts) = (e.text, e.attachments);
assert_eq!(atts.len(), 1);
assert_eq!(atts[0].path, f.path);
assert!(clean.contains("describe it"));
let e = App::extract_attachments("https://example.com/pic.png what is this");
let (_c, atts) = (e.text, e.attachments);
assert_eq!(atts.len(), 1, "URLs are still picked up");
assert_eq!(atts[0].path, "https://example.com/pic.png");
}
#[test]
fn test_prose_alone_is_untouched() {
let msg = "can you look at the png file I mentioned earlier";
let e = App::extract_attachments(msg);
let (clean, atts) = (e.text, e.attachments);
assert!(atts.is_empty());
assert_eq!(clean.trim(), msg, "ordinary prose must pass through");
}
#[test]
fn test_a_bare_filename_is_not_treated_as_a_drop() {
assert_eq!(dropped_path::find("look at File.png now", &[".png"]), None);
}
#[test]
fn test_extension_must_end_at_a_word_boundary() {
assert_eq!(dropped_path::find("/a/b/c.pngfoo bar", &[".png"]), None);
}
#[test]
fn test_video_drops_are_flagged_as_video() {
let f = Fixture::new("video", "Clip 1.mp4");
let e = App::extract_attachments(&format!("{} summarise it", f.path));
let (_clean, atts) = (e.text, e.attachments);
assert_eq!(atts.len(), 1);
assert!(atts[0].is_video, "#1288: a dropped video must stay a video");
}
#[test]
fn test_find_reports_the_range_it_consumed() {
let msg = "/nope/Some File.png tail";
match dropped_path::find(msg, &[".png"]) {
Some(Dropped::Elsewhere { start, end, path }) => {
assert_eq!(&msg[start..end], "/nope/Some File.png");
assert_eq!(path, "/nope/Some File.png");
}
other => panic!("expected an unreachable absolute path, got {other:?}"),
}
}