#![cfg(feature = "json")]
use formualizer_eval::engine::CycleConfig;
use formualizer_workbook::backends::JsonAdapter;
use formualizer_workbook::traits::{CellData, SpreadsheetReader, SpreadsheetWriter};
use formualizer_workbook::{LiteralValue, LoadStrategy, Workbook, WorkbookConfig};
fn iterate_config(max_iterations: u32, max_change: f64) -> WorkbookConfig {
let mut config = WorkbookConfig::ephemeral();
config.eval = config
.eval
.with_cycle(CycleConfig::iterate(max_iterations, max_change));
config
}
fn num(wb: &Workbook, sheet: &str, row: u32, col: u32) -> f64 {
match wb.get_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:?}"),
}
}
fn json_with_self_reference() -> Vec<u8> {
let mut adapter = JsonAdapter::new();
adapter.create_sheet("Sheet1").unwrap();
adapter
.write_cell("Sheet1", 1, 1, CellData::from_formula("=A1+1"))
.unwrap();
adapter
.write_cell("Sheet1", 1, 2, CellData::from_formula("=A1*2"))
.unwrap();
adapter.save_to_bytes().unwrap()
}
#[test]
fn iterate_workbook_self_ref_json_reload_under_default_config_yields_circ() {
let mut wb = Workbook::new_with_config(iterate_config(10, 0.001));
wb.add_sheet("Sheet1").unwrap();
wb.set_formula("Sheet1", 1, 1, "=A1+1").unwrap();
wb.evaluate_all().unwrap();
assert_eq!(num(&wb, "Sheet1", 1, 1), 10.0, "capped accumulator");
let mut adapter = JsonAdapter::new();
adapter.create_sheet("Sheet1").unwrap();
adapter
.write_cell(
"Sheet1",
1,
1,
CellData {
value: wb.get_value("Sheet1", 1, 1),
formula: wb.get_formula("Sheet1", 1, 1),
style: None,
},
)
.unwrap();
let bytes = adapter.save_to_bytes().unwrap();
let adapter = JsonAdapter::open_bytes(bytes).unwrap();
let mut reloaded =
Workbook::from_reader(adapter, LoadStrategy::EagerAll, WorkbookConfig::ephemeral())
.expect("bulk load must accept the self-reference (no load-path gate)");
let formula = reloaded
.get_formula("Sheet1", 1, 1)
.expect("self-referential formula must not be dropped on load");
assert_eq!(formula.replace(' ', ""), "=A1+1");
reloaded.evaluate_all().unwrap();
match reloaded.get_value("Sheet1", 1, 1) {
Some(LiteralValue::Error(e)) => {
assert_eq!(e.kind, formualizer_common::ExcelErrorKind::Circ)
}
other => panic!("expected #CIRC! under default policy, got {other:?}"),
}
}
#[test]
fn json_self_ref_loaded_under_iterate_config_iterates() {
let adapter = JsonAdapter::open_bytes(json_with_self_reference()).unwrap();
let mut wb = Workbook::from_reader(adapter, LoadStrategy::EagerAll, iterate_config(7, 0.001))
.expect("load");
wb.evaluate_all().unwrap();
assert_eq!(num(&wb, "Sheet1", 1, 1), 7.0);
assert_eq!(
num(&wb, "Sheet1", 1, 2),
14.0,
"dependent reads final value"
);
}
#[test]
fn json_self_ref_loaded_under_default_config_yields_circ() {
let adapter = JsonAdapter::open_bytes(json_with_self_reference()).unwrap();
let mut wb =
Workbook::from_reader(adapter, LoadStrategy::EagerAll, WorkbookConfig::ephemeral())
.expect("bulk load must accept the self-reference (no load-path gate)");
wb.evaluate_all().unwrap();
match wb.get_value("Sheet1", 1, 1) {
Some(LiteralValue::Error(e)) => {
assert_eq!(e.kind, formualizer_common::ExcelErrorKind::Circ)
}
other => panic!("expected #CIRC! under default policy, got {other:?}"),
}
}
#[test]
fn json_self_ref_deferred_graph_build_under_default_config_yields_circ() {
let adapter = JsonAdapter::open_bytes(json_with_self_reference()).unwrap();
let mut wb = Workbook::from_reader(
adapter,
LoadStrategy::EagerAll,
WorkbookConfig::interactive(),
)
.expect("staged load must accept the self-reference");
wb.evaluate_all().unwrap();
match wb.get_value("Sheet1", 1, 1) {
Some(LiteralValue::Error(e)) => {
assert_eq!(e.kind, formualizer_common::ExcelErrorKind::Circ)
}
other => panic!("expected #CIRC! under default policy, got {other:?}"),
}
}
#[test]
fn interactive_set_formula_still_rejects_self_ref_under_default_config() {
let mut wb = Workbook::new_with_config(WorkbookConfig::ephemeral());
wb.add_sheet("Sheet1").unwrap();
let err = wb.set_formula("Sheet1", 1, 1, "=A1+1");
assert!(
err.is_err(),
"interactive self-reference must be rejected under default config"
);
}