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

nordisk/facett

facett-graphpan/tests/robot.rs

//! **Robot JSON-drive scenario for the pan/zoom graph canvas**, driven through
//! the SHARED generic-Facet probe in `facett_core::harness` (no hand-rolled
//! render+emit boilerplate). The pane's `update_json` robot seam was a **dead
//! no-op** — the `impl_facet_via_elm!` bridge does not generate one and the pane
//! never overrode `Facet::update_json`, so a headless robot could not drive it
//! (only the in-view `ui_render` self-cell existed). Its `Msg` carries
//! `egui::Vec2` payloads (`PanBy`/`SetPan`), so making it serde-drivable also
//! required teaching those variants the same `[x, y]` codec the Model's `pan`
//! field already uses (`#[serde(with = "vec2_serde")]`). This test (a) proves the
//! now-LIVE seam end-to-end — feed a scripted message list (a node select AND a
//! Vec2 pan), watch `state_json` transition — and (b) adds the shared probe's
//! `headless_render` + `responds_to_input` functional cells via
//! `facett_core::testmatrix::probe_and_emit` (a no-op unless built with
//! `--features testmatrix`). The `responds_to_input` aspect is NEW coverage: it
//! would go RED if the JSON drive seam regressed back to a no-op. See
//! `.nornir/facett-platform-gaps.md`.

use egui::{pos2, Color32};
use facett_graphpan::{GraphEdge, GraphModel, GraphNode, GraphPanView};

fn node(id: &str, x: f32, y: f32) -> GraphNode {
    GraphNode {
        id: id.into(),
        label: id.into(),
        fill: Color32::from_rgb(120, 200, 255),
        stroke: Color32::from_rgb(30, 40, 60),
        pos: pos2(x, y),
    }
}

fn edge(from: &str, to: &str) -> GraphEdge {
    GraphEdge { from: from.into(), to: to.into(), color: Color32::WHITE, dashed: false, label: None }
}

#[test]
fn robot_json_drive_scenario() {
    // a → b → c : selecting `a` lights the whole downstream subtree.
    let model = GraphModel {
        nodes: vec![node("a", 0.0, 0.0), node("b", 40.0, 10.0), node("c", 80.0, 20.0)],
        edges: vec![edge("a", "b"), edge("b", "c")],
    };
    let mut view = GraphPanView::new("Graph").with_model(model);

    // Select a node (lights its subtree), then pan by a Vec2 delta — the exact JSON
    // a headless robot sends through the `update_json` seam (serde-tagged; the pan
    // delta rides in as the `[x, y]` codec).
    let script = [
        r#"{"select":"a"}"#,      // node drill-in
        r#"{"pan_by":[12.0,5.0]}"#, // drag the viewport (exercises the Vec2 codec)
    ];

    let probe = facett_core::testmatrix::probe_and_emit("facett-graphpan", &mut view, &script);

    // The initial render drew real geometry (the node/edge canvas) over non-empty state.
    assert!(probe.drew(), "the graph canvas must tessellate to vertices");
    assert!(probe.cardinality() > 0, "state_json reports the nodes / edges");
    // The input surface is LIVE: at least one scripted message moved state.
    assert!(probe.responded(), "update_json must drive state — a dead seam is a regression");

    // Pane-specific: the select lands in `selected` and lights the subtree; the
    // pan moves the transform's pan vector via the Vec2 codec.
    assert_eq!(probe.steps[0].state["selected"], "a", "select pins the node id");
    assert!(probe.steps[0].changed, "select moved state");
    assert_eq!(probe.final_state["transform"]["pan"][0], 12.0, "pan_by moved the pan.x via the Vec2 codec");
}