Files
ARCADIA-Studio/crates/oa-desktop/src/app/save_as_tests.rs
T

258 lines
9.9 KiB
Rust

use super::*;
fn saved_multifile_app(directory: &std::path::Path) -> (App, Id, PathBuf) {
let mut project = Project::empty();
let activity = project
.add_element(Kind::Activity, Layer::Oa, "Assess incident", None)
.unwrap();
let mut app = App::with_project(project);
let path = directory.join("project.arcadia");
complete_save(&mut app, path.clone());
assert!(app.document.bundle().files.len() > 1);
(app, activity, path)
}
fn rejected_save_preserves_editor(app: &mut App, path: PathBuf) -> String {
let snapshot = app.document.snapshot();
let revision = app.document.revision();
let undo = app.document.can_undo();
let redo = app.document.can_redo();
let draft = app.source_workspace.text().to_owned();
let original_path = app.path.clone();
let dirty = app.dirty();
let result = write_snapshot(
app.canvas_document,
revision,
snapshot.clone(),
path,
app.disk_project.clone(),
);
let error = match result {
Err(error) => error,
Ok(_) => panic!("Expected collision refusal"),
};
let _ = app.update(Message::Saved(Err(error.clone())));
assert!(Arc::ptr_eq(&snapshot, &app.document.snapshot()));
assert_eq!(app.document.revision(), revision);
assert_eq!(app.document.can_undo(), undo);
assert_eq!(app.document.can_redo(), redo);
assert_eq!(app.source_workspace.text(), draft);
assert_eq!(app.path, original_path);
assert_eq!(app.dirty(), dirty);
error.message
}
#[test]
fn same_root_new_entry_collision_preserves_files_draft_and_history() {
let directory = tempfile::tempdir().unwrap();
let (mut app, activity, original) = saved_multifile_app(directory.path());
let bytes: Vec<_> = app
.document
.bundle()
.files
.keys()
.map(|p| (p.clone(), std::fs::read(directory.path().join(p)).unwrap()))
.collect();
app.execute(Command::Rename(activity, "Review incident".into()));
app.execute(Command::Rename(activity, "Evaluate incident".into()));
let _ = app.update(Message::Undo);
assert!(app.document.can_undo() && app.document.can_redo());
let cursor = app.source_workspace.files[&app.source_workspace.entry].cursor;
app.source_workspace.edit(
format!("{}\n// retained draft", app.source_workspace.text()),
cursor,
);
let destination = directory.path().join("renamed.arcadia");
let error = rejected_save_preserves_editor(&mut app, destination.clone());
assert!(error.contains("already exist") && error.contains("Save All"));
assert!(!error.contains("changed outside"));
assert!(!destination.exists());
assert_eq!(app.path, Some(original));
for (path, expected) in bytes {
assert_eq!(
std::fs::read(directory.path().join(path)).unwrap(),
expected
);
}
}
#[test]
fn fresh_root_existing_include_is_protected_before_any_bundle_write() {
let original = tempfile::tempdir().unwrap();
let destination = tempfile::tempdir().unwrap();
let (mut app, activity, _) = saved_multifile_app(original.path());
app.execute(Command::Rename(activity, "Review incident".into()));
let fragment = app.document.compiled().index.declarations[&activity]
.path
.clone();
let collision = destination.path().join(&fragment);
std::fs::create_dir_all(collision.parent().unwrap()).unwrap();
std::fs::write(&collision, b"// independent destination work").unwrap();
let target = destination.path().join("copy.arcadia");
let error = rejected_save_preserves_editor(&mut app, target.clone());
assert!(error.contains(&fragment) && error.contains("fresh directory"));
assert!(!error.contains("changed outside"));
assert!(!target.exists());
for path in app.document.bundle().files.keys() {
let file = destination.path().join(path);
if path == &fragment {
assert_eq!(
std::fs::read(file).unwrap(),
b"// independent destination work"
);
} else {
assert!(!file.exists(), "Unexpected write: {path}");
}
}
}
#[test]
fn observed_dependency_change_remains_external_conflict_and_normal_saves_work() {
let original = tempfile::tempdir().unwrap();
let fresh = tempfile::tempdir().unwrap();
let (mut app, activity, path) = saved_multifile_app(original.path());
app.execute(Command::Rename(activity, "Review incident".into()));
complete_save(&mut app, path.clone());
assert!(!app.dirty());
let fragment = app.document.compiled().index.declarations[&activity]
.path
.clone();
let entry_before = std::fs::read(&path).unwrap();
let original_fragment = std::fs::read(original.path().join(&fragment)).unwrap();
std::fs::write(original.path().join(&fragment), b"// external edit").unwrap();
app.execute(Command::Rename(activity, "Evaluate incident".into()));
let error = rejected_save_preserves_editor(&mut app, path.clone());
assert!(error.contains("changed outside") && error.contains(&fragment));
assert!(!error.contains("fresh directory"));
assert_eq!(std::fs::read(&path).unwrap(), entry_before);
assert_eq!(
std::fs::read(original.path().join(&fragment)).unwrap(),
b"// external edit"
);
// Restore the observed bytes, then exercise the legitimate existing Save All.
std::fs::write(original.path().join(&fragment), original_fragment).unwrap();
complete_save(&mut app, path);
assert!(!app.dirty());
let model = app.project().clone();
let target = fresh.path().join("copy.arcadia");
complete_save(&mut app, target.clone());
let saved = source_files::load_project(&target, LIMITS).unwrap();
let compiled = source_project::compile_bundle(&saved.bundle, LIMITS).unwrap();
assert_eq!(compiled.decoded.project, model);
assert_eq!(&saved.bundle, app.document.bundle());
assert!(!app.dirty());
}
fn complete_save(app: &mut App, path: PathBuf) {
let receipt = write_snapshot(
app.canvas_document,
app.document.revision(),
app.document.snapshot(),
path,
app.disk_project.clone(),
)
.unwrap();
let _ = app.update(Message::Saved(Ok(Some(receipt))));
}
#[test]
fn save_as_receipt_then_one_undo_restores_meaning_and_saving_preserves_redo() {
let directory = tempfile::tempdir().unwrap();
let path = directory.path().join("semantic-accepted.arcadia");
let mut app = App::with_project(Project::empty());
app.document.mark_recovered();
let original = app.document.bundle().files[&app.document.bundle().entry].clone();
app.execute(Command::Rename(
app.project().id,
"Accepted semantic change".into(),
));
let after = app.document.bundle().files[&app.document.bundle().entry].clone();
complete_save(&mut app, path.clone());
assert!(!app.dirty());
assert_eq!(app.path.as_ref(), Some(&path));
assert_eq!(std::fs::read_to_string(&path).unwrap(), after);
let _ = app.update(Message::Undo);
assert_eq!(app.document.bundle().entry, "semantic-accepted.arcadia");
assert_eq!(
app.document.bundle().files[&app.document.bundle().entry],
original
);
assert!(
!app.document.can_undo(),
"Save as did not insert a history step"
);
assert!(app.document.can_redo());
complete_save(&mut app, path.clone());
assert_eq!(std::fs::read_to_string(&path).unwrap(), original);
assert!(
app.document.can_redo(),
"Saving an undone state preserves the future"
);
let _ = app.update(Message::Redo);
assert_eq!(
app.document.bundle().files[&app.document.bundle().entry],
after
);
assert!(app.dirty());
assert_eq!(
source_project::compile_bundle(app.document.bundle(), LIMITS)
.unwrap()
.decoded
.project,
*app.project()
);
}
#[test]
fn save_as_preflight_rejects_historical_collision_before_starting_io() {
let directory = tempfile::tempdir().unwrap();
let path = directory.path().join("taken.arcadia");
let mut app = App::with_project(Project::empty());
let base = app.document.bundle().clone();
let mut included = base.clone();
let text = included.files.get_mut(&included.entry).unwrap();
text.insert_str(text.rfind('}').unwrap(), "\n include \"taken.arcadia\";\n");
included
.files
.insert("taken.arcadia".into(), "// retained\n".into());
app.document.apply_bundle(included).unwrap();
app.document.apply_bundle(base).unwrap();
app.sync_source();
let before = app.document.snapshot();
let task = app.save_to(path.clone());
assert!(iced_runtime::task::into_stream(task).is_none());
assert!(!path.exists());
assert!(!app.busy);
assert!(app.status.contains("Undo/Redo"));
assert!(Arc::ptr_eq(&before, &app.document.snapshot()));
let json = app.save_to(directory.path().join("taken.json"));
assert!(
iced_runtime::task::into_stream(json).is_some(),
"JSON exports do not relocate source entries"
);
}
#[test]
fn save_as_late_receipt_preserves_newer_raw_source_draft_and_destination() {
let directory = tempfile::tempdir().unwrap();
let mut app = App::with_project(Project::empty());
let entry = app.document.bundle().entry.clone();
let receipt = write_snapshot(
app.canvas_document,
app.document.revision(),
app.document.snapshot(),
directory.path().join("accepted.arcadia"),
None,
)
.unwrap();
let raw = format!("{}\nunfinished {{", app.source_workspace.text());
let cursor = app.source_workspace.files[&entry].cursor;
app.source_workspace.edit(raw.clone(), cursor);
let _ = app.update(Message::Saved(Ok(Some(receipt))));
assert_eq!(app.source_workspace.text(), raw);
assert_eq!(app.document.bundle().entry, entry);
assert!(app.path.is_none());
assert!(app.dirty());
assert!(app.status.contains("Newer edits"));
}