tests/viz_uiplane_walk.rs
//! **LAW 9 (UI-PLANE reachability) — walk the LIVE nornir-viz `UiPlan`.**
//!
//! The mega-matrix tests viz panes IN ISOLATION (renders each standalone). This
//! file is the opposite and the point of LAW 9: it models the running viz as a
//! [`UiPlan`] (a plane per `Tab`, a tab-strip click per transition) and **walks the
//! REAL app** with the native [`VizPlaneDriver`] — driving the SAME control channel
//! `viz.click` uses (`apply_command`) and reading present/ran from `state_json`
//! (LAW 6, no pixels). It asserts the thing isolation can't: every tab is
//! **reachable + its pane present**, from the app's own canonical `all_tabs`
//! enumerator (discovery-contract — not a hand-listed set).
//!
//! Sensitivity (the oracle must go RED, baked in):
//! - **orphan plane** — a tab declared with NO transition to it → RED `Unreachable`
//! (the exact Sverige-buttons catch);
//! - **present-not-ran** — a reached tab whose pane is blank → RED `ReachableNotRan`
//! (RAGNARÖK / LAW 2), proven via the live empty-warehouse Manual-style pane.
//!
//! Run:
//! cargo test --features "viz,testmatrix" --test viz_uiplane_walk -- --nocapture
#![cfg(feature = "viz")]
use std::path::PathBuf;
use nornir::viz::UrdrThreadsApp;
use nornir::viz::uiplane_plan::{CLICK_TAB, VizPlaneDriver, viz_plan};
use nornir_testmatrix::uiplane::{RobotPlan, SurfaceVerdict, Transition, UiPlane, UiSurface};
/// Build a fat (local) app over a fresh temp warehouse + render a few frames so the
/// panes populate as far as an empty warehouse allows. Reachability + present do
/// not need populated data; the per-pane RAGNARÖK sweep is the populated job of the
/// functional matrix — here we prove the *navigation graph* is whole.
fn fat_app() -> UrdrThreadsApp {
// Keep the durable action-log + funnel demo from touching shared state.
set_env("NORNIR_VIZ_NO_DURABLE_ACTIONLOG", "1");
set_env("NORNIR_FUNNEL_DEMO_DISABLED", "1");
let dir = std::env::temp_dir().join(format!("nornir-uiplane-{}", std::process::id()));
let _ = std::fs::create_dir_all(&dir);
let mut app = UrdrThreadsApp::with_repos(dir, "nornir".into(), PathBuf::new(), vec![]);
for _ in 0..3 {
app.render_all_tabs_headless();
}
app
}
fn set_env(k: &str, v: &str) {
// SAFETY: set before any cross-thread env read; tests in this file are independent.
unsafe { std::env::set_var(k, v) };
}
/// Read the live `all_tabs` + boot tab from the app (the canonical enumerator).
fn live_tabs(app: &UrdrThreadsApp) -> (Vec<String>, String) {
let state = app.state_json();
let tabs: Vec<String> = state["all_tabs"]
.as_array()
.expect("all_tabs is the canonical tab enumerator")
.iter()
.filter_map(|v| v.as_str().map(String::from))
.collect();
let start = state["tab"]
.as_str()
.expect("app reports a current tab")
.to_string();
(tabs, start)
}
#[test]
fn live_viz_plan_reaches_every_tab_from_the_real_app() {
let mut app = fat_app();
let (tabs, start) = live_tabs(&app);
assert!(
tabs.len() >= 15,
"the viz has many tabs (got {})",
tabs.len()
);
let plan = viz_plan(&tabs, &start);
// The plan itself is whole: no orphan tab, no dangling edge.
assert!(
plan.orphan_planes().is_empty(),
"every tab must be reachable from the boot plane: orphans = {:?}",
plan.orphan_planes()
);
assert!(
plan.dangling_transitions().is_empty(),
"no edge references an undeclared tab"
);
assert_eq!(plan.planes.len(), tabs.len(), "one plane per tab");
// Walk the LIVE app with the native driver.
let mut driver = VizPlaneDriver::new(&mut app, 2);
let report = RobotPlan::walk(&plan, &mut driver);
// LAW 9 reachability completeness: every declared tab actually reached.
assert_eq!(
report.reached_planes.len(),
tabs.len(),
"every tab navigated-to on the real app; unreachable = {:?}",
report.unreachable_planes
);
assert!(
report.unreachable_planes.is_empty(),
"no unreachable tab on the live app"
);
// Every tab's pane block must be PRESENT after navigation (the surface exists
// on its plane in the shipped app — not absent). RAGNARÖK populated-ran is the
// functional matrix's job over a SEEDED warehouse; here the empty warehouse may
// leave some panes legitimately not-ran, so we assert PRESENT (no Absent), and
// count ran for visibility.
let absent: Vec<_> = report
.rows
.iter()
.filter(|r| r.verdict == SurfaceVerdict::ReachableAbsent)
.map(|r| r.check())
.collect();
assert!(
absent.is_empty(),
"no declared pane surface is ABSENT on its tab: {absent:?}"
);
let ran = report
.rows
.iter()
.filter(|r| r.verdict == SurfaceVerdict::ReachableAndRan)
.count();
eprintln!("[uiplane] {}", report.summary());
eprintln!(
"[uiplane] {ran}/{} tab panes populated (ran) on an EMPTY warehouse",
report.rows.len()
);
assert!(
ran > 0,
"at least some panes populate even on an empty warehouse"
);
}
#[test]
fn orphan_tab_with_no_transition_goes_red_on_the_live_app() {
// SENSITIVITY — the Sverige-buttons catch on the REAL app: declare a plane with
// a surface but NO transition to it → RED Unreachable, naming the orphan.
let mut app = fat_app();
let (tabs, start) = live_tabs(&app);
let mut plan = viz_plan(&tabs, &start);
plan = plan.plane(
UiPlane::new("OrphanShowcase")
.surface(UiSurface::new("sverige_button").with_label("Sverige showcase button")),
);
// NOTE: deliberately add NO transition to OrphanShowcase.
assert_eq!(plan.orphan_planes(), vec!["OrphanShowcase".to_string()]);
let mut driver = VizPlaneDriver::new(&mut app, 2);
let report = RobotPlan::walk(&plan, &mut driver);
assert!(
!report.is_green(),
"an orphan tab makes the LAW 9 verdict RED"
);
assert!(
report
.unreachable_planes
.contains(&"OrphanShowcase".to_string())
);
let btn = report
.rows
.iter()
.find(|r| r.surface == "sverige_button")
.expect("the orphan surface got a row");
assert_eq!(btn.verdict, SurfaceVerdict::Unreachable);
assert!(
btn.detail().contains("UNREACHABLE"),
"the row names the missing transition need"
);
}
#[test]
fn broken_transition_to_a_real_tab_goes_red() {
// SENSITIVITY: a transition whose action the driver can't perform (a bad tab
// name) → the tab is unreachable → RED. Proves the walk verifies the LANDING,
// not just that an action was attempted.
let mut app = fat_app();
let (tabs, start) = live_tabs(&app);
// Build a minimal 2-plane plan where the only edge points at a NONEXISTENT tab
// name the app's apply_command will reject (so current_plane never becomes it).
let real_target = tabs.iter().find(|t| **t != start).cloned().unwrap();
let plan = nornir_testmatrix::uiplane::UiPlan::new(start.clone())
.plane(UiPlane::new(start.clone()))
.plane(UiPlane::new(real_target.clone()).surface(UiSurface::new("pane")))
// The action names a bogus tab → apply lands on the wrong plane.
.transition(Transition::new(
start.clone(),
real_target.clone(),
format!("{CLICK_TAB}NoSuchTab"),
));
let mut driver = VizPlaneDriver::new(&mut app, 2);
let report = RobotPlan::walk(&plan, &mut driver);
assert!(!report.is_green(), "a transition that fails to land → RED");
assert!(
report.unreachable_planes.contains(&real_target),
"the target tab is unreachable when the click names the wrong tab"
);
}
// ── feature ON: the walk emits LAW 9 rows into the matrix buffer ──────────────
#[cfg(feature = "testmatrix")]
#[test]
fn live_walk_emits_uiplane_rows_into_the_matrix() {
use nornir_testmatrix::{drain_functional_rows, status as row_status};
let _ = drain_functional_rows(); // clear any leftover
let mut app = fat_app();
let (tabs, start) = live_tabs(&app);
let plan = viz_plan(&tabs, &start);
let mut driver = VizPlaneDriver::new(&mut app, 2);
let report = RobotPlan::walk(&plan, &mut driver);
// The walk's `draw_ui` settles fire OTHER components' render-side
// `functional_status` emits into the same global buffer; drain them so the
// buffer holds ONLY our LAW 9 emit, then assert one row per (plane, surface).
// Drain the walk's draw_ui side-effect emits so the buffer holds ONLY our LAW 9
// emit (it lands on the shared functional buffer, aspect "functional").
let _ = drain_functional_rows();
report.emit("viz-uiplane");
let rows = drain_functional_rows();
let mine: Vec<_> = rows.iter().filter(|r| r.suite == "viz-uiplane").collect();
assert_eq!(
mine.len(),
report.rows.len(),
"one matrix row per (plane,surface) walk row"
);
// Every present-and-ran row is a GREEN matrix row; no Absent rows on the live app.
assert!(
mine.iter().all(|r| !r.test_name.is_empty()),
"rows carry a <plane>/<surface> check id"
);
// The rows that passed are PASS; the rest (empty-warehouse not-ran) are FAIL —
// the matrix carries the honest verdict, never a silent green.
assert!(
mine.iter().any(|r| r.status == row_status::PASS),
"some tab panes populated → green matrix rows"
);
}