facett-cfd/tests/robot.rs
//! **Robot render proof for facett-cfd.** The `Cfd::ui()` render path — the
//! stacked-quad area fill (the `tops` cumulative geometry), the axes, the
//! date labels and the color legend — is never touched by the unit tests, and
//! the `<2 snapshots` placeholder branch has its own early return. This drives
//! both paths through a real `egui_kittest` frame so a geometry panic (e.g. the
//! `n - 1` divisor when a stray single-snapshot slips through) is caught, and
//! asserts the heading / placeholder text reach the AccessKit tree.
use std::collections::BTreeMap;
use chrono::NaiveDate;
use egui_kittest::Harness;
use egui_kittest::kittest::Queryable;
use facett_cfd::{Band, Cfd, Snapshot};
fn d(y: i32, m: u32, day: u32) -> NaiveDate {
NaiveDate::from_ymd_opt(y, m, day).unwrap()
}
fn snap(date: NaiveDate, todo: usize, doing: usize, done: usize) -> Snapshot {
let mut counts = BTreeMap::new();
counts.insert("todo".to_string(), todo);
counts.insert("doing".to_string(), doing);
counts.insert("done".to_string(), done);
Snapshot { date, counts }
}
fn cfd() -> Cfd {
let mut c = Cfd::new(
"Flow",
vec![Band::new("todo", "To do"), Band::new("doing", "Doing"), Band::new("done", "Done")],
);
c.set_series(vec![
snap(d(2026, 6, 1), 8, 1, 0),
snap(d(2026, 6, 3), 5, 2, 2),
snap(d(2026, 6, 5), 3, 3, 4),
]);
c
}
#[test]
fn cfd_renders_the_stacked_area_and_is_addressable() {
let mut c = cfd();
let mut harness = Harness::new_ui(move |ui| {
c.ui(ui);
});
// The full render: cumulative band quads, axes, date labels, the legend.
harness.run();
// The heading + at least one legend label reached the AccessKit tree.
let _heading = harness.get_by_label("Flow");
let _legend = harness.get_by_label("Doing");
}
#[test]
fn cfd_placeholder_path_renders_below_two_snapshots() {
// Zero snapshots → the "need ≥2 daily snapshots" placeholder, not the area.
let mut empty = Cfd::new("Flow", vec![Band::new("todo", "To do")]);
let mut harness = Harness::new_ui(move |ui| {
empty.ui(ui);
});
harness.run();
let _placeholder = harness.get_by_label("Not enough data yet — need ≥2 daily snapshots.");
// Exactly one snapshot → still the placeholder (never reaches the `n - 1`
// divisor), proving the guard holds at the boundary.
let mut one = Cfd::new("Flow", vec![Band::new("todo", "To do")]);
let mut counts = BTreeMap::new();
counts.insert("todo".to_string(), 3usize);
one.set_series(vec![Snapshot { date: d(2026, 6, 1), counts }]);
let mut h2 = Harness::new_ui(move |ui| {
one.ui(ui);
});
h2.run();
let _p2 = h2.get_by_label("Not enough data yet — need ≥2 daily snapshots.");
}
#[test]
fn cfd_state_survives_a_render() {
let c = cfd();
let before = c.state_json();
let mut c2 = cfd();
let mut harness = Harness::new_ui(move |ui| {
c2.ui(ui);
});
harness.run();
assert_eq!(before["snapshots"], 3);
// totals per snapshot: 9, 9, 10 → max 10.
assert_eq!(before["max_total"], 10);
assert_eq!(cfd().state_json(), before);
}