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();
|
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");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user