use crate::engine::named_range::{NameScope, NamedDefinition};
use crate::engine::{Engine, EvalConfig, PreparationPolicy};
use crate::reference::{CellRef, Coord, RangeRef};
use crate::test_workbook::TestWorkbook;
use formualizer_common::{ExcelErrorKind, LiteralValue};
use formualizer_parse::parser::parse;
fn num(engine: &Engine<TestWorkbook>, sheet: &str, row: u32, col: u32) -> Option<f64> {
match engine.get_cell_value(sheet, row, col) {
Some(LiteralValue::Number(n)) => Some(n),
Some(LiteralValue::Int(n)) => Some(n as f64),
_ => None,
}
}
fn set(engine: &mut Engine<TestWorkbook>, sheet: &str, row: u32, col: u32, v: f64) {
engine
.set_cell_value(sheet, row, col, LiteralValue::Number(v))
.unwrap();
}
fn formula(engine: &mut Engine<TestWorkbook>, sheet: &str, row: u32, col: u32, f: &str) {
engine
.set_cell_formula(sheet, row, col, parse(f).unwrap())
.unwrap();
}
#[test]
fn formula_name_defined_after_its_reader_binds_and_orders() {
let mut engine = Engine::new(TestWorkbook::new(), EvalConfig::default());
set(&mut engine, "Sheet1", 1, 1, 5.0);
formula(&mut engine, "Sheet1", 2, 1, "=A1*10");
formula(&mut engine, "Sheet1", 1, 2, "=Later+1");
engine.evaluate_all().unwrap();
match engine.get_cell_value("Sheet1", 1, 2) {
Some(LiteralValue::Error(e)) => assert_eq!(e.kind, ExcelErrorKind::Name),
other => panic!("unbound name should be #NAME?, got {other:?}"),
}
engine
.define_name(
"Later",
NamedDefinition::Formula {
ast: parse("=A2+A1").unwrap(),
dependencies: Vec::new(),
range_deps: Vec::new(),
},
NameScope::Workbook,
)
.unwrap();
engine.evaluate_all().unwrap();
assert_eq!(num(&engine, "Sheet1", 1, 2), Some(56.0));
set(&mut engine, "Sheet1", 1, 1, 2.0);
engine.evaluate_all().unwrap();
assert_eq!(num(&engine, "Sheet1", 2, 1), Some(20.0));
assert_eq!(num(&engine, "Sheet1", 1, 2), Some(23.0));
}
#[test]
fn nested_name_redefinition_rebinds_through_the_outer_name() {
let mut engine = Engine::new(TestWorkbook::new(), EvalConfig::default());
let sheet = engine.sheet_id("Sheet1").unwrap();
let at = |r| CellRef::new(sheet, Coord::from_excel(r, 1, true, true));
set(&mut engine, "Sheet1", 1, 1, 3.0);
formula(&mut engine, "Sheet1", 2, 1, "=A1+100");
engine
.define_name("Inner", NamedDefinition::Cell(at(1)), NameScope::Workbook)
.unwrap();
engine
.define_name(
"Outer",
NamedDefinition::Formula {
ast: parse("=Inner*2").unwrap(),
dependencies: Vec::new(),
range_deps: Vec::new(),
},
NameScope::Workbook,
)
.unwrap();
formula(&mut engine, "Sheet1", 1, 2, "=Outer+1");
engine.evaluate_all().unwrap();
assert_eq!(num(&engine, "Sheet1", 1, 2), Some(7.0));
engine
.update_name("Inner", NamedDefinition::Cell(at(2)), NameScope::Workbook)
.unwrap();
engine.evaluate_all().unwrap();
assert_eq!(num(&engine, "Sheet1", 1, 2), Some(207.0));
set(&mut engine, "Sheet1", 1, 1, 10.0);
engine.evaluate_all().unwrap();
assert_eq!(num(&engine, "Sheet1", 1, 2), Some(221.0));
}
#[test]
fn name_redefined_to_a_new_target_rebinds_readers() {
let mut engine = Engine::new(TestWorkbook::new(), EvalConfig::default());
let sheet = engine.sheet_id("Sheet1").unwrap();
set(&mut engine, "Sheet1", 1, 1, 1.0);
formula(&mut engine, "Sheet1", 2, 1, "=A1*100");
let at = |r| CellRef::new(sheet, Coord::from_excel(r, 1, true, true));
engine
.define_name("Target", NamedDefinition::Cell(at(1)), NameScope::Workbook)
.unwrap();
formula(&mut engine, "Sheet1", 1, 3, "=Target+1");
engine.evaluate_all().unwrap();
assert_eq!(num(&engine, "Sheet1", 1, 3), Some(2.0));
engine
.update_name("Target", NamedDefinition::Cell(at(2)), NameScope::Workbook)
.unwrap();
engine.evaluate_all().unwrap();
assert_eq!(num(&engine, "Sheet1", 1, 3), Some(101.0));
set(&mut engine, "Sheet1", 1, 1, 4.0);
engine.evaluate_all().unwrap();
assert_eq!(num(&engine, "Sheet1", 1, 3), Some(401.0));
}
#[test]
fn sheet_added_after_an_eager_value_reader_tracks_edits() {
let mut engine = Engine::new(TestWorkbook::new(), EvalConfig::default());
set(&mut engine, "Other", 1, 2, 1.0);
formula(&mut engine, "Sheet1", 1, 1, "=Other!A1+Other!B1");
engine.evaluate_all().unwrap();
assert_eq!(num(&engine, "Sheet1", 1, 1), Some(1.0));
formula(&mut engine, "Other", 1, 1, "=B1*3");
engine.evaluate_all().unwrap();
assert_eq!(num(&engine, "Sheet1", 1, 1), Some(4.0));
set(&mut engine, "Other", 1, 2, 2.0);
engine.evaluate_all().unwrap();
assert_eq!(num(&engine, "Sheet1", 1, 1), Some(8.0));
}
fn best_effort(defer: bool) -> EvalConfig {
EvalConfig {
defer_graph_building: defer,
..EvalConfig::default()
}
.with_preparation_policy(PreparationPolicy::BestEffort)
}
#[test]
fn strict_policy_still_rejects_missing_sheet_and_table() {
assert_eq!(
EvalConfig::default().preparation_policy,
PreparationPolicy::BestEffort,
"BestEffort is the default (decision 16)"
);
let mut engine = Engine::new(
TestWorkbook::new(),
EvalConfig::default().with_preparation_policy(PreparationPolicy::Strict),
);
assert!(
engine
.set_cell_formula("Sheet1", 1, 1, parse("=Nope!A1").unwrap())
.is_err()
);
assert!(
engine
.set_cell_formula("Sheet1", 1, 2, parse("=SUM(NoTable[X])").unwrap())
.is_err()
);
}
#[test]
fn best_effort_sheet_added_later_binds_and_orders() {
for defer in [false, true] {
let mut engine = Engine::new(TestWorkbook::new(), best_effort(defer));
formula(&mut engine, "Sheet1", 1, 1, "=Later!A1*2");
formula(&mut engine, "Sheet1", 1, 2, "=SUM(Later!A1:A1000)");
engine.evaluate_all().unwrap();
assert_eq!(num(&engine, "Sheet1", 1, 1), None, "unbound while missing");
engine.add_sheet("Later").unwrap();
set(&mut engine, "Later", 1, 2, 4.0);
formula(&mut engine, "Later", 1, 1, "=B1+1");
engine.evaluate_all().unwrap();
assert_eq!(num(&engine, "Sheet1", 1, 1), Some(10.0), "defer={defer}");
assert_eq!(num(&engine, "Sheet1", 1, 2), Some(5.0), "defer={defer}");
set(&mut engine, "Later", 1, 2, 9.0);
engine.evaluate_all().unwrap();
assert_eq!(num(&engine, "Later", 1, 1), Some(10.0));
assert_eq!(num(&engine, "Sheet1", 1, 1), Some(20.0));
assert_eq!(num(&engine, "Sheet1", 1, 2), Some(10.0));
}
}
#[test]
fn best_effort_sheet_created_implicitly_binds() {
let mut engine = Engine::new(TestWorkbook::new(), best_effort(false));
formula(&mut engine, "Sheet1", 1, 1, "=Later!A1+1");
engine.evaluate_all().unwrap();
set(&mut engine, "Later", 1, 1, 41.0);
engine.evaluate_all().unwrap();
assert_eq!(num(&engine, "Sheet1", 1, 1), Some(42.0));
}
#[test]
fn best_effort_table_defined_later_binds_and_tracks_edits() {
for defer in [false, true] {
let mut engine = Engine::new(TestWorkbook::new(), best_effort(defer));
set(&mut engine, "Sheet1", 2, 1, 5.0);
set(&mut engine, "Sheet1", 3, 1, 7.0);
set(&mut engine, "Sheet1", 4, 1, 10.0);
formula(&mut engine, "Sheet1", 1, 4, "=SUM(Sales[Amount])");
formula(&mut engine, "Sheet1", 1, 5, "=D1+1");
engine.evaluate_all().unwrap();
assert_eq!(num(&engine, "Sheet1", 1, 4), None, "unbound while missing");
let sheet = engine.sheet_id("Sheet1").unwrap();
let range = RangeRef::new(
CellRef::new(sheet, Coord::from_excel(1, 1, true, true)),
CellRef::new(sheet, Coord::from_excel(4, 1, true, true)),
);
engine
.define_table("Sales", range, true, vec!["Amount".into()], false)
.unwrap();
engine.evaluate_all().unwrap();
assert_eq!(num(&engine, "Sheet1", 1, 4), Some(22.0), "defer={defer}");
assert_eq!(num(&engine, "Sheet1", 1, 5), Some(23.0));
set(&mut engine, "Sheet1", 2, 1, 1.0);
engine.evaluate_all().unwrap();
assert_eq!(num(&engine, "Sheet1", 1, 4), Some(18.0));
assert_eq!(num(&engine, "Sheet1", 1, 5), Some(19.0));
}
}
#[test]
fn structured_reader_follows_formula_in_table_body() {
let mut engine = Engine::new(TestWorkbook::new(), EvalConfig::default());
set(&mut engine, "Sheet1", 2, 1, 5.0);
set(&mut engine, "Sheet1", 3, 1, 7.0);
let sheet = engine.sheet_id("Sheet1").unwrap();
let range = RangeRef::new(
CellRef::new(sheet, Coord::from_excel(1, 1, true, true)),
CellRef::new(sheet, Coord::from_excel(4, 1, true, true)),
);
engine
.define_table("Sales", range, true, vec!["Amount".into()], false)
.unwrap();
formula(&mut engine, "Sheet1", 4, 1, "=A2*2");
formula(&mut engine, "Sheet1", 1, 4, "=SUM(Sales[Amount])");
engine.evaluate_all().unwrap();
assert_eq!(num(&engine, "Sheet1", 1, 4), Some(22.0));
set(&mut engine, "Sheet1", 2, 1, 1.0);
engine.evaluate_all().unwrap();
assert_eq!(num(&engine, "Sheet1", 1, 4), Some(10.0));
}