Files
ARCADIA-Studio/crates/oa-core/tests/prepared_commands.rs
T

297 lines
9.3 KiB
Rust

use oa_core::*;
use std::collections::{BTreeMap, BTreeSet};
fn pair() -> (Project, Id, Id) {
let mut project = Project::empty();
let first = project
.add_element(Kind::Activity, Layer::Oa, "First", None)
.unwrap();
let second = project
.add_element(Kind::Activity, Layer::Oa, "Second", None)
.unwrap();
(project, first, second)
}
fn identities(project: &Project) -> BTreeSet<Id> {
project.identities().into_iter().map(|(id, _)| id).collect()
}
fn valid_map(original: &Project, prepared: &PreparedCommand) {
let origins = identities(original);
let present = identities(&prepared.project);
let targets: BTreeSet<_> = prepared.identity_map.values().copied().collect();
assert_eq!(
targets.len(),
prepared.identity_map.len(),
"correspondence is injective"
);
assert!(targets.is_subset(&present));
assert!(prepared.identity_map.keys().all(|id| origins.contains(id)));
}
#[test]
fn explicit_chains_and_nested_swaps_compose_original_identity() {
let (project, first, second) = pair();
let temporary = Id::new_v4();
let final_id = Id::new_v4();
let chained = prepare_command(
&project,
&Command::Batch(vec![
Command::Reidentify(first, temporary),
Command::Batch(vec![Command::Reidentify(temporary, final_id)]),
]),
)
.unwrap();
assert_eq!(
chained.identity_map,
BTreeMap::from([
(project.id, project.id),
(first, final_id),
(second, second)
])
);
assert_eq!(chained.selected, Some(final_id));
valid_map(&project, &chained);
let swapped = prepare_command(
&project,
&Command::Batch(vec![
Command::Reidentify(first, temporary),
Command::Batch(vec![
Command::Reidentify(second, first),
Command::Reidentify(temporary, second),
]),
]),
)
.unwrap();
assert_eq!(
swapped.identity_map,
BTreeMap::from([(project.id, project.id), (first, second), (second, first)])
);
assert_eq!(swapped.project.elements[&second].alias, "first");
assert_eq!(swapped.project.elements[&first].alias, "second");
valid_map(&project, &swapped);
}
#[test]
fn all_edit_commands_record_their_implicit_identity_change() {
let project = Project::demo();
let new = Id::new_v4();
let mut element = project.elements.values().next().unwrap().clone();
let element_id = element.id;
element.id = new;
element.alias = "edited_identity_and_alias".into();
let mut relation = project.relations.values().next().unwrap().clone();
let relation_id = relation.id;
relation.id = new;
let mut diagram = project.diagrams.values().next().unwrap().clone();
let diagram_id = diagram.id;
let mut node = diagram.nodes.values().next().unwrap().clone();
let node_id = node.id;
node.id = new;
diagram.id = new;
let mut artifact = project.artifact_links.values().next().unwrap().clone();
let artifact_id = artifact.id;
artifact.id = new;
let mut trace = project.traces.values().next().unwrap().clone();
let trace_id = trace.id;
trace.id = new;
let cases = [
(element_id, Command::EditElement(element_id, element)),
(relation_id, Command::EditRelation(relation_id, relation)),
(diagram_id, Command::EditDiagram(diagram_id, diagram)),
(
node_id,
Command::EditNode {
diagram: diagram_id,
original: node_id,
node,
},
),
(artifact_id, Command::EditArtifact(artifact_id, artifact)),
(trace_id, Command::EditTrace(trace_id, trace)),
(
project.id,
Command::EditProject {
id: new,
alias: project.alias.clone(),
name: project.name.clone(),
description: project.description.clone(),
},
),
];
for (old, command) in cases {
let prepared = prepare_command(&project, &command).unwrap();
let expected: BTreeMap<_, _> = identities(&project)
.into_iter()
.map(|id| (id, if id == old { new } else { id }))
.collect();
assert_eq!(prepared.identity_map, expected, "{command:?}");
valid_map(&project, &prepared);
}
}
#[test]
fn deletion_and_cascade_discard_origins_before_uuid_reuse() {
let (mut project, first, second) = pair();
let child = project
.add_element(Kind::Activity, Layer::Oa, "Nested work", Some(first))
.unwrap();
let mut fresh = project.elements[&child].clone();
fresh.parent = None;
fresh.alias = "replacement_work".into();
let prepared = prepare_command(
&project,
&Command::Batch(vec![Command::Delete(first), Command::CreateElement(fresh)]),
)
.unwrap();
assert!(prepared.project.contains(child));
assert_eq!(
prepared.identity_map,
BTreeMap::from([(project.id, project.id), (second, second)])
);
valid_map(&project, &prepared);
}
#[test]
fn embedded_diagram_replacement_cannot_revive_deleted_node_origin() {
let mut project = Project::empty();
let element = project.add(Kind::Activity, "Work", 0., 0.);
let mut diagram = project.diagrams.values().next().unwrap().clone();
let node = diagram.nodes.values().next().unwrap().clone();
diagram.nodes.clear();
let prepared = prepare_command(
&project,
&Command::Batch(vec![
Command::EditDiagram(diagram.id, diagram.clone()),
Command::CreateNode {
diagram: diagram.id,
node: node.clone(),
},
]),
)
.unwrap();
assert_eq!(prepared.project, project);
assert_eq!(
prepared.identity_map,
BTreeMap::from([
(project.id, project.id),
(element, element),
(diagram.id, diagram.id)
])
);
assert!(!prepared.identity_map.contains_key(&node.id));
valid_map(&project, &prepared);
}
#[test]
fn fresh_creation_has_no_origin_even_when_subsequently_reidentified() {
let (project, _, _) = pair();
let mut fresh = project
.make_element(Kind::Activity, Layer::Oa, "New work", None)
.unwrap();
let created = fresh.id;
let renamed = Id::new_v4();
fresh.alias = "fresh_identity".into();
let prepared = prepare_command(
&project,
&Command::Batch(vec![
Command::CreateElement(fresh),
Command::Reidentify(created, renamed),
]),
)
.unwrap();
assert_eq!(prepared.selected, Some(renamed));
assert!(prepared.project.contains(renamed));
assert_eq!(
prepared.identity_map,
identities(&project)
.into_iter()
.map(|id| (id, id))
.collect()
);
valid_map(&project, &prepared);
let generated = prepare_command(
&project,
&Command::AddElement {
kind: Kind::Activity,
layer: Layer::Oa,
name: "Generated once".into(),
parent: None,
},
)
.unwrap();
let selected = generated.selected.unwrap();
assert!(generated.project.contains(selected));
assert!(!generated.identity_map.values().any(|id| *id == selected));
assert_eq!(generated.project.elements.len(), project.elements.len() + 1);
}
#[test]
fn replacement_resets_correspondence_against_the_original_input() {
let (project, first, second) = pair();
let temporary = Id::new_v4();
let mut replacement = project.clone();
replacement.elements.remove(&second);
let new = replacement
.add_element(Kind::Activity, Layer::Oa, "Replacement addition", None)
.unwrap();
let final_id = Id::new_v4();
let prepared = prepare_command(
&project,
&Command::Batch(vec![
Command::Reidentify(first, temporary),
Command::Delete(second),
Command::Replace(replacement),
Command::Reidentify(first, final_id),
]),
)
.unwrap();
assert_eq!(
prepared.identity_map,
BTreeMap::from([(project.id, project.id), (first, final_id)])
);
assert!(!prepared.identity_map.values().any(|id| *id == new));
valid_map(&project, &prepared);
let restored = prepare_command(
&project,
&Command::Batch(vec![
Command::Delete(first),
Command::Replace(project.clone()),
]),
)
.unwrap();
assert_eq!(
restored.identity_map,
identities(&project)
.into_iter()
.map(|id| (id, id))
.collect()
);
}
#[test]
fn failed_preparation_leaves_input_unchanged_after_intermediate_identity_work() {
let (project, first, second) = pair();
let before = project.clone();
let temporary = Id::new_v4();
let failed = prepare_command(
&project,
&Command::Batch(vec![
Command::Reidentify(first, temporary),
Command::Reidentify(second, temporary),
]),
);
assert!(failed.is_err());
assert_eq!(project, before);
let failed_validation = prepare_command(
&project,
&Command::Batch(vec![
Command::Reidentify(first, temporary),
Command::SetAlias(temporary, "second".into()),
]),
);
assert!(failed_validation.is_err());
assert_eq!(project, before);
}