feat: preserve nested source projects across desktop and REST
This commit is contained in:
@@ -165,15 +165,16 @@ impl Document {
|
||||
|
||||
pub fn execute(&mut self, command: Command) -> Result<Option<Id>, DocumentError> {
|
||||
let candidate = prepare_command(self.project(), &command)?;
|
||||
if &candidate.project != self.project() {
|
||||
let compiled = source_project::update_bundle_with_identity_map(
|
||||
&self.current,
|
||||
&candidate.project,
|
||||
&candidate.identity_map,
|
||||
self.limits,
|
||||
)?;
|
||||
self.commit(compiled);
|
||||
}
|
||||
// Equal model values can still carry different record provenance, such
|
||||
// as deleting and recreating an identical record with a reused UUID.
|
||||
// Only the resulting model/source pair determines whether this is a no-op.
|
||||
let compiled = source_project::update_bundle_with_identity_map(
|
||||
&self.current,
|
||||
&candidate.project,
|
||||
&candidate.identity_map,
|
||||
self.limits,
|
||||
)?;
|
||||
self.commit(compiled);
|
||||
Ok(candidate.selected)
|
||||
}
|
||||
|
||||
@@ -369,6 +370,136 @@ mod tests {
|
||||
assert_eq!(document.bundle(), &branch);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nested_cross_file_reparent_and_source_edit_share_exact_history() {
|
||||
let bundle = SourceBundle {
|
||||
entry: "project.arcadia".into(),
|
||||
files: [
|
||||
(
|
||||
"project.arcadia".into(),
|
||||
"arcadia 3; project rescue \"Rescue\" { include \"oa/source.arcadia\"; include \"oa/target.arcadia\"; }\n".into(),
|
||||
),
|
||||
(
|
||||
"oa/source.arcadia".into(),
|
||||
"// Source module\r\noa_activity respond \"Respond\" {\r\n // Assessment rationale follows this activity.\r\n oa_activity assess \"Assess\" {}\r\n}\r\n".into(),
|
||||
),
|
||||
(
|
||||
"oa/target.arcadia".into(),
|
||||
"// Destination module\noa_activity coordinate \"Coordinate\" {}\n".into(),
|
||||
),
|
||||
].into(),
|
||||
};
|
||||
let mut document = Document::from_bundle(bundle, ProjectLimits::default()).unwrap();
|
||||
document.mark_saved(document.snapshot());
|
||||
let original = document.snapshot();
|
||||
let id = |alias: &str| {
|
||||
original
|
||||
.decoded
|
||||
.project
|
||||
.elements
|
||||
.values()
|
||||
.find(|element| element.alias == alias)
|
||||
.unwrap()
|
||||
.id
|
||||
};
|
||||
let child = id("assess");
|
||||
let destination = id("coordinate");
|
||||
document
|
||||
.execute(Command::Reparent {
|
||||
element: child,
|
||||
parent: Some(destination),
|
||||
})
|
||||
.unwrap();
|
||||
let moved = document.snapshot();
|
||||
assert_eq!(
|
||||
document.project().elements[&child].parent,
|
||||
Some(destination)
|
||||
);
|
||||
assert_eq!(
|
||||
document.compiled().index.declarations[&child].path,
|
||||
"oa/target.arcadia"
|
||||
);
|
||||
assert!(
|
||||
document.bundle().files["oa/target.arcadia"]
|
||||
.contains("// Assessment rationale follows this activity.")
|
||||
);
|
||||
assert!(
|
||||
!document.bundle().files["oa/source.arcadia"]
|
||||
.contains("// Assessment rationale follows this activity.")
|
||||
);
|
||||
let edited = comment(document.bundle(), "// Reviewed containment decision.\n");
|
||||
assert!(document.apply_bundle(edited.clone()).unwrap());
|
||||
assert_eq!(document.project(), &moved.decoded.project);
|
||||
assert!(document.undo());
|
||||
assert_eq!(document.bundle(), &moved.bundle);
|
||||
assert!(document.undo());
|
||||
assert_eq!(document.bundle(), &original.bundle);
|
||||
assert_eq!(document.project(), &original.decoded.project);
|
||||
assert!(!document.dirty());
|
||||
assert!(document.redo());
|
||||
assert_eq!(document.bundle(), &moved.bundle);
|
||||
assert!(document.redo());
|
||||
assert_eq!(document.bundle(), &edited);
|
||||
let accepted = document.snapshot();
|
||||
let invalid = comment(document.bundle(), "invalid_declaration;");
|
||||
assert!(document.apply_bundle(invalid).is_err());
|
||||
assert!(Arc::ptr_eq(&accepted, &document.snapshot()));
|
||||
let reopened =
|
||||
Document::from_bundle(document.bundle().clone(), ProjectLimits::default()).unwrap();
|
||||
assert_eq!(reopened.project(), document.project());
|
||||
assert_eq!(reopened.bundle(), document.bundle());
|
||||
assert!(!reopened.dirty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn recreated_equal_record_does_not_inherit_deleted_source_ownership() {
|
||||
let bundle = SourceBundle {
|
||||
entry: "project.arcadia".into(),
|
||||
files: [
|
||||
(
|
||||
"project.arcadia".into(),
|
||||
"arcadia 3; project rescue \"Rescue\" { include \"authored.arcadia\"; }\n"
|
||||
.into(),
|
||||
),
|
||||
(
|
||||
"authored.arcadia".into(),
|
||||
"// Rationale for the original record.\noa_activity assess \"Assess\" {}\n"
|
||||
.into(),
|
||||
),
|
||||
]
|
||||
.into(),
|
||||
};
|
||||
let mut document = Document::from_bundle(bundle, ProjectLimits::default()).unwrap();
|
||||
document.mark_saved(document.snapshot());
|
||||
let before = document.snapshot();
|
||||
let element = document.project().elements.values().next().unwrap().clone();
|
||||
let id = element.id;
|
||||
document
|
||||
.execute(Command::Batch(vec![
|
||||
Command::Delete(id),
|
||||
Command::CreateElement(element),
|
||||
]))
|
||||
.unwrap();
|
||||
assert_eq!(document.project(), &before.decoded.project);
|
||||
assert_ne!(
|
||||
document.compiled().index.declarations[&id].path,
|
||||
"authored.arcadia"
|
||||
);
|
||||
assert!(
|
||||
document.bundle().files["authored.arcadia"]
|
||||
.contains("// Rationale for the original record.")
|
||||
);
|
||||
assert!(!document.bundle().files["authored.arcadia"].contains("oa_activity"));
|
||||
assert!(document.dirty());
|
||||
assert_eq!(document.revision(), 1);
|
||||
let recreated = document.snapshot();
|
||||
assert!(document.undo());
|
||||
assert_eq!(document.bundle(), &before.bundle);
|
||||
assert!(!document.dirty());
|
||||
assert!(document.redo());
|
||||
assert_eq!(document.bundle(), &recreated.bundle);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn asynchronous_save_marks_its_snapshot_without_cleaning_later_edits() {
|
||||
let mut document = document();
|
||||
|
||||
Reference in New Issue
Block a user