use crate::engine::{CycleConfig, Engine, EvalConfig};
use crate::test_workbook::TestWorkbook;
use formualizer_common::{ExcelErrorKind, LiteralValue};
use formualizer_parse::parser::parse;
fn iterate_engine(max_iterations: u32, max_change: f64) -> Engine<TestWorkbook> {
Engine::new(
TestWorkbook::new(),
EvalConfig::default().with_cycle(CycleConfig::iterate(max_iterations, max_change)),
)
}
fn set_formula(engine: &mut Engine<TestWorkbook>, sheet: &str, row: u32, col: u32, f: &str) {
engine
.set_cell_formula(sheet, row, col, parse(f).expect("parse"))
.expect("set formula");
}
fn set_value(engine: &mut Engine<TestWorkbook>, sheet: &str, row: u32, col: u32, v: LiteralValue) {
engine
.set_cell_value(sheet, row, col, v)
.expect("set value");
}
fn num(engine: &Engine<TestWorkbook>, sheet: &str, row: u32, col: u32) -> f64 {
match engine.get_cell_value(sheet, row, col) {
Some(LiteralValue::Number(n)) => n,
Some(LiteralValue::Int(i)) => i as f64,
other => panic!("expected number at {sheet} r{row}c{col}, got {other:?}"),
}
}
#[test]
fn insert_row_above_shifts_accumulator_and_iteration_continues() {
let mut engine = iterate_engine(1, 0.001);
set_value(&mut engine, "Sheet1", 1, 1, LiteralValue::Number(5.0));
set_formula(&mut engine, "Sheet1", 1, 2, "=B1+A1");
engine.evaluate_all().unwrap();
assert_eq!(num(&engine, "Sheet1", 1, 2), 5.0);
engine.insert_rows("Sheet1", 1, 1).unwrap();
engine.evaluate_all().unwrap();
assert_eq!(
num(&engine, "Sheet1", 2, 2),
10.0,
"shifted accumulator keeps its value and adds once more"
);
let t = engine.last_cycle_telemetry();
assert_eq!(t.static_sccs, 1, "the shifted member still forms its SCC");
assert_eq!(t.iterated_sccs, 1);
engine.evaluate_all().unwrap();
assert_eq!(num(&engine, "Sheet1", 2, 2), 15.0);
}
#[test]
fn delete_row_of_scc_member_dissolves_cycle_without_panicking() {
let mut engine = iterate_engine(4, 0.001);
set_formula(&mut engine, "Sheet1", 1, 1, "=A2+1");
set_formula(&mut engine, "Sheet1", 2, 1, "=A1+1");
engine.evaluate_all().unwrap();
assert_eq!(num(&engine, "Sheet1", 1, 1), 7.0);
assert_eq!(num(&engine, "Sheet1", 2, 1), 8.0);
engine.delete_rows("Sheet1", 2, 1).unwrap();
engine.evaluate_all().unwrap();
match engine.get_cell_value("Sheet1", 1, 1) {
Some(LiteralValue::Error(e)) => assert_eq!(e.kind, ExcelErrorKind::Ref),
other => panic!("expected #REF! after member deletion, got {other:?}"),
}
assert_eq!(engine.last_cycle_telemetry().static_sccs, 0);
let res = engine.evaluate_all().unwrap();
assert_eq!(engine.last_cycle_telemetry().static_sccs, 0);
assert_eq!(res.computed_vertices, 0, "no perpetual redirty leak");
}
#[test]
fn insert_row_inside_member_read_range_extends_the_range() {
let mut engine = iterate_engine(3, 0.001);
set_value(&mut engine, "Sheet1", 1, 2, LiteralValue::Number(1.0));
set_value(&mut engine, "Sheet1", 3, 2, LiteralValue::Number(2.0));
set_formula(&mut engine, "Sheet1", 2, 2, "=SUM(B1:B3)");
engine.evaluate_all().unwrap();
assert_eq!(num(&engine, "Sheet1", 2, 2), 9.0);
engine.insert_rows("Sheet1", 3, 1).unwrap();
engine.evaluate_all().unwrap();
assert_eq!(
num(&engine, "Sheet1", 2, 2),
18.0,
"range must include the inserted row's shifted contents"
);
let t = engine.last_cycle_telemetry();
assert_eq!(t.iterated_sccs, 1);
assert_eq!(t.capped_sccs, 1);
}
#[test]
fn insert_column_shifts_divergent_pair_and_iteration_continues() {
let mut engine = iterate_engine(2, 0.001);
set_formula(&mut engine, "Sheet1", 1, 2, "=C1+1");
set_formula(&mut engine, "Sheet1", 1, 3, "=B1+1");
engine.evaluate_all().unwrap();
assert_eq!(num(&engine, "Sheet1", 1, 2), 3.0);
assert_eq!(num(&engine, "Sheet1", 1, 3), 4.0);
engine.insert_columns("Sheet1", 1, 1).unwrap();
engine.evaluate_all().unwrap();
assert_eq!(num(&engine, "Sheet1", 1, 3), 7.0);
assert_eq!(num(&engine, "Sheet1", 1, 4), 8.0);
let t = engine.last_cycle_telemetry();
assert_eq!(t.iterated_sccs, 1);
assert_eq!(t.capped_sccs, 1);
}
#[test]
fn delete_unrelated_row_between_recalcs_keeps_scc_iterating() {
let mut engine = iterate_engine(2, 0.001);
set_value(&mut engine, "Sheet1", 1, 1, LiteralValue::Number(99.0)); set_formula(&mut engine, "Sheet1", 10, 1, "=A11+1");
set_formula(&mut engine, "Sheet1", 11, 1, "=A10+1");
engine.evaluate_all().unwrap();
assert_eq!(num(&engine, "Sheet1", 10, 1), 3.0);
assert_eq!(num(&engine, "Sheet1", 11, 1), 4.0);
engine.delete_rows("Sheet1", 1, 1).unwrap();
engine.evaluate_all().unwrap();
assert_eq!(num(&engine, "Sheet1", 9, 1), 7.0);
assert_eq!(num(&engine, "Sheet1", 10, 1), 8.0);
assert_eq!(engine.last_cycle_telemetry().iterated_sccs, 1);
engine.evaluate_all().unwrap();
assert_eq!(num(&engine, "Sheet1", 9, 1), 11.0);
assert_eq!(num(&engine, "Sheet1", 10, 1), 12.0);
}
#[test]
fn delete_one_member_of_a_three_cycle_leaves_a_smaller_live_cycle() {
let mut engine = iterate_engine(3, 0.001);
set_formula(&mut engine, "Sheet1", 1, 1, "=A2+1");
set_formula(&mut engine, "Sheet1", 2, 1, "=A3+1");
set_formula(&mut engine, "Sheet1", 3, 1, "=A1+1");
engine.evaluate_all().unwrap();
let t = engine.last_cycle_telemetry();
assert_eq!(t.iterated_sccs, 1);
assert_eq!(t.capped_sccs, 1);
engine.delete_rows("Sheet1", 2, 1).unwrap();
engine.evaluate_all().unwrap();
match engine.get_cell_value("Sheet1", 1, 1) {
Some(LiteralValue::Error(e)) => {
assert_eq!(e.kind, ExcelErrorKind::Ref);
match engine.get_cell_value("Sheet1", 2, 1) {
Some(LiteralValue::Error(e2)) => assert_eq!(e2.kind, ExcelErrorKind::Ref),
other => panic!("survivor must propagate #REF!, got {other:?}"),
}
}
Some(LiteralValue::Number(_)) | Some(LiteralValue::Int(_)) => {
assert_eq!(engine.last_cycle_telemetry().iterated_sccs, 1);
}
other => panic!("unexpected post-delete state: {other:?}"),
}
engine.evaluate_all().unwrap();
}