facett-graph3d/tests/control_row.rs
//! **The inline control row** — the "3D rotating" + "labels" checkboxes the pane
//! draws above the canvas when a host asks for them ([`Graph3D::with_controls`]).
//!
//! Why this test is shaped the way it is: the two toggles have existed as [`Msg`]
//! since the crate did, and three hosts in dwarves (the warehouse-nav world graph,
//! the funnel 3D DAG, the kernel scene) each spelled their OWN checkbox row to reach
//! them. So the thing under test is not "does `spin` change" — `Msg::SetSpin` was
//! never in doubt — it is **is there a checkbox on the pane, and does clicking it
//! land**. Both are asserted through the a11y tree and a real click, never through a
//! `update(Msg::…)` round-trip, which would pass just as well with no row drawn.
//!
//! The default is OFF, and the first test proves it: a host that does not ask for the
//! row renders exactly the canvas it always did.
use std::cell::RefCell;
use std::rc::Rc;
use egui::Color32;
use egui_kittest::Harness;
use egui_kittest::kittest::Queryable;
use facett_core::Facet;
use facett_graph3d::{Graph3D, Layout3D};
/// A small sphere graph with the animation clock PINNED (FC-7). The pin is not
/// cosmetic: with the ambient clock the live spin requests a repaint every frame, and
/// `Harness::run` — which runs until the ui settles — never settles. Pinning stops the
/// ambient advance and leaves `spin` itself (what these tests toggle) untouched.
fn sample(controls: bool) -> Graph3D {
let nodes: Vec<(String, Color32)> = (0..8).map(|i| (format!("n{i}"), Color32::from_rgb(0, 200, 220))).collect();
let edges: Vec<(usize, usize)> = (0..8).map(|i| (i, (i + 1) % 8)).collect();
Graph3D::new(nodes, edges, Layout3D::Sphere).with_controls(controls).with_clock(0.0)
}
/// Build a harness over a shared graph so the test can read the model back after
/// driving real clicks at it.
fn rig(g: Rc<RefCell<Graph3D>>) -> Harness<'static> {
Harness::builder().with_size(egui::vec2(520.0, 420.0)).build_ui(move |ui| {
g.borrow_mut().ui(ui);
})
}
/// OFF by default: no row, no checkboxes — the bare canvas every existing host and
/// every snapshot golden renders. This is the RED control for the test below: if the
/// row were drawn unconditionally, this fails.
#[test]
fn the_control_row_is_off_by_default() {
let g = Rc::new(RefCell::new(sample(false)));
assert!(!g.borrow().state().controls, "controls default to off");
let mut h = rig(Rc::clone(&g));
h.run();
assert!(h.query_by_label("3D rotating").is_none(), "no spin checkbox on a pane that did not ask for one");
assert!(h.query_by_label("labels").is_none(), "no labels checkbox either");
assert_eq!(Facet::state_json(&*g.borrow())["controls"], false, "and the state says so");
}
/// ON: both checkboxes are really IN the pane (a11y tree), and a real click on each
/// lands on the model through the FC-2 path.
#[test]
fn the_checkboxes_render_and_a_click_lands() {
let g = Rc::new(RefCell::new(sample(true)));
// The starting truth, so the flips below are proven to be flips and not the
// constructor's defaults read back.
assert!(g.borrow().state().spin, "the sample starts spinning");
let labels_before = g.borrow().state().show_labels;
let mut h = rig(Rc::clone(&g));
h.run();
assert!(h.query_by_label("3D rotating").is_some(), "the '3D rotating' checkbox renders in the control row");
assert!(h.query_by_label("labels").is_some(), "the 'labels' checkbox renders in the control row");
assert_eq!(Facet::state_json(&*g.borrow())["controls"], true, "state_json publishes the row");
// A real click on the spin checkbox → the auto-spin stops.
h.get_by_label("3D rotating").click();
h.run();
assert!(!g.borrow().state().spin, "clicking '3D rotating' turned the auto-spin OFF");
assert_eq!(Facet::state_json(&*g.borrow())["spin"], false, "and it is observable as data");
// Clicking it again turns it back on — the checkbox is a toggle, not a one-way
// door (the failure a `set_spin(false)` handler would still pass).
h.get_by_label("3D rotating").click();
h.run();
assert!(g.borrow().state().spin, "clicking '3D rotating' again turned the auto-spin back ON");
// A real click on the labels checkbox → the label pass flips.
h.get_by_label("labels").click();
h.run();
assert_eq!(
g.borrow().state().show_labels,
!labels_before,
"clicking 'labels' flipped the billboarded-label pass"
);
}
/// The row costs the canvas its own height and nothing else: the pane still draws,
/// and the yaw is still exposed as the canvas's a11y value (the drag-to-rotate
/// surface did not get pushed out of the layout by the row above it).
#[test]
fn the_row_does_not_cost_the_canvas() {
let mut with = sample(true);
let mut without = sample(false);
let a = facett_core::harness::render_sized(&mut with, (520.0, 420.0));
let b = facett_core::harness::render_sized(&mut without, (520.0, 420.0));
assert!(a.drew(), "the pane with a control row still tessellates the cloud");
assert!(b.drew(), "so does the bare pane");
// The row ADDS ink (two checkboxes + a count), so the two frames must differ —
// otherwise the row rendered into nothing and the a11y query above was reading a
// node with no paint behind it.
assert_ne!(a.ink_signature(), b.ink_signature(), "the control row is really painted, not just announced");
let g = Rc::new(RefCell::new(sample(true)));
let mut h = rig(Rc::clone(&g));
h.run();
let yaw = h
.get_all_by_role(egui::accesskit::Role::Image)
.filter_map(|n| {
use egui_kittest::kittest::NodeT;
n.accesskit_node().numeric_value()
})
.next()
.expect("the 3D canvas still exposes its yaw value beneath the control row");
assert!((yaw - 0.6).abs() < 1e-5, "at clock 0 the yaw is the base 0.6, got {yaw}");
}