260 lines
9.6 KiB
Rust
260 lines
9.6 KiB
Rust
//! Real current Scene/lifecycle evaluator, independent of the original fixture.
|
|
//! No replacement activation geometry or future peer implementation is used.
|
|
use super::*;
|
|
use crate::document::Document;
|
|
use oa_core::source_project::{ProjectLimits, SourceBundle};
|
|
|
|
fn lifetime_document() -> (Document, Id) {
|
|
let source = include_str!("lifetime.arcadia");
|
|
let parsed = language::parse(source).expect("separate lifetime source parses");
|
|
let errors: Vec<_> = validation::validate(&parsed)
|
|
.into_iter()
|
|
.filter(|issue| issue.severity == validation::Severity::Error)
|
|
.collect();
|
|
assert!(
|
|
errors.is_empty(),
|
|
"lifetime fixture semantic errors: {errors:?}"
|
|
);
|
|
let owner = id(&parsed, "lifetime_conversation");
|
|
let bundle = SourceBundle {
|
|
entry: "lifetime.arcadia".into(),
|
|
files: BTreeMap::from([("lifetime.arcadia".into(), source.into())]),
|
|
};
|
|
let document = Document::from_bundle(bundle.clone(), ProjectLimits::default())
|
|
.expect("lifetime fixture is ordinarily admitted");
|
|
assert_eq!(document.project(), &parsed);
|
|
assert_eq!(document.bundle(), &bundle);
|
|
assert_eq!(language::parse(&language::format(&parsed)).unwrap(), parsed);
|
|
(document, owner)
|
|
}
|
|
|
|
/// Numeric expectations are hand-derived current-Scene metrics, not proposed
|
|
/// permanent spacing rules: Action95, message92, initial occurrence148.
|
|
fn assert_lifetime_geometry(
|
|
scene: &Scene,
|
|
project: &Project,
|
|
owner: Id,
|
|
birth: f32,
|
|
call_y: f32,
|
|
death: f32,
|
|
) {
|
|
let requester = id(project, "requester");
|
|
let provider = id(project, "provider");
|
|
let create = id(project, "create_receiver");
|
|
let call = id(project, "service_call");
|
|
let delete = id(project, "delete_receiver");
|
|
let left = scene.lanes[&requester];
|
|
let right = scene.lanes[&provider];
|
|
assert_eq!((left, right), (160., 400.));
|
|
assert_eq!(row(scene, id(project, "before_birth")), 148.);
|
|
assert_eq!(row(scene, create), birth);
|
|
assert_eq!(row(scene, call), call_y);
|
|
assert_eq!(row(scene, delete), death);
|
|
assert!(
|
|
birth > 148.,
|
|
"receiver creation is delayed beyond the opening action"
|
|
);
|
|
assert_eq!(scene.rows[&create].0, owner);
|
|
assert_eq!(scene.rows[&delete].0, owner);
|
|
|
|
// Exercise the actual Rendering bridge, including marker hit identity and
|
|
// its clipping contract, rather than rebuilding lifetimes from our rows.
|
|
let alive = scene
|
|
.lifecycle
|
|
.alive_spans(provider, owner, 85., scene.size.height - 20.);
|
|
assert_eq!(
|
|
alive
|
|
.iter()
|
|
.map(|span| (span.start, span.end))
|
|
.collect::<Vec<_>>(),
|
|
vec![(birth, death)]
|
|
);
|
|
assert!(
|
|
scene
|
|
.lifecycle
|
|
.alive_spans(provider, owner, 85., birth)
|
|
.is_empty()
|
|
);
|
|
assert!(
|
|
scene
|
|
.lifecycle
|
|
.alive_spans(provider, owner, death, scene.size.height - 20.)
|
|
.is_empty()
|
|
);
|
|
let sender_alive = scene
|
|
.lifecycle
|
|
.alive_spans(requester, owner, 85., scene.size.height - 20.);
|
|
assert_eq!(
|
|
sender_alive
|
|
.iter()
|
|
.map(|span| (span.start, span.end))
|
|
.collect::<Vec<_>>(),
|
|
vec![(85., scene.size.height - 20.)]
|
|
);
|
|
assert_eq!(
|
|
scene.lifecycle.marker_at(Point::new(right, birth)),
|
|
Some(create)
|
|
);
|
|
assert_eq!(
|
|
scene.lifecycle.marker_at(Point::new(right, death)),
|
|
Some(delete)
|
|
);
|
|
assert_eq!(scene.hit(Point::new(right, birth)), Some(create));
|
|
assert_eq!(scene.hit(Point::new(right, death)), Some(delete));
|
|
assert_eq!(
|
|
line(scene, create).points,
|
|
vec![Point::new(left, birth), Point::new(right - 70., birth)]
|
|
);
|
|
assert_eq!(
|
|
line(scene, call).points,
|
|
vec![Point::new(left, call_y), Point::new(right, call_y)]
|
|
);
|
|
assert_eq!(
|
|
line(scene, delete).points,
|
|
vec![Point::new(left, death), Point::new(right, death)]
|
|
);
|
|
assert!(line(scene, create).label_at.y + 22. < birth - 14.);
|
|
|
|
// Inspect actual activation boxes produced by Scene::activation_sequence.
|
|
// A correlated Reply is absent, so death must clip the bar, not close the
|
|
// semantic call. Do not accept a new generic minimum-height tail after death.
|
|
let bars: Vec<_> = scene
|
|
.boxes
|
|
.iter()
|
|
.filter(|item| item.id == call && item.title.is_empty())
|
|
.collect();
|
|
assert_eq!(bars.len(), 1);
|
|
assert_eq!(
|
|
bars[0].rect,
|
|
Rectangle::new(
|
|
Point::new(right - 4., call_y),
|
|
Size::new(8., death - call_y)
|
|
)
|
|
);
|
|
assert_eq!(bars[0].rect.y + bars[0].rect.height, death);
|
|
assert!(
|
|
bars.iter()
|
|
.all(|item| item.rect.y >= birth && item.rect.y + item.rect.height <= death)
|
|
);
|
|
assert_eq!(scene.open_calls, vec![Point::new(right + 12., death - 16.)]);
|
|
assert!(!project.elements.values().any(|element| matches!(
|
|
element.shared(),
|
|
Some(SharedBody::Occurrence {
|
|
body: OccurrenceBody::Exchange {
|
|
message_kind: MessageKind::Reply,
|
|
..
|
|
},
|
|
..
|
|
})
|
|
)));
|
|
}
|
|
|
|
#[test]
|
|
fn delayed_create_call_and_delete_have_real_lifetime_markers_and_exact_clipped_activation() {
|
|
let (document, owner) = lifetime_document();
|
|
let before = document.snapshot();
|
|
let source = document.bundle().clone();
|
|
let scene = Scene::sequence(document.project(), owner, owner);
|
|
assert_lifetime_geometry(&scene, document.project(), owner, 243., 335., 427.);
|
|
assert_eq!(scene.size.height, 564.);
|
|
assert_eq!(scene.sequence_bounds[&owner], (106., 477.));
|
|
same_geometry(&scene, &render(document.project(), owner, &Layout::new()));
|
|
let reparsed = language::parse(&language::format(document.project())).unwrap();
|
|
same_geometry(&scene, &Scene::sequence(&reparsed, owner, owner));
|
|
assert_eq!(document.project(), &before.decoded.project);
|
|
assert_eq!(document.bundle(), &source);
|
|
assert!(!document.dirty() && !document.can_undo() && !document.can_redo());
|
|
}
|
|
|
|
#[test]
|
|
fn moving_create_moves_real_lifetime_and_followers_without_changing_semantics_or_source() {
|
|
let (document, owner) = lifetime_document();
|
|
let before = document.snapshot();
|
|
let source = document.bundle().clone();
|
|
let p = document.project();
|
|
let create = id(p, "create_receiver");
|
|
let provider = id(p, "provider");
|
|
let baseline = Scene::sequence(p, owner, owner);
|
|
let layout = drag(&Layout::new(), &baseline, create, 60.);
|
|
assert_eq!(layout, Layout::from([(create, 303.)]));
|
|
let moved = render(p, owner, &layout);
|
|
assert_lifetime_geometry(&moved, p, owner, 303., 395., 487.);
|
|
assert_eq!(moved.size.height, 624.);
|
|
assert_eq!(
|
|
moved
|
|
.lifecycle
|
|
.marker_at(Point::new(moved.lanes[&provider], 243.)),
|
|
None
|
|
);
|
|
assert_eq!(
|
|
named_box(&moved, id(p, "before_birth")).rect,
|
|
named_box(&baseline, id(p, "before_birth")).rect
|
|
);
|
|
assert_eq!(
|
|
moved.rows.keys().collect::<Vec<_>>(),
|
|
baseline.rows.keys().collect::<Vec<_>>()
|
|
);
|
|
assert_eq!(
|
|
moved.lines.iter().map(|line| line.id).collect::<Vec<_>>(),
|
|
baseline
|
|
.lines
|
|
.iter()
|
|
.map(|line| line.id)
|
|
.collect::<Vec<_>>()
|
|
);
|
|
let reparsed = language::parse(&language::format(p)).unwrap();
|
|
same_geometry(&moved, &render(&reparsed, owner, &layout));
|
|
assert_eq!(p, &before.decoded.project);
|
|
assert_eq!(document.bundle(), &source);
|
|
assert!(!document.dirty() && !document.can_undo() && !document.can_redo());
|
|
}
|
|
|
|
#[test]
|
|
fn moving_delete_extends_only_to_actual_death_and_zero_motion_preserves_latent_lifetime_intent() {
|
|
let (document, owner) = lifetime_document();
|
|
let before = document.snapshot();
|
|
let source = document.bundle().clone();
|
|
let p = document.project();
|
|
let create = id(p, "create_receiver");
|
|
let delete = id(p, "delete_receiver");
|
|
let provider = id(p, "provider");
|
|
let baseline = Scene::sequence(p, owner, owner);
|
|
let later_delete = drag(&Layout::new(), &baseline, delete, 48.);
|
|
let extended = render(p, owner, &later_delete);
|
|
assert_lifetime_geometry(&extended, p, owner, 243., 335., 475.);
|
|
assert_eq!(extended.size.height, 612.);
|
|
assert_eq!(
|
|
extended
|
|
.lifecycle
|
|
.marker_at(Point::new(extended.lanes[&provider], 427.)),
|
|
None
|
|
);
|
|
assert_eq!(
|
|
line(&extended, create).points,
|
|
line(&baseline, create).points
|
|
);
|
|
assert_eq!(
|
|
line(&extended, id(p, "service_call")).points,
|
|
line(&baseline, id(p, "service_call")).points
|
|
);
|
|
|
|
// Create displacement forces the displayed death to487 while its authored
|
|
// request stays427. Both signed zero deltas must preserve that latent value.
|
|
let original = Layout::from([(create, 243.), (delete, 427.)]);
|
|
let displaced = drag(&original, &baseline, create, 60.);
|
|
let frame = render(p, owner, &displaced);
|
|
assert_lifetime_geometry(&frame, p, owner, 303., 395., 487.);
|
|
assert_eq!(displaced[&delete], 427.);
|
|
for delta in [0., -0.] {
|
|
let untouched = drag(&displaced, &frame, delete, delta);
|
|
assert_eq!(untouched, displaced);
|
|
same_geometry(&frame, &render(p, owner, &untouched));
|
|
let restored = drag(&untouched, &frame, create, -60.);
|
|
assert_eq!(restored, original);
|
|
same_geometry(&baseline, &render(p, owner, &restored));
|
|
}
|
|
assert_eq!(p, &before.decoded.project);
|
|
assert_eq!(document.bundle(), &source);
|
|
assert!(!document.dirty() && !document.can_undo() && !document.can_redo());
|
|
}
|