1#![forbid(unsafe_code)]
2
3use std::fmt;
56
57use super::command::{CommandBatch, CommandError, CommandResult, UndoableCmd};
58use super::history::HistoryManager;
59
60pub struct Transaction {
68 batch: CommandBatch,
70 executed_count: usize,
72 finalized: bool,
74}
75
76impl fmt::Debug for Transaction {
77 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
78 f.debug_struct("Transaction")
79 .field("description", &self.batch.description())
80 .field("command_count", &self.batch.len())
81 .field("executed_count", &self.executed_count)
82 .field("finalized", &self.finalized)
83 .finish()
84 }
85}
86
87impl Transaction {
88 #[must_use]
90 pub fn begin(description: impl Into<String>) -> Self {
91 Self {
92 batch: CommandBatch::new(description),
93 executed_count: 0,
94 finalized: false,
95 }
96 }
97
98 pub fn execute(&mut self, mut cmd: Box<dyn UndoableCmd>) -> CommandResult {
107 if self.finalized {
108 return Err(CommandError::InvalidState(
109 "transaction already finalized".to_string(),
110 ));
111 }
112
113 if let Err(e) = cmd.execute() {
115 self.rollback();
117 return Err(e);
118 }
119
120 self.executed_count += 1;
122 self.batch.push_executed(cmd);
123 Ok(())
124 }
125
126 pub fn add_executed(&mut self, cmd: Box<dyn UndoableCmd>) -> CommandResult {
135 if self.finalized {
136 return Err(CommandError::InvalidState(
137 "transaction already finalized".to_string(),
138 ));
139 }
140
141 self.executed_count += 1;
142 self.batch.push_executed(cmd);
143 Ok(())
144 }
145
146 #[must_use]
150 pub fn commit(mut self) -> Option<Box<dyn UndoableCmd>> {
151 if self.finalized {
153 return None;
154 }
155 self.finalized = true;
156
157 if self.batch.is_empty() {
158 None
159 } else {
160 let batch = std::mem::replace(&mut self.batch, CommandBatch::new(""));
164 Some(Box::new(batch))
165 }
166 }
167
168 pub fn rollback(&mut self) {
173 if self.finalized {
174 return;
175 }
176
177 self.finalized = true;
189
190 if self.executed_count > 0 {
192 let _ = self.batch.undo();
194 self.executed_count = 0;
195 }
196 }
197
198 #[must_use]
200 pub fn is_empty(&self) -> bool {
201 self.batch.is_empty()
202 }
203
204 #[must_use]
206 pub fn len(&self) -> usize {
207 self.batch.len()
208 }
209
210 #[must_use]
212 pub fn description(&self) -> &str {
213 self.batch.description()
214 }
215}
216
217impl Drop for Transaction {
218 fn drop(&mut self) {
219 if !self.finalized {
221 self.rollback();
222 }
223 }
224}
225
226pub struct TransactionScope<'a> {
232 history: &'a mut HistoryManager,
234 stack: Vec<Transaction>,
236}
237
238impl<'a> TransactionScope<'a> {
239 #[must_use]
241 pub fn new(history: &'a mut HistoryManager) -> Self {
242 Self {
243 history,
244 stack: Vec::new(),
245 }
246 }
247
248 pub fn begin(&mut self, description: impl Into<String>) {
250 self.stack.push(Transaction::begin(description));
251 }
252
253 pub fn execute(&mut self, cmd: Box<dyn UndoableCmd>) -> CommandResult {
262 if let Some(txn) = self.stack.last_mut() {
263 txn.execute(cmd)
264 } else {
265 let mut cmd = cmd;
267 cmd.execute()?;
268 self.history.push(cmd);
269 Ok(())
270 }
271 }
272
273 pub fn commit(&mut self) -> CommandResult {
282 let txn = self
283 .stack
284 .pop()
285 .ok_or_else(|| CommandError::InvalidState("no active transaction".to_string()))?;
286
287 if let Some(cmd) = txn.commit() {
288 if let Some(parent) = self.stack.last_mut() {
289 parent.add_executed(cmd)?;
291 } else {
292 self.history.push(cmd);
294 }
295 }
296
297 Ok(())
298 }
299
300 pub fn rollback(&mut self) -> CommandResult {
306 let mut txn = self
307 .stack
308 .pop()
309 .ok_or_else(|| CommandError::InvalidState("no active transaction".to_string()))?;
310
311 txn.rollback();
312 Ok(())
313 }
314
315 #[must_use]
317 pub fn is_active(&self) -> bool {
318 !self.stack.is_empty()
319 }
320
321 #[must_use]
323 pub fn depth(&self) -> usize {
324 self.stack.len()
325 }
326}
327
328impl Drop for TransactionScope<'_> {
329 fn drop(&mut self) {
330 while let Some(mut txn) = self.stack.pop() {
332 txn.rollback();
333 }
334 }
335}
336
337#[cfg(test)]
342mod tests {
343 use super::*;
344 use crate::undo::command::{TextInsertCmd, WidgetId};
345 use crate::undo::history::HistoryConfig;
346 use std::sync::Arc;
347 use std::sync::Mutex;
348
349 fn make_cmd(buffer: Arc<Mutex<String>>, text: &str) -> Box<dyn UndoableCmd> {
353 let b1 = buffer.clone();
354 let b2 = buffer.clone();
355 let text = text.to_string();
356 let text_clone = text.clone();
357
358 let mut cmd = TextInsertCmd::new(WidgetId::new(1), 0, text)
359 .with_apply(move |_, _, txt| {
360 let mut buf = b1.lock().unwrap();
361 buf.push_str(txt);
362 Ok(())
363 })
364 .with_remove(move |_, _, _| {
365 let mut buf = b2.lock().unwrap();
366 let new_len = buf.len().saturating_sub(text_clone.len());
367 buf.truncate(new_len);
368 Ok(())
369 });
370
371 cmd.execute().unwrap();
372 Box::new(cmd)
373 }
374
375 #[test]
376 fn test_empty_transaction() {
377 let txn = Transaction::begin("Empty");
378 assert!(txn.is_empty());
379 assert_eq!(txn.len(), 0);
380 assert!(txn.commit().is_none());
381 }
382
383 #[test]
384 fn test_single_command_transaction() {
385 let buffer = Arc::new(Mutex::new(String::new()));
386
387 let mut txn = Transaction::begin("Single");
388 txn.add_executed(make_cmd(buffer.clone(), "hello")).unwrap();
389
390 assert_eq!(txn.len(), 1);
391
392 let cmd = txn.commit();
393 assert!(cmd.is_some());
394 }
395
396 #[test]
397 fn test_transaction_rollback() {
398 let buffer = Arc::new(Mutex::new(String::new()));
399
400 let mut txn = Transaction::begin("Rollback Test");
401 txn.add_executed(make_cmd(buffer.clone(), "hello")).unwrap();
402 txn.add_executed(make_cmd(buffer.clone(), " world"))
403 .unwrap();
404
405 assert_eq!(*buffer.lock().unwrap(), "hello world");
406
407 txn.rollback();
408
409 assert_eq!(*buffer.lock().unwrap(), "");
411 }
412
413 #[test]
414 fn test_transaction_commit_to_history() {
415 let buffer = Arc::new(Mutex::new(String::new()));
416 let mut history = HistoryManager::new(HistoryConfig::unlimited());
417
418 let mut txn = Transaction::begin("Commit Test");
419 txn.add_executed(make_cmd(buffer.clone(), "a")).unwrap();
420 txn.add_executed(make_cmd(buffer.clone(), "b")).unwrap();
421
422 if let Some(cmd) = txn.commit() {
423 history.push(cmd);
424 }
425
426 assert_eq!(history.undo_depth(), 1);
427 assert!(history.can_undo());
428 }
429
430 #[test]
431 fn test_transaction_undo_redo() {
432 let buffer = Arc::new(Mutex::new(String::new()));
433 let mut history = HistoryManager::new(HistoryConfig::unlimited());
434
435 let mut txn = Transaction::begin("Undo/Redo Test");
436 txn.add_executed(make_cmd(buffer.clone(), "hello")).unwrap();
437 txn.add_executed(make_cmd(buffer.clone(), " world"))
438 .unwrap();
439
440 if let Some(cmd) = txn.commit() {
441 history.push(cmd);
442 }
443
444 assert_eq!(*buffer.lock().unwrap(), "hello world");
445
446 history.undo();
448 assert_eq!(*buffer.lock().unwrap(), "");
449
450 history.redo();
452 assert_eq!(*buffer.lock().unwrap(), "hello world");
453 }
454
455 #[test]
456 fn test_scope_basic() {
457 let buffer = Arc::new(Mutex::new(String::new()));
458 let mut history = HistoryManager::new(HistoryConfig::unlimited());
459
460 {
461 let mut scope = TransactionScope::new(&mut history);
462 scope.begin("Scope Test");
463
464 scope.execute(make_scope_cmd(buffer.clone(), "a")).unwrap();
465 scope.execute(make_scope_cmd(buffer.clone(), "b")).unwrap();
466
467 scope.commit().unwrap();
468 }
469
470 assert_eq!(history.undo_depth(), 1);
471 assert_eq!(*buffer.lock().unwrap(), "ab");
472 }
473
474 #[test]
475 fn test_scope_nested() {
476 let buffer = Arc::new(Mutex::new(String::new()));
477 let mut history = HistoryManager::new(HistoryConfig::unlimited());
478
479 {
480 let mut scope = TransactionScope::new(&mut history);
481
482 scope.begin("Outer");
484 scope
485 .execute(make_scope_cmd(buffer.clone(), "outer1"))
486 .unwrap();
487
488 scope.begin("Inner");
490 scope
491 .execute(make_scope_cmd(buffer.clone(), "inner"))
492 .unwrap();
493 scope.commit().unwrap();
494
495 scope
496 .execute(make_scope_cmd(buffer.clone(), "outer2"))
497 .unwrap();
498 scope.commit().unwrap();
499 }
500
501 assert_eq!(history.undo_depth(), 1);
503 assert_eq!(*buffer.lock().unwrap(), "outer1innerouter2");
504 }
505
506 #[test]
507 fn test_scope_rollback() {
508 let buffer = Arc::new(Mutex::new(String::new()));
509 let mut history = HistoryManager::new(HistoryConfig::unlimited());
510
511 {
512 let mut scope = TransactionScope::new(&mut history);
513 scope.begin("Rollback");
514
515 scope.execute(make_scope_cmd(buffer.clone(), "a")).unwrap();
516 scope.execute(make_scope_cmd(buffer.clone(), "b")).unwrap();
517
518 scope.rollback().unwrap();
519 }
520
521 assert_eq!(history.undo_depth(), 0);
523 assert_eq!(*buffer.lock().unwrap(), "");
524 }
525
526 #[test]
527 fn test_scope_auto_rollback_on_drop() {
528 let buffer = Arc::new(Mutex::new(String::new()));
529 let mut history = HistoryManager::new(HistoryConfig::unlimited());
530
531 {
532 let mut scope = TransactionScope::new(&mut history);
533 scope.begin("Will be dropped");
534 scope
535 .execute(make_scope_cmd(buffer.clone(), "test"))
536 .unwrap();
537 }
539
540 assert_eq!(history.undo_depth(), 0);
542 assert_eq!(*buffer.lock().unwrap(), "");
543 }
544
545 #[test]
546 fn test_scope_depth() {
547 let mut history = HistoryManager::new(HistoryConfig::unlimited());
548
549 let mut scope = TransactionScope::new(&mut history);
550 assert_eq!(scope.depth(), 0);
551 assert!(!scope.is_active());
552
553 scope.begin("Level 1");
554 assert_eq!(scope.depth(), 1);
555 assert!(scope.is_active());
556
557 scope.begin("Level 2");
558 assert_eq!(scope.depth(), 2);
559
560 scope.commit().unwrap();
561 assert_eq!(scope.depth(), 1);
562
563 scope.commit().unwrap();
564 assert_eq!(scope.depth(), 0);
565 assert!(!scope.is_active());
566 }
567
568 #[test]
569 fn test_transaction_description() {
570 let txn = Transaction::begin("My Transaction");
571 assert_eq!(txn.description(), "My Transaction");
572 }
573
574 #[test]
575 fn test_finalized_transaction_rejects_commands() {
576 let buffer = Arc::new(Mutex::new(String::new()));
577
578 let mut txn = Transaction::begin("Finalized");
579 txn.rollback();
580
581 let result = txn.add_executed(make_cmd(buffer, "test"));
582 assert!(result.is_err());
583 }
584
585 #[test]
586 fn test_transaction_execute_method() {
587 let buffer = Arc::new(Mutex::new(String::new()));
588 let b1 = buffer.clone();
589 let b2 = buffer.clone();
590
591 let cmd = TextInsertCmd::new(WidgetId::new(1), 0, "exec")
592 .with_apply(move |_, _, txt| {
593 let mut buf = b1.lock().unwrap();
594 buf.push_str(txt);
595 Ok(())
596 })
597 .with_remove(move |_, _, _| {
598 let mut buf = b2.lock().unwrap();
599 buf.drain(..4);
600 Ok(())
601 });
602
603 let mut txn = Transaction::begin("Execute Test");
604 txn.execute(Box::new(cmd)).unwrap();
605 assert_eq!(txn.len(), 1);
606 assert_eq!(*buffer.lock().unwrap(), "exec");
607 }
608
609 #[test]
610 fn test_transaction_finalized_rejects_execute() {
611 let buffer = Arc::new(Mutex::new(String::new()));
612
613 let mut txn = Transaction::begin("Finalized");
614 txn.rollback();
615
616 let result = txn.execute(make_scope_cmd(buffer, "test"));
617 assert!(result.is_err());
618 }
619
620 #[test]
621 fn test_commit_after_rollback_returns_none() {
622 let buffer = Arc::new(Mutex::new(String::new()));
623
624 let mut txn = Transaction::begin("Rollback then commit");
625 txn.add_executed(make_cmd(buffer.clone(), "a")).unwrap();
626 txn.rollback();
627
628 assert!(txn.commit().is_none());
629 assert_eq!(*buffer.lock().unwrap(), "");
630 }
631
632 #[test]
633 fn test_scope_commit_after_execute_failure_does_not_push_rolled_back_batch() {
634 let buffer = Arc::new(Mutex::new(String::new()));
635 let mut history = HistoryManager::new(HistoryConfig::unlimited());
636
637 let failing_cmd = TextInsertCmd::new(WidgetId::new(1), 0, "boom")
638 .with_apply(move |_, _, _| Err(CommandError::Other("boom".to_string())))
639 .with_remove(move |_, _, _| Ok(()));
640
641 {
642 let mut scope = TransactionScope::new(&mut history);
643 scope.begin("Failure path");
644 let b_ok_apply = buffer.clone();
645 let b_ok_remove = buffer.clone();
646 let ok_cmd = TextInsertCmd::new(WidgetId::new(1), 0, "ok")
647 .with_apply(move |_, _, txt| {
648 let mut buf = b_ok_apply.lock().unwrap();
649 buf.push_str(txt);
650 Ok(())
651 })
652 .with_remove(move |_, _, _| {
653 let mut buf = b_ok_remove.lock().unwrap();
654 buf.drain(..2);
655 Ok(())
656 });
657 scope.execute(Box::new(ok_cmd)).unwrap();
658 assert!(scope.execute(Box::new(failing_cmd)).is_err());
659 scope.commit().unwrap();
661 }
662
663 assert_eq!(history.undo_depth(), 0);
664 assert_eq!(*buffer.lock().unwrap(), "");
665 }
666
667 #[test]
668 fn test_scope_execute_without_transaction() {
669 let buffer = Arc::new(Mutex::new(String::new()));
670 let mut history = HistoryManager::new(HistoryConfig::unlimited());
671
672 {
673 let mut scope = TransactionScope::new(&mut history);
674 scope
676 .execute(make_scope_cmd(buffer.clone(), "direct"))
677 .unwrap();
678 }
679
680 assert_eq!(history.undo_depth(), 1);
681 assert_eq!(*buffer.lock().unwrap(), "direct");
682 }
683
684 #[test]
685 fn test_scope_commit_without_begin_errors() {
686 let mut history = HistoryManager::new(HistoryConfig::unlimited());
687
688 let mut scope = TransactionScope::new(&mut history);
689 let result = scope.commit();
690 assert!(result.is_err());
691 }
692
693 #[test]
694 fn test_scope_rollback_without_begin_errors() {
695 let mut history = HistoryManager::new(HistoryConfig::unlimited());
696
697 let mut scope = TransactionScope::new(&mut history);
698 let result = scope.rollback();
699 assert!(result.is_err());
700 }
701
702 #[test]
703 fn test_transaction_multi_command_rollback_order() {
704 let buffer = Arc::new(Mutex::new(String::new()));
705
706 let mut txn = Transaction::begin("Multi Rollback");
707 txn.add_executed(make_cmd(buffer.clone(), "a")).unwrap();
708 txn.add_executed(make_cmd(buffer.clone(), "b")).unwrap();
709 txn.add_executed(make_cmd(buffer.clone(), "c")).unwrap();
710
711 assert_eq!(*buffer.lock().unwrap(), "abc");
712 txn.rollback();
713 assert_eq!(*buffer.lock().unwrap(), "");
714 }
715
716 #[test]
717 fn test_transaction_debug_impl() {
718 let txn = Transaction::begin("Debug Test");
719 let s = format!("{txn:?}");
720 assert!(s.contains("Transaction"));
721 assert!(s.contains("Debug Test"));
722 }
723
724 #[test]
729 fn test_rollback_is_idempotent() {
730 let buffer = Arc::new(Mutex::new(String::new()));
731 let mut txn = Transaction::begin("Double Rollback");
732 txn.add_executed(make_cmd(buffer.clone(), "x")).unwrap();
733
734 txn.rollback();
735 assert_eq!(*buffer.lock().unwrap(), "");
736
737 txn.rollback();
739 assert_eq!(*buffer.lock().unwrap(), "");
740 }
741
742 #[test]
743 fn test_rollback_empty_transaction() {
744 let mut txn = Transaction::begin("Empty Rollback");
745 txn.rollback();
746 assert!(txn.commit().is_none());
748 }
749
750 #[test]
751 fn test_scope_drop_with_multiple_uncommitted() {
752 let mut history = HistoryManager::new(HistoryConfig::unlimited());
753 let buf_a = Arc::new(Mutex::new(String::new()));
754 let buf_b = Arc::new(Mutex::new(String::new()));
755
756 {
757 let mut scope = TransactionScope::new(&mut history);
758 scope.begin("Outer");
759 scope.execute(make_scope_cmd(buf_a.clone(), "a")).unwrap();
761
762 scope.begin("Inner");
763 scope.execute(make_scope_cmd(buf_b.clone(), "b")).unwrap();
764
765 }
767
768 assert_eq!(history.undo_depth(), 0);
770 assert_eq!(*buf_a.lock().unwrap(), "");
771 assert_eq!(*buf_b.lock().unwrap(), "");
772 }
773
774 #[test]
775 fn test_scope_inner_rollback_outer_continues() {
776 let buffer = Arc::new(Mutex::new(String::new()));
777 let mut history = HistoryManager::new(HistoryConfig::unlimited());
778
779 {
780 let mut scope = TransactionScope::new(&mut history);
781
782 scope.begin("Outer");
784 scope
785 .execute(make_scope_cmd(buffer.clone(), "outer"))
786 .unwrap();
787
788 scope.begin("Inner");
790 scope
791 .execute(make_scope_cmd(buffer.clone(), "inner"))
792 .unwrap();
793 scope.rollback().unwrap(); assert_eq!(scope.depth(), 1); assert_eq!(*buffer.lock().unwrap(), "outer");
797
798 scope.commit().unwrap();
800 }
801
802 assert_eq!(history.undo_depth(), 1);
803 }
804
805 #[test]
806 fn test_scope_commit_empty_inner_txn() {
807 let mut history = HistoryManager::new(HistoryConfig::unlimited());
808
809 {
810 let mut scope = TransactionScope::new(&mut history);
811 scope.begin("Outer");
812 scope.begin("Empty Inner");
813 scope.commit().unwrap(); scope.commit().unwrap(); }
816
817 assert_eq!(history.undo_depth(), 0);
819 }
820
821 #[test]
822 fn test_transaction_execute_failure_rolls_back_prior() {
823 let buffer = Arc::new(Mutex::new(String::new()));
824 let b1 = buffer.clone();
825 let b2 = buffer.clone();
826
827 let ok_cmd = TextInsertCmd::new(WidgetId::new(1), 0, "ok")
828 .with_apply(move |_, _, txt| {
829 let mut buf = b1.lock().unwrap();
830 buf.push_str(txt);
831 Ok(())
832 })
833 .with_remove(move |_, _, _| {
834 let mut buf = b2.lock().unwrap();
835 buf.drain(..2);
836 Ok(())
837 });
838
839 let fail_cmd = TextInsertCmd::new(WidgetId::new(1), 2, "fail");
841
842 let mut txn = Transaction::begin("Execute Failure");
843 txn.execute(Box::new(ok_cmd)).unwrap();
844 assert_eq!(*buffer.lock().unwrap(), "ok");
845
846 let result = txn.execute(Box::new(fail_cmd));
848 assert!(result.is_err());
849 assert_eq!(*buffer.lock().unwrap(), "");
850 }
851
852 #[test]
853 fn test_transaction_is_empty_after_add() {
854 let buffer = Arc::new(Mutex::new(String::new()));
855 let mut txn = Transaction::begin("Not Empty");
856 assert!(txn.is_empty());
857
858 txn.add_executed(make_cmd(buffer, "x")).unwrap();
859 assert!(!txn.is_empty());
860 }
861
862 #[test]
863 fn test_scope_execute_failure_without_txn() {
864 let mut history = HistoryManager::new(HistoryConfig::unlimited());
865
866 let fail_cmd = TextInsertCmd::new(WidgetId::new(1), 0, "fail");
868 {
871 let mut scope = TransactionScope::new(&mut history);
872 let result = scope.execute(Box::new(fail_cmd));
873 assert!(result.is_err());
874 }
875 assert_eq!(history.undo_depth(), 0);
876 }
877
878 #[test]
884 fn test_scope_sequential_transactions() {
885 let buffer = Arc::new(Mutex::new(String::new()));
886 let mut history = HistoryManager::new(HistoryConfig::unlimited());
887
888 {
889 let mut scope = TransactionScope::new(&mut history);
890
891 scope.begin("First");
893 scope.execute(make_scope_cmd(buffer.clone(), "a")).unwrap();
894 scope.commit().unwrap();
895
896 scope.begin("Second");
898 scope.execute(make_scope_cmd(buffer.clone(), "b")).unwrap();
899 scope.commit().unwrap();
900 }
901
902 assert_eq!(history.undo_depth(), 2);
904 }
905
906 #[test]
907 fn test_scope_three_level_nesting() {
908 let buffer = Arc::new(Mutex::new(String::new()));
909 let mut history = HistoryManager::new(HistoryConfig::unlimited());
910
911 {
912 let mut scope = TransactionScope::new(&mut history);
913
914 scope.begin("Level 1");
915 scope.execute(make_scope_cmd(buffer.clone(), "a")).unwrap();
916
917 scope.begin("Level 2");
918 scope.execute(make_scope_cmd(buffer.clone(), "b")).unwrap();
919
920 scope.begin("Level 3");
921 scope.execute(make_scope_cmd(buffer.clone(), "c")).unwrap();
922 assert_eq!(scope.depth(), 3);
923
924 scope.commit().unwrap();
925 scope.commit().unwrap();
926 scope.commit().unwrap();
927 }
928
929 assert_eq!(history.undo_depth(), 1);
930 assert_eq!(*buffer.lock().unwrap(), "abc");
931
932 history.undo();
933 assert_eq!(*buffer.lock().unwrap(), "");
934 }
935
936 #[test]
937 fn test_scope_alternating_commit_rollback() {
938 let mut history = HistoryManager::new(HistoryConfig::unlimited());
939
940 {
941 let mut scope = TransactionScope::new(&mut history);
942
943 scope.begin("Committed");
945 let buf1 = Arc::new(Mutex::new(String::new()));
946 scope.execute(make_scope_cmd(buf1, "ok")).unwrap();
947 scope.commit().unwrap();
948
949 scope.begin("Rolled back");
951 let buf2 = Arc::new(Mutex::new(String::new()));
952 scope.execute(make_scope_cmd(buf2, "no")).unwrap();
953 scope.rollback().unwrap();
954
955 scope.begin("Also committed");
957 let buf3 = Arc::new(Mutex::new(String::new()));
958 scope.execute(make_scope_cmd(buf3, "yes")).unwrap();
959 scope.commit().unwrap();
960 }
961
962 assert_eq!(history.undo_depth(), 2);
964 }
965
966 #[test]
967 fn test_scope_rollback_then_new_transaction() {
968 let buf_bad = Arc::new(Mutex::new(String::new()));
969 let buf_good = Arc::new(Mutex::new(String::new()));
970 let mut history = HistoryManager::new(HistoryConfig::unlimited());
971
972 {
973 let mut scope = TransactionScope::new(&mut history);
974
975 scope.begin("Failed attempt");
976 scope
977 .execute(make_scope_cmd(buf_bad.clone(), "bad"))
978 .unwrap();
979 scope.rollback().unwrap();
980
981 scope.begin("Retry");
982 scope
983 .execute(make_scope_cmd(buf_good.clone(), "good"))
984 .unwrap();
985 scope.commit().unwrap();
986 }
987
988 assert_eq!(history.undo_depth(), 1);
989 assert_eq!(*buf_bad.lock().unwrap(), "");
990 assert_eq!(*buf_good.lock().unwrap(), "good");
991 }
992
993 #[test]
994 fn test_transaction_len_after_execute() {
995 let buffer = Arc::new(Mutex::new(String::new()));
996 let b1 = buffer.clone();
997 let b2 = buffer.clone();
998
999 let cmd = TextInsertCmd::new(WidgetId::new(1), 0, "x")
1000 .with_apply(move |_, _, txt| {
1001 b1.lock().unwrap().push_str(txt);
1002 Ok(())
1003 })
1004 .with_remove(move |_, _, _| {
1005 b2.lock().unwrap().drain(..1);
1006 Ok(())
1007 });
1008
1009 let mut txn = Transaction::begin("Len Test");
1010 assert_eq!(txn.len(), 0);
1011
1012 txn.execute(Box::new(cmd)).unwrap();
1013 assert_eq!(txn.len(), 1);
1014 }
1015
1016 #[test]
1017 fn test_transaction_many_commands() {
1018 let buffer = Arc::new(Mutex::new(String::new()));
1019 let mut txn = Transaction::begin("Many Commands");
1020
1021 for _ in 0..20 {
1022 txn.add_executed(make_cmd(buffer.clone(), ".")).unwrap();
1023 }
1024
1025 assert_eq!(txn.len(), 20);
1026 assert_eq!(buffer.lock().unwrap().len(), 20);
1027
1028 txn.rollback();
1029 assert_eq!(*buffer.lock().unwrap(), "");
1030 }
1031
1032 #[test]
1033 fn test_scope_execute_after_all_committed() {
1034 let buffer = Arc::new(Mutex::new(String::new()));
1035 let mut history = HistoryManager::new(HistoryConfig::unlimited());
1036
1037 {
1038 let mut scope = TransactionScope::new(&mut history);
1039
1040 scope.begin("Txn");
1042 scope.execute(make_scope_cmd(buffer.clone(), "a")).unwrap();
1043 scope.commit().unwrap();
1044
1045 scope.execute(make_scope_cmd(buffer.clone(), "b")).unwrap();
1047 }
1048
1049 assert_eq!(history.undo_depth(), 2);
1051 assert_eq!(*buffer.lock().unwrap(), "ab");
1052 }
1053
1054 #[test]
1055 fn test_scope_inner_commit_empty_outer_has_content() {
1056 let buffer = Arc::new(Mutex::new(String::new()));
1057 let mut history = HistoryManager::new(HistoryConfig::unlimited());
1058
1059 {
1060 let mut scope = TransactionScope::new(&mut history);
1061
1062 scope.begin("Outer with content");
1063 scope
1064 .execute(make_scope_cmd(buffer.clone(), "outer"))
1065 .unwrap();
1066
1067 scope.begin("Empty inner");
1068 scope.commit().unwrap();
1069
1070 scope.commit().unwrap();
1071 }
1072
1073 assert_eq!(history.undo_depth(), 1);
1074 assert_eq!(*buffer.lock().unwrap(), "outer");
1075 }
1076
1077 #[test]
1078 fn test_drop_transaction_without_finalize_rolls_back() {
1079 let buffer = Arc::new(Mutex::new(String::new()));
1080
1081 {
1082 let mut txn = Transaction::begin("Will be dropped");
1083 txn.add_executed(make_cmd(buffer.clone(), "dropped"))
1084 .unwrap();
1085 assert_eq!(*buffer.lock().unwrap(), "dropped");
1086 }
1088
1089 assert_eq!(*buffer.lock().unwrap(), "");
1091 }
1092
1093 fn make_scope_cmd(buffer: Arc<Mutex<String>>, text: &str) -> Box<dyn UndoableCmd> {
1095 let b1 = buffer.clone();
1096 let b2 = buffer.clone();
1097 let text = text.to_string();
1098 let text_clone = text.clone();
1099
1100 Box::new(
1101 TextInsertCmd::new(WidgetId::new(1), 0, text)
1102 .with_apply(move |_, _, txt| {
1103 let mut buf = b1.lock().unwrap();
1104 buf.push_str(txt);
1105 Ok(())
1106 })
1107 .with_remove(move |_, _, _| {
1108 let mut buf = b2.lock().unwrap();
1109 let new_len = buf.len().saturating_sub(text_clone.len());
1110 buf.truncate(new_len);
1111 Ok(())
1112 }),
1113 )
1114 }
1115
1116 #[test]
1117 fn test_scope_nested_rollback_preserves_outer() {
1118 let buf_outer = Arc::new(Mutex::new(String::new()));
1119 let buf_inner = Arc::new(Mutex::new(String::new()));
1120 let mut history = HistoryManager::new(HistoryConfig::unlimited());
1121
1122 {
1123 let mut scope = TransactionScope::new(&mut history);
1124
1125 scope.begin("Outer");
1126 scope
1127 .execute(make_scope_cmd(buf_outer.clone(), "A"))
1128 .unwrap();
1129 assert_eq!(*buf_outer.lock().unwrap(), "A");
1130
1131 scope.begin("Inner (will rollback)");
1132 scope
1133 .execute(make_scope_cmd(buf_inner.clone(), "B"))
1134 .unwrap();
1135 assert_eq!(*buf_inner.lock().unwrap(), "B");
1136
1137 scope.rollback().unwrap();
1138 assert_eq!(*buf_inner.lock().unwrap(), "");
1139 assert_eq!(*buf_outer.lock().unwrap(), "A");
1140
1141 scope.commit().unwrap();
1142 }
1143
1144 assert_eq!(history.undo_depth(), 1);
1145 assert_eq!(*buf_outer.lock().unwrap(), "A");
1146 }
1147}