280 lines
11 KiB
Rust
280 lines
11 KiB
Rust
//! Actual arrangement selection and current-record Details admission.
|
|
use super::*;
|
|
use crate::behavior::canvas_workspace::tests::Mounted;
|
|
use crate::behavior::scenario::{CanvasEvent, Context, Shelf};
|
|
use crate::behavior::scenario_layout::Representation;
|
|
use iced::{Point, Size, advanced::widget};
|
|
|
|
const FIXTURE: &str = include_str!("diagram_usages/tests/saved_scenario.arcadia");
|
|
|
|
fn id(app: &App, alias: &str) -> Id {
|
|
app.project()
|
|
.identities()
|
|
.into_iter()
|
|
.find(|(_, name)| *name == alias)
|
|
.unwrap()
|
|
.0
|
|
}
|
|
fn setup() -> (App, Id, Id, Id) {
|
|
let mut app = App::with_project(oa_core::language::parse(FIXTURE).unwrap());
|
|
let a = id(&app, "incident_readable");
|
|
let b = id(&app, "incident_wide");
|
|
let owner = id(&app, "incident_response");
|
|
let _ = app.update(Message::Select(owner));
|
|
let _ = app.update(Message::Page(Page::Behavior));
|
|
choose(&mut app, Representation::Saved { diagram: a });
|
|
let _ = app.update(Message::Select(a));
|
|
app.window_size = Size::new(1440., 1000.);
|
|
(app, a, b, owner)
|
|
}
|
|
fn send(app: &mut App, event: impl FnOnce(Context) -> CanvasEvent) {
|
|
let context = app.scenario_context().unwrap();
|
|
let _ = app.update(Message::ScenarioCanvas(event(context)));
|
|
}
|
|
fn choose(app: &mut App, representation: Representation) {
|
|
send(app, |context| {
|
|
CanvasEvent::Representation(context, representation)
|
|
});
|
|
}
|
|
fn model_state(app: &App) -> (u64, bool, bool, bool) {
|
|
(
|
|
app.document.revision(),
|
|
app.document.dirty(),
|
|
app.document.can_undo(),
|
|
app.document.can_redo(),
|
|
)
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn actual_arrangement_picker_follows_selected_saved_record_and_opens_b_properties() {
|
|
let (mut app, a, b, _) = setup();
|
|
let mut mounted = Mounted::new(&mut app).await;
|
|
let snapshot = app.document.snapshot();
|
|
let before = model_state(&app);
|
|
let captured = app.scenario_context().unwrap();
|
|
let picker = mounted.geometry(&app).bounds("scenario-arrangement-picker");
|
|
assert!(
|
|
app.window_size.height - (picker.y + picker.height) > picker.y,
|
|
"fixture has room for Iced's menu below the actual picker"
|
|
);
|
|
assert_eq!(app.selected, a);
|
|
mounted.click(&mut app, picker.center()).await;
|
|
// iced_widget 0.14.2 overlay/menu.rs uses default line height plus
|
|
// vertical padding for each row. The real workspace orders Automatic,
|
|
// then the fixture's A/B/C Diagram IDs. Select B from the actual overlay.
|
|
let row_height =
|
|
f32::from(iced::advanced::text::LineHeight::default().to_absolute(12.into())) + 10.;
|
|
let b_row = Point::new(
|
|
picker.center_x(),
|
|
picker.y + picker.height + row_height * 2.5,
|
|
);
|
|
assert!(b_row.y < app.window_size.height);
|
|
let events = mounted.click(&mut app, b_row).await;
|
|
assert!(events.iter().any(|event| matches!(event,
|
|
Message::ScenarioCanvas(CanvasEvent::Representation(context, Representation::Saved { diagram }))
|
|
if *context == captured && *diagram == b)), "must exercise the actual arrangement callback");
|
|
assert_eq!(app.selected, b);
|
|
assert_eq!(app.draft.as_ref().unwrap().original, b);
|
|
assert_eq!(
|
|
app.draft.as_ref().unwrap().value["name"],
|
|
"B - Incident review negative and far"
|
|
);
|
|
assert_eq!(app.active_diagram, Some(b));
|
|
assert_eq!(
|
|
app.scenario_context().unwrap().representation,
|
|
Representation::Saved { diagram: b }
|
|
);
|
|
assert_ne!(app.scenario_context().unwrap().visit, captured.visit);
|
|
|
|
send(&mut app, |context| {
|
|
CanvasEvent::Shelf(context, Shelf::Selection)
|
|
});
|
|
mounted.settle(&mut app).await;
|
|
let geometry = mounted.geometry(&app);
|
|
let (details, offset) = geometry.scrolls[&widget::Id::new("scenario-details")];
|
|
let point = geometry.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(_, selected)) if *selected == b)));
|
|
mounted.settle(&mut app).await;
|
|
let geometry = mounted.geometry(&app);
|
|
let (inspector, offset) = geometry.scrolls[&widget::Id::new("inspector")];
|
|
assert!(
|
|
geometry
|
|
.text_inputs
|
|
.iter()
|
|
.any(|bounds| inspector.contains(bounds.center() - offset)),
|
|
"current B Properties must expose an actual editable field"
|
|
);
|
|
assert!(
|
|
!geometry
|
|
.scrolls
|
|
.contains_key(&widget::Id::new("scenario-details"))
|
|
);
|
|
assert_eq!(
|
|
app.inspector_tab,
|
|
crate::navigation::InspectorTab::Properties
|
|
);
|
|
assert_eq!(app.selected, b);
|
|
assert_eq!(app.draft.as_ref().unwrap().original, b);
|
|
assert!(Arc::ptr_eq(&snapshot, &app.document.snapshot()));
|
|
assert_eq!(app.project(), &snapshot.decoded.project);
|
|
assert_eq!(app.document.bundle(), &snapshot.bundle);
|
|
assert_eq!(model_state(&app), before);
|
|
}
|
|
|
|
#[test]
|
|
fn arrangement_change_preserves_independent_semantic_and_foreign_view_selection() {
|
|
for alias in [
|
|
"initial_report",
|
|
"reporter",
|
|
"incident_response",
|
|
"incident_repair",
|
|
] {
|
|
let (mut app, _, b, _) = setup();
|
|
let selected = id(&app, alias);
|
|
let _ = app.update(Message::Select(selected));
|
|
let snapshot = app.document.snapshot();
|
|
let before = model_state(&app);
|
|
choose(&mut app, Representation::Saved { diagram: b });
|
|
assert_eq!(app.selected, selected, "independent selection {alias}");
|
|
assert_eq!(app.draft.as_ref().unwrap().original, selected);
|
|
assert_eq!(app.active_diagram, Some(b));
|
|
assert!(Arc::ptr_eq(&snapshot, &app.document.snapshot()));
|
|
assert_eq!(app.document.bundle(), &snapshot.bundle);
|
|
assert_eq!(model_state(&app), before);
|
|
if alias == "incident_repair" {
|
|
assert!(!behavior::scenario::properties_target(
|
|
app.project(),
|
|
app.scenario_context().unwrap(),
|
|
selected
|
|
));
|
|
}
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn arrangement_follows_view_record_to_automatic_owner_but_not_owner_back_to_saved() {
|
|
let (mut app, _, b, owner) = setup();
|
|
let snapshot = app.document.snapshot();
|
|
let before = model_state(&app);
|
|
choose(&mut app, Representation::Derived { owner });
|
|
assert_eq!(app.selected, owner);
|
|
assert_eq!(app.draft.as_ref().unwrap().original, owner);
|
|
assert_eq!(app.active_diagram, None);
|
|
assert!(behavior::scenario::properties_target(
|
|
app.project(),
|
|
app.scenario_context().unwrap(),
|
|
owner
|
|
));
|
|
choose(&mut app, Representation::Saved { diagram: b });
|
|
assert_eq!(
|
|
app.selected, owner,
|
|
"Scenario owner is now independent semantic selection"
|
|
);
|
|
assert_eq!(app.draft.as_ref().unwrap().original, owner);
|
|
assert_eq!(app.active_diagram, Some(b));
|
|
assert!(Arc::ptr_eq(&snapshot, &app.document.snapshot()));
|
|
assert_eq!(app.document.bundle(), &snapshot.bundle);
|
|
assert_eq!(model_state(&app), before);
|
|
}
|
|
|
|
#[test]
|
|
fn arrangement_applies_only_the_old_view_draft_then_follows_b_with_one_undo() {
|
|
let (mut app, a, b, _) = setup();
|
|
let snapshot = app.document.snapshot();
|
|
let mut expected = app.project().clone();
|
|
expected.diagrams.get_mut(&a).unwrap().name = "Reviewed A".into();
|
|
let revision = app.document.revision();
|
|
let _ = app.update(Message::Field("/name".into(), "Reviewed A".into()));
|
|
choose(&mut app, Representation::Saved { diagram: b });
|
|
assert_eq!(app.project(), &expected);
|
|
assert_eq!(app.selected, b);
|
|
assert_eq!(app.draft.as_ref().unwrap().original, b);
|
|
assert!(!app.draft_dirty());
|
|
assert_eq!(app.document.revision(), revision + 1);
|
|
let accepted = app.document.snapshot();
|
|
let _ = app.update(Message::Undo);
|
|
assert_eq!(app.project(), &snapshot.decoded.project);
|
|
assert_eq!(app.document.bundle(), &snapshot.bundle);
|
|
assert!(!app.document.can_undo() && app.document.can_redo());
|
|
let _ = app.update(Message::Redo);
|
|
assert_eq!(app.project(), &expected);
|
|
assert_eq!(app.document.bundle(), &accepted.bundle);
|
|
assert!(app.document.can_undo() && !app.document.can_redo());
|
|
}
|
|
|
|
#[test]
|
|
fn same_arrangement_uuid_edit_follows_the_reidentified_record_and_round_trips_source() {
|
|
let (mut app, a, _, _) = setup();
|
|
let snapshot = app.document.snapshot();
|
|
let replacement = Id::parse_str("70200000-0000-4000-8000-000000000099").unwrap();
|
|
let mut expected = app.project().clone();
|
|
let mut diagram = expected.diagrams.remove(&a).unwrap();
|
|
diagram.id = replacement;
|
|
expected.diagrams.insert(replacement, diagram);
|
|
let _ = app.update(Message::Field("/id".into(), replacement.to_string()));
|
|
choose(&mut app, Representation::Saved { diagram: a });
|
|
assert_eq!(app.project(), &expected);
|
|
assert_eq!(app.selected, replacement);
|
|
assert_eq!(app.draft.as_ref().unwrap().original, replacement);
|
|
assert_eq!(app.active_diagram, Some(replacement));
|
|
assert_eq!(
|
|
app.scenario_context().unwrap().representation,
|
|
Representation::Saved {
|
|
diagram: replacement
|
|
}
|
|
);
|
|
let accepted = app.document.snapshot();
|
|
let _ = app.update(Message::Undo);
|
|
assert_eq!(app.project(), &snapshot.decoded.project);
|
|
assert_eq!(app.document.bundle(), &snapshot.bundle);
|
|
assert!(!app.document.can_undo() && app.document.can_redo());
|
|
let _ = app.update(Message::Redo);
|
|
assert_eq!(app.project(), &expected);
|
|
assert_eq!(app.document.bundle(), &accepted.bundle);
|
|
assert!(!app.document.can_redo());
|
|
}
|
|
|
|
#[test]
|
|
fn invalid_draft_and_stale_arrangement_preserve_selection_inputs_and_accepted_state() {
|
|
let (mut app, a, b, _) = setup();
|
|
let old = app.scenario_context().unwrap();
|
|
let snapshot = app.document.snapshot();
|
|
let before = model_state(&app);
|
|
let _ = app.update(Message::Field("/id".into(), "not-a-uuid".into()));
|
|
let draft_value = app.draft.as_ref().unwrap().value.clone();
|
|
choose(&mut app, Representation::Saved { diagram: b });
|
|
assert_eq!(app.selected, a);
|
|
assert_eq!(app.active_diagram, Some(a));
|
|
assert_eq!(
|
|
app.scenario_context().unwrap().representation,
|
|
old.representation
|
|
);
|
|
assert_eq!(app.draft.as_ref().unwrap().value, draft_value);
|
|
assert_eq!(app.draft.as_ref().unwrap().inputs["/id"].raw, "not-a-uuid");
|
|
assert!(app.draft_dirty());
|
|
assert!(Arc::ptr_eq(&snapshot, &app.document.snapshot()));
|
|
assert_eq!(model_state(&app), before);
|
|
let _ = app.update(Message::RevertProperties);
|
|
choose(&mut app, Representation::Saved { diagram: b });
|
|
let current = app.scenario_context().unwrap();
|
|
let _ = app.update(Message::Field("/name".into(), "Retained B input".into()));
|
|
let selected = app.selected;
|
|
let _ = app.update(Message::ScenarioCanvas(CanvasEvent::Representation(
|
|
old,
|
|
Representation::Saved { diagram: a },
|
|
)));
|
|
assert_eq!(app.selected, selected);
|
|
assert_eq!(app.active_diagram, Some(b));
|
|
assert_eq!(app.scenario_context().unwrap().visit, current.visit);
|
|
assert_eq!(
|
|
app.draft.as_ref().unwrap().inputs["/name"].raw,
|
|
"Retained B input"
|
|
);
|
|
assert!(Arc::ptr_eq(&snapshot, &app.document.snapshot()));
|
|
assert_eq!(app.document.bundle(), &snapshot.bundle);
|
|
assert_eq!(model_state(&app), before);
|
|
}
|