199 lines
7.1 KiB
Rust
199 lines
7.1 KiB
Rust
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);
|
|
}
|