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

nordisk/facett

facett-app/tests/devid_scene_slots.rs

//! **Every scene-hosted pane paints its DEV-ID** — proven off the live AccessKit tree.
//!
//! `facett-core`'s `devid` unit tests prove the text is right, and facett-demo's prove
//! the `FacetDeck` path paints. Neither covers `facett_app::scene`, which is the
//! container korp and the other scene-based apps actually use — a badge wired only into
//! the deck would show nothing there while every existing test stayed green. That is
//! exactly how the first version of this feature shipped (wired into `Deck::pane_ui`,
//! a path facett-demo never calls).
//!
//! ```bash
//! cargo test -p facett-app --test devid_scene_slots
//! cargo test --release -p facett-app --test devid_scene_slots   # ← must be green too
//! ```
//!
//! # Why every test here opens the gate itself
//!
//! The chip is off by default in a release build (see [`facett_core::devid`]: the aid
//! ships behind `FACETT_DEVID=1`). This file is about the CONTAINER — does
//! `scene::render` paint a chip per pane, does it name the right pane, does it mark the
//! focused one — and that question has the same answer in both profiles. But the
//! assertions could only observe it while the gate happened to be open, so a plain
//! `cargo test --release --workspace` turned **4 of these 5 tests red** (measured
//! 2026-08-31) for a reason that had nothing to do with the scene container. A red
//! nobody can act on is a red nobody reads.
//!
//! So each test calls [`force_devid_on`] first. The alternatives were both worse: a
//! crate-level `#![cfg(debug_assertions)]` makes this file compile to zero tests in
//! exactly the profile that ships, and making each assertion conditional on
//! `devid::enabled()` replaces four positive proofs with "assert nothing when the gate
//! is closed" — a green produced by looking at nothing.
//!
//! Forcing does **not** hide the profile default, which is the bug that shipped once
//! already. That rule keeps its own two guards, neither of which this file touches:
//! `facett_core::devid`'s `resolve_enabled` table, and `tests/devid_release_profile.rs`,
//! which builds a real `--release` binary and proves the chip stays off until the env
//! hatch opens it.

use egui_kittest::kittest::{By, Queryable};
use egui_kittest::Harness;
use facett_app::scene::{self, Axis, NavState, Pane, Scene, SceneHost};
use facett_core::devid::{DEVID_ATOM, UNREGISTERED};
use serde_json::Value;

/// Open the dev-id gate for this test binary, whatever profile it was compiled in.
///
/// `devid::force_gate` panics if the gate was already decided as something else, so this
/// cannot quietly leave a test asserting against a closed gate — and every caller in this
/// file asks for the same value, so the order libtest runs them in does not matter.
fn force_devid_on() {
    facett_core::devid::force_gate(true);
}

/// A host that declares a REGISTERED component (`korp` is in the errcode registry).
struct Korpish;
impl SceneHost for Korpish {
    fn slot(&mut self, id: &str, ui: &mut egui::Ui) -> Value {
        ui.label(format!("content of {id}"));
        Value::Null
    }
    fn component(&self) -> &'static str {
        "korp"
    }
}

/// A host that declares NOTHING — must render the honest marker, never a plausible id.
struct Undeclared;
impl SceneHost for Undeclared {
    fn slot(&mut self, id: &str, ui: &mut egui::Ui) -> Value {
        ui.label(format!("content of {id}"));
        Value::Null
    }
}

fn scene_with_two_slots() -> Scene {
    Scene::new(
        "devid-test",
        Pane::split(
            "root",
            Axis::Horizontal,
            0.5,
            vec![
                Pane::slot("ingest", "ingest_load").title("Ingest"),
                Pane::slot("map", "map").title("Map"),
            ],
        ),
    )
}

