Preserve editable routes across source projects and REST
This commit is contained in:
@@ -0,0 +1,198 @@
|
||||
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);
|
||||
}
|
||||
Reference in New Issue
Block a user