diff --git a/crates/improvise-incremental/tests/oracle.rs b/crates/improvise-incremental/tests/oracle.rs index e91ce427..380ec6d6 100644 --- a/crates/improvise-incremental/tests/oracle.rs +++ b/crates/improvise-incremental/tests/oracle.rs @@ -800,3 +800,145 @@ fn the_harness_checks_a_cell_a_view_draws_that_drops_four_coordinates() { assert_eq!(d.step, 0); assert_eq!(d.probe, Probe::Value(wide_cell(), Aggregation::Exact)); } + +/// East is hidden and Coastal is collapsed (a group is, by default), so the Coastal row folds West only; +/// Inland has a cell under A only, so B's column holds just Coastal's. +fn hidden_workbook() -> improvise_core::workbook::Workbook { + let mut wb = improvise_core::workbook::Workbook::new("hidden"); + for c in ["Region", "Product"] { + wb.add_category(c).unwrap(); + } + wb.add_item_in_group("Region", "East", "Coastal").unwrap(); + wb.add_item_in_group("Region", "West", "Coastal").unwrap(); + wb.add_item("Region", "Inland").unwrap(); + wb.add_item("Product", "A").unwrap(); + wb.add_item("Product", "B").unwrap(); + wb.add_item("_Measure", "Revenue").unwrap(); + let cells = [ + ("East", "A", 10.0), + ("West", "A", 5.0), + ("East", "B", 20.0), + ("West", "B", 1.0), + ("Inland", "A", 2.0), + ]; + for (r, p, v) in cells { + wb.model.set_cell(key(r, p, "Revenue"), CellValue::Number(v)); + } + let view = wb.active_view_mut(); + view.set_axis("Region", Axis::Row); + view.set_axis("Product", Axis::Column); + view.set_axis("_Measure", Axis::None); + view.hide_item("Region", "East"); + wb +} + +/// A backend that reads the view wrongly in exactly one way. +#[derive(Clone, Copy)] +enum Blindness { + CoversIgnoreHidden, + TotalsIgnoreHidden, + TotalsDoNotSpan, + OccupancyCountsHidden, +} + +struct Blind(Eager, Blindness); + +impl Derived for Blind { + fn value(&self, k: &CellKey, agg: Aggregation) -> Option { + self.0.value(k, agg) + } + fn raw_fold(&self, p: &CellKey) -> Option { + self.0.raw_fold(p) + } + fn covered(&self, k: &CellKey, r: &Reading) -> Option { + match self.1 { + Blindness::CoversIgnoreHidden => self.0.covered(k, &Reading::new(r.agg, [])), + _ => self.0.covered(k, r), + } + } + fn total(&self, k: &CellKey, over: &[String], r: &Reading) -> Option { + match self.1 { + Blindness::TotalsIgnoreHidden => self.0.total(k, over, &Reading::new(r.agg, [])), + Blindness::TotalsDoNotSpan => self.0.covered(k, r), + _ => self.0.total(k, over, r), + } + } + fn occupancy(&self, p: &CellKey, r: &Reading) -> usize { + match self.1 { + Blindness::OccupancyCountsHidden => self.0.occupancy(p, &Reading::new(r.agg, [])), + _ => self.0.occupancy(p, r), + } + } +} + +impl Maintained for Blind { + fn apply(&mut self, edit: &Edit) { + self.0.apply(edit); + } +} + +fn run_blind(fault: Option) -> Result { + let wb = hidden_workbook(); + let none = vec!["_Measure".to_string()]; + let eager = Eager::new(wb.model.clone(), none.clone()); + let views: Vec<_> = wb.views.values().cloned().collect(); + let edit = Edit::SetCell(key("West", "A", "Revenue"), CellValue::Number(6.0)); + match fault { + Some(f) => differential_in(&wb.model, &views, &none, Blind(eager, f), &[edit]), + None => differential_in(&wb.model, &views, &none, eager, &[edit]), + } +} + +#[test] +fn the_harness_passes_a_view_reader_that_is_right() { + run_blind(None).unwrap(); +} + +#[test] +fn the_harness_catches_a_fold_that_covers_a_hidden_item() { + let d = run_blind(Some(Blindness::CoversIgnoreHidden)).unwrap_err(); + assert!(matches!(d.probe, Probe::Covered(..)), "{d}"); + assert_eq!((d.eager, d.candidate), (num(5.0), num(15.0)), "Coastal/A"); +} + +#[test] +fn the_harness_catches_a_total_that_counts_a_hidden_item() { + let d = run_blind(Some(Blindness::TotalsIgnoreHidden)).unwrap_err(); + assert!(matches!(d.probe, Probe::Total(..)), "{d}"); +} + +#[test] +fn the_harness_catches_a_total_that_does_not_span_its_rows() { + let d = run_blind(Some(Blindness::TotalsDoNotSpan)).unwrap_err(); + assert!(matches!(d.probe, Probe::Total(..)), "{d}"); +} + +#[test] +fn the_harness_catches_an_occupancy_that_counts_a_hidden_item() { + let d = run_blind(Some(Blindness::OccupancyCountsHidden)).unwrap_err(); + assert!(matches!(d.probe, Probe::Occupancy(..)), "{d}"); +} + +#[test] +fn a_view_is_drawn_as_cells_totals_and_occupancy() { + let wb = hidden_workbook(); + let views: Vec<_> = wb.views.values().cloned().collect(); + let drawn = drawn_probes(&wb.model, &views); + let kinds = [ + drawn.iter().any(|p| matches!(p, Probe::Covered(..))), + drawn.iter().any(|p| matches!(p, Probe::Total(..))), + drawn.iter().any(|p| matches!(p, Probe::Occupancy(..))), + ]; + assert_eq!(kinds, [true; 3]); +} + +#[test] +fn probing_a_key_of_twenty_two_coordinates_is_not_exponential() { + let coords = (0..22).map(|i| (format!("C{i:02}"), "a".to_string())).collect(); + let mut m = Model::new("deep"); + m.set_cell(CellKey::new(coords), CellValue::Number(1.0)); + let started = std::time::Instant::now(); + let n = probes(&m).len(); + assert!(n > 0); + assert!(started.elapsed().as_millis() < 100, "{:?}", started.elapsed()); +}