Files
ARCADIA-Studio/crates/oa-desktop/src/workflow/transition_tests.rs
T

262 lines
8.4 KiB
Rust

use super::*;
fn view(p: &mut Project, number: u128, projection: ProjectionKind) -> Id {
let id = Id::from_u128(number);
p.diagrams.insert(
id,
Diagram {
id,
alias: format!("view_{number}"),
name: format!("View {number}"),
description: String::new(),
layer: Layer::Sa,
projection,
view_options: Default::default(),
nodes: Default::default(),
routes: vec![],
},
);
id
}
fn fixture() -> (Project, Id, Id) {
let mut p = Project::empty();
let source = p
.add_element(Kind::Entity, Layer::Oa, "Water service", None)
.unwrap();
let machine = p
.add_element(Kind::StateMachine, Layer::Sa, "Existing behavior", None)
.unwrap();
let state = view(&mut p, 1, ProjectionKind::StateMachine { machine });
p.place(state, machine, Position { x: 60., y: 60. });
(p, source, state)
}
fn choose_system(p: &Project, source: Id) -> Workflow {
let mut w = Workflow::transition(p, source).unwrap();
w.update(p, Event::MappingKind(source, Kind::System))
.unwrap();
w
}
fn target(w: &Workflow, source: Id) -> Id {
let Workflow::Transition(t) = w else { panic!() };
t.plan
.changes
.iter()
.find(|c| c.source == source)
.unwrap()
.target
}
#[test]
fn basic_transition_skips_lowest_uuid_state_view_and_preserves_oa() {
let (mut p, source, state) = fixture();
let architecture = view(&mut p, 20, ProjectionKind::Allocation);
let mut w = choose_system(&p, source);
let result = target(&w, source);
let accepted = w.update(&p, Event::Apply).unwrap().unwrap();
assert_eq!(accepted.elements[&source], p.elements[&source]);
assert_eq!(accepted.diagrams[&state], p.diagrams[&state]);
assert!(
accepted.diagrams[&architecture]
.nodes
.values()
.any(|n| n.element == result)
);
assert!(
accepted
.traces
.values()
.any(|t| t.oa == source && t.sa == result)
);
}
#[test]
fn basic_transition_creates_overview_when_existing_views_cannot_show_result() {
let (mut p, source, state) = fixture();
let dataflow = view(&mut p, 2, ProjectionKind::FunctionalDataflow);
let mut w = choose_system(&p, source);
let result = target(&w, source);
let accepted = w.update(&p, Event::Apply).unwrap().unwrap();
assert_eq!(accepted.diagrams[&state], p.diagrams[&state]);
assert_eq!(accepted.diagrams[&dataflow], p.diagrams[&dataflow]);
let added: Vec<_> = accepted
.diagrams
.values()
.filter(|d| !p.diagrams.contains_key(&d.id))
.collect();
assert_eq!(added.len(), 1);
assert_eq!(added[0].projection, ProjectionKind::Overview);
assert!(added[0].nodes.values().any(|n| n.element == result));
}
#[test]
fn basic_transition_explicit_destination_appends_below_existing_bounds_and_is_one_source_edit() {
let (mut p, source, _) = fixture();
let automatic = view(&mut p, 20, ProjectionKind::Allocation);
let explicit = view(&mut p, 30, ProjectionKind::Overview);
let old = p
.add_element(
Kind::SystemActor,
Layer::Sa,
"Existing external operator",
None,
)
.unwrap();
let node = p.place(explicit, old, Position { x: 60., y: 600. });
p.diagrams
.get_mut(&explicit)
.unwrap()
.nodes
.get_mut(&node)
.unwrap()
.size
.height = 450.;
let mut w = choose_system(&p, source);
w.update(
&p,
Event::Destination(Choice {
id: explicit,
label: "Chosen overview".into(),
}),
)
.unwrap();
let result = target(&w, source);
let accepted = w.update(&p, Event::Apply).unwrap().unwrap();
assert_eq!(accepted.diagrams[&automatic], p.diagrams[&automatic]);
assert_eq!(
accepted.diagrams[&explicit].nodes[&node],
p.diagrams[&explicit].nodes[&node]
);
assert_eq!(
accepted.diagrams[&explicit].routes,
p.diagrams[&explicit].routes
);
let new = accepted.diagrams[&explicit]
.nodes
.values()
.find(|n| n.element == result)
.unwrap();
assert!(
new.position.y >= 1090.,
"New placement overlaps the existing tall node: {new:?}"
);
let mut document =
crate::document::Document::from_project(p.clone(), Default::default()).unwrap();
let before = document.bundle().clone();
document
.execute(Command::Replace(accepted.clone()))
.unwrap();
let after = document.bundle().clone();
assert_eq!(
oa_core::source_project::compile_bundle(&after, Default::default())
.unwrap()
.decoded
.project,
accepted
);
assert!(document.undo());
assert_eq!(document.project(), &p);
assert_eq!(document.bundle(), &before);
assert!(!document.undo());
assert!(document.redo());
assert_eq!(document.bundle(), &after);
}
#[test]
fn basic_transition_invalid_explicit_destination_retains_choice_and_refuses() {
let (p, source, state) = fixture();
for destination in [state, Id::from_u128(999)] {
let mut w = choose_system(&p, source);
w.update(
&p,
Event::Destination(Choice {
id: destination,
label: "Unavailable result view".into(),
}),
)
.unwrap();
assert!(w.update(&p, Event::Apply).is_err());
let restored = Workflow::from_recovery(&p, w.recovery_snapshot().unwrap()).unwrap();
let Workflow::Transition(restored) = restored else {
panic!()
};
assert_eq!(restored.request.destination_diagram, Some(destination));
let Workflow::Transition(t) = w else { panic!() };
assert_eq!(t.request.destination_diagram, Some(destination));
assert_eq!(t.request.mappings[0].target_kind, Some(Kind::System));
}
}
#[test]
fn basic_transition_boundary_choice_is_required_without_an_inferred_system() {
let (p, source, _) = fixture();
let mut w = Workflow::transition(&p, source).unwrap();
let Workflow::Transition(t) = &w else {
panic!()
};
assert!(t.new_counterpart(&p));
assert!(t.plan.conflicts.iter().any(|c| t.boundary_choice(&p, c)));
assert!(t.request.mappings.is_empty());
assert!(w.update(&p, Event::Apply).is_err());
w.update(&p, Event::MappingKind(source, Kind::SystemActor))
.unwrap();
let result = target(&w, source);
let accepted = w.update(&p, Event::Apply).unwrap().unwrap();
assert_eq!(accepted.elements[&result].kind(), Kind::SystemActor);
assert_eq!(accepted.elements[&source], p.elements[&source]);
assert!(!accepted.elements.values().any(|e| e.kind() == Kind::System));
}
#[test]
fn basic_transition_reconcile_keeps_existing_selected_view_placement_and_route_exact() {
let (mut p, source, _) = fixture();
view(&mut p, 20, ProjectionKind::Allocation);
let mut first = choose_system(&p, source);
let result = target(&first, source);
let mut p = first.update(&p, Event::Apply).unwrap().unwrap();
let explicit = view(&mut p, 30, ProjectionKind::Overview);
let node = p.place(explicit, result, Position { x: 820., y: 912. });
p.diagrams
.get_mut(&explicit)
.unwrap()
.nodes
.get_mut(&node)
.unwrap()
.collapsed = true;
let trace = p
.traces
.values()
.find(|t| t.oa == source && t.sa == result)
.unwrap()
.id;
p.diagrams
.get_mut(&explicit)
.unwrap()
.routes
.push(EdgeRoute {
edge: trace,
source_attachment: None,
target_attachment: None,
waypoints: vec![Position { x: 301., y: 507. }],
label: Some(EdgeLabel {
fraction: 0.35,
offset: Position { x: 19., y: -14. },
}),
});
let mut w = Workflow::transition(&p, source).unwrap();
let Workflow::Transition(t) = &w else {
panic!()
};
assert!(!t.new_counterpart(&p));
w.update(
&p,
Event::Destination(Choice {
id: explicit,
label: "Existing chosen view".into(),
}),
)
.unwrap();
let accepted = w.update(&p, Event::Apply).unwrap().unwrap();
assert_eq!(accepted.diagrams, p.diagrams);
assert_eq!(accepted.elements, p.elements);
}