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

nordisk/facett

facett-syschart/tests/robot_callchain.rs

//! **Robot-UI proof** for the `facett-syschart` system-map skin — drive a REAL
//! AccessKit click on a node and read the resulting selection back from
//! `state_json` (the interactive tail no return value exposes), plus the wgpu
//! non-blank/confinement pixel leg + the final-picture PNG blob.
//!
//! 1. Robot click: build a chart, click node "sys1" by its AccessKit label, assert
//!    `state_json["selected"] == "sys1"` (a wrong/missing selection = RED row).
//! 2. Pixel confinement (wgpu): the chart paints non-blank, confined to its rect.
//! 3. Final PNG blob under `tests/snapshots/`.
//!
//! Run: `cargo test -p facett-syschart --test robot_callchain`
//! Pixel legs need the wgpu backend — they SKIP gracefully without a GPU.

use egui::Color32;
use egui_kittest::Harness;
use egui_kittest::kittest::{NodeT, Queryable};
use facett_core::Facet;
use facett_syschart::{SysEdge, SysNode, SystemChart};

/// A real PKI/OIDC/Nexus-shaped monitoring map with badges + detail.
fn sample_chart() -> SystemChart {
    let nodes = vec![
        SysNode::new("sys0", "PKI", Color32::from_rgb(120, 200, 255), (0.15, 0.2)).badge(3).detail("issued: a\nissued: b"),
        SysNode::new("sys1", "OIDC", Color32::from_rgb(200, 160, 255), (0.85, 0.2)).badge(7).detail("tokens: 7"),
        SysNode::new("sys2", "Nexus", Color32::from_rgb(160, 255, 180), (0.5, 0.85)).badge(0).detail("repos: 4"),
    ];
    let edges = vec![SysEdge::new("sys0", "sys1"), SysEdge::new("sys0", "sys2"), SysEdge::new("sys1", "sys2")];
    SystemChart::new("System Map", nodes, edges)
}

/// **Robot CLICK → selection (CPU a11y, always runs).** Drive a real AccessKit
/// click on the "OIDC" node and assert the chart's `state_json` reports it
/// selected — the live interactive surface read back as data.
#[test]
fn robot_click_selects_node_and_state_reflects_it() {
    use std::cell::RefCell;
    use std::rc::Rc;
    // Shared chart the test inspects after driving the real UI: the ui closure
    // re-renders THIS chart each frame, so a click that toggles its selection is
    // observable in its own `state_json` — the robot read-back as data.
    let chart = Rc::new(RefCell::new(sample_chart()));
    let ui_chart = chart.clone();
    let mut harness = Harness::builder().with_size(egui::vec2(700.0, 520.0)).build_ui(move |ui| {
        ui_chart.borrow_mut().ui(ui);
    });
    harness.run();

    // No selection yet (negative anchor — proves the click is what changes it).
    assert!(chart.borrow().selected().is_none(), "nothing selected before the click");

    // The per-node AccessKit button is labelled "{label} · {badge} events".
    harness.get_by_label("OIDC · 7 events").click();
    harness.run();
    // One more frame so the next-frame AccessKit tree picks up the new selection
    // ring (the `selected` flag is computed from the selection captured at the top
    // of `ui()`, so it lands the frame AFTER the click is applied).
    harness.run();

    // Read the selection back from the live chart's OWN state_json — the data the
    // host/viz reads, and the canonical "see what the user sees" source of truth.
    // A wrong/missing selection is a RED row.
    let st = chart.borrow().state_json();
    let selected = st["selected"].as_str() == Some("sys1");
    eprintln!("[robot] after click, chart.state_json[selected] = {:?}", st["selected"]);
    assert!(selected, "clicking the OIDC node selects sys1 in state_json (got {:?})", st["selected"]);
    // The clicked surface is a real, robot-addressable AccessKit Button node (the
    // node a driver / screen reader finds by label to click). egui surfaces a
    // Button's role but not its `selected` flag, so the selection authority is the
    // state_json above; here we confirm the node is the queryable Button we drove.
    let ak = harness.get_by_label("OIDC · 7 events").accesskit_node();
    let is_button = ak.role() == egui::accesskit::Role::Button;
    eprintln!("[robot] OIDC node role = {:?}", ak.role());
    assert!(is_button, "the OIDC node is a queryable AccessKit Button (robot-addressable)");
    // Toggle off via a second real click → selection clears (round-trips).
    harness.get_by_label("OIDC · 7 events").click();
    harness.run();
    let deselected = chart.borrow().selected().is_none();
    assert!(deselected, "a second click toggles the selection off");

    #[cfg(feature = "testmatrix")]
    facett_core::testmatrix::emit(
        "facett-syschart.robot",
        "click_selects_node",
        selected && is_button && deselected,
        "AccessKit click on OIDC → state_json.selected=sys1; second click toggles off",
    );
}

