facett-graph3d/tests/a11y_determinism.rs
//! **FC-4 + FC-7** interaction-layer proofs for `Graph3D`.
//!
//! * FC-4: the drag-to-rotate canvas is an AccessKit Image node carrying the
//! current yaw (radians) as its numeric value — readable, not just paintable.
//! * FC-7: with an **injected clock**, the spin yaw is a pure function of the
//! clock; two renders of the same state at the same clock produce an identical
//! AccessKit tree (same yaw value). Stepping the clock changes it deterministically.
use egui::Color32;
use egui_kittest::Harness;
use egui_kittest::kittest::{NodeT, Queryable};
use facett_core::Facet;
use facett_graph3d::{Graph3D, Layout3D};
fn sample() -> Graph3D {
let nodes: Vec<(String, Color32)> = (0..6).map(|i| (format!("n{i}"), Color32::from_rgb(0, 200, 220))).collect();
let edges: Vec<(usize, usize)> = (0..6).map(|i| (i, (i + 1) % 6)).collect();
Graph3D::new(nodes, edges, Layout3D::Sphere)
}
/// The numeric yaw carried by the canvas's AccessKit Image node.
fn yaw_value(harness: &Harness) -> f64 {
harness
.get_all_by_role(egui::accesskit::Role::Image)
.filter_map(|n| n.accesskit_node().numeric_value())
.next()
.expect("the 3D canvas exposes an Image node carrying the yaw value")
}
#[test]
fn canvas_exposes_yaw_as_an_accesskit_value() {
let mut g = sample().with_clock(0.0); // clock 0 ⇒ spin offset 0 ⇒ yaw == base (0.6)
let mut harness = Harness::builder().with_size(egui::vec2(400.0, 400.0)).build_ui(move |ui| g.ui(ui));
harness.run();
let yaw = yaw_value(&harness);
assert!((yaw - 0.6).abs() < 1e-5, "at clock 0 the yaw is the base 0.6, got {yaw}");
// Feed the matrix: ok iff the canvas exposes the yaw value as an a11y node.
#[cfg(feature = "testmatrix")]
facett_core::testmatrix::emit(
"Graph3D",
"fc4_yaw_accesskit_value",
(yaw - 0.6).abs() < 1e-5,
&format!("canvas Image node yaw={yaw} at clock 0"),
);
}
#[test]
fn injected_clock_yaw_is_deterministic_and_steps() {
// Same state, same injected clock → identical yaw (FC-7).
let mut a = sample().with_clock(2.0);
let mut b = sample().with_clock(2.0);
let mut ha = Harness::builder().with_size(egui::vec2(400.0, 400.0)).build_ui(move |ui| a.ui(ui));
let mut hb = Harness::builder().with_size(egui::vec2(400.0, 400.0)).build_ui(move |ui| b.ui(ui));
ha.run();
hb.run();
let ya = yaw_value(&ha);
let yb = yaw_value(&hb);
assert_eq!(ya.to_bits(), yb.to_bits(), "same state + clock ⇒ identical yaw (FC-7), {ya} vs {yb}");
// The spin yaw advances with the clock: base 0.6 + clock·spin_rate(0.4) = 1.4.
assert!((ya - (0.6 + 2.0 * 0.4)).abs() < 1e-5, "yaw = base + clock·rate, got {ya}");
// A later clock gives a strictly larger yaw — deterministic stepping.
let mut c = sample().with_clock(5.0);
let mut hc = Harness::builder().with_size(egui::vec2(400.0, 400.0)).build_ui(move |ui| c.ui(ui));
hc.run();
let yc = yaw_value(&hc);
assert!(yc > ya, "advancing the injected clock advances the yaw");
// Feed the matrix: ok iff yaw is deterministic at a fixed clock AND steps.
#[cfg(feature = "testmatrix")]
facett_core::testmatrix::emit(
"Graph3D",
"fc7_clock_determinism",
ya.to_bits() == yb.to_bits() && yc > ya,
&format!("clock2 yaw={ya} (==dup) clock5 yaw={yc} (>)"),
);
}