diff --git a/crates/ui-egui/src/io/save.rs b/crates/ui-egui/src/io/save.rs index 6e38e8cc63..10fae638c4 100644 --- a/crates/ui-egui/src/io/save.rs +++ b/crates/ui-egui/src/io/save.rs @@ -263,7 +263,9 @@ pub(super) fn save_current_in(app: &mut VectorcraftApp, p: &Value, folder: Optio return Ok(r); } let retry = super::read_only_save::Retry::new(&first, folder); - write_plan(app, first, ("file.save", p.clone()), Some(retry)) + // Only `path` was asked about: the files written beside it are checked against the file the + // document was read from when the save writes them. + write_plan(app, plan(app, SaveMode::Save, p)?, ("file.save", p.clone()), Some(retry)) } /// File → Save, Save As…, Save a Copy…, Save as Template… and the options dialogs' OK. `p` is the diff --git a/crates/ui-egui/src/tests/save.rs b/crates/ui-egui/src/tests/save.rs index 33e6709913..b9b4111516 100644 --- a/crates/ui-egui/src/tests/save.rs +++ b/crates/ui-egui/src/tests/save.rs @@ -355,6 +355,37 @@ fn a_save_does_not_replace_the_file_read_from_through_a_file_written_beside_the_ let _ = std::fs::remove_dir_all(d); } +#[test] +fn file_save_does_not_replace_the_file_read_from_through_a_file_written_beside_the_path() { + let d = dir("lossy-beside-save"); + let path = d.join("Art.eps").to_string_lossy().to_string(); + let mut app = app(); + checks_files_there(&mut app); + app.run("file.new", json!({"width": 200, "height": 100, "artboards": 2})).unwrap(); + rect(&mut app); + let options = json!({"useArtboards": true, "previewFormat": "none", "thumbnails": false}); + let _ = app.run("file.saveAs", json!({"path": path, "options": options})); + crate::background::wait_all(&mut app); + let siblings = artboard_files(&d); + assert_eq!(siblings.len(), 2, "{siblings:?}"); + let sibling = siblings[0].clone(); + let saved = std::fs::read(&sibling).unwrap(); + std::fs::remove_file(&path).unwrap(); + let st = app.session.doc_mut().unwrap(); + st.imported_from = Some(sibling.clone()); + st.import_losses = vec!["hidden type".into()]; + rect(&mut app); + // File > Save to the document's own file reuses its options, so it writes the artboard files + // again; the one the document was read from is refused, as on Save As. + let _ = app.run("file.save", json!({})); + crate::background::wait_all(&mut app); + assert!(app.ui.dialog.as_ref().is_none_or(|d| d.kind != dialogs::confirm::REPLACE_FILES)); + assert_eq!(std::fs::read(&sibling).unwrap(), saved); + assert!(!std::path::Path::new(&path).exists(), "no file of the set is written when one is refused"); + assert!(app.session.active().unwrap().is_dirty()); + let _ = std::fs::remove_dir_all(d); +} + /// The app checks for files that are there, as the desktop app does. fn checks_files_there(app: &mut VectorcraftApp) { app.services.exists = Some(std::sync::Arc::new(|p: &str| std::path::Path::new(p).exists()));