use oa_core::{projection::*, *}; #[test] fn unplaced_work_parent_cannot_detach_a_leaf_from_its_visible_performer() { let mut p = Project::empty(); let performer = p .add_element(Kind::Entity, Layer::Oa, "Rescue team", None) .unwrap(); let work = p .add_element(Kind::Activity, Layer::Oa, "Respond", None) .unwrap(); let leaf = p .add_element(Kind::Activity, Layer::Oa, "Dispatch", Some(work)) .unwrap(); p.relate(RelationKind::Allocation, "Performs", performer, leaf); let diagram = p.ensure_diagram(Layer::Oa); p.diagrams.get_mut(&diagram).unwrap().projection = ProjectionKind::Allocation; let owner = p.place(diagram, performer, Position { x: 0., y: 0. }); let child = p.place(diagram, leaf, Position { x: 40., y: 80. }); let view = project_view(&p, diagram); assert_eq!( view.nodes .iter() .find(|n| n.id == child) .unwrap() .visual_parent, Some(owner) ); let summary = p.place(diagram, work, Position { x: 24., y: 64. }); let view = project_view(&p, diagram); assert!( view.nodes .iter() .find(|n| n.id == summary) .unwrap() .allocation_summary ); assert!( !view .nodes .iter() .find(|n| n.id == child) .unwrap() .allocation_summary ); assert_eq!( view.nodes .iter() .find(|n| n.id == child) .unwrap() .visual_parent, Some(summary) ); p.diagrams.get_mut(&diagram).unwrap().projection = ProjectionKind::FunctionalDataflow; assert!( project_view(&p, diagram) .nodes .iter() .all(|n| !n.allocation_summary) ); p.diagrams.get_mut(&diagram).unwrap().projection = ProjectionKind::Allocation; p.diagrams.get_mut(&diagram).unwrap().nodes.remove(&summary); let view = project_view(&p, diagram); assert_eq!( view.nodes .iter() .find(|n| n.id == child) .unwrap() .visual_parent, Some(owner) ); } #[test] fn architecture_is_nested_exchange_context_and_ports_attach_without_placements() { let p = Project::demo(); let d = p .diagrams .values() .find(|d| d.layer == Layer::Sa && d.projection == ProjectionKind::Allocation) .unwrap(); let view = project_view(&p, d.id); assert!(!view.nodes.is_empty()); assert!(view.nodes.iter().all(|n| { let kind = p.elements[&n.element].kind(); kind.is_performer() || kind.is_work() })); for n in view .nodes .iter() .filter(|n| p.elements[&n.element].kind().is_work()) { let parent = view .nodes .iter() .find(|parent| Some(parent.id) == n.visual_parent) .unwrap(); assert!(p.elements[&parent.element].kind().is_performer()); assert!(n.position.x > parent.position.x && n.position.y > parent.position.y); } assert_eq!(view.ports.len(), 4); assert!( d.nodes .values() .all(|n| p.elements[&n.element].kind() != Kind::Port) ); assert!( view.edges .iter() .any(|e| p.relations[&e.id].kind() == RelationKind::BoundaryExchange) ); for edge in &view.edges { assert!(matches!( p.relations[&edge.id].kind(), RelationKind::FunctionalExchange | RelationKind::BoundaryExchange )); for (id, owner) in [ (edge.source_port, edge.source), (edge.target_port, edge.target), ] { let port = view.ports.iter().find(|p| Some(p.element) == id).unwrap(); assert_eq!(port.owner, owner); let node = view.nodes.iter().find(|n| n.id == owner).unwrap(); assert!( port.position.x == node.position.x || port.position.x == node.position.x + node.size.width ); } } } #[test] fn port_identity_collapse_and_group_movement_do_not_invent_semantic_edges() { let mut p = Project::demo(); let diagram = p .diagrams .values() .find(|d| d.layer == Layer::Sa && d.projection == ProjectionKind::Allocation) .unwrap() .id; let view = project_view(&p, diagram); let system = view .nodes .iter() .find(|n| p.elements[&n.element].kind() == Kind::System) .unwrap(); let node = system.id; let before = p.clone(); let moved = execute_batch( &p, &[Command::MoveNode { diagram, node, position: Position { x: p.diagrams[&diagram].nodes[&node].position.x + 140., y: p.diagrams[&diagram].nodes[&node].position.y + 80., }, with_descendants: true, }], ) .unwrap(); let after = project_view(&moved, diagram); for port in view.ports.iter().filter(|port| { port.owner == node || view .nodes .iter() .any(|n| n.id == port.owner && n.visual_parent == Some(node)) }) { let shifted = after .ports .iter() .find(|p| p.element == port.element) .unwrap(); assert_eq!(shifted.position.x, port.position.x + 140.); assert_eq!(shifted.position.y, port.position.y + 80.); } assert_eq!(moved.elements, before.elements); assert_eq!(moved.relations, before.relations); p.diagrams .get_mut(&diagram) .unwrap() .nodes .get_mut(&node) .unwrap() .collapsed = true; let collapsed = project_view(&p, diagram); assert!(collapsed.ports.iter().all(|port| { p.elements[&port.element].parent != Some( view.nodes .iter() .find(|n| n.visual_parent == Some(node)) .unwrap() .element, ) })); let exchange = collapsed .edges .iter() .find(|e| p.relations[&e.id].kind() == RelationKind::FunctionalExchange) .unwrap(); assert_eq!(exchange.target, node); assert_eq!( exchange.target_port, None, "A collapsed link must not invent a system port" ); assert_eq!(p.relations, before.relations); } #[test] fn view_filtering_keeps_placements_and_source_and_separates_capability_content() { let mut p = Project::demo(); let diagram = p .diagrams .values() .find(|d| d.layer == Layer::Oa && d.projection == ProjectionKind::Allocation) .unwrap() .id; let cap = p .elements .values() .find(|e| e.kind() == Kind::Capability) .unwrap() .id; p.place(diagram, cap, Position { x: 600., y: 0. }); let original = p.clone(); assert!( !project_view(&p, diagram) .nodes .iter() .any(|n| n.element == cap) ); p.diagrams.get_mut(&diagram).unwrap().projection = ProjectionKind::Capabilities; let view = project_view(&p, diagram); assert!(view.nodes.iter().any(|n| n.element == cap)); assert!( !view .nodes .iter() .any(|n| p.elements[&n.element].kind().is_work()) ); assert_eq!( p.diagrams[&diagram].nodes, original.diagrams[&diagram].nodes ); assert_eq!(language::parse(&language::format(&p)).unwrap(), p); }