refactor: make active_view and axis_of infallible
Both functions previously returned Option despite their invariants guaranteeing a value: active_view always names an existing view (maintained by new/switch_view/delete_view), and axis_of only returns None for categories never registered with the view (a programming error). Callers no longer need to handle the impossible None case, eliminating ~15 match/if-let Option guards across app.rs, dispatch.rs, grid.rs, tile_bar.rs, and category_panel.rs. Also adds Model::evaluate_f64 (returns 0.0 for empty cells) and collapses the double match-on-axis pattern in tile_bar/category_panel into a single axis_display(Axis) helper. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@ -94,12 +94,14 @@ impl Model {
|
||||
&self.formulas
|
||||
}
|
||||
|
||||
pub fn active_view(&self) -> Option<&View> {
|
||||
pub fn active_view(&self) -> &View {
|
||||
self.views.get(&self.active_view)
|
||||
.expect("active_view always names an existing view")
|
||||
}
|
||||
|
||||
pub fn active_view_mut(&mut self) -> Option<&mut View> {
|
||||
pub fn active_view_mut(&mut self) -> &mut View {
|
||||
self.views.get_mut(&self.active_view)
|
||||
.expect("active_view always names an existing view")
|
||||
}
|
||||
|
||||
pub fn create_view(&mut self, name: impl Into<String>) -> &mut View {
|
||||
@ -161,6 +163,11 @@ impl Model {
|
||||
self.data.get(key).cloned()
|
||||
}
|
||||
|
||||
/// Evaluate a key as a numeric value, returning 0.0 for empty/non-numeric cells.
|
||||
pub fn evaluate_f64(&self, key: &CellKey) -> f64 {
|
||||
self.evaluate(key).and_then(|v| v.as_f64()).unwrap_or(0.0)
|
||||
}
|
||||
|
||||
fn eval_formula(&self, formula: &Formula, context: &CellKey) -> Option<CellValue> {
|
||||
use crate::formula::{Expr, AggFunc};
|
||||
|
||||
@ -294,7 +301,8 @@ mod model_tests {
|
||||
#[test]
|
||||
fn new_model_has_default_view() {
|
||||
let m = Model::new("Test");
|
||||
assert!(m.active_view().is_some());
|
||||
// active_view() panics if missing; this test just ensures it doesn't panic
|
||||
let _ = m.active_view();
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -324,7 +332,8 @@ mod model_tests {
|
||||
fn add_category_notifies_existing_views() {
|
||||
let mut m = Model::new("Test");
|
||||
m.add_category("Region").unwrap();
|
||||
assert_ne!(m.active_view().unwrap().axis_of("Region"), None);
|
||||
// axis_of panics for unknown categories; not panicking here confirms it was registered
|
||||
let _ = m.active_view().axis_of("Region");
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -380,8 +389,9 @@ mod model_tests {
|
||||
m.add_category("Product").unwrap();
|
||||
m.create_view("Secondary");
|
||||
let v = m.views.get("Secondary").unwrap();
|
||||
assert_ne!(v.axis_of("Region"), None);
|
||||
assert_ne!(v.axis_of("Product"), None);
|
||||
// axis_of panics for unknown categories; not panicking confirms categories were registered
|
||||
let _ = v.axis_of("Region");
|
||||
let _ = v.axis_of("Product");
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -427,10 +437,10 @@ mod model_tests {
|
||||
m.add_category("Region").unwrap();
|
||||
m.add_category("Product").unwrap();
|
||||
m.add_category("Time").unwrap();
|
||||
let v = m.active_view().unwrap();
|
||||
assert_eq!(v.axis_of("Region"), Some(Axis::Row));
|
||||
assert_eq!(v.axis_of("Product"), Some(Axis::Column));
|
||||
assert_eq!(v.axis_of("Time"), Some(Axis::Page));
|
||||
let v = m.active_view();
|
||||
assert_eq!(v.axis_of("Region"), Axis::Row);
|
||||
assert_eq!(v.axis_of("Product"), Axis::Column);
|
||||
assert_eq!(v.axis_of("Time"), Axis::Page);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -969,18 +979,19 @@ mod five_category {
|
||||
#[test]
|
||||
fn default_view_first_two_on_axes_rest_on_page() {
|
||||
let m = build_model();
|
||||
let v = m.active_view().unwrap();
|
||||
assert_eq!(v.axis_of("Region"), Some(Axis::Row));
|
||||
assert_eq!(v.axis_of("Product"), Some(Axis::Column));
|
||||
assert_eq!(v.axis_of("Channel"), Some(Axis::Page));
|
||||
assert_eq!(v.axis_of("Time"), Some(Axis::Page));
|
||||
assert_eq!(v.axis_of("Measure"), Some(Axis::Page));
|
||||
let v = m.active_view();
|
||||
assert_eq!(v.axis_of("Region"), Axis::Row);
|
||||
assert_eq!(v.axis_of("Product"), Axis::Column);
|
||||
assert_eq!(v.axis_of("Channel"), Axis::Page);
|
||||
assert_eq!(v.axis_of("Time"), Axis::Page);
|
||||
assert_eq!(v.axis_of("Measure"), Axis::Page);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rearranging_axes_does_not_affect_data() {
|
||||
let mut m = build_model();
|
||||
if let Some(v) = m.active_view_mut() {
|
||||
{
|
||||
let v = m.active_view_mut();
|
||||
v.set_axis("Region", Axis::Page);
|
||||
v.set_axis("Product", Axis::Page);
|
||||
v.set_axis("Channel", Axis::Row);
|
||||
@ -994,16 +1005,17 @@ mod five_category {
|
||||
fn two_views_have_independent_axis_assignments() {
|
||||
let mut m = build_model();
|
||||
m.create_view("Pivot");
|
||||
if let Some(v) = m.views.get_mut("Pivot") {
|
||||
{
|
||||
let v = m.views.get_mut("Pivot").unwrap();
|
||||
v.set_axis("Time", Axis::Row);
|
||||
v.set_axis("Channel", Axis::Column);
|
||||
v.set_axis("Region", Axis::Page);
|
||||
v.set_axis("Product", Axis::Page);
|
||||
v.set_axis("Measure", Axis::Page);
|
||||
}
|
||||
assert_eq!(m.views.get("Default").unwrap().axis_of("Region"), Some(Axis::Row));
|
||||
assert_eq!(m.views.get("Pivot").unwrap().axis_of("Time"), Some(Axis::Row));
|
||||
assert_eq!(m.views.get("Pivot").unwrap().axis_of("Channel"), Some(Axis::Column));
|
||||
assert_eq!(m.views.get("Default").unwrap().axis_of("Region"), Axis::Row);
|
||||
assert_eq!(m.views.get("Pivot").unwrap().axis_of("Time"), Axis::Row);
|
||||
assert_eq!(m.views.get("Pivot").unwrap().axis_of("Channel"), Axis::Column);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user