facett-graph 0.1.12

facett — graph (node/edge) viewer component, on facett-core
Documentation
//! **Robot / headless render-confinement proof + final-picture PNG** for the
//! `facett-graph` 2D node/edge skin — the input→PIXEL tail of the call chain that
//! no return value can expose.
//!
//! Drives a populated `GraphView` through `egui_kittest`'s real frame loop, then:
//!   1. asserts the render is **non-blank** AND **confined** to the allocated
//!      widget rect (nothing painted in the margin below it — the "lines springing
//!      outside the widget" bug), and
//!   2. saves the **final-picture PNG blob** under `tests/snapshots/` so an agent
//!      (or the viz) can see the rendered frame straight from the matrix.
//!
//! Run: `cargo test -p facett-graph --test robot_callchain`
//! Pixel legs need the wgpu backend (lavapipe/GPU) — they SKIP gracefully without.

use egui_kittest::Harness;
use facett_core::{Facet, Layout, Scene, hash_color};
use facett_graph::GraphView;

/// A real multi-label graph (a small social-ish graph): a hub Person joined to
/// Companies/Addresses + a chain — the shape a Cypher/Arrow query hands the skin.
fn populated_graph() -> Scene {
    let mut s = Scene::new();
    let people: Vec<usize> = (0..6).map(|i| s.node(format!("Person{i}"), hash_color("Person"))).collect();
    let co = s.node("Acme", hash_color("Company"));
    let addr = s.node("HQ", hash_color("Address"));
    for &p in &people {
        s.edge(p, co);
    }
    s.edge(co, addr);
    for w in people.windows(2) {
        s.edge(w[0], w[1]);
    }
    s
}

/// **Render confinement + non-blank (wgpu).** The populated graph must paint a
/// non-blank framebuffer whose drawn content is CONFINED to the widget rect — no
/// pixels in a margin BELOW the allocated rect. Skips when no GPU/lavapipe.
#[test]
fn graph_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());
        // Allocate the graph into the TOP HALF only; the bottom half is untouched
        // window background — anything the graph paints there is a confinement leak.
        let full = ui.available_rect_before_wrap();
        let g_rect = egui::Rect::from_min_size(full.min, egui::vec2(full.width(), full.height() * 0.5));
        let mut view = GraphView::new(populated_graph());
        view.set_layout(Layout::Force);
        ui.scope_builder(egui::UiBuilder::new().max_rect(g_rect), |ui| view.ui(ui));
    });
    harness.run_steps(3);
    let img = match harness.render() {
        Ok(img) => img,
        Err(e) => {
            eprintln!("[robot] no wgpu backend ({e}) — graph confinement pixel leg 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_from =
        |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 graph_h = (h as f32 * 0.5) as u32;
    let margin_y0 = (h as f32 * 0.6) as u32; // strictly below the graph rect
    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 < graph_h {
            body_tot += 1;
            if far_from(p) {
                body += 1;
            }
        } else if y >= margin_y0 {
            leak_tot += 1;
            if far_from(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] graph body content {body_ratio:.4}, outside-widget leak {margin_leak:.4}");
    // Non-blank: a node/edge graph is sparse, but its content clears a low floor.
    assert!(body_ratio > 0.01, "the graph rect drew content (ratio {body_ratio:.4})");
    // Confinement: essentially nothing painted below the widget.
    assert!(margin_leak < 0.02, "graph content confined to its rect (leak {margin_leak:.4})");

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

/// **Final-picture PNG blob.** Render the populated graph full-pane and write the
/// frame to `tests/snapshots/graph_final.png` — the matrix's see-the-frame artifact.
#[test]
fn graph_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 view = GraphView::new(populated_graph()).with_title("graph-final");
        view.set_layout(Layout::Force);
        view.ui(ui);
    });
    harness.run_steps(3);
    let img = match harness.render() {
        Ok(img) => img,
        Err(e) => {
            eprintln!("[robot] no wgpu backend ({e}) — graph PNG blob 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("graph_final.png");
    img.save(&path).expect("save graph_final.png");
    // The blob is real: a non-trivial number of bytes for a 900x640 RGBA frame.
    let bytes = std::fs::metadata(&path).unwrap().len();
    assert!(bytes > 1000, "graph_final.png is a real frame ({bytes} bytes)");
    eprintln!("[robot] wrote {} ({bytes} bytes)", path.display());

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