diff --git a/crates/engine/src/commands/batch.rs b/crates/engine/src/commands/batch.rs index 03793adb..e1fc3714 100644 --- a/crates/engine/src/commands/batch.rs +++ b/crates/engine/src/commands/batch.rs @@ -56,6 +56,9 @@ fn batch(s: &mut Session, p: &Value) -> Result { let label = str_p(p, "label").unwrap_or("Batch").to_string(); let atomic = p.get("atomic").and_then(Value::as_bool).unwrap_or(true); let (project, history, state) = (s.project.clone(), s.history.clone(), s.state.clone()); + // Rolled-back steps may have queued events (an `OpenComp`, a Toast) for the state that is + // about to be dropped: the UI must not act on them. + let event_mark = s.events.len(); let mut results: Vec = Vec::with_capacity(steps.len()); for (i, step) in steps.iter().enumerate() { let run = |s: &mut Session, results: &[Value]| -> Result { @@ -87,6 +90,7 @@ fn batch(s: &mut Session, p: &Value) -> Result { s.project = project; s.history = history; s.state = state; + s.events.truncate(event_mark); s.sanitize_after_replacement(edited.as_ref()); s.ime_revision = s.ime_revision.max(interim_revision); s.bump(); diff --git a/crates/engine/src/tests_text_edit.rs b/crates/engine/src/tests_text_edit.rs index 9e0fbcf9..17d0345d 100644 --- a/crates/engine/src/tests_text_edit.rs +++ b/crates/engine/src/tests_text_edit.rs @@ -542,6 +542,29 @@ fn atomic_batch_rollback_after_a_cross_comp_editor_switch_bumps_the_epoch() { assert_ime_epoch_changed(&s, owner, "an identical layer in another comp must not revive the old token"); } +#[test] +fn an_atomic_batch_drops_the_events_its_failed_steps_queued() { + let (mut s, _) = session("abcdef"); + let original = s.active_comp_id().unwrap(); + // `comp.new` opens the comp it creates; go back so the batch's switch is a real one. + let other = new_other_comp(&mut s); + s.execute("comp.open", json!({"comp": original.0})).unwrap(); + s.drain_events(); + let r = s.execute( + "engine.batch", + json!({"atomic": true, "steps": [ + {"command": "comp.open", "params": {"comp": other}}, + {"command": "no.suchCommand", "params": {}} + ]}), + ); + assert!(r.is_err()); + assert_eq!(s.active_comp_id(), Some(original), "the batch restored the original comp"); + assert!( + !s.drain_events().iter().any(|e| matches!(e, crate::Event::OpenComp(id) if id.0 == other)), + "the rolled-back step's OpenComp is not queued for the UI" + ); +} + #[test] fn atomic_batch_rollback_after_closing_the_editor_does_not_revive_it() { let (mut s, t) = session("abcdef");