Preserve nested architecture during authoring and distinguish allocation summaries
This commit is contained in:
@@ -936,3 +936,299 @@ fn behavior_and_data_authoring_do_not_place_nodes_in_unrelated_architecture_view
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn revealing_an_implicit_function_port_unfolds_its_visible_architecture_owner() {
|
||||
let mut p = Project::demo();
|
||||
let diagram = p
|
||||
.diagrams
|
||||
.values()
|
||||
.find(|d| d.layer == Layer::Sa && d.projection == ProjectionKind::Allocation)
|
||||
.unwrap()
|
||||
.id;
|
||||
let view = oa_core::projection::project_view(&p, diagram);
|
||||
let function = view
|
||||
.nodes
|
||||
.iter()
|
||||
.find(|n| p.elements[&n.element].kind() == Kind::Function && n.visual_parent.is_some())
|
||||
.unwrap();
|
||||
let ancestor = function.visual_parent.unwrap();
|
||||
let port = p
|
||||
.children(function.element)
|
||||
.find(|e| e.kind() == Kind::Port)
|
||||
.unwrap()
|
||||
.id;
|
||||
p.diagrams
|
||||
.get_mut(&diagram)
|
||||
.unwrap()
|
||||
.nodes
|
||||
.get_mut(&ancestor)
|
||||
.unwrap()
|
||||
.collapsed = true;
|
||||
assert!(crate::navigation::diagrams_containing(&p, port).contains(&diagram));
|
||||
let mut app = App::with_project(p);
|
||||
send(&mut app, Message::RevealInDiagram(diagram, port));
|
||||
assert_eq!(app.selected, port);
|
||||
assert_eq!(app.active_diagram, Some(diagram));
|
||||
assert!(!app.project().diagrams[&diagram].nodes[&ancestor].collapsed);
|
||||
assert!(
|
||||
oa_core::projection::project_view(app.project(), diagram)
|
||||
.ports
|
||||
.iter()
|
||||
.any(|p| p.element == port)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn creation_from_capabilities_routes_work_to_architecture_and_folded_allocation_inserts_below_existing_work()
|
||||
{
|
||||
let p = Project::demo();
|
||||
let system = p
|
||||
.elements
|
||||
.values()
|
||||
.find(|e| e.kind() == Kind::System)
|
||||
.unwrap()
|
||||
.id;
|
||||
let capabilities = p
|
||||
.diagrams
|
||||
.values()
|
||||
.find(|d| d.layer == Layer::Sa && d.projection == ProjectionKind::Capabilities)
|
||||
.unwrap()
|
||||
.id;
|
||||
let mut app = App::with_project(p);
|
||||
send(&mut app, Message::Layer(Layer::Sa));
|
||||
send(
|
||||
&mut app,
|
||||
Message::Diagram(crate::app::Choice {
|
||||
id: capabilities,
|
||||
label: String::new(),
|
||||
}),
|
||||
);
|
||||
send(&mut app, Message::CreateKind(Kind::Function));
|
||||
send(&mut app, Message::Add(false));
|
||||
let diagram = app.active_diagram.unwrap();
|
||||
assert_eq!(
|
||||
app.project().diagrams[&diagram].projection,
|
||||
ProjectionKind::Allocation
|
||||
);
|
||||
assert!(
|
||||
app.project().diagrams[&diagram]
|
||||
.nodes
|
||||
.values()
|
||||
.any(|n| n.element == app.selected)
|
||||
);
|
||||
send(&mut app, Message::Select(system));
|
||||
let owner = app.selected_node.unwrap();
|
||||
send(&mut app, Message::ToggleCollapse);
|
||||
assert!(app.project().diagrams[&diagram].nodes[&owner].collapsed);
|
||||
assert_eq!(
|
||||
app.selected, system,
|
||||
"Folding must retain semantic selection"
|
||||
);
|
||||
let before = app.project().clone();
|
||||
send(&mut app, Message::Add(true));
|
||||
let created = app.selected;
|
||||
let view = oa_core::projection::project_view(app.project(), diagram);
|
||||
let child = view
|
||||
.nodes
|
||||
.iter()
|
||||
.find(|n| n.element == created)
|
||||
.unwrap_or_else(|| panic!("Created work is not visible: {}", app.status));
|
||||
assert_eq!(child.visual_parent, Some(owner));
|
||||
assert!(!app.project().diagrams[&diagram].nodes[&owner].collapsed);
|
||||
assert!(
|
||||
view.nodes
|
||||
.iter()
|
||||
.filter(|n| n.visual_parent == Some(owner) && n.id != child.id)
|
||||
.all(|n| n.position.y + n.size.height <= child.position.y)
|
||||
);
|
||||
let parent = view.nodes.iter().find(|n| n.id == owner).unwrap();
|
||||
assert!(
|
||||
view.nodes
|
||||
.iter()
|
||||
.filter(|n| n.visual_parent.is_none() && n.id != owner)
|
||||
.all(|n| n.position.x + n.size.width <= parent.position.x
|
||||
|| parent.position.x + parent.size.width <= n.position.x
|
||||
|| n.position.y + n.size.height <= parent.position.y
|
||||
|| parent.position.y + parent.size.height <= n.position.y)
|
||||
);
|
||||
send(&mut app, Message::Undo);
|
||||
assert_eq!(app.project(), &before);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nested_allocation_insertion_reflows_sibling_subtrees_and_preserves_fold_state() {
|
||||
use oa_core::projection::{project_diagram, project_view};
|
||||
for folded in [false, true] {
|
||||
let mut p = Project::empty();
|
||||
let root = p
|
||||
.add_element(Kind::Entity, Layer::Oa, "Root", None)
|
||||
.unwrap();
|
||||
let a = p
|
||||
.add_element(Kind::Entity, Layer::Oa, "A", Some(root))
|
||||
.unwrap();
|
||||
let b = p
|
||||
.add_element(Kind::Entity, Layer::Oa, "B", Some(root))
|
||||
.unwrap();
|
||||
let other = p
|
||||
.add_element(Kind::Entity, Layer::Oa, "Other root", None)
|
||||
.unwrap();
|
||||
let a_work = p
|
||||
.add_element(Kind::Activity, Layer::Oa, "A work", None)
|
||||
.unwrap();
|
||||
let b_work = p
|
||||
.add_element(Kind::Activity, Layer::Oa, "B work", None)
|
||||
.unwrap();
|
||||
let b_port = p
|
||||
.add_element(Kind::Port, Layer::Oa, "B boundary", Some(b))
|
||||
.unwrap();
|
||||
p.relate(RelationKind::Allocation, "A performs", a, a_work);
|
||||
p.relate(RelationKind::Allocation, "B performs", b, b_work);
|
||||
let diagram = p.ensure_diagram(Layer::Oa);
|
||||
p.diagrams.get_mut(&diagram).unwrap().projection = ProjectionKind::Allocation;
|
||||
for id in [root, a, b, other, a_work, b_work] {
|
||||
p.place(diagram, id, Position { x: 60., y: 60. });
|
||||
}
|
||||
let mut p = execute_batch(&p, &[Command::Layout(diagram)]).unwrap();
|
||||
let view = project_view(&p, diagram);
|
||||
let root_node = view.nodes.iter().find(|n| n.element == root).unwrap();
|
||||
let a_node = view.nodes.iter().find(|n| n.element == a).unwrap();
|
||||
let b_node = view.nodes.iter().find(|n| n.element == b).unwrap();
|
||||
// Layout's UUID order is intentionally irrelevant to this scenario:
|
||||
// explicitly put A before B, as a user can arrange these participants.
|
||||
p = execute_batch(
|
||||
&p,
|
||||
&[
|
||||
Command::MoveNode {
|
||||
diagram,
|
||||
node: a_node.id,
|
||||
position: Position {
|
||||
x: root_node.position.x + 24.,
|
||||
y: root_node.position.y + 64.,
|
||||
},
|
||||
with_descendants: true,
|
||||
},
|
||||
Command::MoveNode {
|
||||
diagram,
|
||||
node: b_node.id,
|
||||
position: Position {
|
||||
x: root_node.position.x + 24.,
|
||||
y: root_node.position.y + 64. + a_node.size.height + 16.,
|
||||
},
|
||||
with_descendants: true,
|
||||
},
|
||||
],
|
||||
)
|
||||
.unwrap();
|
||||
let view = project_view(&p, diagram);
|
||||
let root_node = view.nodes.iter().find(|n| n.element == root).unwrap();
|
||||
let other_node = view.nodes.iter().find(|n| n.element == other).unwrap().id;
|
||||
let b_node = view.nodes.iter().find(|n| n.element == b).unwrap().id;
|
||||
let b_work_node = view.nodes.iter().find(|n| n.element == b_work).unwrap().id;
|
||||
p.diagrams
|
||||
.get_mut(&diagram)
|
||||
.unwrap()
|
||||
.nodes
|
||||
.get_mut(&other_node)
|
||||
.unwrap()
|
||||
.position = Position {
|
||||
x: root_node.position.x,
|
||||
y: root_node.position.y + root_node.size.height + 24.,
|
||||
};
|
||||
p.diagrams
|
||||
.get_mut(&diagram)
|
||||
.unwrap()
|
||||
.nodes
|
||||
.get_mut(&b_node)
|
||||
.unwrap()
|
||||
.collapsed = folded;
|
||||
// Deliberately overlapping unrelated roots must not be rearranged.
|
||||
let mut unrelated = Vec::new();
|
||||
for name in ["Unrelated one", "Unrelated two"] {
|
||||
let id = p.add_element(Kind::Entity, Layer::Oa, name, None).unwrap();
|
||||
unrelated.push(p.place(diagram, id, Position { x: 2000., y: 1000. }));
|
||||
}
|
||||
let before = p.clone();
|
||||
let mut expanded = p.diagrams[&diagram].clone();
|
||||
expanded.nodes.get_mut(&b_node).unwrap().collapsed = false;
|
||||
let before_view = project_diagram(&p, &expanded);
|
||||
let before_port = before_view
|
||||
.ports
|
||||
.iter()
|
||||
.find(|p| p.element == b_port)
|
||||
.unwrap()
|
||||
.position;
|
||||
let mut app = App::with_project(p);
|
||||
send(&mut app, Message::Select(a));
|
||||
send(&mut app, Message::CreateKind(Kind::Activity));
|
||||
send(&mut app, Message::Add(true));
|
||||
let created = app.selected;
|
||||
assert_eq!(
|
||||
app.project().elements[&created].kind(),
|
||||
Kind::Activity,
|
||||
"{}",
|
||||
app.status
|
||||
);
|
||||
let after = &app.project().diagrams[&diagram];
|
||||
assert_eq!(after.nodes[&b_node].collapsed, folded);
|
||||
let dy =
|
||||
after.nodes[&b_node].position.y - before.diagrams[&diagram].nodes[&b_node].position.y;
|
||||
assert!(dy > 0., "B must move below the enlarged A");
|
||||
assert_eq!(
|
||||
after.nodes[&b_work_node].position.y,
|
||||
before.diagrams[&diagram].nodes[&b_work_node].position.y + dy
|
||||
);
|
||||
assert!(
|
||||
after.nodes[&other_node].position.y
|
||||
> before.diagrams[&diagram].nodes[&other_node].position.y
|
||||
);
|
||||
for id in unrelated {
|
||||
assert_eq!(after.nodes[&id], before.diagrams[&diagram].nodes[&id]);
|
||||
}
|
||||
let mut expanded = after.clone();
|
||||
expanded.nodes.get_mut(&b_node).unwrap().collapsed = false;
|
||||
let view = project_diagram(app.project(), &expanded);
|
||||
let after_port = view
|
||||
.ports
|
||||
.iter()
|
||||
.find(|p| p.element == b_port)
|
||||
.unwrap()
|
||||
.position;
|
||||
assert_eq!(
|
||||
after_port,
|
||||
Position {
|
||||
x: before_port.x,
|
||||
y: before_port.y + dy
|
||||
}
|
||||
);
|
||||
for (left, right) in [(a, b), (root, other), (a_work, created)] {
|
||||
let left = view.nodes.iter().find(|n| n.element == left).unwrap();
|
||||
let right = view.nodes.iter().find(|n| n.element == right).unwrap();
|
||||
assert!(left.position.y + left.size.height <= right.position.y);
|
||||
}
|
||||
assert!(crate::diagram::insertion_adjustments(app.project(), diagram, created).is_empty());
|
||||
send(&mut app, Message::Undo);
|
||||
assert_eq!(
|
||||
app.project(),
|
||||
&before,
|
||||
"Creation and all reflow must undo together"
|
||||
);
|
||||
|
||||
if folded {
|
||||
send(&mut app, Message::Select(root));
|
||||
send(&mut app, Message::CreateKind(Kind::Entity));
|
||||
send(&mut app, Message::Add(true));
|
||||
let mut expanded = app.project().diagrams[&diagram].clone();
|
||||
expanded.nodes.get_mut(&b_node).unwrap().collapsed = false;
|
||||
let view = project_diagram(app.project(), &expanded);
|
||||
let b = view.nodes.iter().find(|n| n.id == b_node).unwrap();
|
||||
let created = view
|
||||
.nodes
|
||||
.iter()
|
||||
.find(|n| n.element == app.selected)
|
||||
.unwrap();
|
||||
assert!(b.position.y + b.size.height <= created.position.y);
|
||||
assert!(app.project().diagrams[&diagram].nodes[&b_node].collapsed);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user