facett-demo/tests/deck_no_error_atoms.rs
//! UNIVERSAL ERROR-ATOM NET + "FIND THEM ALL" GATE for the facett Facet deck — the
//! facett twin of nornir's atom-walk (`nornir tests/viz_atom_walk.rs`). facett's
//! *correctness* oracle for a component is its `state_json` (the
//! multidimensional/sample-space data), but a **rendered error label must never
//! slip** into any Facet. We render the whole deck (every Facet component, real
//! data) through a real `egui_kittest::Harness` and walk the live AccessKit tree,
//! asserting NO error-text atom is present. egui_kittest 0.34 `Queryable::query_all`
//! + a built-in `By::label_contains` filter (no custom node accessor needed).
//!
//! This is the symmetry sealing "find them all": nornir's hand-rolled egui panes get
//! the AccessKit error scan in `viz_atom_walk`; facett's discoverable Facets get the
//! same net here. (facett also has dedicated a11y support — `facett_core::a11y::node`
//! — so its atoms are queryable by design.)
//!
//! ## The two layers
//! 1. [`facett_deck_renders_no_error_atoms`] — the whole-deck walk: render EVERY
//! Facet at once, scan the live AccessKit tree, assert zero error atoms.
//! 2. [`every_registered_facet_is_discoverable_renders_sane_data_and_no_error_atom`]
//! — the **completeness gate** ("find them all"): enumerate EVERY registered Facet
//! from the deck (`titles()`), cross-check it against the demo's declared `LEGEND`
//! (no silent gaps either way), assert each facet's `state_json` is a non-empty
//! object (real data, not an empty shell), and render EACH facet **in isolation**
//! through a real harness, scanning its own AccessKit subtree for error atoms.
//! Add a Facet to the deck but forget the LEGEND (or vice-versa) → this goes RED.
use egui_kittest::kittest::{By, Queryable};
use egui_kittest::Harness;
use facett_demo::{DemoState, LEGEND};
use std::collections::BTreeSet;
/// The same markers nornir's `nornir_robotui::ERROR_MARKERS` scans for — any of
/// these in a rendered label means a component surfaced an error, not data.
const ERROR_MARKERS: &[&str] = &[
"Failed to load",
"is not served",
"unavailable",
"RPC: status:",
"panicked",
];
/// Walk a freshly-run harness' AccessKit tree for every error marker; return the
/// `"marker ×N"` hits (empty ⇒ clean).
fn scan_error_atoms(harness: &mut Harness) -> Vec<String> {
let mut found = Vec::new();
for m in ERROR_MARKERS {
let n = harness.query_all(By::new().label_contains(m)).count();
if n > 0 {
found.push(format!("{m} ×{n}"));
}
}
found
}
#[test]
fn facett_deck_renders_no_error_atoms() {
// A freshly populated demo — every Facet component with real data.
let mut demo = DemoState::new();
let mut harness = Harness::new_ui(move |ui| {
demo.deck.ui(ui);
});
// Settle the virtualized grid / 3D spin / glow over a few frames.
harness.run_steps(4);
let found = scan_error_atoms(&mut harness);
assert!(
found.is_empty(),
"facett deck rendered error atoms (a component surfaced an error, not data): {found:?}"
);
}
/// THE "FIND THEM ALL" COMPLETENESS GATE — facett-demo's library-wide enforcer
/// (the deck twin of nornir's `viz_atom_walk` discovery gate). It proves the demo's
/// UI coverage is *provably complete*:
///
/// 1. **Discover** every registered Facet from the deck itself (`titles()`) — the
/// enumerator is the deck, not a hand-maintained list.
/// 2. **No silent gaps**: the discovered set MUST equal the demo's declared Facet
/// `LEGEND` keys (`LEGEND` minus the non-Facet `menu`). Add a Facet to the deck
/// but forget the LEGEND (or a LEGEND key with no Facet) → RED.
/// 3. **Sane data (inject-assert, LAW 6)**: each facet's `state_json` (folded into
/// the deck's `state_json["facets"]`) MUST be a NON-EMPTY object — real injected
/// data, never an empty shell.
/// 4. **No error atom, per facet (in isolation)**: render EACH facet alone through a
/// real `egui_kittest` harness and scan its own AccessKit subtree for the error
/// markers, so a failure is attributed to the offending facet (not lost in the
/// whole-deck render).
#[test]
fn every_registered_facet_is_discoverable_renders_sane_data_and_no_error_atom() {
let demo = DemoState::new();
// 1. Discover every registered Facet from the deck (the deck IS the enumerator).
let discovered: BTreeSet<String> = demo.deck.titles().iter().map(|t| t.to_string()).collect();
assert!(!discovered.is_empty(), "the deck must register at least one Facet");
// 2. No silent gaps vs the demo's declared LEGEND (every LEGEND key but the
// non-Facet `menu` overlay must be a registered Facet, and vice-versa).
let legend_facets: BTreeSet<String> =
LEGEND.iter().map(|(k, _)| *k).filter(|k| *k != "menu").map(|k| k.to_string()).collect();
let missing_from_deck: Vec<&String> = legend_facets.difference(&discovered).collect();
let missing_from_legend: Vec<&String> = discovered.difference(&legend_facets).collect();
assert!(
missing_from_deck.is_empty(),
"LEGEND declares Facets the deck never registered (add to the deck): {missing_from_deck:?}"
);
assert!(
missing_from_legend.is_empty(),
"deck registers Facets not in the LEGEND (add to LEGEND): {missing_from_legend:?}"
);
assert_eq!(
discovered, legend_facets,
"the registered Facet set must EXACTLY equal the demo's declared LEGEND Facets"
);
// 3. Sane data: every facet's folded-in state_json is a NON-EMPTY object.
let state = demo.state_json();
let facets = state["deck"]["facets"].as_object().expect("deck folds in a facets map");
let caps = state["deck"]["caps"].as_object().expect("deck folds in a caps map");
assert_eq!(facets.len(), discovered.len(), "one state_json per registered facet");
assert_eq!(caps.len(), discovered.len(), "one caps entry per registered facet");
for title in &discovered {
let sj = facets.get(title).unwrap_or_else(|| panic!("no state_json for facet `{title}`"));
let obj = sj
.as_object()
.unwrap_or_else(|| panic!("facet `{title}` state_json must be a JSON object, got {sj}"));
assert!(
!obj.is_empty(),
"facet `{title}` rendered an EMPTY state_json (an empty shell, not real data)"
);
// The caps surface (the discovery contract's machine-readable row) is present.
assert!(caps.contains_key(title), "facet `{title}` has no caps row");
}
// 4. Per-facet error-atom net: render EACH facet alone and scan its subtree, so a
// surfaced error is attributed to that exact facet.
let titles: Vec<String> = discovered.iter().cloned().collect();
for title in &titles {
let mut d = DemoState::new();
assert!(d.deck.set_active_by_title(title), "could not activate facet `{title}`");
// Move the (now-active) deck into a fresh harness and render only it.
let mut harness = Harness::new_ui(move |ui| {
d.deck.ui(ui);
});
harness.run_steps(4);
let found = scan_error_atoms(&mut harness);
assert!(
found.is_empty(),
"facet `{title}` rendered error atoms (it surfaced an error, not data): {found:?}"
);
}
// The gate is real + non-trivial (the demo wires every facett component).
assert!(discovered.len() >= 20, "expected the demo to register 20+ Facets, got {}", discovered.len());
eprintln!(
"\n══ facett-demo FIND-THEM-ALL gate ══\n {} registered Facets, all discoverable + sane-data + no error atom\n",
discovered.len()
);
}