diff --git a/crates/ui-egui/src/tests_place.rs b/crates/ui-egui/src/tests_place.rs index 6e1190d4f..5577bd763 100644 --- a/crates/ui-egui/src/tests_place.rs +++ b/crates/ui-egui/src/tests_place.rs @@ -55,13 +55,33 @@ impl egui::DroppedFile for Dropped { } } +#[derive(Debug)] +struct MockDrop { + path: std::path::PathBuf, + bytes: Result, String>, +} + +impl egui::DroppedFile for MockDrop { + fn path(&self) -> &std::path::Path { + &self.path + } + fn bytes(&self) -> Result, String> { + self.bytes.clone() + } +} + /// One headless frame of the whole window (800×600) with `events` and `dropped` files. -fn frame(app: &mut VectorcraftApp, ctx: &egui::Context, mut events: Vec, dropped: &[&str], shift: bool) { +fn frame(app: &mut VectorcraftApp, ctx: &egui::Context, events: Vec, dropped: &[&str], shift: bool) { + let files = dropped.iter().map(|p| Arc::new(Dropped(p.into())) as egui::DroppedFileHandle).collect(); + frame_files(app, ctx, events, files, shift); +} + +fn frame_files(app: &mut VectorcraftApp, ctx: &egui::Context, mut events: Vec, dropped: Vec, shift: bool) { events.push(egui::Event::ModifiersChanged(egui::Modifiers { shift, ..Default::default() })); let raw = egui::RawInput { screen_rect: Some(egui::Rect::from_min_size(Pos2::ZERO, vec2(800.0, 600.0))), events, - dropped_files: dropped.iter().map(|p| Arc::new(Dropped(p.into())) as egui::DroppedFileHandle).collect(), + dropped_files: dropped, ..Default::default() }; let mut out = ctx.run_ui(raw, |ui| { @@ -79,7 +99,7 @@ fn selected_image(app: &VectorcraftApp) -> vectorcraft_doc::Node { } /// A tiny SVG document. -const SVG: &str = r#""#; +const SVG: &str = r#""#; /// Two frames: fonts, then the canvas lays out → the canvas. fn laid_out(app: &mut VectorcraftApp, ctx: &egui::Context) -> egui::Rect { @@ -157,6 +177,84 @@ fn a_dropped_document_opens_as_a_tab_of_its_own() { assert_eq!(app.ui.recent_files.first(), Some(&svg)); } +#[test] +fn a_native_svg_drop_uses_file_open_even_with_a_path_only_handle() { + let path = temp_file("path-only.svg", SVG.as_bytes()); + let mut dropped = app(); + let ctx = egui::Context::default(); + laid_out(&mut dropped, &ctx); + let handle = Arc::new(MockDrop { path: path.clone().into(), bytes: Err("only a path".into()) }) as egui::DroppedFileHandle; + frame_files(&mut dropped, &ctx, vec![], vec![handle], false); + + let mut opened = app(); + opened.run("file.open", json!({ "path": path })).unwrap(); + let anchors = |app: &VectorcraftApp| { + let mut count = 0; + app.session.active().unwrap().doc.walk(|n| { + if let NodeKind::Path { path, .. } = &n.kind { + count += path.anchors().count(); + } + }); + count + }; + assert_eq!(anchors(&dropped), anchors(&opened), "same editable SVG anchors as File > Open"); + assert!(anchors(&dropped) >= 3, "the imported path has editable anchors"); + assert_eq!(dropped.session.active().unwrap().path, opened.session.active().unwrap().path); + assert_eq!(dropped.session.active().unwrap().selection.objects.len(), opened.session.active().unwrap().selection.objects.len()); + assert_eq!(dropped.ui.recent_files, opened.ui.recent_files); +} + +#[test] +fn an_svg_drop_without_a_path_is_detected_from_its_bytes() { + let mut app = app(); + let ctx = egui::Context::default(); + laid_out(&mut app, &ctx); + let svg = Arc::new(MockDrop { path: Default::default(), bytes: Ok(SVG.as_bytes().to_vec()) }) as egui::DroppedFileHandle; + frame_files(&mut app, &ctx, vec![], vec![svg], false); + assert_eq!(app.session.documents().len(), 2, "SVG bytes open as a document, not placed art"); + assert!(app.session.active().unwrap().path.is_none(), "no filesystem path was supplied"); + + frame(&mut app, &ctx, vec![], &[], false); + let image = Arc::new(MockDrop { path: Default::default(), bytes: Ok(png(6, 6, 72.0)) }) as egui::DroppedFileHandle; + frame_files(&mut app, &ctx, vec![], vec![image], false); + assert_eq!(app.session.documents().len(), 2, "an image is placed, not opened"); + assert_eq!(images(&app, 1), 1); +} + +#[test] +fn multiple_svg_drops_open_individually() { + let mut app = app(); + let ctx = egui::Context::default(); + laid_out(&mut app, &ctx); + let a = temp_file("first.svg", SVG.as_bytes()); + let b = temp_file("second.svg", SVG.as_bytes()); + frame(&mut app, &ctx, vec![], &[&a, &b], false); + assert_eq!(app.session.documents().len(), 3); + assert_eq!(app.session.active().unwrap().path.as_deref(), Some(b.as_str())); + assert_eq!(&app.ui.recent_files[..2], &[b, a]); +} + +#[test] +fn unsupported_and_unreadable_drops_leave_the_document_open() { + let mut app = app(); + let ctx = egui::Context::default(); + laid_out(&mut app, &ctx); + let invalid = temp_file("not-art.xyz", b"not an image or document"); + frame(&mut app, &ctx, vec![], &[&invalid], false); + assert!(app.ui.status.contains("Couldn't place not-art.xyz"), "{}", app.ui.status); + + let invalid_svg = temp_file("broken.svg", b"