use crate::engine::graph::editor::change_log::{
ChangeEvent, ChangeLog, ChangeLogger, NullChangeLogger,
};
use crate::reference::{CellRef, Coord};
use formualizer_common::LiteralValue;
fn cell_ref(sheet_id: u16, row: u32, col: u32) -> CellRef {
CellRef::new(sheet_id, Coord::new(row, col, false, false))
}
fn create_test_event() -> ChangeEvent {
ChangeEvent::SetValue {
addr: cell_ref(0, 1, 1),
old_value: None,
old_formula: None,
new: LiteralValue::Number(42.0),
}
}
fn create_test_event_with_value(value: f64) -> ChangeEvent {
ChangeEvent::SetValue {
addr: cell_ref(0, 1, 1),
old_value: None,
old_formula: None,
new: LiteralValue::Number(value),
}
}
#[test]
fn test_change_log_basic_operations() {
let mut log = ChangeLog::new();
assert!(log.is_empty());
let event = create_test_event();
log.record(event.clone());
assert_eq!(log.len(), 1);
assert_eq!(log.events()[0], event);
log.clear();
assert!(log.is_empty());
}
#[test]
fn test_change_log_enabled_flag() {
let mut log = ChangeLog::new();
log.set_enabled(false);
log.record(create_test_event());
assert!(log.is_empty());
log.set_enabled(true);
log.record(create_test_event());
assert_eq!(log.len(), 1); }
#[test]
fn test_change_log_take_from() {
let mut log = ChangeLog::new();
for i in 0..5 {
log.record(create_test_event_with_value(i as f64));
}
let taken = log.take_from(3);
assert_eq!(taken.len(), 2); assert_eq!(log.len(), 3); }
#[test]
fn test_change_log_fifo_eviction_drops_oldest() {
let mut log = ChangeLog::with_max_changelog_events(3);
for i in 0..5 {
log.record(create_test_event_with_value(i as f64));
}
assert_eq!(log.len(), 3);
let values: Vec<f64> = log
.events()
.iter()
.filter_map(|e| match e {
ChangeEvent::SetValue {
new: LiteralValue::Number(n),
..
} => Some(*n),
_ => None,
})
.collect();
assert_eq!(values, vec![2.0, 3.0, 4.0]);
}
#[test]
fn test_compound_operations() {
let mut log = ChangeLog::new();
log.begin_compound("InsertRows".to_string());
assert_eq!(log.compound_depth(), 1);
log.record(create_test_event());
log.begin_compound("NestedOp".to_string());
assert_eq!(log.compound_depth(), 2);
log.record(create_test_event_with_value(2.0));
log.end_compound();
assert_eq!(log.compound_depth(), 1);
log.end_compound();
assert_eq!(log.compound_depth(), 0);
let events = log.events();
assert_eq!(events.len(), 6);
assert!(matches!(
events[0],
ChangeEvent::CompoundStart { depth: 1, .. }
));
assert!(matches!(
events[2],
ChangeEvent::CompoundStart { depth: 2, .. }
));
assert!(matches!(events[4], ChangeEvent::CompoundEnd { depth: 2 }));
assert!(matches!(events[5], ChangeEvent::CompoundEnd { depth: 1 }));
}
#[test]
fn test_compound_operations_disabled() {
let mut log = ChangeLog::new();
log.set_enabled(false);
log.begin_compound("InsertRows".to_string());
log.record(create_test_event());
log.end_compound();
assert!(log.is_empty());
}
#[test]
fn test_null_logger() {
let mut logger = NullChangeLogger;
logger.record(create_test_event()); logger.set_enabled(false); logger.begin_compound("test".to_string()); logger.end_compound(); }
#[test]
fn test_change_logger_trait_object() {
let mut log = ChangeLog::new();
let logger: &mut dyn ChangeLogger = &mut log;
logger.record(create_test_event());
assert_eq!(log.len(), 1);
}
#[test]
fn test_change_log_with_formula_events() {
use formualizer_parse::parser::parse;
let mut log = ChangeLog::new();
let old_formula = parse("=A1*2").unwrap();
let new_formula = parse("=A1*3").unwrap();
let event = ChangeEvent::SetFormula {
addr: cell_ref(0, 1, 1),
old_value: None,
old_formula: Some(old_formula.clone()),
new: new_formula.clone(),
};
log.record(event.clone());
assert_eq!(log.len(), 1);
match &log.events()[0] {
ChangeEvent::SetFormula {
old_formula: old,
new,
..
} => {
assert_eq!(old.as_ref(), Some(&old_formula));
assert_eq!(new, &new_formula);
}
_ => panic!("Wrong event type"),
}
}
#[test]
fn test_granular_change_events() {
use crate::engine::vertex::VertexId;
use formualizer_common::Coord as AbsCoord;
use formualizer_parse::parser::parse;
let mut log = ChangeLog::new();
let move_event = ChangeEvent::VertexMoved {
id: VertexId(1),
sheet_id: 0,
old_coord: AbsCoord::new(5, 1),
new_coord: AbsCoord::new(7, 1),
};
log.record(move_event);
let adjust_event = ChangeEvent::FormulaAdjusted {
id: VertexId(2),
addr: None,
old_ast: parse("=A5").unwrap(),
new_ast: parse("=A7").unwrap(),
};
log.record(adjust_event);
let edge_add = ChangeEvent::EdgeAdded {
from: VertexId(1),
to: VertexId(2),
};
log.record(edge_add);
let edge_remove = ChangeEvent::EdgeRemoved {
from: VertexId(3),
to: VertexId(4),
};
log.record(edge_remove);
assert_eq!(log.len(), 4);
assert!(matches!(log.events()[0], ChangeEvent::VertexMoved { .. }));
assert!(matches!(
log.events()[1],
ChangeEvent::FormulaAdjusted { .. }
));
assert!(matches!(log.events()[2], ChangeEvent::EdgeAdded { .. }));
assert!(matches!(log.events()[3], ChangeEvent::EdgeRemoved { .. }));
}
#[test]
fn test_remove_vertex_event() {
use crate::engine::vertex::VertexId;
use formualizer_parse::parser::parse;
let mut log = ChangeLog::new();
let event = ChangeEvent::RemoveVertex {
id: VertexId(1),
old_value: Some(LiteralValue::Number(42.0)),
old_formula: Some(parse("=A1*2").unwrap()),
old_dependencies: vec![VertexId(2), VertexId(3)],
old_dependents: vec![VertexId(4)],
coord: None,
sheet_id: None,
kind: None,
flags: None,
};
log.record(event);
assert_eq!(log.len(), 1);
match &log.events()[0] {
ChangeEvent::RemoveVertex {
id,
old_value,
old_formula,
old_dependencies,
..
} => {
assert_eq!(id, &VertexId(1));
assert_eq!(old_value, &Some(LiteralValue::Number(42.0)));
assert!(old_formula.is_some());
assert_eq!(old_dependencies.len(), 2);
}
_ => panic!("Wrong event type"),
}
}