Files
gridcraft/crates/calc/src/tests.rs
T

218 lines
6.2 KiB
Rust

use gridcraft_core::{CellError, CellRef, Value};
use gridcraft_model::{Cell, Formula, Workbook};
use crate::Calc;
fn c(s: &str) -> CellRef {
CellRef::parse(s).unwrap()
}
struct T {
wb: Workbook,
calc: Calc,
}
impl T {
fn new() -> T {
T { wb: Workbook::new(), calc: Calc::new() }
}
fn set(&mut self, at: &str, input: &str) {
let cell = if input.starts_with('=') {
Cell::formula(Formula::new(input))
} else {
Cell::value(gridcraft_core::parse::parse_input(input, self.wb.date_system).value)
};
self.wb.sheet_mut(0).unwrap().cells.set(c(at), cell);
self.calc.cells_changed(&mut self.wb, &[(0, c(at))]);
}
fn get(&self, at: &str) -> Value {
self.wb.sheet(0).unwrap().value(c(at))
}
fn num(&self, at: &str) -> f64 {
self.get(at).as_f64().unwrap_or_else(|| panic!("{at} = {:?}", self.get(at)))
}
}
#[test]
fn arithmetic_and_dependencies() {
let mut t = T::new();
t.set("A1", "2");
t.set("A2", "3");
t.set("A3", "=A1*A2+1");
assert_eq!(t.num("A3"), 7.0);
t.set("A1", "10");
assert_eq!(t.num("A3"), 31.0);
t.set("B1", "=SUM(A1:A3)");
assert_eq!(t.num("B1"), 44.0);
t.set("A2", "0");
assert_eq!(t.num("A3"), 1.0);
assert_eq!(t.num("B1"), 11.0);
}
#[test]
fn chains_out_of_order() {
let mut t = T::new();
t.set("A5", "=A4+1");
t.set("A4", "=A3+1");
t.set("A3", "=A2+1");
t.set("A2", "=A1+1");
t.set("A1", "1");
assert_eq!(t.num("A5"), 5.0);
}
#[test]
fn long_chain() {
let mut t = T::new();
t.set("A1", "1");
for r in 2..=3000 {
t.set(&format!("A{r}"), &format!("=A{}+1", r - 1));
}
assert_eq!(t.num("A3000"), 3000.0);
t.set("A1", "10");
assert_eq!(t.num("A3000"), 3009.0);
}
#[test]
fn errors_and_cycles() {
let mut t = T::new();
t.set("A1", "=1/0");
assert_eq!(t.get("A1"), Value::Error(CellError::Div0));
t.set("B1", "=B2");
t.set("B2", "=B1");
assert!(t.get("B1").is_error());
t.set("C1", "=NOSUCHFN(1)");
assert_eq!(t.get("C1"), Value::Error(CellError::Name));
t.set("C2", "=1+");
assert_eq!(t.get("C2"), Value::Error(CellError::Name));
}
#[test]
fn operators() {
let mut t = T::new();
t.set("A1", "=\"a\"&1&TRUE");
assert_eq!(t.get("A1"), Value::from("a1TRUE"));
t.set("A2", "=-2^2");
assert_eq!(t.num("A2"), 4.0);
t.set("A3", "=50%");
assert_eq!(t.num("A3"), 0.5);
t.set("A4", "=\"abc\"=\"ABC\"");
assert_eq!(t.get("A4"), Value::Bool(true));
t.set("A5", "=\"5\"+1");
assert_eq!(t.num("A5"), 6.0);
t.set("A6", "=\"x\"+1");
assert_eq!(t.get("A6"), Value::Error(CellError::Value));
t.set("A7", "=B99");
assert_eq!(t.num("A7"), 0.0);
}
#[test]
fn ifs_and_lazy() {
let mut t = T::new();
t.set("A1", "5");
t.set("B1", "=IF(A1>3,\"big\",1/0)");
assert_eq!(t.get("B1"), Value::from("big"));
t.set("B2", "=IFERROR(1/0,\"oops\")");
assert_eq!(t.get("B2"), Value::from("oops"));
t.set("B3", "=CHOOSE(2,\"a\",\"b\",\"c\")");
assert_eq!(t.get("B3"), Value::from("b"));
t.set("B4", "=SWITCH(A1,1,\"one\",5,\"five\",\"other\")");
assert_eq!(t.get("B4"), Value::from("five"));
}
#[test]
fn references_functions() {
let mut t = T::new();
for (i, v) in [10, 20, 30, 40].iter().enumerate() {
t.set(&format!("A{}", i + 1), &v.to_string());
}
t.set("B1", "=ROW(A3)");
assert_eq!(t.num("B1"), 3.0);
t.set("B2", "=ROWS(A1:A4)*COLUMNS(A1:C1)");
assert_eq!(t.num("B2"), 12.0);
t.set("B3", "=SUM(OFFSET(A1,1,0,2,1))");
assert_eq!(t.num("B3"), 50.0);
t.set("B4", "=INDIRECT(\"A\"&4)");
assert_eq!(t.num("B4"), 40.0);
t.set("B5", "=SUM(A1:INDEX(A1:A4,3))");
assert_eq!(t.num("B5"), 60.0);
t.set("B6", "=SUM(A:A)");
assert_eq!(t.num("B6"), 100.0);
t.set("A2", "25");
assert_eq!(t.num("B3"), 55.0);
assert_eq!(t.num("B6"), 105.0);
}
#[test]
fn spills() {
let mut t = T::new();
t.set("A1", "=SEQUENCE(3)");
assert_eq!(t.num("A1"), 1.0);
assert_eq!(t.num("A3"), 3.0);
t.set("B1", "=SUM(A1:A3)");
assert_eq!(t.num("B1"), 6.0);
t.set("A2", "x");
assert_eq!(t.get("A1"), Value::Error(CellError::Spill));
t.set("A2", "");
assert_eq!(t.num("A3"), 3.0);
t.set("C1", "={1,2;3,4}*10");
assert_eq!(t.num("D2"), 40.0);
}
#[test]
fn let_lambda() {
let mut t = T::new();
t.set("A1", "=LET(x,2,y,3,x*y)");
assert_eq!(t.num("A1"), 6.0);
t.set("A2", "=LAMBDA(a,b,a+b)(2,3)");
assert_eq!(t.num("A2"), 5.0);
t.set("A3", "=REDUCE(0,{1,2,3},LAMBDA(acc,v,acc+v))");
assert_eq!(t.num("A3"), 6.0);
t.set("A4", "=SUM(MAP({1,2,3},LAMBDA(v,v*v)))");
assert_eq!(t.num("A4"), 14.0);
}
#[test]
fn names_and_sheets() {
let mut t = T::new();
t.wb.names.push(gridcraft_model::DefinedName { name: "Rate".into(), scope: None, formula: "0.5".into(), comment: String::new(), hidden: false });
t.wb.sheets.push(std::sync::Arc::new(gridcraft_model::Sheet::new("Data")));
t.wb.sheet_mut(1).unwrap().set_value(c("A1"), Value::Number(8.0));
t.calc.rebuild(&t.wb);
t.set("A1", "=Rate*Data!A1");
assert_eq!(t.num("A1"), 4.0);
t.wb.sheet_mut(1).unwrap().set_value(c("A1"), Value::Number(10.0));
t.calc.cells_changed(&mut t.wb, &[(1, c("A1"))]);
assert_eq!(t.num("A1"), 5.0);
}
#[test]
fn text_function() {
let mut t = T::new();
t.set("A1", "=TEXT(1234.5,\"#,##0.00\")");
assert_eq!(t.get("A1"), Value::from("1,234.50"));
}
#[test]
fn subtotal_skips_hidden() {
let mut t = T::new();
t.set("A1", "1");
t.set("A2", "2");
t.set("A3", "3");
t.wb.sheet_mut(0).unwrap().rows.insert(1, gridcraft_model::LineInfo { hidden: true, ..Default::default() });
t.set("B1", "=SUBTOTAL(109,A1:A3)");
t.set("B2", "=SUBTOTAL(9,A1:A3)");
assert_eq!(t.num("B1"), 4.0);
assert_eq!(t.num("B2"), 6.0);
}
#[test]
fn recalc_all_after_load() {
let mut t = T::new();
let s = t.wb.sheet_mut(0).unwrap();
s.cells.set(c("A1"), Cell::value(Value::Number(4.0)));
s.cells.set(c("A2"), Cell::formula(Formula::new("=A1*2")));
s.cells.set(c("A3"), Cell::formula(Formula::new("=A2+A1")));
t.calc.recalc_all(&mut t.wb);
assert_eq!(t.num("A3"), 12.0);
}