#[test]
fn a_declared_host_paints_component_slash_slot_on_every_pane() {
    force_devid_on();
    let scene = scene_with_two_slots();
    let mut nav = NavState::new(&scene);
    let mut host = Korpish;
    let mut harness = Harness::new_ui(move |ui| {
        scene::render(&scene, &mut nav, ui, &mut host);
    });
    harness.run();

    let any = harness.query_all(By::new().label_contains(DEVID_ATOM)).count();
    assert!(any > 0, "no dev-id atom — the scene slot path does not paint the badge");

    // Each pane must be tellable apart. A component-only chip would be identical on
    // both, which is no more use than no chip at all.
    for slot in ["ingest_load", "map"] {
        let n = harness.query_all(By::new().label_contains(&format!("korp/{slot}"))).count();
        assert!(n > 0, "no chip reading korp/{slot}");
    }
    assert_eq!(
        harness.query_all(By::new().label_contains(UNREGISTERED)).count(),
        0,
        "a REGISTERED host rendered the unregistered marker"
    );
}

#[test]
fn an_undeclared_host_is_marked_not_given_a_plausible_id() {
    force_devid_on();
    let scene = scene_with_two_slots();
    let mut nav = NavState::new(&scene);
    let mut host = Undeclared;
    let mut harness = Harness::new_ui(move |ui| {
        scene::render(&scene, &mut nav, ui, &mut host);
    });
    harness.run();

    assert!(
        harness.query_all(By::new().label_contains(UNREGISTERED)).count() > 0,
        "a host with no component must say so, not invent an id"
    );
}

/// **A RELEASE build must be able to show the chips.**
///
/// The badge was gated on `debug_assertions` alone, so the installed release binaries —
/// the ones actually run on this fleet — painted nothing while every test above stayed
/// green. The tests all ran in debug. A debugging aid that only exists in builds nobody
/// runs is not a debugging aid, and no test could see the gap because the tests are the
/// one place `debug_assertions` is always true.
///
/// This asserts the ESCAPE HATCH exists and that the gate honours it in both
/// directions, which is the part a debug-only test can still prove.
///
/// **It used to prove nothing.** The earlier body read
/// `let parsed = matches!(v, "1" | "true" | "on" | "yes"); assert_eq!(parsed, want)` —
/// it re-implemented the rule inside the test and then compared its own copy against the
/// expectation, never touching `devid`. Deleting the entire gate from `devid.rs` would
/// have left it green. It now calls the SHIPPED decision,
/// [`facett_core::devid::resolve_enabled`], with `debug_assertions = false` — the branch
/// `cargo test` can otherwise never reach.
#[test]
fn the_chip_can_be_forced_on_or_off_regardless_of_build_profile() {
    use facett_core::devid::resolve_enabled;

    // The PROFILE DEFAULT — debug shows the chip without being asked. It is asserted on
    // `resolve_enabled`, not on `enabled()`, for two reasons: `debug_assertions` is a
    // compile-time fact so `enabled()` can only ever report the profile this binary was
    // built in, and this binary forces the gate open (module doc) so `enabled()` would be
    // true here by construction — a line that cannot fail. `enabled()` is a one-liner
    // over this function, so there is still exactly one copy of the rule.
    assert!(resolve_enabled(None, true), "debug + unset: the chip is on by default");

    // RELEASE profile — the build that ships. Off unless asked, on when asked.
    assert!(!resolve_enabled(None, false), "release + unset: the chip stays off");
    for v in ["1", "true", "on", "yes"] {
        assert!(resolve_enabled(Some(v), false), "FACETT_DEVID={v} must open the release hatch");
    }
    // DEBUG profile — on by default, and a developer can still silence it.
    for v in ["0", "false", "off", "no"] {
        assert!(!resolve_enabled(Some(v), true), "FACETT_DEVID={v} must silence it in debug");
    }
}

