use oa_core::{language, persistence, source_project::*, *}; use serde_json::json; const SOURCE: &str = r#"arcadia 3; project route_example "Connection review" { oa_activity report "Report" {} oa_activity assess "Assess" {} sa_function selected_assess "Selected assessment" {} interaction signal "Signal" { source @report; target @assess; } trace refinement "Assessment refinement" { oa @assess; sa @selected_assess; } diagram architecture "Architecture" { routes [ { edge @signal; waypoints [{ x 420; y 260; }, { x 690; y 260; }]; label { fraction 0.55; offset { x 0; y -16; }; }; }, { edge @refinement; } ]; } diagram review "Review" { routes [{ edge @signal; }]; } } "#; fn id(project: &Project, alias: &str) -> Id { project .identities() .into_iter() .find(|(_, candidate)| *candidate == alias) .unwrap() .0 } #[test] fn route_source_json_and_bundle_roundtrips_preserve_order_defaults_and_edge_aliases() { let project = language::parse(SOURCE).unwrap(); let diagram = &project.diagrams[&id(&project, "architecture")]; assert_eq!(diagram.routes.len(), 2); assert_eq!(diagram.routes[0].edge, id(&project, "signal")); assert_eq!( diagram.routes[0].waypoints[1], Position { x: 690., y: 260. } ); assert_eq!(diagram.routes[0].label.as_ref().unwrap().fraction, 0.55); assert_eq!(diagram.routes[1].edge, id(&project, "refinement")); assert!(diagram.routes[1].waypoints.is_empty()); assert!(diagram.routes[1].label.is_none()); assert!( diagram.nodes.is_empty(), "hidden edges can retain authored routes" ); assert_eq!( project.diagrams[&id(&project, "review")].routes[0].waypoints, [] ); let formatted = language::format(&project); assert!(formatted.contains("edge @signal;")); assert!(formatted.contains("edge @refinement;")); assert!(!formatted.contains(&format!("edge \"{}\"", id(&project, "signal")))); assert_eq!(language::parse(&formatted).unwrap(), project); assert_eq!( persistence::decode(&serde_json::to_vec(&project).unwrap()).unwrap(), project ); let compiled = compile_bundle(&format_bundle(&project), ProjectLimits::default()).unwrap(); assert_eq!(compiled.decoded.project, project); } #[test] fn omitted_schema_three_routes_default_to_empty_and_v2_reader_stays_frozen() { let project = language::parse("arcadia 3; project p \"P\" { diagram view \"View\" {} }").unwrap(); assert!(project.diagrams.values().next().unwrap().routes.is_empty()); let mut json = serde_json::to_value(&project).unwrap(); json["diagrams"] .as_object_mut() .unwrap() .values_mut() .next() .unwrap() .as_object_mut() .unwrap() .remove("routes"); assert_eq!( persistence::decode(&serde_json::to_vec(&json).unwrap()).unwrap(), project ); let v2 = include_str!("fixtures/v2-coastal-rescue.arcadia"); let migrated = language::parse(v2).unwrap(); assert!( migrated .diagrams .values() .all(|diagram| diagram.routes.is_empty()) ); assert!( language::parse("arcadia 2; project p \"P\" { diagram view \"View\" { routes []; } }") .is_err() ); } #[test] fn invalid_route_source_has_located_errors_and_no_silent_nested_defaults() { for route in [ "{}", "{ edge @signal; label { offset { x 0; y 0; }; }; }", "{ edge @signal; label { fraction 0.5; }; }", "{ edge @signal; label { fraction 1.1; offset { x 0; y 0; }; }; }", "{ edge @signal; waypoints [{ x 1000001; y 0; }]; }", "{ edge @signal; waypoints [{ x 0; }]; }", "{ edge @signal; label { fraction 0.5; offset { x 0; y 0; }; unknown true; }; }", "{ edge @report; }", "{ edge @absent; }", "{ edge @signal; }, { edge @signal; }", ] { let source = format!( "arcadia 3; project p \"P\" {{\n oa_activity report \"Report\" {{}}\n oa_activity assess \"Assess\" {{}}\n interaction signal \"Signal\" {{ source @report; target @assess; }}\n diagram view \"View\" {{\n routes [{route}];\n }}\n}}" ); let errors = language::parse(&source).expect_err(route); assert!( errors.iter().all(|error| error.line >= 5), "{route}: {errors:?}" ); } } #[test] fn route_records_share_model_admission_for_source_json_and_bundles() { let project = language::parse(SOURCE).unwrap(); // Project + three elements + relation + trace + two diagrams = eight, // plus three route records = eleven. Points do not create identities. let limits = language::ParseLimits { max_records: 10, ..language::ParseLimits::UNLIMITED }; assert!(limits.check_model(&project).is_err()); assert!( language::parse_limited(&language::format(&project), limits) .unwrap_err() .iter() .any(|error| error.code == "model_limit") ); assert!(matches!( persistence::decode_limited(&serde_json::to_vec(&project).unwrap(), limits), Err(persistence::FileError::Limit(_)) )); let bundle = format_bundle(&project); assert!( compile_bundle( &bundle, ProjectLimits { model: limits, ..ProjectLimits::default() } ) .unwrap_err() .iter() .any(|error| error.diagnostic.code == "model_limit") ); let admitted = language::ParseLimits { max_records: 11, ..limits }; assert!(admitted.check_model(&project).is_ok()); } #[test] fn route_commands_json_and_one_step_undo_recover_exact_model_and_canonical_source() { let original = language::parse(SOURCE).unwrap(); let diagram = id(&original, "architecture"); let edge = id(&original, "signal"); let before = language::format(&original); let mut session = Session::new(original.clone()); let set_json = json!({"SetEdgeRoute": {"diagram":diagram,"route":{ "edge":edge,"waypoints":[{"x":515.5,"y":-42.0}], "label":{"fraction":0.25,"offset":{"x":12.0,"y":-18.0}} }}}); let set: Command = serde_json::from_value(set_json.clone()).unwrap(); assert_eq!(serde_json::to_value(&set).unwrap(), set_json); session.execute(set).unwrap(); let updated = session.project().clone(); assert_eq!( updated.diagrams[&id(&updated, "review")], original.diagrams[&id(&original, "review")] ); assert_eq!( language::parse(&language::format(&updated)).unwrap(), updated ); session.undo(); assert_eq!(session.project(), &original); assert_eq!(language::format(session.project()), before); session.redo(); assert_eq!(session.project(), &updated); let remove: Command = serde_json::from_value(json!({"RemoveEdgeRoute":{"diagram":diagram,"edge":edge}})).unwrap(); session.execute(remove).unwrap(); assert_eq!(session.project().diagrams[&diagram].routes.len(), 1); session.undo(); assert_eq!(session.project(), &updated); }