Skip to main content

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