Fix State transition multiline label geometry and selection

This commit is contained in:
2026-09-24 15:50:56 -07:00
parent 715036e132
commit 2af40c7e27
15 changed files with 1023 additions and 49 deletions
+145
View File
@@ -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);
}