test(grid): ensure formulas are recomputed before rendering

The render helper used in Grid tests previously did not recompute formulas,
meaning tests for computed values were either ignored or required manual
setup that didn't reflect actual application behavior.

Update the render helper to identify 'None' axis categories and trigger
recompute_formulas, and update all call sites to pass a mutable model.

Co-Authored-By: fiddlerwoaroof/git-smart-commit (gemma-4-31B-it-UD-Q4_K_XL.gguf)
This commit is contained in:
Edward Langley
2026-04-14 00:49:31 -07:00
parent f019577810
commit 8baa4c4865

View File

@ -682,7 +682,14 @@ mod tests {
// ── Helpers ─────────────────────────────────────────────────────────────── // ── Helpers ───────────────────────────────────────────────────────────────
/// Render a GridWidget into a fresh buffer of the given size. /// Render a GridWidget into a fresh buffer of the given size.
fn render(model: &Model, width: u16, height: u16) -> Buffer { fn render(model: &mut Model, width: u16, height: u16) -> Buffer {
let none_cats: Vec<String> = model
.active_view()
.categories_on(crate::view::Axis::None)
.into_iter()
.map(String::from)
.collect();
model.recompute_formulas(&none_cats);
let area = Rect::new(0, 0, width, height); let area = Rect::new(0, 0, width, height);
let mut buf = Buffer::empty(area); let mut buf = Buffer::empty(area);
let bufs = std::collections::HashMap::new(); let bufs = std::collections::HashMap::new();
@ -743,8 +750,8 @@ mod tests {
#[test] #[test]
fn column_headers_appear() { fn column_headers_appear() {
let m = two_cat_model(); let mut m = two_cat_model();
let text = buf_text(&render(&m, 80, 24)); let text = buf_text(&render(&mut m, 80, 24));
assert!(text.contains("Jan"), "expected 'Jan' in:\n{text}"); assert!(text.contains("Jan"), "expected 'Jan' in:\n{text}");
assert!(text.contains("Feb"), "expected 'Feb' in:\n{text}"); assert!(text.contains("Feb"), "expected 'Feb' in:\n{text}");
} }
@ -753,8 +760,8 @@ mod tests {
#[test] #[test]
fn row_headers_appear() { fn row_headers_appear() {
let m = two_cat_model(); let mut m = two_cat_model();
let text = buf_text(&render(&m, 80, 24)); let text = buf_text(&render(&mut m, 80, 24));
assert!(text.contains("Food"), "expected 'Food' in:\n{text}"); assert!(text.contains("Food"), "expected 'Food' in:\n{text}");
assert!(text.contains("Clothing"), "expected 'Clothing' in:\n{text}"); assert!(text.contains("Clothing"), "expected 'Clothing' in:\n{text}");
} }
@ -768,7 +775,7 @@ mod tests {
coord(&[("Type", "Food"), ("Month", "Jan")]), coord(&[("Type", "Food"), ("Month", "Jan")]),
CellValue::Number(123.0), CellValue::Number(123.0),
); );
let text = buf_text(&render(&m, 80, 24)); let text = buf_text(&render(&mut m, 80, 24));
assert!(text.contains("123"), "expected '123' in:\n{text}"); assert!(text.contains("123"), "expected '123' in:\n{text}");
} }
@ -787,7 +794,7 @@ mod tests {
coord(&[("Type", "Clothing"), ("Month", "Jan")]), coord(&[("Type", "Clothing"), ("Month", "Jan")]),
CellValue::Number(50.0), CellValue::Number(50.0),
); );
let text = buf_text(&render(&m, 80, 24)); let text = buf_text(&render(&mut m, 80, 24));
assert!(text.contains("100"), "expected '100' in:\n{text}"); assert!(text.contains("100"), "expected '100' in:\n{text}");
assert!(text.contains("200"), "expected '200' in:\n{text}"); assert!(text.contains("200"), "expected '200' in:\n{text}");
assert!(text.contains("50"), "expected '50' in:\n{text}"); assert!(text.contains("50"), "expected '50' in:\n{text}");
@ -806,7 +813,7 @@ mod tests {
coord(&[("Type", "Food"), ("Month", "Jan")]), coord(&[("Type", "Food"), ("Month", "Jan")]),
CellValue::Number(1.0), CellValue::Number(1.0),
); );
let text = buf_text(&render(&m, 80, 24)); let text = buf_text(&render(&mut m, 80, 24));
// Should not contain large numbers that weren't set // Should not contain large numbers that weren't set
assert!(!text.contains("100"), "unexpected '100' in:\n{text}"); assert!(!text.contains("100"), "unexpected '100' in:\n{text}");
} }
@ -815,8 +822,8 @@ mod tests {
#[test] #[test]
fn total_row_label_appears() { fn total_row_label_appears() {
let m = two_cat_model(); let mut m = two_cat_model();
let text = buf_text(&render(&m, 80, 24)); let text = buf_text(&render(&mut m, 80, 24));
assert!(text.contains("Total"), "expected 'Total' in:\n{text}"); assert!(text.contains("Total"), "expected 'Total' in:\n{text}");
} }
@ -831,7 +838,7 @@ mod tests {
coord(&[("Type", "Clothing"), ("Month", "Jan")]), coord(&[("Type", "Clothing"), ("Month", "Jan")]),
CellValue::Number(50.0), CellValue::Number(50.0),
); );
let text = buf_text(&render(&m, 80, 24)); let text = buf_text(&render(&mut m, 80, 24));
// Food(100) + Clothing(50) = 150 for Jan // Food(100) + Clothing(50) = 150 for Jan
assert!( assert!(
text.contains("150"), text.contains("150"),
@ -858,7 +865,7 @@ mod tests {
c.add_item("Bob"); c.add_item("Bob");
} }
m.active_view_mut().set_page_selection("Payer", "Bob"); m.active_view_mut().set_page_selection("Payer", "Bob");
let text = buf_text(&render(&m, 80, 24)); let text = buf_text(&render(&mut m, 80, 24));
assert!( assert!(
text.contains("Payer = Bob"), text.contains("Payer = Bob"),
"expected 'Payer = Bob' in:\n{text}" "expected 'Payer = Bob' in:\n{text}"
@ -882,7 +889,7 @@ mod tests {
c.add_item("Bob"); c.add_item("Bob");
} }
// No explicit selection — should default to first item // No explicit selection — should default to first item
let text = buf_text(&render(&m, 80, 24)); let text = buf_text(&render(&mut m, 80, 24));
assert!( assert!(
text.contains("Payer = Alice"), text.contains("Payer = Alice"),
"expected 'Payer = Alice' in:\n{text}" "expected 'Payer = Alice' in:\n{text}"
@ -892,19 +899,13 @@ mod tests {
// ── Formula evaluation ──────────────────────────────────────────────────── // ── Formula evaluation ────────────────────────────────────────────────────
#[test] #[test]
#[ignore = "needs render harness update for _Measure virtual category"]
fn formula_cell_renders_computed_value() { fn formula_cell_renders_computed_value() {
let mut m = Model::new("Test"); let mut m = Model::new("Test");
m.add_category("_Measure").unwrap(); // → Row
m.add_category("Region").unwrap(); // → Column m.add_category("Region").unwrap(); // → Column
if let Some(c) = m.category_mut("_Measure") { m.category_mut("_Measure").unwrap().add_item("Revenue");
c.add_item("Revenue"); m.category_mut("_Measure").unwrap().add_item("Cost");
c.add_item("Cost"); // Profit is a formula target — dynamically included in _Measure
c.add_item("Profit"); m.category_mut("Region").unwrap().add_item("East");
}
if let Some(c) = m.category_mut("Region") {
c.add_item("East");
}
m.set_cell( m.set_cell(
coord(&[("_Measure", "Revenue"), ("Region", "East")]), coord(&[("_Measure", "Revenue"), ("Region", "East")]),
CellValue::Number(1000.0), CellValue::Number(1000.0),
@ -914,12 +915,16 @@ mod tests {
CellValue::Number(600.0), CellValue::Number(600.0),
); );
m.add_formula(parse_formula("Profit = Revenue - Cost", "_Measure").unwrap()); m.add_formula(parse_formula("Profit = Revenue - Cost", "_Measure").unwrap());
m.active_view_mut()
.set_axis("_Index", crate::view::Axis::None);
m.active_view_mut()
.set_axis("_Dim", crate::view::Axis::None);
m.active_view_mut() m.active_view_mut()
.set_axis("_Measure", crate::view::Axis::Row); .set_axis("_Measure", crate::view::Axis::Row);
m.active_view_mut() m.active_view_mut()
.set_axis("Region", crate::view::Axis::Column); .set_axis("Region", crate::view::Axis::Column);
let text = buf_text(&render(&m, 80, 24)); let text = buf_text(&render(&mut m, 80, 24));
assert!(text.contains("400"), "expected '400' (Profit) in:\n{text}"); assert!(text.contains("400"), "expected '400' (Profit) in:\n{text}");
} }
@ -954,7 +959,7 @@ mod tests {
} }
} }
let text = buf_text(&render(&m, 80, 24)); let text = buf_text(&render(&mut m, 80, 24));
// Multi-level row headers: category values shown separately, not joined with / // Multi-level row headers: category values shown separately, not joined with /
assert!( assert!(
!text.contains("Food/Alice"), !text.contains("Food/Alice"),
@ -994,7 +999,7 @@ mod tests {
coord(&[("Month", "Jan"), ("Recipient", "Alice"), ("Type", "Food")]), coord(&[("Month", "Jan"), ("Recipient", "Alice"), ("Type", "Food")]),
CellValue::Number(77.0), CellValue::Number(77.0),
); );
let text = buf_text(&render(&m, 80, 24)); let text = buf_text(&render(&mut m, 80, 24));
assert!(text.contains("77"), "expected '77' in:\n{text}"); assert!(text.contains("77"), "expected '77' in:\n{text}");
} }
@ -1024,7 +1029,7 @@ mod tests {
); );
} }
let text = buf_text(&render(&m, 80, 24)); let text = buf_text(&render(&mut m, 80, 24));
// Multi-level column headers: category values shown separately, not joined with / // Multi-level column headers: category values shown separately, not joined with /
assert!( assert!(
!text.contains("Jan/2024"), !text.contains("Jan/2024"),