mannequin/tests/gui_robot.rs
//! Mannequin GUI robot-UI cell — the **first concrete robot-UI matrix row** in
//! holger. It drives the mannequin facett GUI *headlessly* (no display, no GPU)
//! via `facett_core::harness`, the same way nornir viz's matrix observes its
//! components through `state_json`.
//!
//! This is NOT a stub: it renders the real mannequin `SystemChart` offscreen,
//! injects real badge counts, clicks a node, and asserts the component's
//! observable `state_json` reflects exactly what an operator would see — the 3
//! mock-system nodes (PKI/OIDC/Nexus), their badges, the 3 edges, and the
//! selection. When `nornir-robotui` lands it will wrap this exact pattern (drive
//! + observe + matrix row); until then this test IS the robot-UI cell.
//!
//! LAW: inject real input + assert real output. We feed concrete badge numbers
//! and a click, then assert the rendered state carries them back — never just
//! "didn't panic".
use facett_core::harness::{headless_render, render_themed};
use facett_core::look::Theme as LookTheme;
use mannequin::event_log::SystemKind;
use mannequin::gui::build_system_chart;
/// Render the mannequin system chart headless and assert it drew the 3 nodes,
/// their injected badges, and the 3 edges — the operator's whole system map.
#[test]
fn mannequin_gui_renders_three_system_nodes_with_badges() {
// Inject real, distinct badge counts so we can prove they survive the render.
let mut chart = build_system_chart([
(SystemKind::Pki, 7),
(SystemKind::Oidc, 3),
(SystemKind::Nexus, 5),
]);
// Drive: render the component offscreen and capture what it drew.
let report = headless_render(&mut chart);
// It actually tessellated geometry (the "robot saw pixels" proxy).
assert!(
report.drew(),
"the system chart must draw something (got 0 vertices)"
);
// Observe: the component's own state_json — exactly what the operator sees.
let nodes = report.state["nodes"]
.as_array()
.expect("state_json has a nodes array");
assert_eq!(nodes.len(), 3, "PKI + OIDC + Nexus = three system nodes");
let edges = report.state["edges"]
.as_array()
.expect("state_json has an edges array");
assert_eq!(
edges.len(),
3,
"the three systems are fully connected (3 edges)"
);
// The injected badges round-trip through the render, per node id.
let badge_of = |id: &str| -> u64 {
nodes
.iter()
.find(|n| n["id"] == id)
.and_then(|n| n["badge"].as_u64())
.unwrap_or_else(|| panic!("node {id} missing from rendered state"))
};
assert_eq!(badge_of("pki"), 7, "PKI badge reflects the injected count");
assert_eq!(
badge_of("oidc"),
3,
"OIDC badge reflects the injected count"
);
assert_eq!(
badge_of("nexus"),
5,
"Nexus badge reflects the injected count"
);
// Nothing selected before a click.
assert!(
report.state["selected"].is_null(),
"no node selected on first render"
);
}
/// Drive a click (headless select) and assert the selection shows up in the
/// component's observable state across a re-render — the robot-UI "interact then
/// read back" loop.
#[test]
fn mannequin_gui_click_selects_node_and_state_reflects_it() {
let mut chart = build_system_chart([
(SystemKind::Pki, 1),
(SystemKind::Oidc, 0),
(SystemKind::Nexus, 0),
]);
// First render: nothing selected.
let before = headless_render(&mut chart);
assert!(before.state["selected"].is_null(), "starts deselected");
// Robot action: click/select the OIDC node (the headless analogue of a tap).
chart.select("oidc");
// Re-render and observe the new state.
let after = headless_render(&mut chart);
assert_eq!(
after.state["selected"], "oidc",
"after selecting OIDC the rendered state must report it selected"
);
assert!(after.drew(), "selected-detail render still draws");
// Re-selecting the same node deselects (the chart's documented toggle).
chart.select("oidc");
assert!(
chart.selected().is_none(),
"clicking the selected node toggles it off"
);
}
/// Look-&-feel cell: the mannequin's system map renders coherently under EVERY
/// unified facett preset (Windows/macOS/Device, light+dark) — the "holger too"
/// migration. For each preset we publish its derived legacy palette (the COH-1
/// bridge) and render the chart headlessly, asserting it still draws and its
/// observable state survives the re-theme. The mannequin used to ship egui's bare
/// default; now it follows the unified look like every other facett consumer.
///
/// LAW: inject real input + assert real output. We inject each concrete preset
/// and concrete badges, then assert the themed render draws + reports the exact
/// nodes/badges back — never just "didn't panic".
#[test]
fn mannequin_chart_renders_under_every_look_preset() {
// The presets are exactly facett's canonical roster (not a hand-typed list).
let presets = LookTheme::PRESETS;
assert!(
presets.len() >= 5,
"facett ships Windows L/D, macOS L/D, Device"
);
for make in presets {
let theme = make();
let mut chart = build_system_chart([
(SystemKind::Pki, 4),
(SystemKind::Oidc, 2),
(SystemKind::Nexus, 6),
]);
// Drive: render the chart headless under THIS preset's derived legacy
// palette (what `Theme::apply` publishes for every facett view, COH-1).
let report = render_themed(&mut chart, theme.to_legacy_palette());
// It actually tessellated geometry under the themed paint path.
assert!(
report.drew(),
"preset `{}` must still draw the system map",
theme.name
);
// The observable state survives the re-theme: 3 nodes, 3 edges, injected
// badges intact — exactly what the operator sees under this look.
let nodes = report.state["nodes"].as_array().expect("nodes array");
assert_eq!(
nodes.len(),
3,
"preset `{}`: PKI+OIDC+Nexus survive",
theme.name
);
assert_eq!(report.state["edges"].as_array().map(|e| e.len()), Some(3));
let badge_of = |id: &str| -> u64 {
nodes
.iter()
.find(|n| n["id"] == id)
.and_then(|n| n["badge"].as_u64())
.unwrap()
};
assert_eq!(
badge_of("nexus"),
6,
"preset `{}`: badge round-trips themed",
theme.name
);
}
}