/// **Render confinement + non-blank (wgpu).** The chart paints a non-blank
/// framebuffer confined to its rect — nothing in the margin below. Skips without GPU.
#[test]
fn syschart_renders_non_blank_and_confined() {
    let mut harness = Harness::builder().with_size(egui::vec2(640.0, 480.0)).build_ui(move |ui| {
        facett_core::set_theme(ui.ctx(), facett_core::Theme::sci_fi());
        let full = ui.available_rect_before_wrap();
        let c_rect = egui::Rect::from_min_size(full.min, egui::vec2(full.width(), full.height() * 0.55));
        let mut chart = sample_chart().with_canvas_height(c_rect.height());
        chart.select("sys0");
        ui.scope_builder(egui::UiBuilder::new().max_rect(c_rect), |ui| chart.ui(ui));
    });
    harness.run_steps(3);
    let img = match harness.render() {
        Ok(img) => img,
        Err(e) => {
            eprintln!("[robot] no wgpu backend ({e}) — syschart confinement skipped");
            return;
        }
    };
    let (w, h) = (img.width(), img.height());
    let win_bg = *img.get_pixel(w / 2, h - 3);
    let (wr, wg, wb) = (win_bg.0[0] as i32, win_bg.0[1] as i32, win_bg.0[2] as i32);
    let far =
        |p: &image::Rgba<u8>| (p.0[0] as i32 - wr).abs() + (p.0[1] as i32 - wg).abs() + (p.0[2] as i32 - wb).abs() > 24;
    let canvas_h = (h as f32 * 0.5) as u32;
    let margin_y0 = (h as f32 * 0.75) as u32; // well below the canvas (detail panel sits between)
    let (mut body, mut body_tot, mut leak, mut leak_tot) = (0usize, 0usize, 0usize, 0usize);
    for (_x, y, p) in img.enumerate_pixels() {
        if y < canvas_h {
            body_tot += 1;
            if far(p) {
                body += 1;
            }
        } else if y >= margin_y0 {
            leak_tot += 1;
            if far(p) {
                leak += 1;
            }
        }
    }
    let body_ratio = body as f64 / body_tot.max(1) as f64;
    let margin_leak = leak as f64 / leak_tot.max(1) as f64;
    eprintln!("[robot] syschart body content {body_ratio:.4}, outside leak {margin_leak:.4}");
    assert!(body_ratio > 0.01, "syschart canvas drew content (ratio {body_ratio:.4})");
    assert!(margin_leak < 0.05, "syschart content confined (leak {margin_leak:.4})");

    #[cfg(feature = "testmatrix")]
    facett_core::testmatrix::emit(
        "facett-syschart.robot_confined",
        "non_blank_and_confined",
        body_ratio > 0.01 && margin_leak < 0.05,
        &format!("canvas_ratio={body_ratio:.4} outside_leak={margin_leak:.4}"),
    );
}

/// **Final-picture PNG blob** under `tests/snapshots/`.
#[test]
fn syschart_final_picture_png_blob() {
    let mut harness = Harness::builder().with_size(egui::vec2(900.0, 640.0)).build_ui(move |ui| {
        facett_core::set_theme(ui.ctx(), facett_core::Theme::cyberpunk_neon());
        let mut chart = sample_chart().with_canvas_height(520.0);
        chart.select("sys1");
        chart.ui(ui);
    });
    harness.run_steps(3);
    let img = match harness.render() {
        Ok(img) => img,
        Err(e) => {
            eprintln!("[robot] no wgpu backend ({e}) — syschart PNG skipped");
            return;
        }
    };
    let dir = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/snapshots");
    std::fs::create_dir_all(&dir).unwrap();
    let path = dir.join("syschart_final.png");
    img.save(&path).expect("save syschart_final.png");
    let bytes = std::fs::metadata(&path).unwrap().len();
    assert!(bytes > 1000, "syschart_final.png is a real frame ({bytes} bytes)");
    eprintln!("[robot] wrote {} ({bytes} bytes)", path.display());

    #[cfg(feature = "testmatrix")]
    facett_core::testmatrix::emit(
        "facett-syschart.robot_png",
        "final_picture_blob_written",
        bytes > 1000,
        &format!("syschart_final.png {bytes} bytes"),
    );
}