//! App-boundary regressions for the coordinated parent wiring. No I/O task is //! polled: file dialogs, checkpoint writes and lesson loading remain inactive. use super::*; use crate::app::{App, Message, Page, Pending}; use std::sync::Arc; fn send(app: &mut App, event: Event) { let _ = app.update(Message::Behavior(crate::behavior::Event::Creation(event))); } fn raw_property(app: &App) -> serde_json::Value { serde_json::to_value(app.draft.as_ref().unwrap().recovery_snapshot()).unwrap() } fn begin(app: &mut App, layer: Layer, family: Family, name: &str) { let _ = app.update(Message::Layer(layer)); let _ = app.update(Message::Page(Page::Behavior)); send(app, Event::Open(layer)); send(app, Event::Choose(family)); send(app, Event::Name(name.into())); } fn focused_escape(app: &App) -> Message { let mut messages = crate::app::escape::tests::focused_input_messages(app); assert_eq!(messages.len(), 1); assert!(matches!(messages[0], Message::Escape)); messages.remove(0) } #[test] fn app_first_captured_escape_dismisses_file_or_pending_overlay_before_creation() { for file in [false, true] { let mut app = App::with_project(Project::empty()); begin( &mut app, Layer::Oa, Family::Sequence, "Keep while overlay closes", ); let raw = app.behavior.recovery_snapshot().unwrap(); let accepted = app.document.snapshot(); if file { app.file_prompt = Some(crate::app::FilePrompt { operation: crate::app::FileOperation::Save, path: "unsubmitted-name.arcadia".into(), overwrite: false, }); } else { app.pending = Some(Pending::Walkthrough); } let _ = app.update(focused_escape(&app)); assert!(app.file_prompt.is_none()); assert!(app.pending.is_none()); assert_eq!(app.behavior.recovery_snapshot().unwrap(), raw); assert!(Arc::ptr_eq(&accepted, &app.document.snapshot())); let _ = app.update(focused_escape(&app)); assert!(!app.behavior.has_pending()); assert!(Arc::ptr_eq(&accepted, &app.document.snapshot())); assert!(!app.document.can_undo()); } } #[test] fn app_creation_opens_the_actual_new_owner_instead_of_first_or_previously_active_behavior() { for layer in [Layer::Oa, Layer::Sa] { let mut p = Project::empty(); let old = p .add_element(Family::Sequence.kind(layer), layer, "Old behavior", None) .unwrap(); let mut app = App::with_project(p.clone()); let _ = app.update(Message::Select(old)); begin(&mut app, layer, Family::Sequence, " New exact path 雨 "); let target = app.behavior.creation.created().unwrap(); assert_ne!(target.behavior, old); let before = app.document.bundle().clone(); send(&mut app, Event::Submit); assert_eq!(app.page, Page::Behavior); assert_eq!(app.layer, layer); assert_eq!(app.selected, target.behavior); assert_eq!(app.behavior.active_owner, Some(target.behavior)); assert_eq!(crate::behavior::selected_owner(&app), Some(target.behavior)); assert_eq!( app.project().elements[&target.behavior].name, " New exact path 雨 " ); assert!(!app.behavior.has_pending()); let after = app.document.bundle().clone(); let _ = app.update(Message::Undo); assert_eq!(app.document.bundle(), &before); assert!(!app.document.can_undo()); let _ = app.update(Message::Redo); assert_eq!(app.document.bundle(), &after); assert_eq!(app.project().elements[&old], p.elements[&old]); } } #[test] fn app_machine_behavior_submit_opens_its_opted_in_view_as_one_undoable_transaction() { for first_view in [false, true] { let mut p = Project::empty(); let owner = p .add_element(Kind::System, Layer::Sa, "Flight system", None) .unwrap(); let mut app = App::with_project(p.clone()); begin(&mut app, Layer::Sa, Family::Machine, " Flight modes 雨 "); send(&mut app, Event::Owner(owner)); send(&mut app, Event::FirstView(first_view)); let target = app.behavior.creation.created().unwrap(); let before = app.document.bundle().clone(); let _ = app.update(Message::Behavior(crate::behavior::Event::Submit)); assert!(!app.behavior.has_pending(), "{}", app.status); assert_eq!(app.selected, target.behavior); assert_eq!(app.behavior.active_owner, Some(target.behavior)); assert_eq!(app.behavior.diagram.diagram, target.diagram); assert_eq!( crate::behavior::state_view::diagram(&app, target.behavior), target.diagram ); assert_eq!(app.project().elements[&target.behavior].parent, Some(owner)); assert_eq!(app.page, Page::Behavior); let after = app.document.bundle().clone(); let _ = app.update(Message::Undo); assert_eq!(app.document.bundle(), &before); assert_eq!(app.project(), &p); assert!(!app.document.can_undo()); let _ = app.update(Message::Redo); assert_eq!(app.document.bundle(), &after); } } #[test] fn app_stale_layer_open_never_starts_or_replaces_a_draft() { let mut app = App::with_project(Project::empty()); let _ = app.update(Message::Layer(Layer::Sa)); app.page = Page::Behavior; let accepted = app.document.snapshot(); send(&mut app, Event::Open(Layer::Oa)); assert!(!app.behavior.has_pending()); assert_eq!(app.layer, Layer::Sa); send(&mut app, Event::Open(Layer::Sa)); send(&mut app, Event::Choose(Family::Sequence)); send(&mut app, Event::Name("Retained system chain".into())); let raw = app.behavior.recovery_snapshot().unwrap(); send(&mut app, Event::Open(Layer::Oa)); assert_eq!(app.behavior.recovery_snapshot().unwrap(), raw); assert!(Arc::ptr_eq(&accepted, &app.document.snapshot())); } #[test] fn app_new_never_auto_applies_valid_or_invalid_property_and_source_drafts() { for dirty in [ "property-valid", "property-invalid", "source-valid", "source-invalid", ] { let mut app = App::with_project(Project::empty()); app.page = Page::Behavior; match dirty { "property-valid" => app .draft .as_mut() .unwrap() .input("/name", "Raw property name".into()), "property-invalid" => app.draft.as_mut().unwrap().input("/id", "-".into()), "source-valid" => app.source_workspace.edit( format!("{}\n// Retained comment\n", app.source_workspace.text()), app.source_editor.cursor(), ), _ => app .source_workspace .edit("unfinished source 雨".into(), app.source_editor.cursor()), } let accepted = app.document.snapshot(); let property = raw_property(&app); let workspace = app.source_workspace.recovery_snapshot(); send(&mut app, Event::Open(Layer::Oa)); assert!( !app.behavior.creation.has_pending(), "{dirty}: New must refuse without applying another editor" ); assert!(Arc::ptr_eq(&accepted, &app.document.snapshot())); assert_eq!(raw_property(&app), property); assert_eq!( serde_json::to_value(app.source_workspace.recovery_snapshot()).unwrap(), serde_json::to_value(workspace).unwrap() ); assert!(!app.document.can_undo()); } } #[test] fn app_choosing_another_behavior_or_layer_cannot_commit_a_creation_draft() { let mut p = Project::empty(); let old = p .add_element(Kind::Process, Layer::Oa, "Old process", None) .unwrap(); let mut app = App::with_project(p); begin(&mut app, Layer::Oa, Family::Sequence, "Retain this process"); let raw = app.behavior.recovery_snapshot().unwrap(); let accepted = app.document.snapshot(); let _ = app.update(Message::Layer(Layer::Sa)); let _ = app.update(Message::BehaviorOwner(crate::app::Choice { id: old, label: "Old process".into(), })); assert_eq!(app.layer, Layer::Oa); assert_eq!(app.behavior.recovery_snapshot().unwrap(), raw); assert!(Arc::ptr_eq(&accepted, &app.document.snapshot())); let _ = app.update(Message::Escape); assert!(!app.behavior.has_pending()); assert!(Arc::ptr_eq(&accepted, &app.document.snapshot())); assert!(!app.document.can_undo()); } #[test] fn app_lesson_request_cancel_retains_creation_then_escape_discards_only_the_draft() { let mut app = App::with_project(Project::empty()); begin( &mut app, Layer::Oa, Family::Sequence, "Unfinished engineering path", ); let raw = app.behavior.recovery_snapshot().unwrap(); let accepted = app.document.snapshot(); let task = app.update(Message::Home(crate::home::Event::Walkthrough)); assert!(matches!(app.pending, Some(Pending::Walkthrough))); assert!(iced_runtime::task::into_stream(task).is_none()); let _ = app.update(Message::Escape); assert!(app.pending.is_none()); assert_eq!(app.behavior.recovery_snapshot().unwrap(), raw); assert!(Arc::ptr_eq(&accepted, &app.document.snapshot())); let _ = app.update(Message::Escape); assert!(!app.behavior.has_pending()); assert!(Arc::ptr_eq(&accepted, &app.document.snapshot())); } #[test] fn app_recovered_rootless_sa_creation_retains_layer_ids_and_other_raw_drafts() { let mut app = App::with_project(Project::empty()); begin( &mut app, Layer::Sa, Family::Scenario, " Recovered system scenario 雨 ", ); send( &mut app, Event::Viewpoint(ScenarioViewpoint::BoundaryInterface), ); // Recovery can contain simultaneous drafts even though new input is guarded. app.draft .as_mut() .unwrap() .input("/name", "Retained property".into()); app.source_workspace.edit( "unfinished source preserved".into(), app.source_editor.cursor(), ); let snapshot = crate::recovery::Snapshot::capture( app.canvas_document, &app.document, &app.source_workspace, app.draft.as_ref(), &app.behavior, None, app.selected, ) .unwrap(); let expected = snapshot.clone().restore().unwrap(); let raw = app.behavior.recovery_snapshot().unwrap(); let mut restored = App::with_project(Project::empty()); let _ = restored.update(Message::CheckpointLoaded(Ok(Arc::new(snapshot)))); assert_eq!(restored.page, Page::Behavior); assert_eq!(restored.layer, Layer::Sa); assert_eq!(restored.behavior.recovery_snapshot().unwrap(), raw); assert_eq!( restored.behavior.creation.created(), app.behavior.creation.created() ); assert_eq!(restored.project(), expected.document.project()); assert_eq!(restored.document.bundle(), expected.document.bundle()); assert_eq!( restored.source_workspace.bundle(), expected.workspace.bundle() ); assert_eq!(raw_property(&restored), raw_property(&app)); let before = restored.document.snapshot(); send(&mut restored, Event::Submit); assert!(Arc::ptr_eq(&before, &restored.document.snapshot())); assert_eq!(restored.behavior.recovery_snapshot().unwrap(), raw); assert_eq!(raw_property(&restored), raw_property(&app)); }