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

nordisk/dwarves

dwarves-gui/tests/robot_code_road.rs

//! **ROBOT-UI: ⚒ reads the CODE over wasm, not the table** — gap #9 of
//! `.nornir/build-thing-model-design.md`, the ⚒ half.
//!
//! Two claims, driven through the real widgets over a staged constellation:
//!
//! 1. **the same recipe through the wasm and through the table is the same matrix**
//!    (law B) — `orten` resolved over its `<repo>-build-thing` cdylib lands with `how`
//!    naming the module (`kod: orten-build-thing @ <key> (wasm…)`) and its rows equal
//!    to the rows the table road renders for the identical declaration; while the wasm
//!    is being built the console shows the cargo lines and `how` says the table is
//!    answering *until* it lands;
//! 2. **a repo with no recipe crate is refused by name**, and the table is the fallback
//!    the refusal names — on `how`, on screen, never a silent second path.
//!
//! RED BEFORE GREEN: on the parent commit `how` was `hela receptet` for every recipe
//! and nothing in ⚒ ever loaded a wasm module; the `/how` assertions below fail on it.
//!
//! The wasm build is real — INSIDE the toolchain container, off the registry `nornir-build-thing` — and
//! its module is cached under `$CARGO_TARGET_DIR/dwarves-build-thing-wasm`, shared with
//! `crates/dwarves/tests/code_road.rs` — whichever runs first pays the build.

#![cfg(not(target_arch = "wasm32"))]

use std::time::{Duration, Instant};

use facett_core::Facet;
use nornir_robotui::egui_kittest::Harness;
use nornir_robotui::{Observable, RobotSession};

use dwarves::build_thing::code::testing;
use dwarves_gui::build_forge::DeclaredForge;
use dwarves_gui::build_scene::BuildScene;

struct SceneApp(BuildScene);

impl Observable for SceneApp {
    fn state_json(&self) -> serde_json::Value {
        Facet::state_json(&self.0)
    }
}

fn session(root: &std::path::Path) -> RobotSession<'_, SceneApp> {
    let scene = BuildScene::new(Box::new(DeclaredForge::at(root.to_path_buf())));
    let harness = Harness::builder()
        .with_size(nornir_robotui::egui::Vec2::new(1400.0, 1000.0))
        .build_ui_state(
            |ui, app: &mut SceneApp| Facet::ui(&mut app.0, ui),
            SceneApp(scene),
        );
    let mut robot = RobotSession::new(harness, "dwarves-gui");
    robot.run(2);
    robot
}

fn resolve(robot: &mut RobotSession<'_, SceneApp>, name: &str) {
    robot
        .type_into("artefakt", name)
        .expect("the artefakt field is reachable");
    robot
        .click_label("sök")
        .expect("the sök button is reachable");
    robot.run(2);
}

fn how(robot: &RobotSession<'_, SceneApp>) -> String {
    robot.state()["how"]
        .as_str()
        .unwrap_or_default()
        .to_string()
}

/// The rows as the pane renders them — names, badges, columns and their settings —
/// which is what law B is about.
fn rows(robot: &RobotSession<'_, SceneApp>) -> serde_json::Value {
    robot.state()["rows"].clone()
}

/// Ten minutes for a cold wasm32 build; a cached one lands on the first frame.
const DEADLINE: Duration = Duration::from_secs(600);

#[test]
fn the_matrix_through_the_wasm_is_the_matrix_through_the_table_and_says_which() {
    // ── THE TABLE ROAD, as the reference: the identical declaration, no crate ──
    let table_dir = tempfile::tempdir().unwrap();
    testing::stage_orten(table_dir.path(), None);
    let mut table = session(table_dir.path());
    resolve(&mut table, "orten");
    let table_how = how(&table);
    assert!(
        table_how.contains("kodvägen VÄGRAD") && table_how.contains("`orten-build-thing`"),
        "no crate ⇒ refused BY NAME on `how`: {table_how:?}"
    );
    assert!(
        table_how.contains("toml"),
        "the fallback is named: {table_how:?}"
    );
    let table_rows = rows(&table);
    assert_eq!(table_rows.as_array().map(Vec::len), Some(2), "{table_rows}");

    // ── THE CODE ROAD ─────────────────────────────────────────────────────────
    let code_dir = tempfile::tempdir().unwrap();
    let repo = testing::stage_orten(code_dir.path(), Some(true));
    testing::lock_fixture(&repo).expect("the fixture's lockfile (host scaffolding, not the build)");
    let mut robot = session(code_dir.path());
    resolve(&mut robot, "orten");
    let first = how(&robot);
    assert!(
        first.starts_with("hela receptet · "),
        "the first half of `how` is still the resolve's own: {first:?}"
    );
    let building = first.contains("tills `orten-build-thing` byggt till wasm");
    let cached = first.contains("kod: orten-build-thing @");
    assert!(
        building || cached,
        "the code road is either in flight or answered from the cache: {first:?}"
    );
    if building {
        // The table answers meanwhile — and SAYS so — and the console shows cargo.
        assert_eq!(
            rows(&robot),
            table_rows,
            "the table's rows while the wasm builds"
        );
        let lines = robot.state()["lines"].clone();
        assert!(
            lines.as_array().is_some_and(|l| l.iter().any(|x| x
                .as_str()
                .is_some_and(|s| s.starts_with("── kod · cargo build")))),
            "the wasm build is a build like any other, on the console: {lines}"
        );
    }

    // Wait for the landing — the scene polls the forge once a frame.
    let until = Instant::now() + DEADLINE;
    while !how(&robot).contains("kod: orten-build-thing @") {
        // A refusal is terminal and NAMED — the container road never falls back to host
        // cargo, so a box whose toolchain image lacks wasm32 reads its own fix here.
        let h = how(&robot);
        if h.contains("kodvägen VÄGRAD") {
            // The toolchain image on this box carries no std for wasm32: the refusal must
            // NAME the fix, and the table answers with the refusal on it. Any other
            // refusal is red.
            assert!(
                h.contains("rustup target add wasm32-unknown-unknown"),
                "the code road was refused for another reason than the image's wasm32 std: {h}"
            );
            assert_eq!(
                rows(&robot),
                table_rows,
                "the table answers, and says so, when the container refuses"
            );
            eprintln!("SKIP law B on screen — the toolchain image lacks wasm32 std; the refusal names the fix:\n{h}");
            return;
        }
        assert!(
            Instant::now() < until,
            "the wasm never landed; how = {:?}",
            how(&robot)
        );
        std::thread::sleep(Duration::from_millis(150));
        robot.run(1);
    }
    let landed = how(&robot);
    assert!(landed.contains("(wasm"), "{landed:?}");
    assert!(
        !landed.contains('⚠'),
        "law B: no drift between code and table: {landed:?}"
    );

    // LAW B, ON SCREEN: the rows the code answers are the rows the table declares.
    assert_eq!(
        rows(&robot),
        table_rows,
        "the same matrix through the wasm and the table"
    );

    // A row is still pickable after the swap — every existing click survives.
    robot
        .click_label("orten-server")
        .expect("the row is clickable after the swap");
    robot.run(2);
    assert_eq!(robot.state()["row"].as_str(), Some("orten-server"));
}