Don't record an undo step for an abandoned empty text box

Fixes #423

Co-Authored-By: Claude Haiku 5.5 <[email protected]>
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
This commit is contained in:
NetterSilverandClaude Haiku 5.5 committed 2026-10-15 13:05:07 +00:00
1 parent 102404e8c3
commit 0d125374d3
2 files changed
+67 -6

No files matched your search

+7 -6
View File
@@ -38,16 +38,17 @@ pub fn end(s: &mut Session) -> Result<()> {
let _ = doc.remove(te.id);
s.ds_mut()?.selection.clear();
}
s.commit_interaction()?;
if empty && te.created {
// Nothing to undo for a text box that was never filled.
// Nothing to undo for a text box that was never filled. The gesture snapshot was taken
// after the layer was added, so it still holds it: re-base it on the document without the
// layer, and committing then records no step (and leaves the redo stack alone).
let ds = s.ds_mut()?;
if let Some(last) = ds.history.undo.last()
&& *last.doc == *ds.doc
{
ds.history.undo.pop();
let doc = ds.doc.clone();
if let Some(it) = ds.interaction.as_mut() {
it.doc = doc;
}
}
s.commit_interaction()?;
let ds = s.ds_mut()?;
ds.revision += 1;
Ok(())
@@ -0,0 +1,60 @@
#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
use figcraft_engine::{
Session,
doc::NodeKind,
tools::{PointerEvent, PointerKind},
};
use serde_json::json;
fn text_count(s: &Session) -> usize {
let doc = s.doc().unwrap();
doc.node(s.page().unwrap()).unwrap().children.iter().filter(|id| matches!(doc.node(**id).unwrap().kind, NodeKind::Text(_))).count()
}
fn rect_count(s: &Session) -> usize {
let doc = s.doc().unwrap();
doc.node(s.page().unwrap()).unwrap().children.len() - text_count(s)
}
fn click_and_abandon(s: &mut Session) {
s.execute("tool.text", &json!({})).unwrap();
s.pointer(&PointerEvent::at(PointerKind::Down, 300.0, 300.0, 1.0)).unwrap();
s.pointer(&PointerEvent::at(PointerKind::Up, 300.0, 300.0, 1.0)).unwrap();
assert_eq!(text_count(s), 1);
s.execute("text.edit.end", &json!({})).unwrap();
assert_eq!(text_count(s), 0);
}
#[test]
fn abandoning_empty_text_does_not_create_an_undo_step() {
let mut s = Session::new();
assert_eq!(s.undo_depth(), (0, 0));
click_and_abandon(&mut s);
assert_eq!(s.undo_depth(), (0, 0));
s.execute("edit.undo", &json!({})).unwrap();
assert_eq!(text_count(&s), 0, "Undo restored the abandoned empty layer");
}
#[test]
fn abandoning_empty_text_keeps_the_previous_undo_step_first() {
let mut s = Session::new();
s.execute("shape.rectangle", &json!({"x": 0, "y": 0, "width": 10, "height": 10})).unwrap();
click_and_abandon(&mut s);
assert_eq!(s.undo_labels(2), vec!["Rectangle".to_string()]);
s.execute("edit.undo", &json!({})).unwrap();
assert_eq!(text_count(&s), 0);
assert_eq!(rect_count(&s), 0, "the first Undo must remove the rectangle");
}
#[test]
fn abandoning_empty_text_keeps_the_redo_stack() {
let mut s = Session::new();
s.execute("shape.rectangle", &json!({"x": 0, "y": 0, "width": 10, "height": 10})).unwrap();
s.execute("edit.undo", &json!({})).unwrap();
assert_eq!(s.undo_depth(), (0, 1));
click_and_abandon(&mut s);
assert_eq!(s.undo_depth(), (0, 1), "the document is back where it was, so Redo still applies");
s.execute("edit.redo", &json!({})).unwrap();
assert_eq!(rect_count(&s), 1);
}