Fix State transition multiline label geometry and selection
This commit is contained in:
@@ -0,0 +1,145 @@
|
||||
use oa_core::{projection::project_diagram, routing::*, *};
|
||||
|
||||
fn fixture() -> (Project, Id, Id) {
|
||||
let mut project = language::parse(include_str!(
|
||||
"../../../harness/experiments/edge-attachments-20260922/native-fixture.arcadia"
|
||||
))
|
||||
.unwrap();
|
||||
let edge = Id::parse_str("ea202922-0033-4000-8000-000000000041").unwrap();
|
||||
let diagram = Id::parse_str("ea202922-0033-4000-8000-000000000200").unwrap();
|
||||
let relation = project.relations.get_mut(&edge).unwrap();
|
||||
relation.name = "Ready\nfor review".into();
|
||||
if let RelationBody::StateTransition { guard, .. } = &mut relation.body {
|
||||
*guard = "request_ready".into();
|
||||
}
|
||||
(project, diagram, edge)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn state_two_block_layout_separates_lines_and_keeps_authored_geometry_and_project() {
|
||||
let (project, diagram, edge) = fixture();
|
||||
let before = project.clone();
|
||||
let diagram = &project.diagrams[&diagram];
|
||||
let view = project_diagram(&project, diagram);
|
||||
let routed = route_diagram(&project, diagram, &view);
|
||||
let edge_geometry = &routed.edges[&edge];
|
||||
let label = &edge_geometry.label;
|
||||
let layout = label.state_layout.as_ref().unwrap();
|
||||
assert_eq!(layout.title_lines, ["Ready", "for review"]);
|
||||
assert_eq!(
|
||||
layout.summary_lines,
|
||||
["External · Completion [request_ready]"]
|
||||
);
|
||||
assert_eq!(label.text, "External · Completion [request_ready]");
|
||||
assert_eq!(layout.title_line_position(1), Position { x: 5., y: 12. });
|
||||
assert_eq!(layout.summary_line_position(0), Position { x: 5., y: 24. });
|
||||
assert_eq!(
|
||||
label.bounds.size,
|
||||
Size {
|
||||
width: 269.,
|
||||
height: 36.
|
||||
}
|
||||
);
|
||||
let authored = diagram
|
||||
.routes
|
||||
.iter()
|
||||
.find(|r| r.edge == edge)
|
||||
.unwrap()
|
||||
.label
|
||||
.as_ref()
|
||||
.unwrap();
|
||||
assert_eq!(label.fraction, authored.fraction);
|
||||
let anchor = point_along_polyline(&edge_geometry.points, authored.fraction).unwrap();
|
||||
assert_eq!(label.anchor, anchor);
|
||||
assert_eq!(
|
||||
label.center,
|
||||
Position {
|
||||
x: anchor.x + authored.offset.x,
|
||||
y: anchor.y + authored.offset.y
|
||||
}
|
||||
);
|
||||
assert_eq!(project, before);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn state_display_breaks_tabs_and_blank_lines_are_single_sourced() {
|
||||
let summary = "A sufficiently long unchanged summary";
|
||||
for title in [
|
||||
"Ready\nfor review",
|
||||
"Ready\r\nfor review",
|
||||
"Ready\rfor review",
|
||||
"Ready\u{85}for review",
|
||||
"Ready\u{2028}for review",
|
||||
"Ready\u{2029}for review",
|
||||
] {
|
||||
let layout = StateLabelLayout::new(title, summary, &|s| s.chars().count() as f32);
|
||||
assert_eq!(layout.title_lines, ["Ready", "for review"]);
|
||||
assert_eq!(layout.summary_line_position(0).y, 24.);
|
||||
}
|
||||
let layout = StateLabelLayout::new("Ready\n\nfor\treview\n", summary, &|s| {
|
||||
s.chars().count() as f32
|
||||
});
|
||||
assert_eq!(layout.title_lines, ["Ready", "", "for review", ""]);
|
||||
assert_eq!(layout.summary_line_position(0).y, 48.);
|
||||
assert_eq!(layout.size().height, 60.);
|
||||
let empty = StateLabelLayout::new("", "", &|_| 0.);
|
||||
assert!(empty.title_lines.is_empty() && empty.summary_lines.is_empty());
|
||||
assert_eq!(empty.size().height, 24.);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn state_summary_raw_prefix_and_title_budget_survive_display_normalization() {
|
||||
let (mut project, diagram, edge) = fixture();
|
||||
let relation = project.relations.get_mut(&edge).unwrap();
|
||||
relation.name = "T".repeat(80);
|
||||
if let RelationBody::StateTransition { guard, .. } = &mut relation.body {
|
||||
*guard = format!("first\r\n{}", "guard".repeat(20));
|
||||
}
|
||||
let full = state_transition_summary(&project, &project.relations[&edge]);
|
||||
let raw: String = full.chars().take(47).chain(std::iter::once('…')).collect();
|
||||
let d = &project.diagrams[&diagram];
|
||||
let routed = route_diagram(&project, d, &project_diagram(&project, d));
|
||||
let label = &routed.edges[&edge].label;
|
||||
assert_eq!(label.text, raw);
|
||||
assert!(label.text.contains("\r\n"));
|
||||
let layout = label.state_layout.as_ref().unwrap();
|
||||
assert_eq!(layout.title_lines, [format!("{}…", "T".repeat(56))]);
|
||||
assert_eq!(
|
||||
layout.summary_lines,
|
||||
raw.replace("\r\n", "\n")
|
||||
.split('\n')
|
||||
.map(str::to_owned)
|
||||
.collect::<Vec<_>>()
|
||||
);
|
||||
assert_eq!(label.text.chars().count(), 48);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn supplied_measurement_controls_settled_and_preview_state_bounds_only() {
|
||||
let (project, diagram, edge) = fixture();
|
||||
let d = &project.diagrams[&diagram];
|
||||
let view = project_diagram(&project, d);
|
||||
let baseline = route_diagram(&project, d, &view);
|
||||
let wide = |_: &str| 400.;
|
||||
let measured = route_diagram_with_state_label_measurement(&project, d, &view, &wide);
|
||||
let label = &measured.edges[&edge].label;
|
||||
assert_eq!(
|
||||
label.bounds.size,
|
||||
Size {
|
||||
width: 410.,
|
||||
height: 36.
|
||||
}
|
||||
);
|
||||
assert_eq!(label.center, baseline.edges[&edge].label.center);
|
||||
assert_eq!(measured.edges[&edge].points, baseline.edges[&edge].points);
|
||||
let preview =
|
||||
route_edge_preview_with_state_label_measurement(&project, d, &view, &measured, edge, &wide);
|
||||
assert_eq!(preview.edges[&edge].label, *label);
|
||||
let mut ordinary = d.clone();
|
||||
ordinary.projection = ProjectionKind::Allocation;
|
||||
let ordinary_measured =
|
||||
route_diagram_with_state_label_measurement(&project, &ordinary, &view, &wide);
|
||||
let ordinary_default = route_diagram(&project, &ordinary, &view);
|
||||
assert!(ordinary_measured.edges[&edge].label.state_layout.is_none());
|
||||
assert_eq!(ordinary_measured, ordinary_default);
|
||||
}
|
||||
Reference in New Issue
Block a user