Test Process and Allocation stale gestures after real Text Apply
This commit is contained in:
@@ -190,6 +190,165 @@ fn exact_history(
|
||||
assert!(!app.document.can_redo());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn completed_text_apply_rejects_old_allocation_packets_without_retiring_a_new_drag() {
|
||||
for layer in [Layer::Oa, Layer::Sa] {
|
||||
let mut g = group(layer);
|
||||
select(&mut g.f.app, &[g.f.node, g.yn]);
|
||||
let before = g.f.app.document.snapshot();
|
||||
assert!(!g.f.app.document.can_undo());
|
||||
let from = node_point(&g.f.app, g.f.node);
|
||||
let mut old_canvas = CanvasState::default();
|
||||
let old = begin(&mut g.f.app, &mut old_canvas, from);
|
||||
assert!(g.f.app.allocation_drag.active.is_some());
|
||||
let location = g.f.app.document.compiled().index.declarations[&g.f.work].clone();
|
||||
let original = &before.bundle.files[&location.path];
|
||||
let quoted_name = format!("\"{}\"", g.f.app.project().elements[&g.f.work].name);
|
||||
assert_eq!(original.matches(quoted_name.as_str()).count(), 1);
|
||||
let edited = original.replace(quoted_name.as_str(), "\"Changed through Text\"");
|
||||
let mut expected = before.decoded.project.clone();
|
||||
expected.elements.get_mut(&g.f.work).unwrap().name = "Changed through Text".into();
|
||||
let mut expected_bundle = before.bundle.clone();
|
||||
expected_bundle
|
||||
.files
|
||||
.insert(location.path.clone(), edited.clone());
|
||||
|
||||
let _ = g.f.app.update(Message::Page(Page::Text));
|
||||
assert!(g.f.app.allocation_drag.active.is_none());
|
||||
let _ = g.f.app.update(Message::SourceFile(location.path));
|
||||
let _ =
|
||||
g.f.app
|
||||
.update(Message::SourceEdit(text_editor::Action::SelectAll));
|
||||
let _ =
|
||||
g.f.app
|
||||
.update(Message::SourceEdit(text_editor::Action::Edit(
|
||||
text_editor::Edit::Paste(Arc::new(edited.clone())),
|
||||
)));
|
||||
assert!(g.f.app.source_dirty());
|
||||
assert_eq!(g.f.app.source_editor.text(), edited);
|
||||
assert!(Arc::ptr_eq(&before, &g.f.app.document.snapshot()));
|
||||
let _ = g.f.app.update(Message::ApplySource);
|
||||
assert!(!g.f.app.source_dirty(), "{}", g.f.app.status);
|
||||
assert_eq!(g.f.app.project(), &expected);
|
||||
assert_eq!(g.f.app.document.bundle(), &expected_bundle);
|
||||
|
||||
let _ = g.f.app.update(Message::Page(Page::Diagram));
|
||||
assert_eq!(g.f.app.active_diagram, Some(g.f.diagram));
|
||||
select(&mut g.f.app, &[g.f.node, g.yn]);
|
||||
let from = node_point(&g.f.app, g.f.node);
|
||||
let to = from + Vector::new(1400., 0.);
|
||||
let mut current_canvas = CanvasState::default();
|
||||
let current = begin(&mut g.f.app, &mut current_canvas, from);
|
||||
assert_ne!(current.token, old.token);
|
||||
assert_ne!(current.stamp.context, old.stamp.context);
|
||||
move_pointer(&mut g.f.app, &mut current_canvas, to);
|
||||
let preview =
|
||||
g.f.app
|
||||
.allocation_drag
|
||||
.preview
|
||||
.as_ref()
|
||||
.expect(&g.f.app.status)
|
||||
.clone();
|
||||
let Candidate::Assignment(plan) = &preview.candidate else {
|
||||
panic!("the current gesture must be a real group assignment");
|
||||
};
|
||||
assert_eq!(plan.changed_count(), 2);
|
||||
let accepted = g.f.app.document.snapshot();
|
||||
let revision = g.f.app.document.revision();
|
||||
let held = selection(&g.f.app);
|
||||
let selected = g.f.app.multiselect_snapshot().unwrap();
|
||||
let workspace = g.f.app.source_workspace.bundle();
|
||||
let editor = g.f.app.source_editor.text();
|
||||
let camera = (
|
||||
g.f.app.zoom,
|
||||
g.f.app.pan,
|
||||
g.f.app.fit_to_view,
|
||||
g.f.app.canvas_size,
|
||||
);
|
||||
let epochs = (
|
||||
g.f.app.canvas_epoch,
|
||||
g.f.app.camera_epoch,
|
||||
g.f.app.allocation_drag.epoch,
|
||||
);
|
||||
let draft =
|
||||
g.f.app
|
||||
.draft
|
||||
.as_ref()
|
||||
.map(|d| serde_json::to_value(d.recovery_snapshot()).unwrap());
|
||||
let form = g.f.app.behavior.recovery_snapshot();
|
||||
let status = g.f.app.status.clone();
|
||||
let pending_reveal = g.f.app.pending_diagram_reveal.clone();
|
||||
let arrange = g.f.app.allocation_drag.arrange_next;
|
||||
// An idle obsolete press intentionally reports a stale-view explanation.
|
||||
// With a newer real gesture active, both old packets must be fully inert.
|
||||
for event in [
|
||||
Event::Press(old.clone()),
|
||||
Event::Release(old.stamp, old.token, Some(to)),
|
||||
] {
|
||||
let _ = g.f.app.update(Message::WorkDrag(event));
|
||||
assert!(Arc::ptr_eq(&accepted, &g.f.app.document.snapshot()));
|
||||
assert_eq!(g.f.app.project(), &expected);
|
||||
assert_eq!(g.f.app.document.bundle(), &expected_bundle);
|
||||
assert_eq!(g.f.app.document.revision(), revision);
|
||||
assert_eq!(selection(&g.f.app), held);
|
||||
assert_eq!(g.f.app.multiselect_snapshot(), Some(selected.clone()));
|
||||
assert!(Arc::ptr_eq(
|
||||
&g.f.app.allocation_drag.active.as_ref().unwrap().press,
|
||||
¤t
|
||||
));
|
||||
assert!(Arc::ptr_eq(
|
||||
g.f.app.allocation_drag.preview.as_ref().unwrap(),
|
||||
&preview
|
||||
));
|
||||
assert_eq!(g.f.app.source_workspace.bundle(), workspace);
|
||||
assert_eq!(g.f.app.source_editor.text(), editor);
|
||||
assert_eq!(
|
||||
(
|
||||
g.f.app.zoom,
|
||||
g.f.app.pan,
|
||||
g.f.app.fit_to_view,
|
||||
g.f.app.canvas_size
|
||||
),
|
||||
camera
|
||||
);
|
||||
assert_eq!(
|
||||
(
|
||||
g.f.app.canvas_epoch,
|
||||
g.f.app.camera_epoch,
|
||||
g.f.app.allocation_drag.epoch
|
||||
),
|
||||
epochs
|
||||
);
|
||||
assert_eq!(
|
||||
g.f.app
|
||||
.draft
|
||||
.as_ref()
|
||||
.map(|d| serde_json::to_value(d.recovery_snapshot()).unwrap()),
|
||||
draft
|
||||
);
|
||||
assert_eq!(g.f.app.behavior.recovery_snapshot(), form);
|
||||
assert_eq!(g.f.app.pending_diagram_reveal, pending_reveal);
|
||||
assert_eq!(g.f.app.allocation_drag.arrange_next, arrange);
|
||||
assert_eq!(g.f.app.status, status);
|
||||
assert!(!g.f.app.source_dirty());
|
||||
assert!(g.f.app.document.can_undo());
|
||||
assert!(!g.f.app.document.can_redo());
|
||||
}
|
||||
// The current gesture remains only a preview. History contains solely
|
||||
// the accepted text rename, never either stale assignment packet.
|
||||
let _ = g.f.app.update(Message::Undo);
|
||||
assert_eq!(g.f.app.project(), &before.decoded.project);
|
||||
assert_eq!(g.f.app.document.bundle(), &before.bundle);
|
||||
assert!(!g.f.app.document.can_undo());
|
||||
assert!(g.f.app.document.can_redo());
|
||||
let _ = g.f.app.update(Message::Redo);
|
||||
assert_eq!(g.f.app.project(), &expected);
|
||||
assert_eq!(g.f.app.document.bundle(), &expected_bundle);
|
||||
assert!(g.f.app.document.can_undo());
|
||||
assert!(!g.f.app.document.can_redo());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn group_assignment_split_rebuild_preserves_primary_and_commits_one_complete_candidate() {
|
||||
for layer in [Layer::Oa, Layer::Sa] {
|
||||
|
||||
@@ -144,6 +144,151 @@ fn marker(app: &mut App, owner: Id, occurrence: Id) {
|
||||
assert!(app.diagram_focus(Surface::Process(owner)).is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn completed_text_apply_rejects_captured_process_reorder_without_retiring_current_state() {
|
||||
for layer in [Layer::Oa, Layer::Sa] {
|
||||
let mut f = fixture(layer);
|
||||
pair(&mut f);
|
||||
let stale = f.app.multiselect_snapshot().unwrap();
|
||||
let before = f.app.document.snapshot();
|
||||
assert!(!f.app.document.can_undo());
|
||||
let element = f.nodes[0];
|
||||
let location = f.app.document.compiled().index.declarations[&element].clone();
|
||||
let original = &before.bundle.files[&location.path];
|
||||
let quoted_name = format!("\"{}\"", f.app.project().elements[&element].name);
|
||||
assert_eq!(original.matches(quoted_name.as_str()).count(), 1);
|
||||
let edited = original.replace(quoted_name.as_str(), "\"Changed through Text\"");
|
||||
let mut expected = before.decoded.project.clone();
|
||||
expected.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 _ = f.app.update(Message::Page(Page::Text));
|
||||
let _ = f.app.update(Message::SourceFile(location.path));
|
||||
let _ = f
|
||||
.app
|
||||
.update(Message::SourceEdit(text_editor::Action::SelectAll));
|
||||
let _ = f.app.update(Message::SourceEdit(text_editor::Action::Edit(
|
||||
text_editor::Edit::Paste(Arc::new(edited.clone())),
|
||||
)));
|
||||
assert!(f.app.source_dirty());
|
||||
assert_eq!(f.app.source_editor.text(), edited);
|
||||
assert!(Arc::ptr_eq(&before, &f.app.document.snapshot()));
|
||||
let _ = f.app.update(Message::ApplySource);
|
||||
assert!(!f.app.source_dirty(), "{}", f.app.status);
|
||||
assert_eq!(f.app.project(), &expected);
|
||||
assert_eq!(f.app.document.bundle(), &expected_bundle);
|
||||
source_matches(&f.app);
|
||||
|
||||
let _ = f.app.update(Message::Page(Page::Behavior));
|
||||
assert_eq!(f.app.topology_context().unwrap().owner, f.owner);
|
||||
pair(&mut f);
|
||||
let current = f.app.multiselect_snapshot().unwrap();
|
||||
assert_eq!(current.targets(), stale.targets());
|
||||
assert_eq!(current.primary(), stale.primary());
|
||||
assert_ne!(current.context(), stale.context());
|
||||
let destination = Destination::After(f.nodes[2]);
|
||||
assert!(
|
||||
matches!(
|
||||
Prepared::new(f.app.project(), f.owner, current.targets())
|
||||
.unwrap()
|
||||
.plan(destination)
|
||||
.unwrap(),
|
||||
Plan::Change(_)
|
||||
),
|
||||
"the replay must be a real reorder, not a no-op"
|
||||
);
|
||||
assert!(
|
||||
f.app
|
||||
.multiselect_envelope(&Event::ProcessReorder(current.clone(), destination))
|
||||
);
|
||||
let accepted = f.app.document.snapshot();
|
||||
let revision = f.app.document.revision();
|
||||
let workspace = f.app.source_workspace.bundle();
|
||||
let editor = f.app.source_editor.text();
|
||||
let selected = (
|
||||
f.app.selected,
|
||||
f.app.selected_node,
|
||||
f.app.selected_bend,
|
||||
f.app.selected_attachment,
|
||||
);
|
||||
let camera = (f.app.zoom, f.app.pan, f.app.fit_to_view, f.app.canvas_size);
|
||||
let process_camera = f.app.behavior.topology.camera_boundary();
|
||||
let epochs = (
|
||||
f.app.canvas_epoch,
|
||||
f.app.camera_epoch,
|
||||
f.app.allocation_drag.epoch,
|
||||
);
|
||||
let draft = f
|
||||
.app
|
||||
.draft
|
||||
.as_ref()
|
||||
.map(|d| serde_json::to_value(d.recovery_snapshot()).unwrap());
|
||||
let form = f.app.behavior.recovery_snapshot();
|
||||
let status = f.app.status.clone();
|
||||
let pending_reveal = f.app.pending_diagram_reveal.clone();
|
||||
|
||||
send(&mut f.app, Event::ProcessReorder(stale, destination));
|
||||
assert!(Arc::ptr_eq(&accepted, &f.app.document.snapshot()));
|
||||
assert_eq!(f.app.project(), &expected);
|
||||
assert_eq!(f.app.document.bundle(), &expected_bundle);
|
||||
assert_eq!(f.app.document.revision(), revision);
|
||||
assert_eq!(f.app.source_workspace.bundle(), workspace);
|
||||
assert_eq!(f.app.source_editor.text(), editor);
|
||||
assert_eq!(f.app.multiselect_snapshot(), Some(current));
|
||||
assert_eq!(
|
||||
(
|
||||
f.app.selected,
|
||||
f.app.selected_node,
|
||||
f.app.selected_bend,
|
||||
f.app.selected_attachment
|
||||
),
|
||||
selected
|
||||
);
|
||||
assert_eq!(
|
||||
(f.app.zoom, f.app.pan, f.app.fit_to_view, f.app.canvas_size),
|
||||
camera
|
||||
);
|
||||
assert!(f.app.behavior.topology.camera_boundary() == process_camera);
|
||||
assert_eq!(
|
||||
(
|
||||
f.app.canvas_epoch,
|
||||
f.app.camera_epoch,
|
||||
f.app.allocation_drag.epoch
|
||||
),
|
||||
epochs
|
||||
);
|
||||
assert_eq!(
|
||||
f.app
|
||||
.draft
|
||||
.as_ref()
|
||||
.map(|d| serde_json::to_value(d.recovery_snapshot()).unwrap()),
|
||||
draft
|
||||
);
|
||||
assert_eq!(f.app.behavior.recovery_snapshot(), form);
|
||||
assert_eq!(f.app.pending_diagram_reveal, pending_reveal);
|
||||
assert_eq!(f.app.status, status);
|
||||
assert!(!f.app.source_dirty());
|
||||
assert!(f.app.document.can_undo());
|
||||
assert!(!f.app.document.can_redo());
|
||||
|
||||
let _ = f.app.update(Message::Undo);
|
||||
assert_eq!(f.app.project(), &before.decoded.project);
|
||||
assert_eq!(f.app.document.bundle(), &before.bundle);
|
||||
assert!(!f.app.document.can_undo());
|
||||
assert!(f.app.document.can_redo());
|
||||
source_matches(&f.app);
|
||||
let _ = f.app.update(Message::Redo);
|
||||
assert_eq!(f.app.project(), &expected);
|
||||
assert_eq!(f.app.document.bundle(), &expected_bundle);
|
||||
assert!(f.app.document.can_undo());
|
||||
assert!(!f.app.document.can_redo());
|
||||
source_matches(&f.app);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn process_group_reverse_click_order_changes_only_owner_steps_and_undoes_once() {
|
||||
for layer in [Layer::Oa, Layer::Sa] {
|
||||
|
||||
Reference in New Issue
Block a user