2026-07-09 02:59:53 +03:00
|
|
|
use hcie_history::{HistoryManager, UndoableAction};
|
|
|
|
|
use hcie_protocol::Layer;
|
|
|
|
|
|
|
|
|
|
struct SetPixelAction {
|
|
|
|
|
layer_idx: usize,
|
|
|
|
|
old_color: [u8; 4],
|
|
|
|
|
new_color: [u8; 4],
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl UndoableAction<Vec<Layer>> for SetPixelAction {
|
|
|
|
|
fn undo(&mut self, layers: &mut Vec<Layer>) {
|
|
|
|
|
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<Layer>) {
|
|
|
|
|
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::<Vec<Layer>>::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::<Vec<Layer>>::new(10);
|
|
|
|
|
hist.push(Box::new(SetPixelAction {
|
2026-07-21 04:25:23 +03:00
|
|
|
layer_idx: 0,
|
|
|
|
|
old_color: [0; 4],
|
|
|
|
|
new_color: [255, 0, 0, 255],
|
2026-07-09 02:59:53 +03:00
|
|
|
}));
|
|
|
|
|
hist.push(Box::new(SetPixelAction {
|
2026-07-21 04:25:23 +03:00
|
|
|
layer_idx: 0,
|
|
|
|
|
old_color: [255, 0, 0, 255],
|
|
|
|
|
new_color: [0, 255, 0, 255],
|
2026-07-09 02:59:53 +03:00
|
|
|
}));
|
|
|
|
|
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::<Vec<Layer>>::new(10);
|
|
|
|
|
hist.push(Box::new(SetPixelAction {
|
2026-07-21 04:25:23 +03:00
|
|
|
layer_idx: 0,
|
|
|
|
|
old_color: [0; 4],
|
|
|
|
|
new_color: [255; 4],
|
2026-07-09 02:59:53 +03:00
|
|
|
}));
|
|
|
|
|
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::<Vec<Layer>>::new(10);
|
|
|
|
|
hist.push(Box::new(SetPixelAction {
|
2026-07-21 04:25:23 +03:00
|
|
|
layer_idx: 0,
|
|
|
|
|
old_color: [0; 4],
|
|
|
|
|
new_color: [255; 4],
|
2026-07-09 02:59:53 +03:00
|
|
|
}));
|
|
|
|
|
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::<Vec<Layer>>::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::<Vec<Layer>>::new(10);
|
|
|
|
|
hist.push(Box::new(SetPixelAction {
|
2026-07-21 04:25:23 +03:00
|
|
|
layer_idx: 0,
|
|
|
|
|
old_color: [0; 4],
|
|
|
|
|
new_color: [1; 4],
|
2026-07-09 02:59:53 +03:00
|
|
|
}));
|
|
|
|
|
hist.push(Box::new(SetPixelAction {
|
2026-07-21 04:25:23 +03:00
|
|
|
layer_idx: 0,
|
|
|
|
|
old_color: [1; 4],
|
|
|
|
|
new_color: [2; 4],
|
2026-07-09 02:59:53 +03:00
|
|
|
}));
|
|
|
|
|
hist.undo(&mut layers);
|
|
|
|
|
assert!(hist.can_redo());
|
|
|
|
|
|
|
|
|
|
hist.push(Box::new(SetPixelAction {
|
2026-07-21 04:25:23 +03:00
|
|
|
layer_idx: 0,
|
|
|
|
|
old_color: [1; 4],
|
|
|
|
|
new_color: [3; 4],
|
2026-07-09 02:59:53 +03:00
|
|
|
}));
|
|
|
|
|
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::<Vec<Layer>>::new(10);
|
|
|
|
|
hist.push(Box::new(SetPixelAction {
|
2026-07-21 04:25:23 +03:00
|
|
|
layer_idx: 0,
|
|
|
|
|
old_color: [0; 4],
|
|
|
|
|
new_color: [1; 4],
|
2026-07-09 02:59:53 +03:00
|
|
|
}));
|
|
|
|
|
hist.push(Box::new(SetPixelAction {
|
2026-07-21 04:25:23 +03:00
|
|
|
layer_idx: 0,
|
|
|
|
|
old_color: [1; 4],
|
|
|
|
|
new_color: [2; 4],
|
2026-07-09 02:59:53 +03:00
|
|
|
}));
|
|
|
|
|
hist.push(Box::new(SetPixelAction {
|
2026-07-21 04:25:23 +03:00
|
|
|
layer_idx: 0,
|
|
|
|
|
old_color: [2; 4],
|
|
|
|
|
new_color: [3; 4],
|
2026-07-09 02:59:53 +03:00
|
|
|
}));
|
|
|
|
|
hist.jump_to(-1, &mut layers);
|
|
|
|
|
assert!(!hist.can_undo(), "jump to -1 should be at bottom");
|
2026-07-21 04:25:23 +03:00
|
|
|
assert!(
|
|
|
|
|
hist.can_redo(),
|
|
|
|
|
"jump to -1 should allow replaying the first entry"
|
|
|
|
|
);
|
2026-07-09 02:59:53 +03:00
|
|
|
assert_eq!(layers[0].get_pixel(0, 0), [0; 4]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_entry_description() {
|
|
|
|
|
let mut hist = HistoryManager::<Vec<Layer>>::new(10);
|
|
|
|
|
hist.push(Box::new(SetPixelAction {
|
2026-07-21 04:25:23 +03:00
|
|
|
layer_idx: 0,
|
|
|
|
|
old_color: [0; 4],
|
|
|
|
|
new_color: [255; 4],
|
2026-07-09 02:59:53 +03:00
|
|
|
}));
|
|
|
|
|
assert_eq!(hist.entry_description(0), Some("SetPixel".to_string()));
|
|
|
|
|
assert_eq!(hist.entry_description(99), None);
|
|
|
|
|
}
|