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

nordisk/nornir

tests/funnel_3d.rs

//! Inject-assert proof for the **rotating 3-D funnel view** (sphere/helix/force).
//!
//! LAW #1: inject real input + assert real output. LAW #6: the view exposes what
//! it renders as `state_json()["funnel"]["graph3d"]`.
//!
//! Flow: build a LOCAL `UrdrThreadsApp` on a tempdir warehouse, run the funnel
//! demo (the `🌱 Run demo` button's path) to inject a real plan DAG, then drive
//! the funnel tab's 3-D toggle + layout-cycle test hooks exactly as the `🌐 3D`
//! and `⟳` buttons do, and assert the 3-D graph reflects the injected plan
//! (node/edge counts) and that the layout cycles sphere → helix → force → sphere.
#![cfg(feature = "viz")]

use nornir::viz::UrdrThreadsApp;
use serde_json::Value;

#[test]
fn demo_then_3d_view_reflects_plan_and_cycles_layouts() {
    // Free the catalog's exclusive lock for this single-process test (the viz
    // durable action-log sink would otherwise hold it; see src/viz/app.rs).
    // TODO: Audit that the environment access only happens in single-threaded code.
    unsafe { std::env::set_var("NORNIR_VIZ_NO_DURABLE_ACTIONLOG", "1") };

    let tmp = tempfile::tempdir().unwrap();
    let ws = tmp.path().join("ws");
    let warehouse = ws.join(".nornir").join("warehouse");
    std::fs::create_dir_all(&warehouse).unwrap();

    let mut app = UrdrThreadsApp::with_repos(warehouse, "viz-3d-test".to_string(), ws, vec![]);

    // --- inject a real plan DAG via the demo (size 4) ---
    let size = 4;
    app.funnel_tab_for_test()
        .run_demo_for_test(size)
        .expect("run demo");

    // The 2-D state reports the plan shape we expect to mirror in 3-D.
    let s = app.state_json();
    let plan = &s["funnel"]["plans"][0];
    let plan_nodes = plan["nodes"].as_u64().expect("plan nodes") as usize;
    let plan_edges = plan["edges"].as_u64().expect("plan edges") as usize;
    assert_eq!(plan_nodes, size, "one node per fake repo");
    assert!(plan_edges > 0, "a layered repo DAG must have dep edges");
    // Before toggling, the view is 2-D.
    assert_eq!(
        s["funnel"]["view3d"].as_bool(),
        Some(false),
        "starts in 2-D"
    );

    // --- toggle the rotating 3-D view (the 🌐 3D button) ---
    let (g_nodes, g_edges) = app.funnel_tab_for_test().set_3d_for_test(true);
    assert_eq!(g_nodes, plan_nodes, "3-D cloud has one node per plan node");
    assert_eq!(g_edges, plan_edges, "3-D cloud has one edge per plan dep");

    let s = app.state_json();
    let g3 = &s["funnel"]["graph3d"];
    assert_eq!(s["funnel"]["view3d"].as_bool(), Some(true), "now in 3-D");
    assert_eq!(
        g3["nodes"].as_u64(),
        Some(plan_nodes as u64),
        "graph3d node count = plan"
    );
    assert_eq!(
        g3["edges"].as_u64(),
        Some(plan_edges as u64),
        "graph3d edge count = plan deps"
    );
    assert_eq!(
        g3["layout"].as_str(),
        Some("sphere"),
        "first layout is sphere"
    );
    assert_eq!(g3["spin"].as_bool(), Some(true), "auto-spins by default");
    // yaw is a finite number (the spin angle the user sees).
    assert!(matches!(&g3["yaw"], Value::Number(_)), "yaw is numeric");

    // --- cycle the layout (the ⟳ button): sphere → helix → force → sphere ---
    assert_eq!(
        app.funnel_tab_for_test().cycle_3d_layout_for_test(),
        "helix"
    );
    assert_eq!(
        app.state_json()["funnel"]["graph3d"]["layout"].as_str(),
        Some("helix")
    );
    assert_eq!(
        app.funnel_tab_for_test().cycle_3d_layout_for_test(),
        "force"
    );
    assert_eq!(
        app.state_json()["funnel"]["graph3d"]["layout"].as_str(),
        Some("force")
    );
    assert_eq!(
        app.funnel_tab_for_test().cycle_3d_layout_for_test(),
        "sphere",
        "wraps back"
    );

    // --- toggle back to 2-D ---
    app.funnel_tab_for_test().set_3d_for_test(false);
    assert_eq!(
        app.state_json()["funnel"]["view3d"].as_bool(),
        Some(false),
        "toggles back to the 2-D snarl DAG"
    );
}