/// **The gate must be readable WITHOUT a screenshot.**
///
/// When the chips did not appear, nobody could tell from outside the process whether the
/// gate was even open — so the diagnosis ran on inference for three rounds. `render`'s
/// `state_json` now carries `devid`, which is what korp's `$KORP_STATE` dump exposes.
///
/// This is a narrowing tool, not a verdict: `enabled: true` says the switch is on, and
/// both earlier misses (badge wired into a path nobody calls; badge absent from this very
/// container) would ALSO have reported `enabled: true`. Only looking at the window closes
/// that gap — see `tests/devid_release_profile.rs` for the assertion on applied output.
#[test]
fn the_scene_state_reports_the_devid_gate() {
    force_devid_on();
    let scene = scene_with_two_slots();
    let mut nav = NavState::new(&scene);
    let mut host = Korpish;
    let mut harness = Harness::new_ui(move |ui| {
        let state = scene::render(&scene, &mut nav, ui, &mut host);
        ui.data_mut(|d| d.insert_temp(egui::Id::new("devid-state"), state.to_string()));
    });
    harness.run();
    let dumped: String =
        harness.ctx.data_mut(|d| d.get_temp(egui::Id::new("devid-state")).unwrap_or_default());
    let state: Value = serde_json::from_str(&dumped).expect("render returned JSON");

    let gate = &state["devid"];
    assert!(!gate.is_null(), "scene state_json carries no `devid` gate: {state:#}");
    assert_eq!(
        gate["enabled"],
        Value::Bool(facett_core::devid::enabled()),
        "the scene reports a gate that disagrees with the one badge() obeys: {gate}"
    );
    assert_eq!(
        gate["debug_assertions"],
        Value::Bool(cfg!(debug_assertions)),
        "the reported build profile is not this binary's: {gate}"
    );
    assert!(gate.get("env").is_some(), "`env` must be present (null when unset): {gate}");
    // `forced` is the third legal value and the one this binary produces (see the module
    // doc). It has to be its own answer: a forced gate reporting `profile` would be a
    // dump asserting this build's profile said something it did not.
    assert_eq!(
        gate["source"], "forced",
        "this binary forces the gate, so the dump must SAY forced — not launder it as \
         the profile's own answer: {gate}"
    );
    assert!(
        matches!(gate["source"].as_str(), Some("env" | "profile" | "forced")),
        "`source` must name who decided the gate: {gate}"
    );
}

/// **Which pane is the robot in?** — the focus mark, asserted off the tree.
///
/// The owner's ask was to make the chip flash when its pane has focus, "nice for robot
/// tests". A visible highlight helps a human watching a drive, but the useful half is that
/// a driver can ASK — `label_contains("[focused]")` answers "which pane am I in" instead of
/// inferring it from whatever it clicked last.
///
/// Deliberately pointer-containment, not an animation: the render oracle compares snapshot
/// images, so anything time-based would make every snapshot differ from the last and the
/// oracle would be worthless. A scripted pointer is deterministic.
#[test]
fn the_focused_pane_says_so_in_the_tree_and_the_others_do_not() {
    use facett_core::devid::FOCUSED_MARK;

    force_devid_on();
    let scene = scene_with_two_slots();
    let mut nav = NavState::new(&scene);
    let mut host = Korpish;
    let mut harness = Harness::builder()
        .with_size(egui::vec2(800.0, 600.0))
        .build_ui(move |ui| {
            scene::render(&scene, &mut nav, ui, &mut host);
        });
    harness.run();

    // No pointer anywhere ⇒ nothing claims focus. A chip that were always focused would
    // pass the positive half below while telling a driver nothing.
    harness.remove_cursor();
    harness.run();
    assert_eq!(
        harness.query_all(By::new().label_contains(FOCUSED_MARK)).count(),
        0,
        "with no pointer on screen, no pane may claim focus"
    );

    // Pointer into the LEFT pane (the scene is a 0.5 horizontal split, so x=100 is left).
    harness.hover_at(egui::pos2(100.0, 300.0));
    harness.run();
    let focused: usize = harness.query_all(By::new().label_contains(FOCUSED_MARK)).count();
    assert!(focused > 0, "the pane under the pointer must carry {FOCUSED_MARK}");

    // And it must be the INGEST one, not the map one — a mark on every chip is no signal.
    let ingest_focused = harness
        .query_all(By::new().label_contains(&format!("korp/ingest_load {FOCUSED_MARK}")))
        .count();
    assert!(ingest_focused > 0, "the LEFT pane (ingest_load) is the one under the pointer");
    let map_focused =
        harness.query_all(By::new().label_contains(&format!("korp/map {FOCUSED_MARK}"))).count();
    assert_eq!(map_focused, 0, "the pane the pointer is NOT in must not claim focus");
}