use hcie_history::{HistoryManager, UndoableAction}; use hcie_protocol::Layer; struct SetPixelAction { layer_idx: usize, old_color: [u8; 4], new_color: [u8; 4], } impl UndoableAction> for SetPixelAction { fn undo(&mut self, layers: &mut Vec) { if let Some(layer) = layers.get_mut(self.layer_idx) { layer.set_pixel(0, 0, self.old_color); } } fn redo(&mut self, layers: &mut Vec) { if let Some(layer) = layers.get_mut(self.layer_idx) { layer.set_pixel(0, 0, self.new_color); } } fn description(&self) -> String { "SetPixel".to_string() } } fn make_layer() -> Layer { Layer::new_transparent("test", 10, 10) } #[test] fn test_history_new_is_empty() { let hist = HistoryManager::>::new(10); assert_eq!(hist.len(), 0); assert!(!hist.can_undo()); assert!(!hist.can_redo()); assert_eq!(hist.current_index(), -1); } #[test] fn test_history_push_undo_redo() { let mut layers = vec![make_layer()]; let mut hist = HistoryManager::>::new(10); hist.push(Box::new(SetPixelAction { layer_idx: 0, old_color: [0; 4], new_color: [255, 0, 0, 255], })); hist.push(Box::new(SetPixelAction { layer_idx: 0, old_color: [255, 0, 0, 255], new_color: [0, 255, 0, 255], })); assert!(hist.can_undo()); assert!(!hist.can_redo()); hist.undo(&mut layers); assert_eq!(layers[0].get_pixel(0, 0), [255, 0, 0, 255]); assert!(hist.can_redo()); hist.redo(&mut layers); assert_eq!(layers[0].get_pixel(0, 0), [0, 255, 0, 255]); } #[test] fn test_history_undo_twice_is_stable() { let mut layers = vec![make_layer()]; let mut hist = HistoryManager::>::new(10); hist.push(Box::new(SetPixelAction { layer_idx: 0, old_color: [0; 4], new_color: [255; 4], })); hist.undo(&mut layers); hist.undo(&mut layers); assert!(!hist.can_undo(), "should reach bottom"); } #[test] fn test_history_redo_twice_is_stable() { let mut layers = vec![make_layer()]; let mut hist = HistoryManager::>::new(10); hist.push(Box::new(SetPixelAction { layer_idx: 0, old_color: [0; 4], new_color: [255; 4], })); hist.undo(&mut layers); hist.redo(&mut layers); hist.redo(&mut layers); assert!(!hist.can_redo(), "should reach top"); } #[test] fn test_history_max_steps() { let _layers = vec![make_layer()]; let mut hist = HistoryManager::>::new(3); for i in 0..10 { hist.push(Box::new(SetPixelAction { layer_idx: 0, old_color: [0; 4], new_color: [i; 4], })); } assert_eq!(hist.len(), 3, "should cap at max_steps=3"); } #[test] fn test_history_new_action_truncates_redo() { let mut layers = vec![make_layer()]; let mut hist = HistoryManager::>::new(10); hist.push(Box::new(SetPixelAction { layer_idx: 0, old_color: [0; 4], new_color: [1; 4], })); hist.push(Box::new(SetPixelAction { layer_idx: 0, old_color: [1; 4], new_color: [2; 4], })); hist.undo(&mut layers); assert!(hist.can_redo()); hist.push(Box::new(SetPixelAction { layer_idx: 0, old_color: [1; 4], new_color: [3; 4], })); assert!(!hist.can_redo(), "new action should truncate redo stack"); } #[test] fn test_history_jump_to() { let mut layers = vec![make_layer()]; let mut hist = HistoryManager::>::new(10); hist.push(Box::new(SetPixelAction { layer_idx: 0, old_color: [0; 4], new_color: [1; 4], })); hist.push(Box::new(SetPixelAction { layer_idx: 0, old_color: [1; 4], new_color: [2; 4], })); hist.push(Box::new(SetPixelAction { layer_idx: 0, old_color: [2; 4], new_color: [3; 4], })); hist.jump_to(-1, &mut layers); assert!(!hist.can_undo(), "jump to -1 should be at bottom"); assert!( hist.can_redo(), "jump to -1 should allow replaying the first entry" ); assert_eq!(layers[0].get_pixel(0, 0), [0; 4]); } #[test] fn test_entry_description() { let mut hist = HistoryManager::>::new(10); hist.push(Box::new(SetPixelAction { layer_idx: 0, old_color: [0; 4], new_color: [255; 4], })); assert_eq!(hist.entry_description(0), Some("SetPixel".to_string())); assert_eq!(hist.entry_description(99), None); }