use fulgur_chart::frontend::chartjs;
use fulgur_chart::render::render_chart;
fn render(json: &str) -> String {
render_chart(&chartjs::parse(json, false).unwrap())
}
#[test]
fn pie_has_one_path_per_slice() {
let svg = render(
r#"{"type":"pie","data":{"labels":["A","B","C"],"datasets":[{"data":[10,20,30]}]}}"#,
);
assert!(svg.matches("<path").count() >= 3);
assert!(svg.contains(" A ")); assert!(!svg.contains("NaN") && !svg.contains("inf"));
assert!(svg.starts_with("<svg") && svg.trim_end().ends_with("</svg>"));
}
#[test]
fn pie_uses_per_slice_colors() {
let svg = render(
r##"{"type":"pie","data":{"labels":["A","B"],"datasets":[{"data":[1,1],"backgroundColor":["#ff0000","#0000ff"]}]}}"##,
);
assert!(svg.contains("#ff0000") && svg.contains("#0000ff"));
}
#[test]
fn doughnut_has_inner_arc() {
let svg =
render(r#"{"type":"doughnut","data":{"labels":["A","B"],"datasets":[{"data":[1,1]}]}}"#);
assert!(svg.matches(" A ").count() >= 4); }
#[test]
fn single_value_full_circle_does_not_panic() {
let svg = render(r#"{"type":"pie","data":{"labels":["only"],"datasets":[{"data":[5]}]}}"#);
assert!(svg.matches("<path").count() >= 2); assert!(!svg.contains("NaN"));
}
#[test]
fn zero_total_does_not_panic() {
let svg = render(r#"{"type":"pie","data":{"labels":["A","B"],"datasets":[{"data":[0,0]}]}}"#);
assert!(svg.starts_with("<svg")); assert!(!svg.contains("NaN"));
}
#[test]
fn pie_legend_shows_categories() {
let svg = render(
r#"{"type":"pie","data":{"labels":["Apple","Banana"],"datasets":[{"data":[1,2]}]}}"#,
);
assert!(svg.contains(">Apple</text>") && svg.contains(">Banana</text>"));
}
#[test]
fn pie_deterministic() {
let j = r#"{"type":"pie","data":{"labels":["A","B"],"datasets":[{"data":[1,2]}]}}"#;
assert_eq!(render(j), render(j));
}
fn nth_path_d(svg: &str, n: usize) -> String {
let mut rest = svg;
for _ in 0..n {
let p = rest.find("<path").expect("enough <path>");
rest = &rest[p + 5..];
}
let p = rest.find("<path").expect("a <path>");
let dstart = rest[p..].find("d=\"").expect("d attr") + p + 3;
let dend = rest[dstart..].find('"').expect("close quote") + dstart;
rest[dstart..dend].to_string()
}
#[test]
fn pie_starts_at_top_and_advances_clockwise() {
let svg = render(r#"{"type":"pie","data":{"labels":["A","B"],"datasets":[{"data":[3,1]}]}}"#);
let d0 = nth_path_d(&svg, 0);
let t: Vec<&str> = d0.split_whitespace().collect();
assert_eq!(t[0], "M", "d0={d0}");
let cx: f64 = t[1].parse().unwrap();
let cy: f64 = t[2].parse().unwrap();
assert_eq!(t[3], "L");
let l0x: f64 = t[4].parse().unwrap();
let l0y: f64 = t[5].parse().unwrap();
assert!(
(l0x - cx).abs() < 0.05,
"start should be directly above center (x): l0x={l0x} cx={cx}"
);
assert!(
l0y < cy,
"start should be above center (top): l0y={l0y} cy={cy}"
);
assert_eq!(t[6], "A");
assert_eq!(t[10], "1", "large-arc-flag must be 1 for the 270° slice");
assert_eq!(t[11], "1", "sweep flag must be 1 (clockwise)");
let o1 = (t[12].to_string(), t[13].to_string());
let d1 = nth_path_d(&svg, 1);
let t1: Vec<&str> = d1.split_whitespace().collect();
assert_eq!(
(t1[4], t1[5]),
(o1.0.as_str(), o1.1.as_str()),
"slice 2 must start where slice 1 ended (clockwise progression)"
);
}
#[test]
fn pie_snapshot() {
let svg = render(
r#"{"type":"doughnut","data":{"labels":["A","B","C"],"datasets":[{"data":[30,50,20]}]},"options":{"plugins":{"title":{"display":true,"text":"内訳"}}}}"#,
);
insta::assert_snapshot!(svg);
}