tests/viz_uncovered_tabs_robot.rs
//! **Robot-UI live drive of the four formerly-undriven viz tabs** — 🚇 Metro,
//! 🧬 Time Helix, 🩸 Bloodstream, 🏢 Warehouse Deck.
//!
//! The owner's mandate: wherever a UI surface isn't driven, ADD a real headless
//! robot drive (nornir-robotui / `robot_viz.rs` pattern). These four tabs had a
//! `state_json` slice but no consolidated robot drive AND no `viz_functional_matrix`
//! coverage row, so its per-tab oracle would have failed on them. This file fires
//! up the REAL viz headless, switches to each tab via a real AccessKit header click,
//! renders it (the tab-switch IS the primary interaction — each tab is a
//! feed-on-draw view), and asserts the tab's `state_json` responded:
//!
//! • 🚇 Metro — the facett MetroView was FED a map (fed=true) + names its
//! LAW-2 empty-state reason;
//! • 🧬 Time Helix — the HelixView drew a non-blank scene (coils ≥ 1, the demo
//! helix fallback so it's never empty);
//! • 🩸 Bloodstream — the circulatory body painted organs (organs ≥ 1, nornir's
//! own body from the embedded lockfile);
//! • 🏢 Wh Deck — the injected WarehouseDeck wall reflects its pane count.
//!
//! Each `(action → outcome)` fills a `robot-ui` matrix row (round-tripped through a
//! `JsonFileSink`), and a coverage gate goes RED if any of the four wasn't driven.
//!
//! Run: `cargo test --features viz --test viz_uncovered_tabs_robot -- --nocapture`
#![cfg(feature = "viz")]
use std::path::PathBuf;
use std::sync::Arc;
use std::sync::atomic::{AtomicU64, Ordering};
use arrow::array::{Int64Array, StringArray};
use arrow::datatypes::{DataType, Field, Schema};
use arrow::record_batch::RecordBatch;
use egui_kittest::Harness;
use facett_warehousedeck::{Color32, GraphSpec, PaneData, WarehouseDeck};
use nornir::viz::UrdrThreadsApp;
use nornir_robotui::{
Action, Assert, JsonFileSink, Observable, RobotSession, Scenario, Step, TestSink, status,
};
use serde_json::{Value, json};
struct VizApp(UrdrThreadsApp);
impl Observable for VizApp {
fn state_json(&self) -> Value {
self.0.state_json()
}
}
fn node(label: &str) -> (String, Color32) {
(label.to_string(), Color32::from_rgb(120, 200, 255))
}
/// A minimal warehouse deck (two 3D graphs + a populated grid) so the 🏢 Wh Deck
/// wall has real panes to reflect.
fn seeded_deck() -> WarehouseDeck {
let mut deck = WarehouseDeck::new("🏢 Warehouse Deck").with_cols(2);
deck.add_for(
"call graph",
"call_edges",
PaneData::Graph(GraphSpec::new(
vec![node("main"), node("run_ops"), node("scan")],
vec![(0, 1), (1, 2)],
)),
);
deck.add_for(
"dependency graph",
"dep_graph_edges",
PaneData::Graph(GraphSpec::new(
vec![node("nornir"), node("facett"), node("znippy")],
vec![(0, 1), (0, 2)],
)),
);
let batch = RecordBatch::try_new(
Arc::new(Schema::new(vec![
Field::new("run_id", DataType::Utf8, false),
Field::new("passed", DataType::Int64, false),
])),
vec![
Arc::new(StringArray::from(vec!["run-1", "run-2"])),
Arc::new(Int64Array::from(vec![10, 9])),
],
)
.unwrap();
deck.add_for("bench_runs", "bench_runs", PaneData::Grid(vec![batch]));
deck
}
/// The REAL viz app (workspace "nornir", a warehouse deck injected so the Wh Deck
/// wall has panes), wrapped in a [`RobotSession`]. The inject dismisses the splash.
fn robot() -> RobotSession<'static, VizApp> {
// SAFETY: set before any app is constructed; one test thread per binary.
unsafe { std::env::set_var("NORNIR_VIZ_NO_DURABLE_ACTIONLOG", "1") };
static SEQ: AtomicU64 = AtomicU64::new(0);
let n = SEQ.fetch_add(1, Ordering::Relaxed);
let warehouse_root =
std::env::temp_dir().join(format!("nornir-uncovered-{}-{n}", std::process::id()));
let _ = std::fs::create_dir_all(&warehouse_root);
let mut app = UrdrThreadsApp::with_repos(
warehouse_root,
"nornir".to_string(),
PathBuf::new(),
vec!["nornir".to_string()],
);
// Inject a deck so the Wh Deck wall reflects real panes (also dismisses the
// splash + selects WarehouseDeck; the robot clicks other headers first).
app.inject_warehouse_deck_for_test(seeded_deck());
let harness = Harness::builder()
.with_size(egui::Vec2::new(1400.0, 900.0))
.build_ui_state(|ui, app: &mut VizApp| app.0.draw_ui(ui), VizApp(app));
RobotSession::new(harness, "nornir")
}
/// **Imperative live drive** — click each of the four headers, render the tab, and
/// assert its `state_json` slice responded (inject-assert LAW #1, no "didn't panic").
#[test]
fn robot_drives_the_four_uncovered_tabs_and_state_reflects_each() {
let mut robot = robot();
robot.run(4); // settle layout + first paint
// ── 🚇 Metro — switching feeds the MetroView a map (fed) + names its reason ──
robot
.click_label("🚇 Metro")
.expect("🚇 Metro header is clickable");
robot.run(2);
assert_eq!(
robot.state_at("/tab"),
Some(json!("Metro")),
"clicked to Metro"
);
assert_eq!(
robot.state_at("/metro/fed"),
Some(json!(true)),
"the Metro tab fed the MetroView a map on draw"
);
let reason = robot
.state_at("/metro/empty_state")
.and_then(|v| v.as_str().map(String::from));
assert!(
matches!(
reason.as_deref(),
Some("populated") | Some("in_route") | Some("not_scanned")
),
"Metro names its LAW-2 empty-state reason, got {reason:?}"
);
// ── 🧬 Time Helix — the demo-helix fallback draws a non-blank scene ──────────
robot
.click_label("🧬 Time Helix")
.expect("🧬 Time Helix header is clickable");
robot.run(2);
assert_eq!(
robot.state_at("/tab"),
Some(json!("Helix")),
"clicked to Helix"
);
// The HelixView drew a non-blank scene: the demo-helix fallback carries real
// coils + nodes (the delegated facett `coil_count`/`total_nodes`; the wrapper's
// scalar `coils` is 0 for the demo, so assert on the live scene counts).
let coils = robot
.state_at("/helix/coil_count")
.and_then(|v| v.as_u64())
.unwrap_or(0);
let nodes = robot
.state_at("/helix/total_nodes")
.and_then(|v| v.as_u64())
.unwrap_or(0);
assert!(
coils >= 1 && nodes >= 1,
"the Helix drew a non-blank scene (coils={coils} nodes={nodes})"
);
assert!(
robot
.state_at("/helix/from_data")
.map(|v| v.is_boolean())
.unwrap_or(false),
"Helix reports real-vs-demo scene provenance"
);
// ── 🩸 Bloodstream — the circulatory body paints organs (nornir's own body) ──
robot
.click_label("🩸 Bloodstream")
.expect("🩸 Bloodstream header is clickable");
robot.run(2);
assert_eq!(
robot.state_at("/tab"),
Some(json!("Bloodstream")),
"clicked to Bloodstream"
);
let organs = robot
.state_at("/bloodstream/organs")
.and_then(|v| v.as_array().map(|a| a.len()))
.unwrap_or(0);
assert!(
organs >= 1,
"the Bloodstream painted a non-empty body (organs={organs})"
);
// ── 🏢 Wh Deck — the injected wall reflects its panes ───────────────────────
robot
.click_label("🏢 Wh Deck")
.expect("🏢 Wh Deck header is clickable");
robot.run(2);
assert_eq!(
robot.state_at("/tab"),
Some(json!("WarehouseDeck")),
"clicked to Wh Deck"
);
let panes = robot
.state_at("/warehouse_deck/pane_count")
.and_then(|v| v.as_u64())
.unwrap_or(0);
assert!(
panes >= 1,
"the Warehouse Deck wall reflects its injected panes (pane_count={panes})"
);
}
/// **Scenario form** — the four tab switches expressed declaratively so each
/// `(action → outcome)` fills a `robot-ui` matrix row; then round-trip the rows
/// through a `JsonFileSink` and gate on all four tabs having been driven.
#[test]
fn scenario_drives_the_four_tabs_and_fills_the_matrix() {
let mut robot = robot();
robot.run(4);
let scenario = Scenario::new("uncovered-tabs")
.step(
Step::new("click 🚇 Metro → fed a map")
.action(Action::ClickLabel("🚇 Metro".into()))
.assert(Assert::State {
pointer: "/metro/fed".into(),
expected: json!(true),
}),
)
.step(
Step::new("Metro tab is active after the click").assert(Assert::State {
pointer: "/tab".into(),
expected: json!("Metro"),
}),
)
.step(
Step::new("click 🧬 Time Helix → tab == Helix")
.action(Action::ClickLabel("🧬 Time Helix".into()))
.assert(Assert::State {
pointer: "/tab".into(),
expected: json!("Helix"),
}),
)
.step(
Step::new("Helix reports scene provenance").assert(Assert::StateContains {
pointer: "/helix/from_data".into(),
needle: "false".into(),
}),
)
.step(
Step::new("click 🩸 Bloodstream → tab == Bloodstream")
.action(Action::ClickLabel("🩸 Bloodstream".into()))
.assert(Assert::State {
pointer: "/tab".into(),
expected: json!("Bloodstream"),
}),
)
.step(
Step::new("click 🏢 Wh Deck → tab == WarehouseDeck")
.action(Action::ClickLabel("🏢 Wh Deck".into()))
.assert(Assert::State {
pointer: "/tab".into(),
expected: json!("WarehouseDeck"),
}),
)
.step(
Step::new("the Wh Deck wall reflects ≥1 pane").assert(Assert::State {
pointer: "/warehouse_deck/pane_count".into(),
expected: json!(3),
}),
)
.step(Step::new("the tab drew widgets").assert(Assert::Drew));
let rows = robot.run_scenario(&scenario);
assert_eq!(rows.len(), 8, "one matrix row per scenario step");
assert!(
rows.iter().all(|r| r.aspect == "robot-ui"),
"rows tagged robot-ui"
);
assert!(
rows.iter().all(|r| r.suite == "uncovered-tabs"),
"suite carried through"
);
for r in &rows {
assert_eq!(
r.status,
status::PASS,
"step {:?} should pass; message: {}",
r.test_name,
r.message
);
}
// ── PROVE the rows fill a matrix sink (round-trip a JsonFileSink) ───────────
let sink_path = std::env::temp_dir().join(format!(
"nornir-uncovered-matrix-{}.jsonl",
std::process::id()
));
let _ = std::fs::remove_file(&sink_path);
let sink = JsonFileSink::new(&sink_path);
sink.append(&rows)
.expect("ship the robot rows to the matrix sink");
let back = sink.read_all().expect("read the matrix back");
assert_eq!(back.len(), 8, "the matrix sink has all 8 robot rows");
// ── COVERAGE GATE: all four formerly-undriven tabs were driven ──────────────
let driven = back
.iter()
.map(|r| r.test_name.clone())
.collect::<Vec<_>>()
.join("\n");
for needle in ["Metro", "Helix", "Bloodstream", "Wh Deck"] {
assert!(
driven.contains(needle),
"coverage gate: `{needle}` was not driven by the robot: {driven}"
);
}
eprintln!(
"ROBOT-UI MATRIX (suite=uncovered-tabs, repo=nornir):\n{}",
nornir_robotui::nornir_testmatrix::render_matrix(&back)
);
let _ = std::fs::remove_file(&sink_path);
}