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

nordisk/nornir

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