use crate::engine::{Engine, EvalConfig};
use crate::reference::{CellRef, Coord};
use crate::test_workbook::TestWorkbook;
use formualizer_common::LiteralValue;
use formualizer_parse::parser::parse;
fn make_engine() -> Engine<TestWorkbook> {
Engine::new(TestWorkbook::new(), EvalConfig::default())
}
fn rebuilds_for_edit_burst(n: u32) -> u64 {
let mut engine = make_engine();
for row in 1..=n {
engine
.set_cell_value("Sheet1", row, 1, LiteralValue::Int(row as i64))
.unwrap();
}
let before = engine.graph.edges_rebuild_count();
for row in 1..=n {
let ast = parse(format!("=A{row}+1")).unwrap();
engine.set_cell_formula("Sheet1", row, 2, ast).unwrap();
}
engine.graph.edges_rebuild_count() - before
}
#[test]
fn per_cell_formula_edits_amortize_csr_rebuilds() {
let n = 2000u32;
let rebuilds = rebuilds_for_edit_burst(n);
assert!(
rebuilds <= 20,
"expected amortized rebuilds (~total_ops/1000) for {n} per-cell edits, got {rebuilds}"
);
}
#[test]
fn csr_rebuild_count_scales_linearly_with_edits() {
let small = rebuilds_for_edit_burst(2000);
let large = rebuilds_for_edit_burst(8000);
assert!(
large <= 4 * small + 8,
"rebuild count grew superlinearly: {small} rebuilds at 2k edits vs {large} at 8k"
);
}
#[test]
fn unrebuilt_edits_are_visible_to_dependent_reads() {
let mut engine = make_engine();
engine
.set_cell_value("Sheet1", 1, 1, LiteralValue::Int(1))
.unwrap();
engine
.set_cell_value("Sheet1", 1, 3, LiteralValue::Int(3))
.unwrap();
engine
.set_cell_formula("Sheet1", 1, 2, parse("=A1").unwrap())
.unwrap();
let sheet_id = engine.graph.sheet_id("Sheet1").unwrap();
let a1 = engine
.graph
.get_vertex_for_cell(&CellRef::new(sheet_id, Coord::from_excel(1, 1, true, true)))
.unwrap();
let b1 = engine
.graph
.get_vertex_for_cell(&CellRef::new(sheet_id, Coord::from_excel(1, 2, true, true)))
.unwrap();
let c1 = engine
.graph
.get_vertex_for_cell(&CellRef::new(sheet_id, Coord::from_excel(1, 3, true, true)))
.unwrap();
assert!(
engine.graph.get_dependents(a1).contains(&b1),
"freshly added edge A1<-B1 must be visible to dependent reads"
);
engine
.set_cell_formula("Sheet1", 1, 2, parse("=C1").unwrap())
.unwrap();
assert!(
!engine.graph.get_dependents(a1).contains(&b1),
"removed edge A1<-B1 must disappear from dependent reads"
);
assert!(
engine.graph.get_dependents(c1).contains(&b1),
"new edge C1<-B1 must be visible to dependent reads"
);
}
#[test]
fn dirty_propagation_sees_unrebuilt_edges() {
let mut engine = make_engine();
engine
.set_cell_value("Sheet1", 1, 1, LiteralValue::Int(1))
.unwrap();
engine
.set_cell_formula("Sheet1", 1, 2, parse("=A1*10").unwrap())
.unwrap();
let sheet_id = engine.graph.sheet_id("Sheet1").unwrap();
let b1 = engine
.graph
.get_vertex_for_cell(&CellRef::new(sheet_id, Coord::from_excel(1, 2, true, true)))
.unwrap();
engine
.set_cell_value("Sheet1", 1, 1, LiteralValue::Int(5))
.unwrap();
assert!(
engine.graph.get_evaluation_vertices().contains(&b1),
"dirty propagation must reach B1 through the un-rebuilt edge"
);
engine.evaluate_all().unwrap();
assert_eq!(
engine.get_cell_value("Sheet1", 1, 2),
Some(LiteralValue::Number(50.0))
);
}
#[test]
fn edit_then_evaluate_respects_fresh_dependencies() {
let mut engine = make_engine();
engine
.set_cell_value("Sheet1", 1, 1, LiteralValue::Int(2))
.unwrap();
engine
.set_cell_value("Sheet1", 1, 3, LiteralValue::Int(7))
.unwrap();
engine
.set_cell_formula("Sheet1", 1, 2, parse("=A1+1").unwrap())
.unwrap();
engine.evaluate_all().unwrap();
assert_eq!(
engine.get_cell_value("Sheet1", 1, 2),
Some(LiteralValue::Number(3.0))
);
engine
.set_cell_formula("Sheet1", 1, 2, parse("=C1+1").unwrap())
.unwrap();
engine.evaluate_all().unwrap();
assert_eq!(
engine.get_cell_value("Sheet1", 1, 2),
Some(LiteralValue::Number(8.0))
);
engine
.set_cell_value("Sheet1", 1, 3, LiteralValue::Int(40))
.unwrap();
engine.evaluate_all().unwrap();
assert_eq!(
engine.get_cell_value("Sheet1", 1, 2),
Some(LiteralValue::Number(41.0))
);
}
#[test]
fn edit_burst_then_evaluate_is_correct() {
let n = 256u32;
let mut engine = make_engine();
for row in 1..=n {
engine
.set_cell_value("Sheet1", row, 1, LiteralValue::Int(row as i64))
.unwrap();
}
for row in 1..=n {
let ast = parse(format!("=A{row}*2")).unwrap();
engine.set_cell_formula("Sheet1", row, 2, ast).unwrap();
}
engine.evaluate_all().unwrap();
for row in 1..=n {
assert_eq!(
engine.get_cell_value("Sheet1", row, 2),
Some(LiteralValue::Number(row as f64 * 2.0)),
"row {row}"
);
}
}