test: reject captured group actions after real Text Apply
This commit is contained in:
@@ -145,6 +145,202 @@ fn assert_source_matches_model(app: &App) {
|
||||
assert_eq!(&compiled.bundle, app.document.bundle());
|
||||
}
|
||||
|
||||
fn text_apply_group_fixture(state: bool) -> (App, Id, Id, Id) {
|
||||
let ordinary = fixture();
|
||||
if !state {
|
||||
return (ordinary.app, ordinary.diagram, ordinary.a, ordinary.b);
|
||||
}
|
||||
// Keep the ordinary diagram, routes and opposite-layer view in the same
|
||||
// Project so the complete-model oracle also protects non-active surfaces.
|
||||
let mut project = ordinary.app.project().clone();
|
||||
let ordinary_view = project.diagrams[&ordinary.diagram].clone();
|
||||
let system = project
|
||||
.add_element(Kind::System, Layer::Sa, "System", None)
|
||||
.unwrap();
|
||||
let machine = project
|
||||
.add_element(Kind::StateMachine, Layer::Sa, "Modes", Some(system))
|
||||
.unwrap();
|
||||
let region = project
|
||||
.add_element(Kind::Region, Layer::Sa, "Region", Some(machine))
|
||||
.unwrap();
|
||||
let first = project
|
||||
.add_element(Kind::State, Layer::Sa, "Before text", Some(region))
|
||||
.unwrap();
|
||||
let second = project
|
||||
.add_element(Kind::State, Layer::Sa, "Other state", Some(region))
|
||||
.unwrap();
|
||||
let diagram = project.ensure_diagram(Layer::Sa);
|
||||
assert_ne!(diagram, ordinary.diagram);
|
||||
project.diagrams.get_mut(&diagram).unwrap().projection =
|
||||
ProjectionKind::StateMachine { machine };
|
||||
let a = project.place(diagram, first, point(20., 30.));
|
||||
let b = project.place(diagram, second, point(420., 30.));
|
||||
assert_eq!(project.diagrams[&ordinary.diagram], ordinary_view);
|
||||
let mut app = App::with_project(project);
|
||||
app.page = Page::Behavior;
|
||||
app.layer = Layer::Sa;
|
||||
app.active_diagram = Some(ordinary.diagram);
|
||||
app.behavior.active_owner = Some(machine);
|
||||
app.behavior.diagram = crate::behavior::state_view::State {
|
||||
diagram: Some(diagram),
|
||||
..Default::default()
|
||||
};
|
||||
app.selected = diagram;
|
||||
app.selected_node = None;
|
||||
app.refresh_draft();
|
||||
app.reconcile_multiselect();
|
||||
assert_eq!(app.state_context().unwrap().diagram, diagram);
|
||||
(app, diagram, a, b)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn completed_text_apply_rejects_captured_ordinary_and_state_group_actions() {
|
||||
for state in [false, true] {
|
||||
let (mut app, diagram, a, b) = text_apply_group_fixture(state);
|
||||
choose(&mut app, Target::Placement(a), GroupChoice::Replace);
|
||||
choose(&mut app, Target::Placement(b), GroupChoice::Add);
|
||||
let stale = app.multiselect_snapshot().unwrap();
|
||||
let before = app.document.snapshot();
|
||||
assert!(!app.document.can_undo());
|
||||
let element = app.project().diagrams[&diagram].nodes[&a].element;
|
||||
let location = app.document.compiled().index.declarations[&element].clone();
|
||||
let old_name = if state { "Before text" } else { "Send" };
|
||||
assert_eq!(app.project().elements[&element].name, old_name);
|
||||
let quoted_name = format!("\"{old_name}\"");
|
||||
let original = &before.bundle.files[&location.path];
|
||||
assert_eq!(original.matches(quoted_name.as_str()).count(), 1);
|
||||
let edited = original.replace(quoted_name.as_str(), "\"Changed through Text\"");
|
||||
let mut expected_model = before.decoded.project.clone();
|
||||
expected_model.elements.get_mut(&element).unwrap().name = "Changed through Text".into();
|
||||
let mut expected_bundle = before.bundle.clone();
|
||||
expected_bundle
|
||||
.files
|
||||
.insert(location.path.clone(), edited.clone());
|
||||
|
||||
let _ = app.update(Message::Page(Page::Text));
|
||||
let _ = app.update(Message::SourceFile(location.path));
|
||||
let _ = app.update(Message::SourceEdit(text_editor::Action::SelectAll));
|
||||
let _ = app.update(Message::SourceEdit(text_editor::Action::Edit(
|
||||
text_editor::Edit::Paste(Arc::new(edited.clone())),
|
||||
)));
|
||||
assert!(app.source_dirty());
|
||||
assert_eq!(app.source_editor.text(), edited);
|
||||
assert_eq!(app.project(), &before.decoded.project);
|
||||
assert_eq!(app.document.bundle(), &before.bundle);
|
||||
let _ = app.update(Message::ApplySource);
|
||||
assert!(!app.source_dirty(), "{}", app.status);
|
||||
assert_eq!(app.project(), &expected_model);
|
||||
assert_eq!(app.document.bundle(), &expected_bundle);
|
||||
assert!(app.document.can_undo());
|
||||
assert!(!app.document.can_redo());
|
||||
assert_source_matches_model(&app);
|
||||
|
||||
let _ = app.update(Message::Page(if state {
|
||||
Page::Behavior
|
||||
} else {
|
||||
Page::Diagram
|
||||
}));
|
||||
choose(&mut app, Target::Placement(a), GroupChoice::Replace);
|
||||
choose(&mut app, Target::Placement(b), GroupChoice::Add);
|
||||
let current = app.multiselect_snapshot().unwrap();
|
||||
assert_eq!(
|
||||
current.targets(),
|
||||
&[Target::Placement(a), Target::Placement(b)]
|
||||
);
|
||||
assert_eq!(current.primary(), Some(&Target::Placement(b)));
|
||||
assert_ne!(current.context(), stale.context());
|
||||
assert!(app.multiselect_envelope(&GroupEvent::Hide(current.clone())));
|
||||
app.status = "Feedback after completed Text Apply".into();
|
||||
app.allocation_drag.arrange_next = true;
|
||||
let revision = app.document.revision();
|
||||
let epochs = (
|
||||
app.canvas_epoch,
|
||||
app.camera_epoch,
|
||||
app.allocation_drag.epoch,
|
||||
);
|
||||
let legacy = (
|
||||
app.selected,
|
||||
app.selected_node,
|
||||
app.behavior.diagram.placement,
|
||||
);
|
||||
let camera = (app.zoom, app.pan, app.fit_to_view, app.canvas_size);
|
||||
let state_camera = (
|
||||
app.behavior.diagram.zoom,
|
||||
app.behavior.diagram.pan,
|
||||
app.behavior.diagram.fit,
|
||||
app.behavior.diagram.viewport,
|
||||
);
|
||||
let draft = app
|
||||
.draft
|
||||
.as_ref()
|
||||
.map(|d| serde_json::to_value(d.recovery_snapshot()).unwrap());
|
||||
for event in [
|
||||
GroupEvent::Move(stale.clone(), point(900., 800.)),
|
||||
GroupEvent::Hide(stale.clone()),
|
||||
GroupEvent::Collapse(stale.clone(), Target::Placement(a)),
|
||||
GroupEvent::Refuse(stale.clone(), "Obsolete refusal".into()),
|
||||
] {
|
||||
dispatch(&mut app, event);
|
||||
assert_eq!(app.project(), &expected_model);
|
||||
assert_eq!(app.document.bundle(), &expected_bundle);
|
||||
assert_eq!(app.document.revision(), revision);
|
||||
assert_eq!(app.multiselect_snapshot(), Some(current.clone()));
|
||||
assert_eq!(
|
||||
(
|
||||
app.selected,
|
||||
app.selected_node,
|
||||
app.behavior.diagram.placement
|
||||
),
|
||||
legacy
|
||||
);
|
||||
assert_eq!(
|
||||
(
|
||||
app.canvas_epoch,
|
||||
app.camera_epoch,
|
||||
app.allocation_drag.epoch
|
||||
),
|
||||
epochs
|
||||
);
|
||||
assert_eq!(
|
||||
(app.zoom, app.pan, app.fit_to_view, app.canvas_size),
|
||||
camera
|
||||
);
|
||||
assert_eq!(
|
||||
(
|
||||
app.behavior.diagram.zoom,
|
||||
app.behavior.diagram.pan,
|
||||
app.behavior.diagram.fit,
|
||||
app.behavior.diagram.viewport
|
||||
),
|
||||
state_camera
|
||||
);
|
||||
assert_eq!(app.status, "Feedback after completed Text Apply");
|
||||
assert!(app.allocation_drag.arrange_next);
|
||||
assert!(!app.source_dirty());
|
||||
assert_eq!(
|
||||
app.draft
|
||||
.as_ref()
|
||||
.map(|d| serde_json::to_value(d.recovery_snapshot()).unwrap()),
|
||||
draft
|
||||
);
|
||||
assert!(app.document.can_undo());
|
||||
assert!(!app.document.can_redo());
|
||||
}
|
||||
// Actual Text Apply is the sole history entry; no stale group action
|
||||
// can create a hidden second edit or damage either round-trip snapshot.
|
||||
let _ = app.update(Message::Undo);
|
||||
assert_eq!(app.project(), &before.decoded.project);
|
||||
assert_eq!(app.document.bundle(), &before.bundle);
|
||||
assert!(!app.document.can_undo());
|
||||
assert!(app.document.can_redo());
|
||||
let _ = app.update(Message::Redo);
|
||||
assert_eq!(app.project(), &expected_model);
|
||||
assert_eq!(app.document.bundle(), &expected_bundle);
|
||||
assert!(app.document.can_undo());
|
||||
assert!(!app.document.can_redo());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn toggled_group_moves_model_and_source_once_and_keeps_primary_through_history() {
|
||||
let mut f = fixture();
|
||||
|
||||
Reference in New Issue
Block a user