presentar_core/
history.rs

1// Undo/Redo Command History - WASM-first command pattern implementation
2//
3// Provides:
4// - Command pattern for undoable operations
5// - History stack with undo/redo navigation
6// - Batch command grouping
7// - Memory-limited history
8// - Checkpoints and save points
9// - History branching support
10
11use std::any::Any;
12use std::collections::HashMap;
13use std::sync::Arc;
14
15/// Unique identifier for a command in history
16#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
17pub struct CommandId(u64);
18
19impl CommandId {
20    pub fn as_u64(self) -> u64 {
21        self.0
22    }
23}
24
25/// Unique identifier for a command group
26#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
27pub struct GroupId(u64);
28
29impl GroupId {
30    pub fn as_u64(self) -> u64 {
31        self.0
32    }
33}
34
35/// Unique identifier for a checkpoint
36#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
37pub struct CheckpointId(u64);
38
39impl CheckpointId {
40    pub fn as_u64(self) -> u64 {
41        self.0
42    }
43}
44
45/// Result of executing or undoing a command
46#[derive(Debug, Clone, PartialEq, Eq)]
47pub enum CommandResult {
48    /// Command executed successfully
49    Success,
50    /// Command failed with error message
51    Failed(String),
52    /// Command was cancelled
53    Cancelled,
54    /// Command requires confirmation
55    NeedsConfirmation(String),
56}
57
58impl CommandResult {
59    pub fn is_success(&self) -> bool {
60        matches!(self, Self::Success)
61    }
62
63    pub fn is_failed(&self) -> bool {
64        matches!(self, Self::Failed(_))
65    }
66}
67
68/// Trait for undoable commands
69pub trait Command: Send + Sync {
70    /// Execute the command
71    fn execute(&mut self) -> CommandResult;
72
73    /// Undo the command
74    fn undo(&mut self) -> CommandResult;
75
76    /// Redo the command (default: re-execute)
77    fn redo(&mut self) -> CommandResult {
78        self.execute()
79    }
80
81    /// Get command description
82    fn description(&self) -> &str;
83
84    /// Check if command can be merged with another
85    fn can_merge(&self, _other: &dyn Command) -> bool {
86        false
87    }
88
89    /// Merge with another command (returns merged command)
90    fn merge(&mut self, _other: Box<dyn Command>) -> Option<Box<dyn Command>> {
91        None
92    }
93
94    /// Estimate memory usage of this command
95    fn memory_size(&self) -> usize {
96        // Default to a reasonable estimate for trait objects
97        64
98    }
99
100    /// Get command metadata
101    fn metadata(&self) -> Option<&dyn Any> {
102        None
103    }
104}
105
106/// Entry in the history stack
107struct HistoryEntry {
108    #[allow(dead_code)]
109    id: CommandId,
110    command: Box<dyn Command>,
111    group_id: Option<GroupId>,
112    timestamp: u64,
113    #[allow(dead_code)]
114    executed: bool,
115}
116
117/// Configuration for command history
118#[derive(Debug, Clone)]
119pub struct HistoryConfig {
120    /// Maximum number of commands to keep
121    pub max_commands: usize,
122    /// Maximum memory usage in bytes
123    pub max_memory: usize,
124    /// Enable command merging
125    pub enable_merging: bool,
126    /// Automatically group rapid commands
127    pub auto_group_interval_ms: u64,
128}
129
130impl Default for HistoryConfig {
131    fn default() -> Self {
132        Self {
133            max_commands: 1000,
134            max_memory: 10 * 1024 * 1024, // 10 MB
135            enable_merging: true,
136            auto_group_interval_ms: 500,
137        }
138    }
139}
140
141/// Checkpoint in history
142#[derive(Debug, Clone)]
143pub struct Checkpoint {
144    pub id: CheckpointId,
145    pub name: String,
146    pub position: usize,
147    pub timestamp: u64,
148}
149
150/// State change notification
151#[derive(Debug, Clone, PartialEq, Eq)]
152pub enum HistoryEvent {
153    /// Command was executed
154    Executed(CommandId),
155    /// Command was undone
156    Undone(CommandId),
157    /// Command was redone
158    Redone(CommandId),
159    /// History was cleared
160    Cleared,
161    /// Checkpoint was created
162    CheckpointCreated(CheckpointId),
163    /// Restored to checkpoint
164    CheckpointRestored(CheckpointId),
165    /// History limit reached, old commands dropped
166    Trimmed(usize),
167}
168
169/// Callback for history events
170pub type HistoryCallback = Arc<dyn Fn(HistoryEvent) + Send + Sync>;
171
172/// Command history manager
173pub struct CommandHistory {
174    config: HistoryConfig,
175    next_command_id: u64,
176    next_group_id: u64,
177    next_checkpoint_id: u64,
178
179    /// Commands that have been executed (undo stack)
180    undo_stack: Vec<HistoryEntry>,
181    /// Commands that have been undone (redo stack)
182    redo_stack: Vec<HistoryEntry>,
183
184    /// Active command group
185    current_group: Option<GroupId>,
186    /// Saved checkpoints
187    checkpoints: HashMap<CheckpointId, Checkpoint>,
188
189    /// Current timestamp
190    timestamp: u64,
191    /// Last command timestamp for auto-grouping
192    last_command_time: u64,
193
194    /// Event listeners
195    listeners: Vec<HistoryCallback>,
196
197    /// Current memory usage
198    current_memory: usize,
199
200    /// Whether history is recording
201    recording: bool,
202}
203
204impl Default for CommandHistory {
205    fn default() -> Self {
206        Self::new(HistoryConfig::default())
207    }
208}
209
210impl CommandHistory {
211    pub fn new(config: HistoryConfig) -> Self {
212        Self {
213            config,
214            next_command_id: 1,
215            next_group_id: 1,
216            next_checkpoint_id: 1,
217            undo_stack: Vec::new(),
218            redo_stack: Vec::new(),
219            current_group: None,
220            checkpoints: HashMap::new(),
221            timestamp: 0,
222            last_command_time: 0,
223            listeners: Vec::new(),
224            current_memory: 0,
225            recording: true,
226        }
227    }
228
229    /// Execute a command and add to history
230    pub fn execute(&mut self, mut command: Box<dyn Command>) -> CommandResult {
231        if !self.recording {
232            return command.execute();
233        }
234
235        let result = command.execute();
236        if !result.is_success() {
237            return result;
238        }
239
240        // Clear redo stack when new command is executed
241        self.redo_stack.clear();
242
243        let id = CommandId(self.next_command_id);
244        self.next_command_id += 1;
245
246        // Check for auto-grouping
247        let group_id = if self.current_group.is_some() {
248            self.current_group
249        } else if self.config.auto_group_interval_ms > 0
250            && self.timestamp.saturating_sub(self.last_command_time)
251                < self.config.auto_group_interval_ms
252            && !self.undo_stack.is_empty()
253        {
254            // Auto-group with previous command
255            self.undo_stack.last().and_then(|e| e.group_id)
256        } else {
257            None
258        };
259
260        // Check for merging
261        if self.config.enable_merging && !self.undo_stack.is_empty() {
262            let can_merge = self
263                .undo_stack
264                .last()
265                .is_some_and(|last| last.command.can_merge(command.as_ref()));
266
267            if can_merge {
268                if let Some(last_entry) = self.undo_stack.last_mut() {
269                    if let Some(merged) = last_entry.command.merge(command) {
270                        // Update memory tracking
271                        self.current_memory -= last_entry.command.memory_size();
272                        self.current_memory += merged.memory_size();
273                        last_entry.command = merged;
274                        self.emit(&HistoryEvent::Executed(id));
275                        return result;
276                    }
277                }
278                // If merge returned None, we can't use command anymore
279                // This shouldn't happen in practice if can_merge is implemented correctly
280                return result;
281            }
282        }
283
284        let memory = command.memory_size();
285        self.current_memory += memory;
286
287        let entry = HistoryEntry {
288            id,
289            command,
290            group_id,
291            timestamp: self.timestamp,
292            executed: true,
293        };
294
295        self.undo_stack.push(entry);
296        self.last_command_time = self.timestamp;
297
298        // Trim if needed
299        self.trim_if_needed();
300
301        self.emit(&HistoryEvent::Executed(id));
302        result
303    }
304
305    /// Undo the last command
306    pub fn undo(&mut self) -> Option<CommandResult> {
307        let entry = self.undo_stack.pop()?;
308        let id = entry.id;
309        let group_id = entry.group_id;
310
311        let mut command = entry.command;
312        let result = command.undo();
313
314        if result.is_success() {
315            self.current_memory -= command.memory_size();
316
317            let redo_entry = HistoryEntry {
318                id,
319                command,
320                group_id,
321                timestamp: entry.timestamp,
322                executed: false,
323            };
324            self.current_memory += redo_entry.command.memory_size();
325            self.redo_stack.push(redo_entry);
326
327            self.emit(&HistoryEvent::Undone(id));
328
329            // Undo entire group if applicable
330            if let Some(gid) = group_id {
331                while let Some(last) = self.undo_stack.last() {
332                    if last.group_id == Some(gid) {
333                        self.undo();
334                    } else {
335                        break;
336                    }
337                }
338            }
339        }
340
341        Some(result)
342    }
343
344    /// Redo the last undone command
345    pub fn redo(&mut self) -> Option<CommandResult> {
346        let entry = self.redo_stack.pop()?;
347        let id = entry.id;
348        let group_id = entry.group_id;
349
350        let mut command = entry.command;
351        let result = command.redo();
352
353        if result.is_success() {
354            self.current_memory -= command.memory_size();
355
356            let undo_entry = HistoryEntry {
357                id,
358                command,
359                group_id,
360                timestamp: entry.timestamp,
361                executed: true,
362            };
363            self.current_memory += undo_entry.command.memory_size();
364            self.undo_stack.push(undo_entry);
365
366            self.emit(&HistoryEvent::Redone(id));
367
368            // Redo entire group if applicable
369            if let Some(gid) = group_id {
370                while let Some(last) = self.redo_stack.last() {
371                    if last.group_id == Some(gid) {
372                        self.redo();
373                    } else {
374                        break;
375                    }
376                }
377            }
378        }
379
380        Some(result)
381    }
382
383    /// Check if undo is available
384    pub fn can_undo(&self) -> bool {
385        !self.undo_stack.is_empty()
386    }
387
388    /// Check if redo is available
389    pub fn can_redo(&self) -> bool {
390        !self.redo_stack.is_empty()
391    }
392
393    /// Get number of undoable commands
394    pub fn undo_count(&self) -> usize {
395        self.undo_stack.len()
396    }
397
398    /// Get number of redoable commands
399    pub fn redo_count(&self) -> usize {
400        self.redo_stack.len()
401    }
402
403    /// Get description of next undo command
404    pub fn undo_description(&self) -> Option<&str> {
405        self.undo_stack.last().map(|e| e.command.description())
406    }
407
408    /// Get description of next redo command
409    pub fn redo_description(&self) -> Option<&str> {
410        self.redo_stack.last().map(|e| e.command.description())
411    }
412
413    /// Start a command group
414    pub fn begin_group(&mut self) -> GroupId {
415        let id = GroupId(self.next_group_id);
416        self.next_group_id += 1;
417        self.current_group = Some(id);
418        id
419    }
420
421    /// End current command group
422    pub fn end_group(&mut self) {
423        self.current_group = None;
424    }
425
426    /// Execute multiple commands as a group
427    pub fn execute_group<I>(&mut self, commands: I) -> Vec<CommandResult>
428    where
429        I: IntoIterator<Item = Box<dyn Command>>,
430    {
431        let _group = self.begin_group();
432        let results: Vec<_> = commands.into_iter().map(|cmd| self.execute(cmd)).collect();
433        self.end_group();
434        results
435    }
436
437    /// Create a checkpoint at current position
438    pub fn create_checkpoint(&mut self, name: impl Into<String>) -> CheckpointId {
439        let id = CheckpointId(self.next_checkpoint_id);
440        self.next_checkpoint_id += 1;
441
442        let checkpoint = Checkpoint {
443            id,
444            name: name.into(),
445            position: self.undo_stack.len(),
446            timestamp: self.timestamp,
447        };
448
449        self.checkpoints.insert(id, checkpoint);
450        self.emit(&HistoryEvent::CheckpointCreated(id));
451        id
452    }
453
454    /// Restore to a checkpoint
455    pub fn restore_checkpoint(&mut self, id: CheckpointId) -> bool {
456        let checkpoint = match self.checkpoints.get(&id) {
457            Some(c) => c.clone(),
458            None => return false,
459        };
460
461        // Undo until we reach checkpoint position
462        while self.undo_stack.len() > checkpoint.position {
463            if self.undo().is_none() {
464                break;
465            }
466        }
467
468        self.emit(&HistoryEvent::CheckpointRestored(id));
469        true
470    }
471
472    /// Get checkpoint by ID
473    pub fn get_checkpoint(&self, id: CheckpointId) -> Option<&Checkpoint> {
474        self.checkpoints.get(&id)
475    }
476
477    /// List all checkpoints
478    pub fn checkpoints(&self) -> impl Iterator<Item = &Checkpoint> {
479        self.checkpoints.values()
480    }
481
482    /// Clear all history
483    pub fn clear(&mut self) {
484        self.undo_stack.clear();
485        self.redo_stack.clear();
486        self.checkpoints.clear();
487        self.current_memory = 0;
488        self.emit(&HistoryEvent::Cleared);
489    }
490
491    /// Get current memory usage
492    pub fn memory_usage(&self) -> usize {
493        self.current_memory
494    }
495
496    /// Update timestamp (call each frame or periodically)
497    pub fn tick(&mut self, delta_ms: u64) {
498        self.timestamp += delta_ms;
499    }
500
501    /// Pause recording
502    pub fn pause(&mut self) {
503        self.recording = false;
504    }
505
506    /// Resume recording
507    pub fn resume(&mut self) {
508        self.recording = true;
509    }
510
511    /// Check if recording
512    pub fn is_recording(&self) -> bool {
513        self.recording
514    }
515
516    /// Add event listener
517    pub fn on_event(&mut self, callback: HistoryCallback) {
518        self.listeners.push(callback);
519    }
520
521    fn emit(&self, event: &HistoryEvent) {
522        for listener in &self.listeners {
523            listener(event.clone());
524        }
525    }
526
527    fn trim_if_needed(&mut self) {
528        let mut trimmed = 0;
529
530        // Trim by count
531        while self.undo_stack.len() > self.config.max_commands {
532            if let Some(entry) = self.undo_stack.first() {
533                self.current_memory -= entry.command.memory_size();
534            }
535            self.undo_stack.remove(0);
536            trimmed += 1;
537        }
538
539        // Trim by memory
540        while self.current_memory > self.config.max_memory && !self.undo_stack.is_empty() {
541            if let Some(entry) = self.undo_stack.first() {
542                self.current_memory -= entry.command.memory_size();
543            }
544            self.undo_stack.remove(0);
545            trimmed += 1;
546        }
547
548        if trimmed > 0 {
549            // Update checkpoint positions
550            for checkpoint in self.checkpoints.values_mut() {
551                checkpoint.position = checkpoint.position.saturating_sub(trimmed);
552            }
553            self.emit(&HistoryEvent::Trimmed(trimmed));
554        }
555    }
556}
557
558/// Builder for creating composite commands
559pub struct CompositeCommand {
560    description: String,
561    commands: Vec<Box<dyn Command>>,
562    executed: Vec<bool>,
563}
564
565impl CompositeCommand {
566    pub fn new(description: impl Into<String>) -> Self {
567        Self {
568            description: description.into(),
569            commands: Vec::new(),
570            executed: Vec::new(),
571        }
572    }
573
574    pub fn with_command(mut self, command: Box<dyn Command>) -> Self {
575        self.commands.push(command);
576        self
577    }
578
579    pub fn build(self) -> Box<dyn Command> {
580        Box::new(CompositeCommandImpl {
581            description: self.description,
582            commands: self.commands,
583            executed: self.executed,
584        })
585    }
586}
587
588struct CompositeCommandImpl {
589    description: String,
590    commands: Vec<Box<dyn Command>>,
591    executed: Vec<bool>,
592}
593
594impl Command for CompositeCommandImpl {
595    fn execute(&mut self) -> CommandResult {
596        self.executed.clear();
597        for cmd in &mut self.commands {
598            let result = cmd.execute();
599            if !result.is_success() {
600                // Rollback executed commands
601                for (i, was_executed) in self.executed.iter().enumerate().rev() {
602                    if *was_executed {
603                        self.commands[i].undo();
604                    }
605                }
606                return result;
607            }
608            self.executed.push(true);
609        }
610        CommandResult::Success
611    }
612
613    fn undo(&mut self) -> CommandResult {
614        for (i, cmd) in self.commands.iter_mut().enumerate().rev() {
615            if i < self.executed.len() && self.executed[i] {
616                let result = cmd.undo();
617                if !result.is_success() {
618                    return result;
619                }
620            }
621        }
622        self.executed.clear();
623        CommandResult::Success
624    }
625
626    fn description(&self) -> &str {
627        &self.description
628    }
629
630    fn memory_size(&self) -> usize {
631        std::mem::size_of::<Self>() + self.commands.iter().map(|c| c.memory_size()).sum::<usize>()
632    }
633}
634
635/// Simple value change command
636pub struct SetValueCommand<T: Clone + Send + Sync + 'static> {
637    description: String,
638    value: Arc<std::sync::RwLock<T>>,
639    old_value: Option<T>,
640    new_value: T,
641}
642
643impl<T: Clone + Send + Sync + 'static> SetValueCommand<T> {
644    pub fn new(
645        description: impl Into<String>,
646        value: Arc<std::sync::RwLock<T>>,
647        new_value: T,
648    ) -> Self {
649        Self {
650            description: description.into(),
651            value,
652            old_value: None,
653            new_value,
654        }
655    }
656}
657
658impl<T: Clone + Send + Sync + 'static> Command for SetValueCommand<T> {
659    fn execute(&mut self) -> CommandResult {
660        let Ok(mut guard) = self.value.write() else {
661            return CommandResult::Failed("Lock poisoned".into());
662        };
663        self.old_value = Some(guard.clone());
664        *guard = self.new_value.clone();
665        CommandResult::Success
666    }
667
668    fn undo(&mut self) -> CommandResult {
669        let Some(old) = self.old_value.clone() else {
670            return CommandResult::Failed("No old value".into());
671        };
672        let Ok(mut guard) = self.value.write() else {
673            return CommandResult::Failed("Lock poisoned".into());
674        };
675        *guard = old;
676        CommandResult::Success
677    }
678
679    fn description(&self) -> &str {
680        &self.description
681    }
682}
683
684#[cfg(test)]
685#[allow(clippy::unwrap_used)]
686mod tests {
687    use super::*;
688    use std::sync::atomic::{AtomicI32, Ordering};
689
690    // Test command that increments/decrements a counter
691    struct IncrementCommand {
692        counter: Arc<AtomicI32>,
693        amount: i32,
694    }
695
696    impl Command for IncrementCommand {
697        fn execute(&mut self) -> CommandResult {
698            self.counter.fetch_add(self.amount, Ordering::SeqCst);
699            CommandResult::Success
700        }
701
702        fn undo(&mut self) -> CommandResult {
703            self.counter.fetch_sub(self.amount, Ordering::SeqCst);
704            CommandResult::Success
705        }
706
707        fn description(&self) -> &str {
708            "Increment counter"
709        }
710    }
711
712    // Test command that can fail
713    struct FailingCommand {
714        should_fail: bool,
715    }
716
717    impl Command for FailingCommand {
718        fn execute(&mut self) -> CommandResult {
719            if self.should_fail {
720                CommandResult::Failed("Intentional failure".into())
721            } else {
722                CommandResult::Success
723            }
724        }
725
726        fn undo(&mut self) -> CommandResult {
727            CommandResult::Success
728        }
729
730        fn description(&self) -> &str {
731            "Failing command"
732        }
733    }
734
735    #[test]
736    fn test_basic_execute() {
737        let mut history = CommandHistory::default();
738        let counter = Arc::new(AtomicI32::new(0));
739
740        let cmd = Box::new(IncrementCommand {
741            counter: counter.clone(),
742            amount: 5,
743        });
744
745        let result = history.execute(cmd);
746        assert!(result.is_success());
747        assert_eq!(counter.load(Ordering::SeqCst), 5);
748        assert_eq!(history.undo_count(), 1);
749    }
750
751    #[test]
752    fn test_undo() {
753        let mut history = CommandHistory::default();
754        let counter = Arc::new(AtomicI32::new(0));
755
756        history.execute(Box::new(IncrementCommand {
757            counter: counter.clone(),
758            amount: 10,
759        }));
760        assert_eq!(counter.load(Ordering::SeqCst), 10);
761
762        let result = history.undo();
763        assert!(result.is_some());
764        assert!(result.unwrap().is_success());
765        assert_eq!(counter.load(Ordering::SeqCst), 0);
766    }
767
768    #[test]
769    fn test_redo() {
770        let mut history = CommandHistory::default();
771        let counter = Arc::new(AtomicI32::new(0));
772
773        history.execute(Box::new(IncrementCommand {
774            counter: counter.clone(),
775            amount: 7,
776        }));
777        history.undo();
778        assert_eq!(counter.load(Ordering::SeqCst), 0);
779
780        let result = history.redo();
781        assert!(result.is_some());
782        assert!(result.unwrap().is_success());
783        assert_eq!(counter.load(Ordering::SeqCst), 7);
784    }
785
786    #[test]
787    fn test_multiple_undo_redo() {
788        let mut history = CommandHistory::default();
789        let counter = Arc::new(AtomicI32::new(0));
790
791        history.execute(Box::new(IncrementCommand {
792            counter: counter.clone(),
793            amount: 1,
794        }));
795        history.execute(Box::new(IncrementCommand {
796            counter: counter.clone(),
797            amount: 2,
798        }));
799        history.execute(Box::new(IncrementCommand {
800            counter: counter.clone(),
801            amount: 3,
802        }));
803
804        assert_eq!(counter.load(Ordering::SeqCst), 6);
805        assert_eq!(history.undo_count(), 3);
806
807        history.undo();
808        assert_eq!(counter.load(Ordering::SeqCst), 3);
809
810        history.undo();
811        assert_eq!(counter.load(Ordering::SeqCst), 1);
812
813        history.redo();
814        assert_eq!(counter.load(Ordering::SeqCst), 3);
815    }
816
817    #[test]
818    fn test_can_undo_redo() {
819        let mut history = CommandHistory::default();
820        let counter = Arc::new(AtomicI32::new(0));
821
822        assert!(!history.can_undo());
823        assert!(!history.can_redo());
824
825        history.execute(Box::new(IncrementCommand {
826            counter: counter.clone(),
827            amount: 1,
828        }));
829
830        assert!(history.can_undo());
831        assert!(!history.can_redo());
832
833        history.undo();
834        assert!(!history.can_undo());
835        assert!(history.can_redo());
836    }
837
838    #[test]
839    fn test_redo_cleared_on_new_execute() {
840        let mut history = CommandHistory::default();
841        let counter = Arc::new(AtomicI32::new(0));
842
843        history.execute(Box::new(IncrementCommand {
844            counter: counter.clone(),
845            amount: 1,
846        }));
847        history.execute(Box::new(IncrementCommand {
848            counter: counter.clone(),
849            amount: 2,
850        }));
851
852        history.undo();
853        assert!(history.can_redo());
854
855        // New command clears redo stack
856        history.execute(Box::new(IncrementCommand {
857            counter: counter.clone(),
858            amount: 5,
859        }));
860
861        assert!(!history.can_redo());
862    }
863
864    #[test]
865    fn test_failed_command_not_added() {
866        let mut history = CommandHistory::default();
867
868        let result = history.execute(Box::new(FailingCommand { should_fail: true }));
869        assert!(result.is_failed());
870        assert_eq!(history.undo_count(), 0);
871    }
872
873    #[test]
874    fn test_command_descriptions() {
875        let mut history = CommandHistory::default();
876        let counter = Arc::new(AtomicI32::new(0));
877
878        assert!(history.undo_description().is_none());
879
880        history.execute(Box::new(IncrementCommand {
881            counter: counter.clone(),
882            amount: 1,
883        }));
884
885        assert_eq!(history.undo_description(), Some("Increment counter"));
886    }
887
888    #[test]
889    fn test_command_groups() {
890        let mut history = CommandHistory::default();
891        let counter = Arc::new(AtomicI32::new(0));
892
893        let _group = history.begin_group();
894        history.execute(Box::new(IncrementCommand {
895            counter: counter.clone(),
896            amount: 1,
897        }));
898        history.execute(Box::new(IncrementCommand {
899            counter: counter.clone(),
900            amount: 2,
901        }));
902        history.execute(Box::new(IncrementCommand {
903            counter: counter.clone(),
904            amount: 3,
905        }));
906        history.end_group();
907
908        assert_eq!(counter.load(Ordering::SeqCst), 6);
909
910        // Undo should undo entire group
911        history.undo();
912        assert_eq!(counter.load(Ordering::SeqCst), 0);
913    }
914
915    #[test]
916    fn test_execute_group() {
917        let mut history = CommandHistory::default();
918        let counter = Arc::new(AtomicI32::new(0));
919
920        let commands: Vec<Box<dyn Command>> = vec![
921            Box::new(IncrementCommand {
922                counter: counter.clone(),
923                amount: 1,
924            }),
925            Box::new(IncrementCommand {
926                counter: counter.clone(),
927                amount: 2,
928            }),
929        ];
930
931        let results = history.execute_group(commands);
932        assert!(results.iter().all(|r| r.is_success()));
933        assert_eq!(counter.load(Ordering::SeqCst), 3);
934    }
935
936    #[test]
937    fn test_checkpoints() {
938        let mut history = CommandHistory::default();
939        let counter = Arc::new(AtomicI32::new(0));
940
941        history.execute(Box::new(IncrementCommand {
942            counter: counter.clone(),
943            amount: 5,
944        }));
945
946        let checkpoint = history.create_checkpoint("Initial state");
947
948        history.execute(Box::new(IncrementCommand {
949            counter: counter.clone(),
950            amount: 10,
951        }));
952        history.execute(Box::new(IncrementCommand {
953            counter: counter.clone(),
954            amount: 15,
955        }));
956
957        assert_eq!(counter.load(Ordering::SeqCst), 30);
958
959        // Restore to checkpoint
960        let restored = history.restore_checkpoint(checkpoint);
961        assert!(restored);
962        assert_eq!(counter.load(Ordering::SeqCst), 5);
963    }
964
965    #[test]
966    fn test_get_checkpoint() {
967        let mut history = CommandHistory::default();
968
969        let id = history.create_checkpoint("Test checkpoint");
970        let checkpoint = history.get_checkpoint(id);
971
972        assert!(checkpoint.is_some());
973        assert_eq!(checkpoint.unwrap().name, "Test checkpoint");
974    }
975
976    #[test]
977    fn test_list_checkpoints() {
978        let mut history = CommandHistory::default();
979
980        history.create_checkpoint("First");
981        history.create_checkpoint("Second");
982        history.create_checkpoint("Third");
983
984        let checkpoints: Vec<_> = history.checkpoints().collect();
985        assert_eq!(checkpoints.len(), 3);
986    }
987
988    #[test]
989    fn test_clear() {
990        let mut history = CommandHistory::default();
991        let counter = Arc::new(AtomicI32::new(0));
992
993        history.execute(Box::new(IncrementCommand {
994            counter: counter.clone(),
995            amount: 1,
996        }));
997        history.execute(Box::new(IncrementCommand {
998            counter: counter.clone(),
999            amount: 2,
1000        }));
1001        history.create_checkpoint("Test");
1002
1003        history.clear();
1004
1005        assert!(!history.can_undo());
1006        assert!(!history.can_redo());
1007        assert_eq!(history.checkpoints().count(), 0);
1008    }
1009
1010    #[test]
1011    fn test_max_commands_limit() {
1012        let config = HistoryConfig {
1013            max_commands: 3,
1014            ..Default::default()
1015        };
1016        let mut history = CommandHistory::new(config);
1017        let counter = Arc::new(AtomicI32::new(0));
1018
1019        for i in 0..5 {
1020            history.execute(Box::new(IncrementCommand {
1021                counter: counter.clone(),
1022                amount: i + 1,
1023            }));
1024        }
1025
1026        assert_eq!(history.undo_count(), 3);
1027    }
1028
1029    #[test]
1030    fn test_pause_resume_recording() {
1031        let mut history = CommandHistory::default();
1032        let counter = Arc::new(AtomicI32::new(0));
1033
1034        history.execute(Box::new(IncrementCommand {
1035            counter: counter.clone(),
1036            amount: 1,
1037        }));
1038
1039        history.pause();
1040        assert!(!history.is_recording());
1041
1042        history.execute(Box::new(IncrementCommand {
1043            counter: counter.clone(),
1044            amount: 2,
1045        }));
1046
1047        // Command executed but not recorded
1048        assert_eq!(counter.load(Ordering::SeqCst), 3);
1049        assert_eq!(history.undo_count(), 1);
1050
1051        history.resume();
1052        assert!(history.is_recording());
1053
1054        history.execute(Box::new(IncrementCommand {
1055            counter: counter.clone(),
1056            amount: 3,
1057        }));
1058        assert_eq!(history.undo_count(), 2);
1059    }
1060
1061    #[test]
1062    fn test_event_callbacks() {
1063        use std::sync::atomic::AtomicUsize;
1064
1065        let mut history = CommandHistory::default();
1066        let counter = Arc::new(AtomicI32::new(0));
1067        let event_count = Arc::new(AtomicUsize::new(0));
1068
1069        let ec = event_count.clone();
1070        history.on_event(Arc::new(move |_event| {
1071            ec.fetch_add(1, Ordering::SeqCst);
1072        }));
1073
1074        history.execute(Box::new(IncrementCommand {
1075            counter: counter.clone(),
1076            amount: 1,
1077        }));
1078        history.undo();
1079        history.redo();
1080
1081        assert_eq!(event_count.load(Ordering::SeqCst), 3);
1082    }
1083
1084    #[test]
1085    fn test_memory_tracking() {
1086        let mut history = CommandHistory::default();
1087        let counter = Arc::new(AtomicI32::new(0));
1088
1089        assert_eq!(history.memory_usage(), 0);
1090
1091        history.execute(Box::new(IncrementCommand {
1092            counter: counter.clone(),
1093            amount: 1,
1094        }));
1095
1096        assert!(history.memory_usage() > 0);
1097    }
1098
1099    #[test]
1100    fn test_tick() {
1101        let mut history = CommandHistory::default();
1102        history.tick(100);
1103        history.tick(50);
1104        // Just verify it doesn't panic
1105    }
1106
1107    #[test]
1108    fn test_command_result_helpers() {
1109        let success = CommandResult::Success;
1110        let failed = CommandResult::Failed("error".into());
1111        let cancelled = CommandResult::Cancelled;
1112
1113        assert!(success.is_success());
1114        assert!(!success.is_failed());
1115
1116        assert!(!failed.is_success());
1117        assert!(failed.is_failed());
1118
1119        assert!(!cancelled.is_success());
1120        assert!(!cancelled.is_failed());
1121    }
1122
1123    #[test]
1124    fn test_command_id() {
1125        let id = CommandId(42);
1126        assert_eq!(id.as_u64(), 42);
1127    }
1128
1129    #[test]
1130    fn test_group_id() {
1131        let id = GroupId(123);
1132        assert_eq!(id.as_u64(), 123);
1133    }
1134
1135    #[test]
1136    fn test_checkpoint_id() {
1137        let id = CheckpointId(456);
1138        assert_eq!(id.as_u64(), 456);
1139    }
1140
1141    #[test]
1142    fn test_composite_command() {
1143        let counter = Arc::new(AtomicI32::new(0));
1144
1145        let composite = CompositeCommand::new("Add 6")
1146            .with_command(Box::new(IncrementCommand {
1147                counter: counter.clone(),
1148                amount: 1,
1149            }))
1150            .with_command(Box::new(IncrementCommand {
1151                counter: counter.clone(),
1152                amount: 2,
1153            }))
1154            .with_command(Box::new(IncrementCommand {
1155                counter: counter.clone(),
1156                amount: 3,
1157            }))
1158            .build();
1159
1160        let mut history = CommandHistory::default();
1161        history.execute(composite);
1162
1163        assert_eq!(counter.load(Ordering::SeqCst), 6);
1164
1165        history.undo();
1166        assert_eq!(counter.load(Ordering::SeqCst), 0);
1167
1168        history.redo();
1169        assert_eq!(counter.load(Ordering::SeqCst), 6);
1170    }
1171
1172    #[test]
1173    fn test_composite_command_rollback_on_failure() {
1174        let counter = Arc::new(AtomicI32::new(0));
1175
1176        let composite = CompositeCommand::new("Should fail")
1177            .with_command(Box::new(IncrementCommand {
1178                counter: counter.clone(),
1179                amount: 5,
1180            }))
1181            .with_command(Box::new(FailingCommand { should_fail: true }))
1182            .build();
1183
1184        let mut history = CommandHistory::default();
1185        let result = history.execute(composite);
1186
1187        assert!(result.is_failed());
1188        // First command should be rolled back
1189        assert_eq!(counter.load(Ordering::SeqCst), 0);
1190    }
1191
1192    #[test]
1193    fn test_set_value_command() {
1194        let value = Arc::new(std::sync::RwLock::new(10));
1195
1196        let cmd = Box::new(SetValueCommand::new("Set to 42", value.clone(), 42));
1197
1198        let mut history = CommandHistory::default();
1199        history.execute(cmd);
1200
1201        assert_eq!(*value.read().unwrap(), 42);
1202
1203        history.undo();
1204        assert_eq!(*value.read().unwrap(), 10);
1205
1206        history.redo();
1207        assert_eq!(*value.read().unwrap(), 42);
1208    }
1209
1210    #[test]
1211    fn test_default_config() {
1212        let config = HistoryConfig::default();
1213        assert_eq!(config.max_commands, 1000);
1214        assert_eq!(config.max_memory, 10 * 1024 * 1024);
1215        assert!(config.enable_merging);
1216        assert_eq!(config.auto_group_interval_ms, 500);
1217    }
1218
1219    #[test]
1220    fn test_undo_on_empty_returns_none() {
1221        let mut history = CommandHistory::default();
1222        assert!(history.undo().is_none());
1223    }
1224
1225    #[test]
1226    fn test_redo_on_empty_returns_none() {
1227        let mut history = CommandHistory::default();
1228        assert!(history.redo().is_none());
1229    }
1230
1231    #[test]
1232    fn test_restore_invalid_checkpoint() {
1233        let mut history = CommandHistory::default();
1234        let invalid_id = CheckpointId(999);
1235        assert!(!history.restore_checkpoint(invalid_id));
1236    }
1237
1238    #[test]
1239    fn test_history_event_variants() {
1240        let events = vec![
1241            HistoryEvent::Executed(CommandId(1)),
1242            HistoryEvent::Undone(CommandId(2)),
1243            HistoryEvent::Redone(CommandId(3)),
1244            HistoryEvent::Cleared,
1245            HistoryEvent::CheckpointCreated(CheckpointId(1)),
1246            HistoryEvent::CheckpointRestored(CheckpointId(1)),
1247            HistoryEvent::Trimmed(5),
1248        ];
1249
1250        // Verify all variants can be created and compared
1251        for event in &events {
1252            assert_eq!(event, event);
1253        }
1254    }
1255
1256    #[test]
1257    fn test_redo_description() {
1258        let mut history = CommandHistory::default();
1259        let counter = Arc::new(AtomicI32::new(0));
1260
1261        assert!(history.redo_description().is_none());
1262
1263        history.execute(Box::new(IncrementCommand {
1264            counter: counter.clone(),
1265            amount: 1,
1266        }));
1267        history.undo();
1268
1269        assert_eq!(history.redo_description(), Some("Increment counter"));
1270    }
1271
1272    #[test]
1273    fn test_redo_count() {
1274        let mut history = CommandHistory::default();
1275        let counter = Arc::new(AtomicI32::new(0));
1276
1277        assert_eq!(history.redo_count(), 0);
1278
1279        history.execute(Box::new(IncrementCommand {
1280            counter: counter.clone(),
1281            amount: 1,
1282        }));
1283        history.execute(Box::new(IncrementCommand {
1284            counter: counter.clone(),
1285            amount: 2,
1286        }));
1287
1288        history.undo();
1289        assert_eq!(history.redo_count(), 1);
1290
1291        history.undo();
1292        assert_eq!(history.redo_count(), 2);
1293    }
1294
1295    #[test]
1296    fn test_composite_command_description() {
1297        let counter = Arc::new(AtomicI32::new(0));
1298
1299        let composite = CompositeCommand::new("My Composite")
1300            .with_command(Box::new(IncrementCommand {
1301                counter: counter.clone(),
1302                amount: 1,
1303            }))
1304            .build();
1305
1306        assert_eq!(composite.description(), "My Composite");
1307    }
1308
1309    // =========================================================================
1310    // Additional Edge Case Tests
1311    // =========================================================================
1312
1313    #[test]
1314    fn test_command_id_debug() {
1315        let id = CommandId(42);
1316        let debug = format!("{id:?}");
1317        assert!(debug.contains("CommandId"));
1318        assert!(debug.contains("42"));
1319    }
1320
1321    #[test]
1322    fn test_command_id_eq() {
1323        let id1 = CommandId(100);
1324        let id2 = CommandId(100);
1325        let id3 = CommandId(200);
1326        assert_eq!(id1, id2);
1327        assert_ne!(id1, id3);
1328    }
1329
1330    #[test]
1331    fn test_command_id_hash() {
1332        use std::collections::HashSet;
1333        let mut set = HashSet::new();
1334        set.insert(CommandId(1));
1335        set.insert(CommandId(2));
1336        set.insert(CommandId(1)); // Duplicate
1337        assert_eq!(set.len(), 2);
1338    }
1339
1340    #[test]
1341    fn test_group_id_debug() {
1342        let id = GroupId(99);
1343        let debug = format!("{id:?}");
1344        assert!(debug.contains("GroupId"));
1345    }
1346
1347    #[test]
1348    fn test_group_id_hash() {
1349        use std::collections::HashSet;
1350        let mut set = HashSet::new();
1351        set.insert(GroupId(1));
1352        set.insert(GroupId(2));
1353        assert_eq!(set.len(), 2);
1354    }
1355
1356    #[test]
1357    fn test_checkpoint_id_debug() {
1358        let id = CheckpointId(77);
1359        let debug = format!("{id:?}");
1360        assert!(debug.contains("CheckpointId"));
1361    }
1362
1363    #[test]
1364    fn test_checkpoint_id_hash() {
1365        use std::collections::HashSet;
1366        let mut set = HashSet::new();
1367        set.insert(CheckpointId(1));
1368        set.insert(CheckpointId(2));
1369        assert_eq!(set.len(), 2);
1370    }
1371
1372    #[test]
1373    fn test_command_result_debug() {
1374        let result = CommandResult::Success;
1375        let debug = format!("{result:?}");
1376        assert!(debug.contains("Success"));
1377
1378        let failed = CommandResult::Failed("error".to_string());
1379        let debug = format!("{failed:?}");
1380        assert!(debug.contains("Failed"));
1381    }
1382
1383    #[test]
1384    fn test_command_result_clone() {
1385        let result = CommandResult::NeedsConfirmation("confirm?".to_string());
1386        let cloned = result.clone();
1387        assert_eq!(result, cloned);
1388    }
1389
1390    #[test]
1391    fn test_command_result_all_variants() {
1392        let variants = vec![
1393            CommandResult::Success,
1394            CommandResult::Failed("error".to_string()),
1395            CommandResult::Cancelled,
1396            CommandResult::NeedsConfirmation("confirm".to_string()),
1397        ];
1398
1399        for variant in &variants {
1400            let _ = format!("{variant:?}");
1401            let cloned = variant.clone();
1402            assert_eq!(variant, &cloned);
1403        }
1404    }
1405
1406    #[test]
1407    fn test_history_config_debug() {
1408        let config = HistoryConfig::default();
1409        let debug = format!("{config:?}");
1410        assert!(debug.contains("HistoryConfig"));
1411    }
1412
1413    #[test]
1414    fn test_history_config_clone() {
1415        let config = HistoryConfig {
1416            max_commands: 500,
1417            max_memory: 5 * 1024 * 1024,
1418            enable_merging: false,
1419            auto_group_interval_ms: 1000,
1420        };
1421        let cloned = config.clone();
1422        assert_eq!(cloned.max_commands, 500);
1423        assert!(!cloned.enable_merging);
1424    }
1425
1426    #[test]
1427    fn test_checkpoint_debug() {
1428        let checkpoint = Checkpoint {
1429            id: CheckpointId(1),
1430            name: "Test".to_string(),
1431            position: 5,
1432            timestamp: 1000,
1433        };
1434        let debug = format!("{checkpoint:?}");
1435        assert!(debug.contains("Checkpoint"));
1436    }
1437
1438    #[test]
1439    fn test_checkpoint_clone() {
1440        let checkpoint = Checkpoint {
1441            id: CheckpointId(1),
1442            name: "Test".to_string(),
1443            position: 5,
1444            timestamp: 1000,
1445        };
1446        let cloned = checkpoint.clone();
1447        assert_eq!(cloned.name, "Test");
1448        assert_eq!(cloned.position, 5);
1449    }
1450
1451    #[test]
1452    fn test_history_event_debug() {
1453        let event = HistoryEvent::Cleared;
1454        let debug = format!("{event:?}");
1455        assert!(debug.contains("Cleared"));
1456    }
1457
1458    #[test]
1459    fn test_history_event_clone() {
1460        let event = HistoryEvent::Trimmed(10);
1461        let cloned = event.clone();
1462        assert_eq!(event, cloned);
1463    }
1464
1465    #[test]
1466    fn test_memory_limit_trimming() {
1467        let config = HistoryConfig {
1468            max_commands: 1000,
1469            max_memory: 200, // Very small limit
1470            ..Default::default()
1471        };
1472        let mut history = CommandHistory::new(config);
1473        let counter = Arc::new(AtomicI32::new(0));
1474
1475        // Each command takes ~64 bytes by default
1476        for i in 0..10 {
1477            history.execute(Box::new(IncrementCommand {
1478                counter: counter.clone(),
1479                amount: i,
1480            }));
1481        }
1482
1483        // Should have trimmed some commands due to memory limit
1484        assert!(history.undo_count() < 10);
1485    }
1486
1487    #[test]
1488    fn test_default_command_methods() {
1489        let counter = Arc::new(AtomicI32::new(0));
1490        let cmd = IncrementCommand {
1491            counter: counter.clone(),
1492            amount: 1,
1493        };
1494
1495        // Test default implementations
1496        assert!(!cmd.can_merge(&cmd));
1497        assert_eq!(cmd.memory_size(), 64);
1498        assert!(cmd.metadata().is_none());
1499    }
1500
1501    #[test]
1502    fn test_composite_command_memory_size() {
1503        let counter = Arc::new(AtomicI32::new(0));
1504
1505        let composite = CompositeCommand::new("Multi")
1506            .with_command(Box::new(IncrementCommand {
1507                counter: counter.clone(),
1508                amount: 1,
1509            }))
1510            .with_command(Box::new(IncrementCommand {
1511                counter: counter.clone(),
1512                amount: 2,
1513            }))
1514            .build();
1515
1516        let memory = composite.memory_size();
1517        assert!(memory > 64); // Should include both commands
1518    }
1519
1520    #[test]
1521    fn test_group_redo() {
1522        let mut history = CommandHistory::default();
1523        let counter = Arc::new(AtomicI32::new(0));
1524
1525        let _group = history.begin_group();
1526        history.execute(Box::new(IncrementCommand {
1527            counter: counter.clone(),
1528            amount: 1,
1529        }));
1530        history.execute(Box::new(IncrementCommand {
1531            counter: counter.clone(),
1532            amount: 2,
1533        }));
1534        history.end_group();
1535
1536        assert_eq!(counter.load(Ordering::SeqCst), 3);
1537
1538        history.undo();
1539        assert_eq!(counter.load(Ordering::SeqCst), 0);
1540
1541        history.redo();
1542        assert_eq!(counter.load(Ordering::SeqCst), 3);
1543    }
1544
1545    #[test]
1546    fn test_checkpoint_position_updates_on_trim() {
1547        let config = HistoryConfig {
1548            max_commands: 3,
1549            ..Default::default()
1550        };
1551        let mut history = CommandHistory::new(config);
1552        let counter = Arc::new(AtomicI32::new(0));
1553
1554        history.execute(Box::new(IncrementCommand {
1555            counter: counter.clone(),
1556            amount: 1,
1557        }));
1558
1559        let checkpoint_id = history.create_checkpoint("Early");
1560
1561        // Add more commands to trigger trim
1562        for i in 2..=5 {
1563            history.execute(Box::new(IncrementCommand {
1564                counter: counter.clone(),
1565                amount: i,
1566            }));
1567        }
1568
1569        // Checkpoint position should have been adjusted
1570        let checkpoint = history.get_checkpoint(checkpoint_id);
1571        assert!(checkpoint.is_some());
1572    }
1573
1574    #[test]
1575    fn test_multiple_event_listeners() {
1576        use std::sync::atomic::AtomicUsize;
1577
1578        let mut history = CommandHistory::default();
1579        let counter = Arc::new(AtomicI32::new(0));
1580        let event_count1 = Arc::new(AtomicUsize::new(0));
1581        let event_count2 = Arc::new(AtomicUsize::new(0));
1582
1583        let ec1 = event_count1.clone();
1584        history.on_event(Arc::new(move |_| {
1585            ec1.fetch_add(1, Ordering::SeqCst);
1586        }));
1587
1588        let ec2 = event_count2.clone();
1589        history.on_event(Arc::new(move |_| {
1590            ec2.fetch_add(1, Ordering::SeqCst);
1591        }));
1592
1593        history.execute(Box::new(IncrementCommand {
1594            counter: counter.clone(),
1595            amount: 1,
1596        }));
1597
1598        assert_eq!(event_count1.load(Ordering::SeqCst), 1);
1599        assert_eq!(event_count2.load(Ordering::SeqCst), 1);
1600    }
1601
1602    #[test]
1603    fn test_set_value_command_description() {
1604        let value = Arc::new(std::sync::RwLock::new(0));
1605        let cmd = SetValueCommand::new("Set value", value, 100);
1606        assert_eq!(cmd.description(), "Set value");
1607    }
1608
1609    #[test]
1610    fn test_undo_without_old_value() {
1611        let value = Arc::new(std::sync::RwLock::new(10));
1612        let mut cmd = SetValueCommand::new("Set", value.clone(), 42);
1613
1614        // Try undo without execute first
1615        let result = cmd.undo();
1616        assert!(result.is_failed());
1617    }
1618
1619    #[test]
1620    fn test_empty_composite_command() {
1621        let composite = CompositeCommand::new("Empty").build();
1622
1623        let mut history = CommandHistory::default();
1624        let result = history.execute(composite);
1625
1626        assert!(result.is_success());
1627    }
1628
1629    #[test]
1630    fn test_get_nonexistent_checkpoint() {
1631        let history = CommandHistory::default();
1632        let result = history.get_checkpoint(CheckpointId(999));
1633        assert!(result.is_none());
1634    }
1635
1636    #[test]
1637    fn test_history_clear_resets_memory() {
1638        let mut history = CommandHistory::default();
1639        let counter = Arc::new(AtomicI32::new(0));
1640
1641        history.execute(Box::new(IncrementCommand {
1642            counter: counter.clone(),
1643            amount: 1,
1644        }));
1645
1646        assert!(history.memory_usage() > 0);
1647
1648        history.clear();
1649        assert_eq!(history.memory_usage(), 0);
1650    }
1651
1652    // Test mergeable command
1653    struct MergeableIncrement {
1654        counter: Arc<AtomicI32>,
1655        total_amount: i32,
1656    }
1657
1658    impl Command for MergeableIncrement {
1659        fn execute(&mut self) -> CommandResult {
1660            self.counter.fetch_add(self.total_amount, Ordering::SeqCst);
1661            CommandResult::Success
1662        }
1663
1664        fn undo(&mut self) -> CommandResult {
1665            self.counter.fetch_sub(self.total_amount, Ordering::SeqCst);
1666            CommandResult::Success
1667        }
1668
1669        fn description(&self) -> &str {
1670            "Mergeable increment"
1671        }
1672
1673        fn can_merge(&self, _other: &dyn Command) -> bool {
1674            true
1675        }
1676
1677        fn merge(&mut self, _other: Box<dyn Command>) -> Option<Box<dyn Command>> {
1678            // For testing, just add 10 more
1679            self.total_amount += 10;
1680            None
1681        }
1682    }
1683
1684    #[test]
1685    fn test_command_merging() {
1686        let config = HistoryConfig {
1687            enable_merging: true,
1688            auto_group_interval_ms: 0, // Disable auto-grouping
1689            ..Default::default()
1690        };
1691        let mut history = CommandHistory::new(config);
1692        let counter = Arc::new(AtomicI32::new(0));
1693
1694        history.execute(Box::new(MergeableIncrement {
1695            counter: counter.clone(),
1696            total_amount: 5,
1697        }));
1698
1699        history.execute(Box::new(MergeableIncrement {
1700            counter: counter.clone(),
1701            total_amount: 3,
1702        }));
1703
1704        // Second command should be merged, so only 1 command in history
1705        assert_eq!(history.undo_count(), 1);
1706    }
1707
1708    #[test]
1709    fn test_redo_clears_on_new_execute() {
1710        let mut history = CommandHistory::default();
1711        let counter = Arc::new(AtomicI32::new(0));
1712
1713        history.execute(Box::new(IncrementCommand {
1714            counter: counter.clone(),
1715            amount: 1,
1716        }));
1717
1718        history.undo();
1719        assert_eq!(history.redo_count(), 1);
1720
1721        history.execute(Box::new(IncrementCommand {
1722            counter: counter.clone(),
1723            amount: 2,
1724        }));
1725
1726        assert_eq!(history.redo_count(), 0);
1727    }
1728}