use crate::common::build_workbook;
use formualizer_common::{ExcelErrorKind, LiteralValue};
use formualizer_eval::engine::ingest::EngineLoadStream;
use formualizer_eval::engine::{Engine, EvalConfig};
use formualizer_parse::parser::parse;
use formualizer_workbook::{CalamineAdapter, SpreadsheetReader};
#[test]
fn stream_single_sheet_alignment_and_eval() {
let path = build_workbook(|book| {
let sh = book.get_sheet_by_name_mut("Sheet1").unwrap();
sh.get_cell_mut((4, 5)).set_value_number(99.0); sh.get_cell_mut((5, 5)).set_formula("=D5+1"); sh.get_cell_mut((6, 5)).set_value_bool(true); sh.get_cell_mut((4, 7)).set_value("x"); sh.get_cell_mut((5, 7)).set_formula("=D7"); sh.get_cell_mut((7, 5)).set_formula("=F5"); });
let mut backend = CalamineAdapter::open_path(&path).unwrap();
let ctx = formualizer_eval::test_workbook::TestWorkbook::new();
let mut engine: Engine<_> = Engine::new(ctx, EvalConfig::default());
backend.stream_into_engine(&mut engine).unwrap();
engine.evaluate_all().unwrap();
match engine.get_cell_value("Sheet1", 5, 4) {
Some(LiteralValue::Number(n)) => {
assert!((n - 99.0).abs() < 1e-9, "D5 expected 99, got {n}")
}
other => panic!("Unexpected D5: {other:?}"),
}
match engine.get_cell_value("Sheet1", 5, 6) {
Some(LiteralValue::Boolean(b)) => assert!(b, "Expected TRUE at F5"),
other => panic!("Unexpected F5: {other:?}"),
}
match engine.get_cell_value("Sheet1", 7, 4) {
Some(LiteralValue::Text(s)) => assert_eq!(s, "x"),
other => panic!("Unexpected D7: {other:?}"),
}
match engine.get_cell_value("Sheet1", 5, 5) {
Some(LiteralValue::Number(n)) => {
assert!((n - 100.0).abs() < 1e-9, "E5 expected 100, got {n}")
}
other => panic!("Unexpected E5: {other:?}"),
}
let (ast_e5, val_e5) = engine.get_cell("Sheet1", 5, 5).expect("cell E5 present");
assert!(ast_e5.is_some(), "E5 should have a formula AST");
assert_eq!(val_e5, engine.get_cell_value("Sheet1", 5, 5));
match engine.get_cell_value("Sheet1", 7, 5) {
Some(LiteralValue::Text(s)) => assert_eq!(s, "x"),
other => panic!("Unexpected E7: {other:?}"),
}
let (ast_e7, val_e7) = engine.get_cell("Sheet1", 7, 5).expect("cell E7 present");
assert!(ast_e7.is_some(), "E7 should have a formula AST");
assert_eq!(val_e7, engine.get_cell_value("Sheet1", 7, 5));
match engine.get_cell_value("Sheet1", 5, 7) {
Some(LiteralValue::Boolean(b)) => assert!(b, "Expected TRUE at G5"),
other => panic!("Unexpected G5: {other:?}"),
}
let (ast_g5, val_g5) = engine.get_cell("Sheet1", 5, 7).expect("cell G5 present");
assert!(ast_g5.is_some(), "G5 should have a formula AST");
assert_eq!(val_g5, engine.get_cell_value("Sheet1", 5, 7));
let (ast_d5, val_d5) = engine.get_cell("Sheet1", 5, 4).expect("cell D5 present");
assert!(ast_d5.is_none(), "D5 should be a value cell");
assert_eq!(val_d5, engine.get_cell_value("Sheet1", 5, 4));
let (ast_d7, val_d7) = engine.get_cell("Sheet1", 7, 4).expect("cell D7 present");
assert!(ast_d7.is_none(), "D7 should be a value cell");
assert_eq!(val_d7, engine.get_cell_value("Sheet1", 7, 4));
let (ast_f5, val_f5) = engine.get_cell("Sheet1", 5, 6).expect("cell F5 present");
assert!(ast_f5.is_none(), "F5 should be a value cell");
assert_eq!(val_f5, engine.get_cell_value("Sheet1", 5, 6));
}
#[test]
fn stream_multi_sheet_cross_ref() {
let path = build_workbook(|book| {
let _ = book.new_sheet("Data");
let data = book.get_sheet_by_name_mut("Data").unwrap();
data.get_cell_mut((4, 5)).set_value_number(21.0); let s1 = book.get_sheet_by_name_mut("Sheet1").unwrap();
s1.get_cell_mut((1, 1)).set_formula("=Data!D5*2"); });
let mut backend = CalamineAdapter::open_path(&path).unwrap();
let ctx = formualizer_eval::test_workbook::TestWorkbook::new();
let mut engine: Engine<_> = Engine::new(ctx, EvalConfig::default());
backend.stream_into_engine(&mut engine).unwrap();
engine.evaluate_all().unwrap();
match engine.get_cell_value("Sheet1", 1, 1) {
Some(LiteralValue::Number(n)) => assert!((n - 42.0).abs() < 1e-9, "Expected 42 got {n}"),
other => panic!("Unexpected A1: {other:?}"),
}
let (ast_a1, val_a1) = engine.get_cell("Sheet1", 1, 1).expect("A1 present");
assert!(ast_a1.is_some(), "A1 should have a formula AST");
assert_eq!(val_a1, engine.get_cell_value("Sheet1", 1, 1));
match engine.get_cell_value("Data", 5, 4) {
Some(LiteralValue::Number(n)) => {
assert!((n - 21.0).abs() < 1e-9, "Data!D5 expected 21, got {n}")
}
other => panic!("Unexpected Data!D5: {other:?}"),
}
}
#[test]
fn stream_mixed_types_in_column() {
let path = build_workbook(|book| {
let sh = book.get_sheet_by_name_mut("Sheet1").unwrap();
sh.get_cell_mut((4, 5)).set_value_number(1.0); sh.get_cell_mut((4, 6)).set_value_bool(true); sh.get_cell_mut((4, 7)).set_value("abc"); sh.get_cell_mut((4, 8)).set_formula("=1/0");
sh.get_cell_mut((5, 5)).set_formula("=D5");
sh.get_cell_mut((5, 6)).set_formula("=D6");
sh.get_cell_mut((5, 7)).set_formula("=D7");
sh.get_cell_mut((5, 8)).set_formula("=D8");
});
let mut backend = CalamineAdapter::open_path(&path).unwrap();
let ctx = formualizer_eval::test_workbook::TestWorkbook::new();
let mut engine: Engine<_> = Engine::new(ctx, EvalConfig::default());
backend.stream_into_engine(&mut engine).unwrap();
engine.evaluate_all().unwrap();
match engine.get_cell_value("Sheet1", 8, 4) {
Some(LiteralValue::Error(e)) => assert_eq!(e.kind, ExcelErrorKind::Div),
other => panic!("D8 expected #DIV/0!, got {other:?}"),
}
match engine.get_cell_value("Sheet1", 5, 4) {
Some(LiteralValue::Number(n)) => assert!((n - 1.0).abs() < 1e-9),
other => panic!("D5 expected 1.0, got {other:?}"),
}
match engine.get_cell_value("Sheet1", 6, 4) {
Some(LiteralValue::Boolean(b)) => assert!(b),
other => panic!("D6 expected TRUE, got {other:?}"),
}
match engine.get_cell_value("Sheet1", 7, 4) {
Some(LiteralValue::Text(s)) => assert_eq!(s, "abc"),
other => panic!("D7 expected 'abc', got {other:?}"),
}
match engine.get_cell_value("Sheet1", 5, 5) {
Some(LiteralValue::Number(n)) => assert!((n - 1.0).abs() < 1e-9),
other => panic!("E5 expected 1.0, got {other:?}"),
}
let (ast_e5, val_e5) = engine.get_cell("Sheet1", 5, 5).expect("E5 present");
assert!(ast_e5.is_some());
assert_eq!(val_e5, engine.get_cell_value("Sheet1", 5, 5));
match engine.get_cell_value("Sheet1", 6, 5) {
Some(LiteralValue::Boolean(b)) => assert!(b),
other => panic!("E6 expected TRUE, got {other:?}"),
}
let (ast_e6, val_e6) = engine.get_cell("Sheet1", 6, 5).expect("E6 present");
assert!(ast_e6.is_some());
assert_eq!(val_e6, engine.get_cell_value("Sheet1", 6, 5));
match engine.get_cell_value("Sheet1", 7, 5) {
Some(LiteralValue::Text(s)) => assert_eq!(s, "abc"),
other => panic!("E7 expected 'abc', got {other:?}"),
}
let (ast_e7, val_e7) = engine.get_cell("Sheet1", 7, 5).expect("E7 present");
assert!(ast_e7.is_some());
assert_eq!(val_e7, engine.get_cell_value("Sheet1", 7, 5));
match engine.get_cell_value("Sheet1", 8, 5) {
Some(LiteralValue::Error(e)) => assert_eq!(e.kind, ExcelErrorKind::Div),
other => panic!("E8 expected #DIV/0!, got {other:?}"),
}
let (ast_e8, val_e8) = engine.get_cell("Sheet1", 8, 5).expect("E8 present");
assert!(ast_e8.is_some());
assert_eq!(val_e8, engine.get_cell_value("Sheet1", 8, 5));
let (ast_d5, val_d5) = engine.get_cell("Sheet1", 5, 4).expect("D5 present");
assert!(ast_d5.is_none());
assert_eq!(val_d5, engine.get_cell_value("Sheet1", 5, 4));
let (ast_d6, val_d6) = engine.get_cell("Sheet1", 6, 4).expect("D6 present");
assert!(ast_d6.is_none());
assert_eq!(val_d6, engine.get_cell_value("Sheet1", 6, 4));
let (ast_d7, val_d7) = engine.get_cell("Sheet1", 7, 4).expect("D7 present");
assert!(ast_d7.is_none());
assert_eq!(val_d7, engine.get_cell_value("Sheet1", 7, 4));
}
#[test]
fn stream_edit_propagation_chain() {
let path = build_workbook(|book| {
let sh = book.get_sheet_by_name_mut("Sheet1").unwrap();
sh.get_cell_mut((1, 1)).set_value_number(10.0); sh.get_cell_mut((2, 1)).set_formula("=A1"); sh.get_cell_mut((3, 1)).set_formula("=B1"); });
let mut backend = CalamineAdapter::open_path(&path).unwrap();
let ctx = formualizer_eval::test_workbook::TestWorkbook::new();
let mut engine: Engine<_> = Engine::new(ctx, EvalConfig::default());
backend.stream_into_engine(&mut engine).unwrap();
engine.evaluate_all().unwrap();
match engine.get_cell_value("Sheet1", 1, 2) {
Some(formualizer_workbook::LiteralValue::Number(n)) => assert!((n - 10.0).abs() < 1e-9),
other => panic!("Unexpected B1 initial: {other:?}"),
}
match engine.get_cell_value("Sheet1", 1, 3) {
Some(formualizer_workbook::LiteralValue::Number(n)) => assert!((n - 10.0).abs() < 1e-9),
other => panic!("Unexpected C1 initial: {other:?}"),
}
engine
.set_cell_formula("Sheet1", 1, 1, parse("=20").unwrap())
.unwrap();
engine.evaluate_all().unwrap();
match engine.get_cell_value("Sheet1", 1, 2) {
Some(formualizer_workbook::LiteralValue::Number(n)) => assert!((n - 20.0).abs() < 1e-9),
other => panic!("Unexpected B1 after A1 edit: {other:?}"),
}
match engine.get_cell_value("Sheet1", 1, 3) {
Some(formualizer_workbook::LiteralValue::Number(n)) => assert!((n - 20.0).abs() < 1e-9),
other => panic!("Unexpected C1 after A1 edit: {other:?}"),
}
engine
.set_cell_formula("Sheet1", 1, 2, parse("=A1*3").unwrap())
.unwrap();
engine.evaluate_all().unwrap();
match engine.get_cell_value("Sheet1", 1, 2) {
Some(formualizer_workbook::LiteralValue::Number(n)) => assert!((n - 60.0).abs() < 1e-9),
other => panic!("Unexpected B1 after B1 edit: {other:?}"),
}
match engine.get_cell_value("Sheet1", 1, 3) {
Some(formualizer_workbook::LiteralValue::Number(n)) => assert!((n - 60.0).abs() < 1e-9),
other => panic!("Unexpected C1 after B1 edit: {other:?}"),
}
}
#[test]
fn stream_sumifs_cross_sheet_multi_predicate() {
let path = build_workbook(|book| {
let _ = book.new_sheet("Data");
let data = book.get_sheet_by_name_mut("Data").unwrap();
data.get_cell_mut((1, 5)).set_value("A");
data.get_cell_mut((2, 5)).set_value_number(1);
data.get_cell_mut((3, 5)).set_value_number(10.0);
data.get_cell_mut((1, 6)).set_value("B");
data.get_cell_mut((2, 6)).set_value_number(1);
data.get_cell_mut((3, 6)).set_value_number(20.0);
data.get_cell_mut((1, 7)).set_value("A");
data.get_cell_mut((2, 7)).set_value_number(0);
data.get_cell_mut((3, 7)).set_value_number(30.0);
data.get_cell_mut((1, 8)).set_value("A");
data.get_cell_mut((2, 8)).set_value_number(1);
data.get_cell_mut((3, 8)).set_value_number(40.0);
let s1 = book.get_sheet_by_name_mut("Sheet1").unwrap();
s1.get_cell_mut((1, 1))
.set_formula("=SUMIFS(Data!C:C, Data!A:A, \"A\", Data!B:B, 1)");
});
let mut backend = CalamineAdapter::open_path(&path).unwrap();
let ctx = formualizer_eval::test_workbook::TestWorkbook::new();
let mut engine: Engine<_> = Engine::new(ctx, EvalConfig::default());
backend.stream_into_engine(&mut engine).unwrap();
engine.evaluate_all().unwrap();
match engine.get_cell_value("Sheet1", 1, 1) {
Some(LiteralValue::Number(n)) => assert!((n - 50.0).abs() < 1e-9, "Expected 50, got {n}"),
other => panic!("Unexpected SUMIFS result: {other:?}"),
}
let (ast_a1, val_a1) = engine.get_cell("Sheet1", 1, 1).expect("A1 present");
assert!(ast_a1.is_some());
assert_eq!(val_a1, engine.get_cell_value("Sheet1", 1, 1));
match engine.get_cell_value("Data", 5, 1) {
Some(LiteralValue::Text(s)) => assert_eq!(s, "A"),
other => panic!("Unexpected Data!A5: {other:?}"),
}
match engine.get_cell_value("Data", 5, 3) {
Some(LiteralValue::Number(n)) => assert!((n - 10.0).abs() < 1e-9),
other => panic!("Unexpected Data!C5: {other:?}"),
}
}