//! The actual State workspace picker, App root and subscriptions. use super::*; use crate::behavior::canvas_workspace::tests::{Mounted, key}; use iced::{keyboard::key::Named, mouse}; async fn press_escape(mounted: &mut Mounted, app: &mut App) -> Vec { let input = key(Named::Escape); let (mut messages, statuses) = mounted.input_with_status( app, std::slice::from_ref(&input), mouse::Cursor::Unavailable, ); assert_eq!( statuses, [iced::event::Status::Captured], "the mounted picker must capture Escape before App subscriptions" ); crate::app::escape::tests::subscriptions(app, input, statuses[0], &mut messages); for message in messages.iter().cloned() { let task = app.update(message); mounted.perform(app, task).await; } mounted.settle(app).await; messages } #[tokio::test] async fn mounted_state_missing_picker_escape_preserves_exact_document_then_reopens_and_places() { let mut f = fixture(); let diagram = create_view(&mut f).diagram; let mut project = f.app.project().clone(); project .diagrams .get_mut(&diagram) .unwrap() .nodes .retain(|_, node| node.element != f.b); let mut app = App::with_project(project); app.window_size = iced::Size::new(1800., 1100.); send(&mut app, Message::Select(f.a)); send(&mut app, Message::Page(Page::Behavior)); let choice = Choice { id: diagram, label: app.project().diagrams[&diagram].name.clone(), }; send(&mut app, Message::BehaviorDiagram(choice)); let mut mounted = Mounted::new(&mut app).await; assert_eq!(state_edit::unplaced(app.project(), diagram), vec![f.b]); // Locate the stable picker slot itself: selection controls may sit between // it and the canvas. The single menu option opens below this slot. let bounds = mounted.geometry(&app).bounds("state-missing-picker"); let picker = bounds.center(); let option = iced::Point::new(picker.x, bounds.y + bounds.height * 1.5); let before = app.document.snapshot(); let original = app.project().clone(); let source = app.document.bundle().clone(); let selection = ( app.selected, app.selected_node, app.behavior.diagram.placement, ); let camera = ( app.behavior.diagram.zoom, app.behavior.diagram.pan, app.behavior.diagram.fit, ); let revision = app.document.revision(); let history = (app.document.can_undo(), app.document.can_redo()); mounted.click(&mut app, picker).await; assert!( press_escape(&mut mounted, &mut app).await.is_empty(), "open picker owns Escape, not the State canvas or App" ); assert!(std::sync::Arc::ptr_eq(&before, &app.document.snapshot())); assert_eq!(app.document.bundle(), &source); assert_eq!( ( app.selected, app.selected_node, app.behavior.diagram.placement ), selection ); assert_eq!( ( app.behavior.diagram.zoom, app.behavior.diagram.pan, app.behavior.diagram.fit ), camera ); assert_eq!(app.document.revision(), revision); assert_eq!((app.document.can_undo(), app.document.can_redo()), history); // If Escape left the menu open this click would close it, and the following // option click would fail to place. This checks actual overlay dismissal. mounted.click(&mut app, picker).await; let messages = mounted.click(&mut app, option).await; assert!(messages.iter().any(|message| matches!(message, Message::StateDiagram(Event::Place { element, .. }) if *element == f.b))); assert!(state_edit::unplaced(app.project(), diagram).is_empty()); assert_eq!(app.project().elements, original.elements); assert_eq!(app.project().relations, original.relations); let _ = app.update(Message::Undo); assert_eq!(app.document.bundle(), &source); }