use super::common::arrow_eval_config;
use crate::engine::{Engine, EvalConfig};
use crate::test_workbook::TestWorkbook;
use formualizer_common::LiteralValue;
use formualizer_parse::parser::parse;
fn num(engine: &mut Engine<TestWorkbook>, formula: &str) -> LiteralValue {
let ast = parse(formula).unwrap();
engine.set_cell_formula("Sheet1", 1, 20, ast).unwrap();
engine.evaluate_cell("Sheet1", 1, 20).unwrap();
engine.get_cell_value("Sheet1", 1, 20).unwrap()
}
fn setup_finite(engine: &mut Engine<TestWorkbook>) {
engine
.set_cell_value("Sheet1", 1, 1, LiteralValue::Text("Debt".into()))
.unwrap();
engine
.set_cell_value("Sheet1", 1, 2, LiteralValue::Number(10.0))
.unwrap();
engine
.set_cell_value("Sheet1", 2, 2, LiteralValue::Number(20.0))
.unwrap();
engine
.set_cell_value("Sheet1", 3, 1, LiteralValue::Text("Equity".into()))
.unwrap();
engine
.set_cell_value("Sheet1", 3, 2, LiteralValue::Number(30.0))
.unwrap();
}
#[test]
fn averageifs_ne_blank_includes_blank_row() {
let mut engine = Engine::new(TestWorkbook::new(), EvalConfig::default());
setup_finite(&mut engine);
assert_eq!(
num(&mut engine, "=AVERAGEIFS(B1:B3, A1:A3, \"<>Debt\")"),
LiteralValue::Number(25.0),
"AVERAGEIFS <>Debt must average the blank + Equity rows"
);
}
#[test]
fn ne_empty_operator_does_not_count_blanks() {
let mut engine = Engine::new(TestWorkbook::new(), EvalConfig::default());
setup_finite(&mut engine);
assert_eq!(
num(&mut engine, "=COUNTIFS(A1:A3, \"<>\")"),
LiteralValue::Number(2.0),
"<> must count non-blank cells only"
);
assert_eq!(
num(&mut engine, "=SUMIFS(B1:B3, A1:A3, \"<>\")"),
LiteralValue::Number(40.0),
"<> sums Debt+Equity rows (10+30), not the blank row"
);
}
#[test]
fn empty_string_and_equality_semantics_on_blanks() {
let mut engine = Engine::new(TestWorkbook::new(), EvalConfig::default());
setup_finite(&mut engine);
assert_eq!(
num(&mut engine, "=COUNTIFS(A1:A3, \"\")"),
LiteralValue::Number(1.0),
"empty-string criteria matches the single blank cell"
);
assert_eq!(
num(&mut engine, "=COUNTIFS(A1:A3, \"=\")"),
LiteralValue::Number(1.0),
"\"=\" matches the single blank cell"
);
assert_eq!(
num(&mut engine, "=COUNTIFS(A1:A3, \"Debt\")"),
LiteralValue::Number(1.0),
);
assert_eq!(
num(&mut engine, "=COUNTIFS(A1:A3, \"=Debt\")"),
LiteralValue::Number(1.0),
);
}
#[test]
fn numeric_predicates_still_exclude_blanks() {
let mut engine = Engine::new(TestWorkbook::new(), EvalConfig::default());
engine
.set_cell_value("Sheet1", 1, 1, LiteralValue::Number(5.0))
.unwrap();
engine
.set_cell_value("Sheet1", 3, 1, LiteralValue::Number(-3.0))
.unwrap();
assert_eq!(
num(&mut engine, "=COUNTIFS(A1:A3, \">0\")"),
LiteralValue::Number(1.0),
">0 must not count the blank cell"
);
assert_eq!(
num(&mut engine, "=COUNTIFS(A1:A3, \"<5\")"),
LiteralValue::Number(1.0),
"<5 counts only -3, not the blank"
);
}
#[test]
fn wildcard_on_blanks_matches_excel() {
let mut engine = Engine::new(TestWorkbook::new(), EvalConfig::default());
engine
.set_cell_value("Sheet1", 1, 1, LiteralValue::Text("apple".into()))
.unwrap();
engine
.set_cell_value("Sheet1", 3, 1, LiteralValue::Text("apricot".into()))
.unwrap();
assert_eq!(
num(&mut engine, "=COUNTIFS(A1:A3, \"ap*\")"),
LiteralValue::Number(2.0),
);
}
#[test]
fn multi_criteria_ne_with_blanks() {
let mut engine = Engine::new(TestWorkbook::new(), EvalConfig::default());
let cats = [Some("Debt"), None, Some("Equity"), Some("Debt")];
let amts = [10.0, 20.0, 30.0, 40.0];
for (i, (cat, amt)) in cats.iter().zip(amts.iter()).enumerate() {
let row = (i + 1) as u32;
if let Some(c) = cat {
engine
.set_cell_value("Sheet1", row, 1, LiteralValue::Text((*c).into()))
.unwrap();
}
engine
.set_cell_value("Sheet1", row, 2, LiteralValue::Number(*amt))
.unwrap();
}
assert_eq!(
num(
&mut engine,
"=SUMIFS(B1:B4, A1:A4, \"<>Debt\", B1:B4, \">=20\")"
),
LiteralValue::Number(50.0),
"multi-criteria <> must include the blank-category row"
);
assert_eq!(
num(&mut engine, "=COUNTIFS(A1:A4, \"<>Debt\", B1:B4, \">=20\")"),
LiteralValue::Number(2.0),
);
}
#[test]
fn whole_column_ne_does_not_explode() {
let mut cfg = arrow_eval_config();
cfg.enable_parallel = false;
cfg.range_expansion_limit = 2_000_000; let mut engine = Engine::new(TestWorkbook::new(), cfg);
let chunk_rows = 64usize;
let total_rows = 128u32;
{
let mut ab = engine.begin_bulk_ingest_arrow();
ab.add_sheet("Sheet1", 2, chunk_rows);
for i in 0..total_rows {
let a = match i % 4 {
0 => LiteralValue::Text("Debt".into()),
1 => LiteralValue::Empty, _ => LiteralValue::Text("Equity".into()),
};
let b = LiteralValue::Int((i + 1) as i64);
ab.append_row("Sheet1", &[a, b]).unwrap();
}
ab.finish().unwrap();
}
let count = num(&mut engine, "=COUNTIFS(A:A, \"<>Debt\")");
assert_eq!(
count,
LiteralValue::Number(96.0),
"whole-column <>Debt must count only used-region rows (incl. interior blanks), not 1M blanks"
);
let finite = num(&mut engine, "=COUNTIFS(A1:A128, \"<>Debt\")");
assert_eq!(finite, LiteralValue::Number(96.0));
}