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());
|
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]
|
#[test]
|
||||||
fn toggled_group_moves_model_and_source_once_and_keeps_primary_through_history() {
|
fn toggled_group_moves_model_and_source_once_and_keeps_primary_through_history() {
|
||||||
let mut f = fixture();
|
let mut f = fixture();
|
||||||
|
|||||||
Reference in New Issue
Block a user