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

nordisk/facett

facett-coderoom-view/tests/robot.rs

//! **Robot world-coderoom scenario for the shared pane** (`egui_kittest`).
//!
//! Renders the REAL [`facett_coderoom_view::CoderoomPane`] body over the stub
//! [`CoderoomDataSeam`] and asserts, off the live AccessKit tree + the pane's
//! `state_json`, that every render layer reaches the surface a robot (or a screen
//! reader) sees:
//! 1. **Dependency graph** — the REUSED `DecoratedGraphView` receives + paints the
//!    laid-out scene (5 crate nodes, 4 compat edges); its header reaches the tree.
//! 2. **Compat edges** — the 3 breaking edges paint **red**, the 1 compatible edge
//!    **green** (asserted as data off the pane's `state_json`).
//! 3. **What-breaks mode** — selecting the dropped-symbol crate (`a@0.2.0`) lights
//!    exactly the 3 impacted callers `{b, c, d}` (a red ring in the scene) and
//!    dims the rest; `e` (a dependent that does NOT break) is not impacted.
//! 4. **Summary line** — `N crates · M breaking edges · K impacted callers` paints
//!    as real widgets.
//! 5. No crash: the whole pane body runs through the harness without panicking.
//!
//! This is the L4 UI proof for the suite's world-coderoom surface: nornir/dwarves
//! delete any bespoke dep/compat body and mount THIS one (implementing
//! `CoderoomDataSeam` over the real coderoom warehouse), so this test guards
//! theirs. RED-when-broken (verified by neutralizing): if the graph, the breaking
//! colour, the impact highlight, or the summary regressed, an assertion goes red.
//! One matrix cell `facett-coderoom-view/coderoom-pane`.

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

use egui_kittest::kittest::Queryable;
use egui_kittest::Harness;
use facett_coderoom_view::{CoderoomMode, CoderoomPane, Facet, StubCoderoomData, GREEN, RED, STUB_TARGET};

/// Emit shim (real row with `--features testmatrix`, no-op otherwise).
#[cfg(feature = "testmatrix")]
fn emit(check: &str, ok: bool, detail: &str) {
    nornir_testmatrix::functional_status("facett-coderoom-view", check, ok, detail);
}
#[cfg(not(feature = "testmatrix"))]
fn emit(_check: &str, _ok: bool, _detail: &str) {}

fn hex(c: facett_coderoom_view::Color) -> String {
    format!("#{:02x}{:02x}{:02x}", c.r, c.g, c.b)
}

#[test]
fn coderoom_pane_renders_graph_colours_and_what_breaks() {
    let pane = Rc::new(RefCell::new(CoderoomPane::new(Box::new(StubCoderoomData))));

    let mut harness = Harness::builder().with_size(egui::vec2(760.0, 720.0)).build_ui({
        let pane = Rc::clone(&pane);
        move |ui| {
            egui::ScrollArea::both().show(ui, |ui| {
                pane.borrow_mut().ui(ui);
            });
        }
    });

    // ≥1 = present (a label can ride in as multiple nodes).
    let seen = |h: &Harness, label: &str| h.query_all_by_label(label).count() >= 1;

    // Run a frame — proves the whole body paints without panicking (no crash).
    harness.run();

    // (1) The REUSED dep-graph viz received + painted the scene.
    let graph_ok = {
        let guard = pane.borrow_mut();
        let s = guard.state_json();
        s["graph_nodes"] == 5 && s["graph_edges"] == 4
    };
    assert!(graph_ok, "the reused DecoratedGraphView holds all 5 nodes + 4 edges");
    // Its header (a real widget) reaches the a11y tree.
    let graph_header_seen = seen(&harness, "5 nodes · 4 edges");
    assert!(graph_header_seen, "the reused graph header paints");

    // (2) Compat edges: 3 breaking (red), 1 compatible (green) — asserted as data.
    let colours_ok = {
        let guard = pane.borrow_mut();
        let s = guard.state_json();
        let edges = s["edges"].as_array().unwrap().clone();
        let breaking: Vec<_> = edges.iter().filter(|e| e["breaking"] == true).collect();
        let compat: Vec<_> = edges.iter().filter(|e| e["breaking"] == false).collect();
        breaking.len() == 3
            && breaking.iter().all(|e| e["color"] == hex(RED))
            && compat.len() == 1
            && compat[0]["color"] == hex(GREEN)
    };
    assert!(colours_ok, "3 breaking edges paint red, 1 compatible edge green");

    // (3) The base summary line paints (real widgets).
    let base_summary_seen = seen(&harness, "5 crates") && seen(&harness, "3 breaking edges");
    assert!(base_summary_seen, "the base summary line paints");

    // (4) What-breaks mode: select the dropped-symbol crate `a@0.2.0`.
    pane.borrow_mut().select_target(Some(STUB_TARGET.to_string()));
    harness.run();

    let (impact_ok, rings_ok) = {
        let guard = pane.borrow_mut();
        let s = guard.state_json();
        let impacted: std::collections::BTreeSet<String> =
            s["impacted"].as_array().unwrap().iter().map(|v| v.as_str().unwrap().to_string()).collect();
        let want: std::collections::BTreeSet<String> =
            ["b@1.0.0", "c@1.1.0", "d@0.9.0"].iter().map(|s| s.to_string()).collect();
        let impact_ok = s["impacted_count"] == 3 && impacted == want && !impacted.contains("e@2.0.0");
        // The 3 impacted callers carry a red ring in the scene handed to the viz.
        let decos = &guard.graph_decorations();
        let rings_ok = ["b@1.0.0", "c@1.1.0", "d@0.9.0"]
            .iter()
            .all(|id| decos.get(*id).and_then(|d| d.ring) == Some(RED))
            && decos.get("e@2.0.0").and_then(|d| d.ring).is_none();
        (impact_ok, rings_ok)
    };
    assert!(impact_ok, "selecting a@0.2.0 highlights exactly the 3 impacted callers");
    assert!(rings_ok, "the 3 impacted callers get a red ring; the non-breaking dependent none");

    // (5) The what-breaks summary line paints the impacted count.
    let impacted_summary_seen = seen(&harness, "3 impacted callers");
    assert!(impacted_summary_seen, "the what-breaks summary line paints the impacted count");

    let ok = graph_ok
        && graph_header_seen
        && colours_ok
        && base_summary_seen
        && impact_ok
        && rings_ok
        && impacted_summary_seen;
    emit(
        "coderoom-pane",
        ok,
        &format!(
            "graph={graph_ok} header={graph_header_seen} colours={colours_ok} \
             base_summary={base_summary_seen} impact={impact_ok} rings={rings_ok} \
             impacted_summary={impacted_summary_seen}"
        ),
    );
    assert!(ok, "every world-coderoom layer reached the render");
}

