use oa_core::{language, projection::*, routing::*, *}; fn pos(x: f32, y: f32) -> Position { Position { x, y } } fn id(project: &Project, alias: &str) -> Id { project .identities() .into_iter() .find(|(_, a)| *a == alias) .unwrap() .0 } fn authored(edge: Id) -> EdgeRoute { EdgeRoute { edge, source_attachment: Some(EdgeAttachment { side: PortAnchorSide::Bottom, offset: 0.2, }), target_attachment: Some(EdgeAttachment { side: PortAnchorSide::Top, offset: 0.8, }), waypoints: vec![pos(520., 420.)], label: Some(EdgeLabel { fraction: 0.35, offset: pos(17., -23.), }), } } fn routed(project: &Project, diagram: Id) -> RoutedView { let d = &project.diagrams[&diagram]; route_diagram(project, d, &project_diagram(project, d)) } fn fixture() -> (Project, Id, Id, Id, Id, Id, Id) { let mut p = language::parse( r#"arcadia 7; project lifecycle "Attachment lifecycle" { oa_activity group "Group" { oa_activity first "First" {} } oa_activity second "Second" {} oa_activity replacement "Replacement" {} interaction signal "Signal" { source @first; target @second; } diagram view "View" {} diagram other "Other" {} }"#, ) .unwrap(); let (d, other, group, first, second, replacement, edge) = ( id(&p, "view"), id(&p, "other"), id(&p, "group"), id(&p, "first"), id(&p, "second"), id(&p, "replacement"), id(&p, "signal"), ); for diagram in [d, other] { p.place(diagram, group, pos(20., 20.)); p.place(diagram, first, pos(100., 130.)); p.place(diagram, second, pos(750., 150.)); p.place(diagram, replacement, pos(750., 570.)); p.diagrams .get_mut(&diagram) .unwrap() .routes .push(authored(edge)); } (p, d, other, group, first, replacement, edge) } #[test] fn attachment_fold_promotion_suspends_the_complete_route_and_expand_restores_it() { let (mut p, d, other, group, _, _, edge) = fixture(); assert_eq!(semantic_port_endpoint(&p, edge, EdgeEnd::Source), None); assert_eq!(semantic_port_endpoint(&p, edge, EdgeEnd::Target), None); let before = p.clone(); let expanded = routed(&p, d); let other_geometry = routed(&p, other); let parent = p.diagrams[&d] .nodes .values() .find(|n| n.element == group) .unwrap() .id; p.diagrams .get_mut(&d) .unwrap() .nodes .get_mut(&parent) .unwrap() .collapsed = true; let folded = routed(&p, d); assert!(folded.edges[&edge].custom_suspended); assert_eq!(folded.edges[&edge].pins, []); assert!( folded.edges[&edge] .warnings .contains(&RouteWarning::PromotedEndpoints) ); assert_eq!(p.diagrams[&d].routes, before.diagrams[&d].routes); let mut automatic = p.clone(); automatic.diagrams.get_mut(&d).unwrap().routes.clear(); assert_eq!( folded, routed(&automatic, d), "attachments, pins and label must all be dormant on promotion" ); assert_eq!(p.diagrams[&other], before.diagrams[&other]); assert_eq!(routed(&p, other), other_geometry); p.diagrams .get_mut(&d) .unwrap() .nodes .get_mut(&parent) .unwrap() .collapsed = false; assert_eq!(p, before); assert_eq!(routed(&p, d), expanded); } #[test] fn attachment_reconnect_retains_slot_preferences_and_other_view_records_with_exact_history() { let (p, d, other, _, _, replacement, edge) = fixture(); let before = routed(&p, d); let mut relation = p.relations[&edge].clone(); let RelationBody::Interaction { target, .. } = &mut relation.body else { unreachable!() }; *target = replacement; let mut expected = p.clone(); expected.relations.insert(edge, relation.clone()); let mut session = Session::new(p.clone()); session .execute(Command::EditRelation(edge, relation)) .unwrap(); assert_eq!( session.project(), &expected, "reconnect changes only the semantic endpoint; all per-view records remain exact" ); assert_eq!(session.project().diagrams[&other], p.diagrams[&other]); for diagram in [d, other] { let current = session.project(); let view = project_diagram(current, ¤t.diagrams[&diagram]); let node = view .nodes .iter() .find(|n| n.element == replacement) .unwrap(); let preference = current.diagrams[&diagram].routes[0] .target_attachment .unwrap(); let expected_tip = attachment_point(current, ¤t.diagrams[&diagram], node, &preference); let result = routed(current, diagram); assert_eq!(result.edges[&edge].points.last(), Some(&expected_tip)); assert_eq!( result.edges[&edge].pins, p.diagrams[&diagram].routes[0].waypoints ); } assert_ne!( routed(session.project(), d).edges[&edge].points.last(), before.edges[&edge].points.last() ); session.undo(); assert_eq!(session.project(), &p); assert_eq!(routed(session.project(), d), before); session.redo(); assert_eq!(session.project(), &expected); } #[test] fn attachment_direct_port_endpoints_remain_authoritative_when_their_glyphs_are_filtered() { let mut p = Project::empty(); let system = p .add_element(Kind::System, Layer::Sa, "System", None) .unwrap(); let actor = p .add_element(Kind::SystemActor, Layer::Sa, "Actor", None) .unwrap(); let function = p .add_element(Kind::Function, Layer::Sa, "Send", None) .unwrap(); let mut ports = Vec::new(); for (owner, name, contract) in [ ( system, "Output", PortContract::Component { kind: ComponentPortKind::Flow, orientation: ComponentPortOrientation::Out, }, ), ( actor, "Input", PortContract::Component { kind: ComponentPortKind::Flow, orientation: ComponentPortOrientation::In, }, ), ( function, "Function output", PortContract::FunctionOutput { outgoing_items: vec![], }, ), ] { let port = Id::new_v4(); p.elements .insert(port, make_port(&p, port, owner, name, contract).unwrap()); ports.push(port); } p.relate( RelationKind::Allocation, "Function allocation", system, function, ); let boundary = p.relate( RelationKind::BoundaryExchange, "Boundary", ports[0], ports[1], ); let allocation = p.relate( RelationKind::PortAllocation, "Port allocation", ports[0], ports[2], ); let d = p.ensure_diagram(Layer::Sa); p.place(d, system, pos(0., 0.)); p.place(d, actor, pos(700., 0.)); p.place(d, function, pos(0., 450.)); let original = p.clone(); let baseline = routed(&p, d); for (edge, target) in [(boundary, ports[1]), (allocation, ports[2])] { assert!(baseline.edges.contains_key(&edge)); assert_eq!( semantic_port_endpoint(&p, edge, EdgeEnd::Source), Some(ports[0]) ); assert_eq!( semantic_port_endpoint(&p, edge, EdgeEnd::Target), Some(target) ); let mut route = authored(edge); route.waypoints.clear(); route.label = None; p.diagrams.get_mut(&d).unwrap().routes.push(route); } assert_eq!(routed(&p, d), baseline); let mut hidden = project_diagram(&p, &p.diagrams[&d]); hidden.ports.clear(); for edge in &mut hidden.edges { edge.source_port = None; edge.target_port = None; } assert_eq!( route_diagram(&p, &p.diagrams[&d], &hidden), route_diagram(&original, &original.diagrams[&d], &hidden), "filtered direct Port endpoints must not activate their stored preferences" ); }