use charton::core::layer::Layer;
use charton::prelude::*;
use std::collections::HashSet;
use std::error::Error;
#[test]
fn test_facet_wrap_cyl() -> Result<(), Box<dyn Error>> {
let ds = load_dataset("mtcars")?;
assert_eq!(ds.height(), 32, "mtcars should contain 32 rows");
let chart = Chart::build(ds.clone())?
.mark_point()?
.encode((alt::x("wt"), alt::y("mpg")))?;
let lc = chart.facet("cyl");
let svg = lc.to_svg()?;
assert!(
!svg.is_empty(),
"A faceted chart should export a non-empty SVG"
);
Ok(())
}
#[test]
fn test_facet_grid_cyl_gear() -> Result<(), Box<dyn Error>> {
let ds = load_dataset("mtcars")?;
let chart = Chart::build(ds.clone())?
.mark_point()?
.encode((alt::x("wt"), alt::y("mpg")))?;
let lc = chart.facet(("cyl", "gear"));
let svg = lc.to_svg()?;
assert!(
!svg.is_empty(),
"A grid-faceted chart should export a non-empty SVG"
);
Ok(())
}
#[test]
fn test_facet_svg_uses_unique_clip_paths() -> Result<(), Box<dyn Error>> {
let ds = load_dataset("mtcars")?;
let svg = Chart::build(ds)?
.mark_point()?
.encode((alt::x("wt"), alt::y("mpg")))?
.facet(FacetSpec::grid("vs", "am"))
.to_svg()?;
let clip_ids: HashSet<&str> = svg
.split("clipPath id=\"")
.skip(1)
.filter_map(|part| part.split('"').next())
.collect();
let clip_refs = svg.matches("clip-path=\"url(#").count();
assert_eq!(clip_ids.len(), 4, "each grid panel needs its own clipPath");
assert_eq!(clip_refs, 4, "each grid panel needs its own clip reference");
Ok(())
}
#[test]
fn test_multi_panel_shows_grid_by_default_but_can_be_disabled() -> Result<(), Box<dyn Error>> {
let ds = load_dataset("mtcars")?;
let chart = Chart::build(ds.clone())?
.mark_point()?
.encode((alt::x("wt"), alt::y("mpg")))?
.facet(FacetSpec::grid("vs", "am"));
let default_svg = chart.to_svg()?;
let disabled_svg = Chart::build(ds)?
.mark_point()?
.encode((alt::x("wt"), alt::y("mpg")))?
.facet(FacetSpec::grid("vs", "am"))
.with_grid(false)
.to_svg()?;
assert!(
default_svg.matches("stroke-opacity=\"0.500\"").count() > 0,
"multi-panel charts should show grid lines by default"
);
assert_eq!(
disabled_svg.matches("stroke-opacity=\"0.500\"").count(),
0,
"explicit with_grid(false) should disable grid lines"
);
Ok(())
}
#[test]
fn test_facet_wrap_with_columns_and_strategy() -> Result<(), Box<dyn Error>> {
let ds = load_dataset("mtcars")?;
let chart = Chart::build(ds.clone())?
.mark_point()?
.encode((alt::x("wt"), alt::y("mpg")))?;
let lc = chart.facet(FacetSpec::wrap("cyl").with_columns(2).with_strategy("free"));
let svg = lc.to_svg()?;
assert!(
!svg.is_empty(),
"Faceting with columns and strategy should export SVG"
);
Ok(())
}
#[test]
fn test_facet_missing_field_errors() -> Result<(), Box<dyn Error>> {
let ds = load_dataset("mtcars")?;
let chart = Chart::build(ds.clone())?
.mark_point()?
.encode((alt::x("wt"), alt::y("mpg")))?;
let lc = chart.facet("not_a_column");
let result = lc.to_svg();
assert!(
result.is_err(),
"Rendering should error when the facet field is missing"
);
Ok(())
}
#[test]
fn test_facet_filter_subset_correctness() -> Result<(), Box<dyn Error>> {
let ds = load_dataset("mtcars")?;
let chart = Chart::build(ds.clone())?
.mark_point()?
.encode((alt::x("wt"), alt::y("mpg")))?;
let filter = vec![("cyl".to_string(), "4".to_string())];
let layer: &dyn Layer = &chart;
let filtered = layer
.with_facet_filter(&filter)?
.expect("A non-empty filter should return Some");
let filtered_ds = filtered.get_dataset();
assert_eq!(
filtered_ds.height(),
11,
"The cyl=4 subset should have 11 rows, got {}",
filtered_ds.height()
);
for row in 0..filtered_ds.height() {
let cyl = filtered_ds.get("cyl", row).to_string();
assert_eq!(
cyl.as_deref(),
Some("4"),
"Row {} should have cyl == 4, got {:?}",
row,
cyl
);
}
Ok(())
}