Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/nornir

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"
    );
}