//! 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![(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![(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::>(), baseline.rows.keys().collect::>() ); assert_eq!( moved.lines.iter().map(|line| line.id).collect::>(), baseline .lines .iter() .map(|line| line.id) .collect::>() ); 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()); }