258 lines
9.9 KiB
Rust
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"));
|
|
}
|