use oa_core::{projection, routing, *}; struct Fixture { p: Project, d: Id, edge: Id, source: Id, target: Id, blocker: Id, } fn fixture(ports: bool, gap: f32) -> Fixture { let mut p = Project::empty(); let kind = if ports { Kind::Function } else { Kind::DataType }; let a = p.add_element(kind, Layer::Sa, "Source", None).unwrap(); let b = p.add_element(kind, Layer::Sa, "Target", None).unwrap(); let c = p.add_element(kind, Layer::Sa, "Blocker", None).unwrap(); for element in p.elements.values_mut() { if let Some(SharedBody::DataType { definition, .. }) = element.shared_mut() { *definition = DataTypeDefinition::Record { supertype: None }; } } let edge = p.relate( if ports { RelationKind::FunctionalExchange } else { RelationKind::DataAssociation }, "Flow", a, b, ); let d = p.ensure_diagram(Layer::Sa); let source = p.place(d, a, Position { x: 0., y: 0. }); let target = p.place( d, b, Position { x: 500., y: 180. + gap, }, ); let blocker = p.place(d, c, Position { x: 500., y: 20. }); for n in p.diagrams.get_mut(&d).unwrap().nodes.values_mut() { n.size = Size { width: 224., height: 160., }; } if ports { for (owner, end, side) in [ (source, EdgeEnd::Source, PortAnchorSide::Right), (target, EdgeEnd::Target, PortAnchorSide::Top), ] { let port = routing::semantic_port_endpoint(&p, edge, end).unwrap(); p.diagrams .get_mut(&d) .unwrap() .nodes .get_mut(&owner) .unwrap() .port_anchors .push(PortAnchor { port, side, offset: 0.5, }); } } validate_candidate(&p).unwrap(); Fixture { p, d, edge, source, target, blocker, } } fn report(f: &Fixture) -> Vec { routing::lead_incompatibilities( &f.p, &f.p.diagrams[&f.d], &projection::project_view(&f.p, f.d), ) } fn attachment_route(edge: Id) -> EdgeRoute { EdgeRoute { edge, source_attachment: Some(EdgeAttachment { side: PortAnchorSide::Top, offset: 0.25, }), target_attachment: Some(EdgeAttachment { side: PortAnchorSide::Bottom, offset: 0.75, }), waypoints: vec![Position { x: 300., y: 400. }], label: None, } } #[test] fn exact_twenty_gap_is_blocked_thirty_plus_clears_without_mutation() { let f = fixture(true, 20.); let original = f.p.clone(); let r = report(&f); assert_eq!(r.len(), 1); assert_eq!( ( r[0].edge, r[0].allowed_pair_count, r[0].lead_clear_pair_count ), (f.edge, 1, 0) ); assert_eq!(r[0].source[0].owner, f.source); let end = &r[0].target[0]; assert_eq!(end.owner, f.target); assert_eq!(end.tip, Position { x: 612., y: 200. }); assert_eq!(end.stub, Position { x: 612., y: 182. }); assert_eq!(end.blockers.len(), 1); assert_eq!(end.blockers[0].id, f.blocker); assert_eq!( end.blockers[0].bounds, routing::RoutingBounds { position: Position { x: 488., y: 8. }, size: Size { width: 248., height: 184. }, } ); assert_eq!(end.stub_containers, end.blockers); assert_eq!(f.p, original); assert!(report(&fixture(true, 30.)).is_empty()); assert!(report(&fixture(true, 31.)).is_empty()); } #[test] fn semantic_manual_ports_override_dormant_route_attachments_and_pins() { let mut f = fixture(true, 20.); let expected = report(&f); f.p.diagrams .get_mut(&f.d) .unwrap() .routes .push(attachment_route(f.edge)); assert_eq!(report(&f), expected); let view = projection::project_view(&f.p, f.d); let routed = routing::route_diagram(&f.p, &f.p.diagrams[&f.d], &view); assert_eq!( routed.edges[&f.edge].points.last(), Some(&expected[0].target[0].tip) ); assert!( routed.edges[&f.edge] .warnings .contains(&routing::RouteWarning::NoClearPath) ); } #[test] fn unconstrained_nonport_alternative_is_not_hard_blocked() { let mut f = fixture(false, 20.); assert!(report(&f).is_empty()); let mut route = attachment_route(f.edge); route.source_attachment = Some(EdgeAttachment { side: PortAnchorSide::Right, offset: 0.5, }); route.target_attachment = Some(EdgeAttachment { side: PortAnchorSide::Top, offset: 0.5, }); f.p.diagrams.get_mut(&f.d).unwrap().routes.push(route); let r = report(&f); assert_eq!(r.len(), 1); assert_eq!(r[0].allowed_pair_count, 1); assert_eq!(r[0].target[0].blockers[0].id, f.blocker); } #[test] fn nested_endpoint_body_is_traversable_and_promoted_attachments_are_suspended() { let mut f = fixture(false, 20.); // DataType containment is not required for this routing-only view witness: // construct the supplied projected forest explicitly, retaining real owners. let mut view = projection::project_view(&f.p, f.d); let parent = Id::new_v4(); let source = view.nodes.iter_mut().find(|n| n.id == f.source).unwrap(); source.visual_parent = Some(parent); source.position = Position { x: 40., y: 100. }; let element = source.element; view.nodes.push(projection::ProjectedNode { id: parent, element, position: Position { x: 0., y: 0. }, size: Size { width: 300., height: 300., }, visual_parent: None, depth: 0, collapsed: false, allocation_summary: false, }); let mut route = attachment_route(f.edge); route.source_attachment = Some(EdgeAttachment { side: PortAnchorSide::Right, offset: 0.5, }); route.target_attachment = Some(EdgeAttachment { side: PortAnchorSide::Top, offset: 0.5, }); f.p.diagrams.get_mut(&f.d).unwrap().routes.push(route); let r = routing::lead_incompatibilities(&f.p, &f.p.diagrams[&f.d], &view); assert_eq!(r.len(), 1); assert!( r[0].source[0].blockers.is_empty(), "Ancestor body must not block source lead" ); // Promotion changes actual projected owner: custom attachments no longer apply. view.edges .iter_mut() .find(|e| e.id == f.edge) .unwrap() .source = parent; let with_dormant = routing::lead_incompatibilities(&f.p, &f.p.diagrams[&f.d], &view); f.p.diagrams.get_mut(&f.d).unwrap().routes.clear(); assert_eq!( with_dormant, routing::lead_incompatibilities(&f.p, &f.p.diagrams[&f.d], &view) ); }