262 lines
8.4 KiB
Rust
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);
|
|
}
|