chore: improve test coverage of mod.rs
This commit is contained in:
@ -803,4 +803,265 @@ mod tests {
|
||||
let m2: Model = serde_json::from_str(&json).unwrap();
|
||||
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");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user