/// **What-changed (snapshot-diff) mode** — the second matrix cell. Renders the REAL
/// pane over the stub seam, toggles into the snapshot-diff view, and asserts off the
/// live AccessKit tree + `state_json` that:
/// 1. the stub diff reaches the pane (`f@0.1.0` added, `d@0.9.0` removed, `a@0.2.0`
///    changed with `+2 −1 ~3` symbol churn);
/// 2. each row paints as a real widget (green `+ added`, red `− removed`, amber
///    `~ changed`), and the `1 added · 1 removed · 1 changed` summary paints;
/// 3. toggling back to the graph works (the reused graph header paints again);
/// 4. no crash across every frame.
///
/// RED-when-broken: neutralize the diff, a row, or the toggle and an assertion fails.
/// Matrix cell `facett-coderoom-view/coderoom-what-changed`.
#[test]
fn coderoom_pane_what_changed_mode() {
    let pane = Rc::new(RefCell::new(CoderoomPane::new(Box::new(StubCoderoomData))));

    let mut harness = Harness::builder().with_size(egui::vec2(760.0, 720.0)).build_ui({
        let pane = Rc::clone(&pane);
        move |ui| {
            egui::ScrollArea::both().show(ui, |ui| {
                pane.borrow_mut().ui(ui);
            });
        }
    });

    let seen = |h: &Harness, label: &str| h.query_all_by_label(label).count() >= 1;

    // First frame in the default graph mode — proves the toggle offer paints.
    harness.run();
    let toggle_offered = seen(&harness, "What changed");
    assert!(toggle_offered, "the what-changed toggle is offered (the host feeds a diff)");

    // Toggle into the snapshot-diff view.
    pane.borrow_mut().set_mode(CoderoomMode::WhatChanged);
    harness.run();

    // (1) The stub diff reached the pane (asserted off state_json).
    let diff_ok = {
        let guard = pane.borrow_mut();
        let s = guard.state_json();
        let d = &s["snapshot_diff"];
        s["mode"] == "what_changed"
            && s["diff_added"] == 1
            && s["diff_removed"] == 1
            && s["diff_changed"] == 1
            && d["crates_added"][0] == "f@0.1.0"
            && d["crates_removed"][0] == "d@0.9.0"
            && d["crates_changed"][0]["crate_id"] == "a@0.2.0"
            && d["crates_changed"][0]["symbols_added"] == 2
            && d["crates_changed"][0]["symbols_removed"] == 1
            && d["crates_changed"][0]["symbols_changed"] == 3
    };
    assert!(diff_ok, "the stub snapshot diff reaches the pane");

    // (2) Each diff row + the summary paint as real widgets.
    let added_seen = seen(&harness, "+ f@0.1.0");
    let removed_seen = seen(&harness, "− d@0.9.0");
    let changed_seen = seen(&harness, "~ a@0.2.0 +2 −1 ~3");
    let summary_seen = seen(&harness, "1 added · 1 removed · 1 changed");
    assert!(added_seen, "the added crate row paints (green +)");
    assert!(removed_seen, "the removed crate row paints (red −)");
    assert!(changed_seen, "the changed crate row paints with its symbol churn counts");
    assert!(summary_seen, "the what-changed summary line paints");

    // (3) Toggle back to the graph — the reused graph header paints again.
    pane.borrow_mut().set_mode(CoderoomMode::Graph);
    harness.run();
    let back_to_graph = seen(&harness, "5 nodes · 4 edges");
    assert!(back_to_graph, "toggling back to the graph re-paints the dependency graph");

    let ok = toggle_offered
        && diff_ok
        && added_seen
        && removed_seen
        && changed_seen
        && summary_seen
        && back_to_graph;
    emit(
        "coderoom-what-changed",
        ok,
        &format!(
            "toggle={toggle_offered} diff={diff_ok} added={added_seen} removed={removed_seen} \
             changed={changed_seen} summary={summary_seen} back_to_graph={back_to_graph}"
        ),
    );
    assert!(ok, "the snapshot-diff what-changed mode reached the render");
}