Allow Details properties for the exact displayed Saved scenario view

This commit is contained in:
2026-09-13 04:02:31 -07:00
parent ef1c1be704
commit 8bf7607a3f
4 changed files with 144 additions and 3 deletions
+1 -1
View File
@@ -1308,7 +1308,7 @@ impl App {
&& (self.scenario_context() != Some(captured)
|| self.selected != *id
|| !self.project().contains(*id)
|| behavior::selected_behavior(self.project(), *id) != Some(captured.scenario)
|| !behavior::scenario::properties_target(self.project(), captured, *id)
|| self.behavior.has_pending()
|| self.draft_dirty()
|| self.source_dirty()
@@ -18,6 +18,14 @@ mod workspace;
pub(crate) use workspace::NAME_INPUT;
pub use workspace::{Shelf, view};
// A picker may select the Saved diagram record itself. Another view of the
// same Scenario is not the property target of this displayed representation.
pub(crate) fn properties_target(p: &Project, context: Context, id: Id) -> bool {
super::selected_behavior(p, id) == Some(context.scenario)
|| matches!(context.representation, Representation::Saved { diagram } if diagram == id)
&& presentation::checked_owner(p, context.representation) == Some(context.scenario)
}
#[cfg(test)]
#[path = "scenario_operation_tests.rs"]
mod operation_tests;
@@ -372,7 +372,7 @@ pub fn view<'a>(app: &'a App, owner: Id, out: Column<'a, Message>) -> Element<'a
let mut details = column![
text(format!("Selected: {}", p.named(app.selected))).size(12),
container(button(text("Properties").size(12)).on_press_maybe(
(clean && super::super::selected_behavior(p, app.selected) == Some(owner)).then_some(
(clean && properties_target(p, context, app.selected)).then_some(
Message::ScenarioCanvas(CanvasEvent::Properties(context, app.selected)),
),
))
@@ -227,6 +227,111 @@ fn details_properties_refuses_pending_property_source_and_behavior_drafts_withou
}
}
#[tokio::test]
async fn focused_details_opens_current_saved_view_properties_after_following_diagram_picker() {
let (mut app, a, _) = setup();
let b = id(&app, "incident_wide");
// Selecting the current view record makes the real picker follow that
// record selection to B. Do not seed selected=B or its property draft.
let _ = app.update(Message::Select(a));
assert_eq!(app.selected, a);
let _ = app.update(Message::Diagram(crate::app::Choice {
id: b,
label: "B".into(),
}));
assert_eq!(app.selected, b);
assert_eq!(app.draft.as_ref().unwrap().original, b);
assert_eq!(
app.scenario_context().unwrap().representation,
Representation::Saved { diagram: b }
);
let _ = app.update(Message::InspectorTab(
crate::navigation::InspectorTab::Connections,
));
let mut mounted = Mounted::new(&mut app).await;
open_focused_details(&mut app);
mounted.settle(&mut app).await;
let context = app.scenario_context().unwrap();
let camera = stored_camera(&app);
let snapshot = app.document.snapshot();
let bundle = app.document.bundle().clone();
let before = mounted.geometry(&app);
assert!(!before.scrolls.contains_key(&widget::Id::new("inspector")));
let (details, offset) = before.scrolls[&widget::Id::new("scenario-details")];
let point = before.bounds("scenario-properties").center() - offset;
assert!(details.contains(point));
let events = mounted.click(&mut app, point).await;
assert!(events.iter().any(|event| matches!(event,
Message::ScenarioCanvas(CanvasEvent::Properties(c, selected))
if *c == context && *selected == b)));
mounted.settle(&mut app).await;
let shown = mounted.geometry(&app);
assert!(
!shown
.scrolls
.contains_key(&widget::Id::new("scenario-details"))
);
let (inspector, offset) = shown.scrolls[&widget::Id::new("inspector")];
assert!(
shown
.text_inputs
.iter()
.any(|bounds| inspector.contains(bounds.center() - offset)),
"current Saved diagram has a visible editable inspector"
);
assert!(!app.behavior.scenario.focused());
assert_eq!(
app.inspector_tab,
crate::navigation::InspectorTab::Properties
);
assert_eq!(app.selected, b);
assert_eq!(app.draft.as_ref().unwrap().original, b);
assert_eq!(app.active_diagram, Some(b));
assert_eq!(app.page, crate::app::Page::Behavior);
assert_eq!(
app.scenario_context().unwrap().representation,
context.representation
);
assert_eq!(app.scenario_context().unwrap().visit, context.visit);
assert_eq!(stored_camera(&app), camera);
assert!(Arc::ptr_eq(&snapshot, &app.document.snapshot()));
assert_eq!(app.document.bundle(), &bundle);
assert!(!app.document.can_undo() && !app.document.can_redo());
}
#[test]
fn details_properties_refuses_another_saved_view_even_with_the_same_scenario_owner() {
let (mut app, a, _) = setup();
let b = id(&app, "incident_wide");
let _ = app.update(Message::Diagram(crate::app::Choice {
id: b,
label: "B".into(),
}));
let _ = app.update(Message::Select(a));
assert_eq!(app.selected, a);
open_focused_details(&mut app);
let context = app.scenario_context().unwrap();
assert_eq!(context.representation, Representation::Saved { diagram: b });
assert_eq!(
presentation::checked_owner(app.project(), Representation::Saved { diagram: a }),
Some(context.scenario)
);
assert!(!properties_target(app.project(), context, a));
let camera = app.behavior.scenario.camera_boundary();
let epochs = (app.canvas_epoch, app.camera_epoch);
let snapshot = app.document.snapshot();
let bundle = app.document.bundle().clone();
let _ = app.update(Message::ScenarioCanvas(CanvasEvent::Properties(context, a)));
assert!(app.behavior.scenario.camera_boundary() == camera);
assert_eq!((app.canvas_epoch, app.camera_epoch), epochs);
assert_eq!(app.scenario_context(), Some(context));
assert_eq!(app.selected, a);
assert_eq!(app.draft.as_ref().unwrap().original, a);
assert!(Arc::ptr_eq(&snapshot, &app.document.snapshot()));
assert_eq!(app.document.bundle(), &bundle);
assert!(!app.document.can_undo() && !app.document.can_redo());
}
#[test]
fn details_properties_rejects_retired_view_selection_and_epoch_without_changing_panels() {
for kind in 0..3 {
@@ -294,6 +399,34 @@ fn opening_scenario_properties_cancels_held_spacing_and_rejects_its_late_finish(
let _ = app.update(Message::ScenarioCanvas(CanvasEvent::Spacing(begin)));
assert_eq!(app.behavior.scenario.spacing_token(), Some(begin.token()));
let snapshot = app.document.snapshot();
let end = press_screen + Vector::new(0., 12.);
let _ = app.update(Message::ScenarioCanvas(CanvasEvent::Spacing(
Spacing::Move {
stamp: begin.stamp(),
token: begin.token(),
screen: end,
},
)));
let paint = app
.behavior
.scenario
.spacing_paint(begin.stamp().context, begin.stamp().camera_epoch)
.unwrap();
assert!(paint.refusal.is_none());
let line = paint
.scene
.lines
.iter()
.find(|line| line.id == message)
.unwrap();
assert!(
line.points[0].y > 160.,
"a real proposed scene moved the first message"
);
assert!(
Arc::ptr_eq(&snapshot, &app.document.snapshot()),
"preview is not authored"
);
let camera = stored_camera(&app);
send(&mut app, |c| CanvasEvent::Properties(c, message));
assert!(app.behavior.scenario.spacing_token().is_none());
@@ -302,7 +435,7 @@ fn opening_scenario_properties_cancels_held_spacing_and_rejects_its_late_finish(
Spacing::Finish {
stamp: begin.stamp(),
token: begin.token(),
screen: press_screen + Vector::new(0., 40.),
screen: end,
},
)));
assert!(Arc::ptr_eq(&snapshot, &app.document.snapshot()));