chore: improve test coverage of mod.rs

This commit is contained in:
Edward Langley
2026-04-08 23:28:07 -07:00
parent c8607b78ba
commit a83a4f604f

View File

@ -803,4 +803,265 @@ mod tests {
let m2: Model = serde_json::from_str(&json).unwrap(); let m2: Model = serde_json::from_str(&json).unwrap();
assert_eq!(m2.name, "Budget"); assert_eq!(m2.name, "Budget");
} }
// ── save/load roundtrip via file ────────────────────────────────────
#[test]
fn save_and_load_roundtrip_plain() {
let mut m = two_cat_model();
m.set_cell(
coord(&[("Type", "Food"), ("Month", "Jan")]),
CellValue::Number(42.0),
);
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("test.improv");
super::save(&m, &path).unwrap();
let loaded = super::load(&path).unwrap();
assert_eq!(loaded.name, "Budget");
assert_eq!(
loaded.get_cell(&coord(&[("Type", "Food"), ("Month", "Jan")])),
Some(&CellValue::Number(42.0))
);
}
#[test]
fn save_and_load_roundtrip_gzip() {
let mut m = two_cat_model();
m.set_cell(
coord(&[("Type", "Gas"), ("Month", "Feb")]),
CellValue::Number(99.0),
);
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("test.improv.gz");
super::save(&m, &path).unwrap();
let loaded = super::load(&path).unwrap();
assert_eq!(loaded.name, "Budget");
assert_eq!(
loaded.get_cell(&coord(&[("Type", "Gas"), ("Month", "Feb")])),
Some(&CellValue::Number(99.0))
);
}
// ── autosave_path ───────────────────────────────────────────────────
#[test]
fn autosave_path_inserts_dot_prefix() {
let p = std::path::Path::new("/home/user/data/budget.improv");
let auto = super::autosave_path(p);
assert_eq!(auto.file_name().unwrap().to_str().unwrap(), ".budget.improv.autosave");
}
// ── format_md: collapsed groups ─────────────────────────────────────
#[test]
fn format_md_collapsed_group() {
let mut m = two_cat_model();
m.active_view_mut()
.toggle_group_collapse("Type", "MyGroup");
let text = format_md(&m);
assert!(text.contains("collapsed: Type/MyGroup"));
}
// ── format_md: page axis without selection ──────────────────────────
#[test]
fn format_md_page_without_selection() {
let mut m = two_cat_model();
m.active_view_mut().set_axis("Month", Axis::Page);
// Don't set a page selection
let text = format_md(&m);
assert!(text.contains("Month: page"));
// Should NOT have a comma after "page" (no selection)
let line = text.lines().find(|l| l.starts_with("Month:")).unwrap();
assert!(!line.contains(','), "Expected no selection, got: {line}");
}
// ── format_md: none axis ────────────────────────────────────────────
#[test]
fn format_md_none_axis() {
let mut m = two_cat_model();
m.active_view_mut().set_axis("Month", Axis::None);
let text = format_md(&m);
assert!(text.contains("Month: none"));
}
// ── format_md: number format ────────────────────────────────────────
#[test]
fn format_md_includes_number_format() {
let mut m = two_cat_model();
m.active_view_mut().number_format = ",.2f".to_string();
let text = format_md(&m);
assert!(text.contains("format: ,.2f"));
}
// ── parse_md: comments and blank lines ──────────────────────────────
#[test]
fn parse_md_ignores_blank_and_comment_lines() {
let text = r#"# Test Model
## Category: Type
- Food
- Gas
## Data
Type=Food = 42
"#;
let m = parse_md(text).unwrap();
assert_eq!(m.name, "Test Model");
assert!(m.category("Type").is_some());
}
// ── parse_md: text values ───────────────────────────────────────────
#[test]
fn parse_md_round_trips_text_cell_values() {
let mut m = two_cat_model();
m.set_cell(
coord(&[("Type", "Food"), ("Month", "Jan")]),
CellValue::Text("pending".to_string()),
);
let text = format_md(&m);
let loaded = parse_md(&text).unwrap();
assert_eq!(
loaded.get_cell(&coord(&[("Type", "Food"), ("Month", "Jan")])),
Some(&CellValue::Text("pending".to_string()))
);
}
// ── parse_md: collapsed groups roundtrip ────────────────────────────
#[test]
fn parse_md_round_trips_collapsed_group() {
let mut m = two_cat_model();
m.active_view_mut()
.toggle_group_collapse("Type", "MyGroup");
let text = format_md(&m);
let loaded = parse_md(&text).unwrap();
assert!(loaded.active_view().is_group_collapsed("Type", "MyGroup"));
}
// ── parse_md: number format roundtrip ───────────────────────────────
#[test]
fn parse_md_round_trips_number_format() {
let mut m = two_cat_model();
m.active_view_mut().number_format = ",.2f".to_string();
let text = format_md(&m);
let loaded = parse_md(&text).unwrap();
assert_eq!(loaded.active_view().number_format, ",.2f");
}
// ── parse_md: none axis roundtrip ───────────────────────────────────
#[test]
fn parse_md_round_trips_none_axis() {
let mut m = two_cat_model();
m.active_view_mut().set_axis("Month", Axis::None);
let text = format_md(&m);
let loaded = parse_md(&text).unwrap();
assert_eq!(loaded.active_view().axis_of("Month"), Axis::None);
}
// ── parse_md: multiple views ────────────────────────────────────────
#[test]
fn parse_md_round_trips_multiple_views() {
let mut m = two_cat_model();
m.create_view("Alternate");
{
let v = m.views.get_mut("Alternate").unwrap();
v.set_axis("Type", Axis::Column);
v.set_axis("Month", Axis::Row);
}
let text = format_md(&m);
let loaded = parse_md(&text).unwrap();
assert!(loaded.views.contains_key("Default"));
assert!(loaded.views.contains_key("Alternate"));
let alt = loaded.views.get("Alternate").unwrap();
assert_eq!(alt.axis_of("Type"), Axis::Column);
assert_eq!(alt.axis_of("Month"), Axis::Row);
}
// ── export_csv ──────────────────────────────────────────────────────
#[test]
fn export_csv_produces_valid_output() {
let mut m = two_cat_model();
m.set_cell(
coord(&[("Type", "Food"), ("Month", "Jan")]),
CellValue::Number(100.0),
);
m.set_cell(
coord(&[("Type", "Gas"), ("Month", "Feb")]),
CellValue::Number(50.0),
);
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("export.csv");
super::export_csv(&m, "Default", &path).unwrap();
let content = std::fs::read_to_string(&path).unwrap();
// Should have a header and data rows
let lines: Vec<&str> = content.lines().collect();
assert!(lines.len() >= 2, "Expected header + data, got: {content}");
// Header should contain column labels
assert!(lines[0].contains(','), "Expected CSV header, got: {}", lines[0]);
}
#[test]
fn export_csv_unknown_view_returns_error() {
let m = two_cat_model();
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("export.csv");
assert!(super::export_csv(&m, "Nonexistent", &path).is_err());
}
// ── Full save/load/format roundtrip with all features ───────────────
#[test]
fn full_roundtrip_preserves_all_features() {
let mut m = two_cat_model();
// Add data
m.set_cell(
coord(&[("Type", "Food"), ("Month", "Jan")]),
CellValue::Number(100.0),
);
m.set_cell(
coord(&[("Type", "Gas"), ("Month", "Feb")]),
CellValue::Text("pending".to_string()),
);
// Add formula
let f = parse_formula("Gas = Food * 2", "Type").unwrap();
m.add_formula(f);
// Configure view
m.active_view_mut().set_axis("Month", Axis::Page);
m.active_view_mut()
.set_page_selection("Month", "Jan");
m.active_view_mut().hide_item("Type", "Gas");
m.active_view_mut()
.toggle_group_collapse("Type", "G1");
m.active_view_mut().number_format = ",.0".to_string();
let text = format_md(&m);
let loaded = parse_md(&text).unwrap();
// Verify everything roundtripped
assert_eq!(loaded.name, "Budget");
assert_eq!(
loaded.get_cell(&coord(&[("Type", "Food"), ("Month", "Jan")])),
Some(&CellValue::Number(100.0))
);
assert_eq!(
loaded.get_cell(&coord(&[("Type", "Gas"), ("Month", "Feb")])),
Some(&CellValue::Text("pending".to_string()))
);
assert!(!loaded.formulas().is_empty());
let v = loaded.active_view();
assert_eq!(v.axis_of("Month"), Axis::Page);
assert_eq!(v.page_selection("Month"), Some("Jan"));
assert!(v.is_hidden("Type", "Gas"));
assert!(v.is_group_collapsed("Type", "G1"));
assert_eq!(v.number_format, ",.0");
}
} }