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

nordisk/facett

facett-buildthing/tests/robot_deck.rs

//! **RED-when-broken robot proof for the build-thing DECK** (F2).
//!
//! The `BuildThingDeck` / `BuildGraphPane` / `view_pane` surface had no real robot
//! scenario — every test drove it through `update_json` / `pending_select_for_test`.
//! This renders the panes headless through a REAL `egui_kittest` AccessKit harness
//! (CPU, no wgpu) and drives them as clicks:
//!
//! 1. **VARIANTS pane** — click a variant `selectable_label` and assert
//!    `state_json["selected"]` flips; click **▶ Build** and assert the raised
//!    [`Msg::Build`], then (as a host would) apply the result and assert
//!    `state_json["result"]`.
//! 2. **GRAPH pane** — a REAL pointer click on a graph node routes through
//!    `draw_graph → clicked_node → pending_select` (graph_pane.rs), the actual path,
//!    NOT the `pending_select_for_test` stand-in, and selects the variant it carries.

use std::cell::RefCell;
use std::rc::Rc;

use egui_kittest::kittest::Queryable;
use egui_kittest::Harness;

use facett_buildthing::{BuildResult, BuildThingDeck, Msg, PANE_GRAPH, PANE_VARIANTS};
use nornir_build_thing::plugin::{BuildGraph, BuildMenu, GraphNode, NodeKind};

/// A holger-shaped menu: a SERVER group (embedded / mini / full) + a CLIENT group
/// (ui / agent), `full` the declared normal case. This fixture was PROMOTED into
/// the crate as [`facett_buildthing::demo_menu`] (the facett-demo gallery mounts it
/// as a seeded tab); this delegates so there is exactly one writer (LAW 5).
fn menu() -> BuildMenu {
    facett_buildthing::demo_menu()
}

/// (1) The VARIANTS pane: a real click on a variant label selects it; **▶ Build**
/// raises a Build msg the host acts on, and the result reaches `state_json`.
#[test]
fn robot_clicks_a_variant_and_build_in_the_variants_pane() {
    let deck = Rc::new(RefCell::new(BuildThingDeck::new(menu())));
    let msgs = Rc::new(RefCell::new(Vec::<Msg>::new()));

    let d = deck.clone();
    let m = msgs.clone();
    let mut h = Harness::builder().with_size(egui::vec2(380.0, 560.0)).build_ui(move |ui| {
        let out = d.borrow_mut().view_pane(PANE_VARIANTS, ui);
        m.borrow_mut().extend(out);
    });
    h.run();

    // Opens on the declared normal case.
    assert_eq!(deck.borrow().state_json()["selected"], "full", "the deck opens on `full`");

    // Click the `mini` variant's selectable_label (a real AccessKit node). At this
    // point `mini` is unique — the bottom "selected" echo still reads `full`.
    h.get_by_label("mini").click();
    h.run();
    assert_eq!(deck.borrow().state_json()["selected"], "mini", "the click selected `mini`");
    assert!(
        msgs.borrow().iter().any(|x| matches!(x, Msg::SelectVariant(v) if v == "mini")),
        "the click raised a SelectVariant msg the host can act on"
    );

    // Click ▶ Build → a Build msg is raised for the selected variant.
    msgs.borrow_mut().clear();
    h.get_by_label("▶ Build").click();
    h.run();
    assert!(
        msgs.borrow().iter().any(|x| matches!(x, Msg::Build(v) if v == "mini")),
        "▶ Build raised a Build msg for the selected variant"
    );

    // As the host would: record the outcome. The RESULT pane then reports it.
    deck.borrow_mut().result = BuildResult {
        variant: "mini".into(),
        green: true,
        headline: "built mini".into(),
        artifacts: vec![("target/release/mini".into(), "6 MB".into())],
        errors: Vec::new(),
        running: false,
    };
    let sj = deck.borrow().state_json();
    assert_eq!(sj["result"]["variant"], "mini", "the build result surfaces in state_json");
    assert_eq!(sj["result"]["green"], true);
    assert_eq!(sj["result"]["artifact_count"], 1);
}

/// (2) The GRAPH pane: a REAL pointer click on a node routes through
/// `draw_graph → clicked_node → pending_select` (the actual path, not the test
/// stand-in) and selects the variant the node carries.
#[test]
fn robot_clicks_a_graph_node_to_select_a_variant() {
    let deck = Rc::new(RefCell::new(BuildThingDeck::new(menu())));
    // A single Variant node → column 0, row 0 → world position (0,0), so it projects
    // to the graph painter's centre (`origin = rect.center() + pan`, pan = 0). It
    // selects `agent`, distinct from the deck's opening selection (`full`), so the
    // transition is unambiguous.
    deck.borrow_mut().set_graph(BuildGraph {
        repo: "holger".into(),
        root: "agent".into(),
        nodes: vec![GraphNode {
            id: "variant:agent".into(),
            label: "agent".into(),
            kind: NodeKind::Variant,
            detail: None,
            selects: Some("agent".into()),
        }],
        edges: Vec::new(),
    });
    assert_eq!(deck.borrow().state_json()["selected"], "full", "opens on `full`");

    let msgs = Rc::new(RefCell::new(Vec::<Msg>::new()));
    let d = deck.clone();
    let m = msgs.clone();
    let size = egui::vec2(360.0, 420.0);
    let mut h = Harness::builder().with_size(size).build_ui(move |ui| {
        let out = d.borrow_mut().view_pane(PANE_GRAPH, ui);
        m.borrow_mut().extend(out);
    });
    h.run();

    // The graph painter fills the pane under a two-row header (filter tabs + legend);
    // the lone node sits at its centre (≈ y 230 of the 420 px pane). Click there — a
    // real move+press+release, the same events kittest's `Node::click` fires. The chip
    // hit box is 184×34 world px, forgiving enough that the exact centre is not needed.
    let pos = egui::pos2(size.x * 0.5, 230.0);
    h.hover_at(pos);
    h.drag_at(pos);
    h.drop_at(pos);
    h.run();
    h.run();

    // The real path: `draw_graph` returned `clicked_node`, `BuildGraphPane::ui` mapped
    // it to `pending_select`, and `view_pane`'s `take_msgs` drained it as a
    // `SelectVariant` for the host — NOT the `pending_select_for_test` stand-in.
    assert!(
        msgs.borrow().iter().any(|x| matches!(x, Msg::SelectVariant(v) if v == "agent")),
        "a real click on the graph node raised SelectVariant('agent') through draw_graph→pending_select, got {:?}",
        msgs.borrow()
    );

    // A host acts on that msg — and the selection flips, the round trip the graph
    // exists for (pick a build by clicking its node).
    assert!(deck.borrow_mut().select("agent"), "the selection was `full`, the click asked for `agent`");
    assert_eq!(deck.borrow().state_json()["selected"], "agent", "the host applied the graph-node selection");
}