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_build_scene_fixture.rs

//! **ROBOT-UI: the DECLARED FIXTURE affordance, through real widgets.**
//!
//! A recipe may declare that a per-site parameter can be filled by a fixture the forge
//! mints itself (`[build.installer.fixtures] hub_conf = "wg-hub"`). The scene must offer
//! that — and ONLY that — where the recipe says so: the scene knows no fixture by name
//! any more than it knows a product by name. So this file stages a constellation with one
//! installer cell declaring two parameters, one with a fixture and one without, and
//! drives the scene through the live AccessKit tree:
//!
//! * RED — the parameter WITHOUT a declared fixture has no "använd fixtur" control at all;
//! * GREEN — the one WITH it has an addressable checkbox, unticked; ticking it puts the
//!   CLI's own `@fixture` word into the settings the build will be given;
//! * the staged text file is a MULTI-LINE value in the scene's own state (the 08-16
//!   friction: a hub.conf in a single-line field);
//! * the result row for an applied fixture is covered by `robot_build_scene.rs`
//!   (`outcome.applied`), since pressing BYGG here would start a real build.

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

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)
    }
}

/// The fixture's label as the recipe below spells it — asserted, never assumed, by
/// reading it back out of the scene's state.
const FIXTURE_TOGGLE: &str = "hub_conf: använd fixtur (wg-hub)";
const NO_FIXTURE_TOGGLE: &str = "site: använd fixtur (wg-hub)";

/// One checkout, one exe atom, one installer cell with two declared parameters:
/// `site` (a cmdline token, no fixture) and `hub_conf` (a staged text file WITH a
/// declared `wg-hub` fixture). Not any product's names.
fn staged() -> tempfile::TempDir {
    let dir = tempfile::tempdir().expect("tempdir");
    let repo = dir.path().join("orten");
    std::fs::create_dir_all(repo.join(".nornir")).expect("mkdir");
    std::fs::write(
        repo.join("Cargo.toml"),
        "[package]\nname = \"orten\"\nversion = \"1.0.0\"\nedition = \"2021\"\n\n[dependencies]\n",
    )
    .expect("manifest");
    std::fs::write(
        repo.join("Cargo.lock"),
        "version = 4\n\n[[package]]\nname = \"orten\"\nversion = \"1.0.0\"\n",
    )
    .expect("lockfile");
    std::fs::write(
        repo.join(".nornir").join("build-variants.toml"),
        r#"
[build]
base_suite = "ORIGINAL"

[[build.variant]]
name = "orten-server"
package = "orten"
bin = "orten-server"
role = "server"
default = true
suites = ["ORIGINAL"]
kind = "exe"

# The installer pointer variant: carries the cell's name (the Iso shape).
[[build.variant]]
name = "orten-installer"
role = "server"
default = false
suites = ["ORIGINAL"]
kind = "installer"
linkage = "fat"

[[build.installer]]
name = "orten-installer"
variant = "orten-server"
volume_id = "ORTEN_INSTALL"
boot_params = ["korp.entry=orten"]

[[build.installer.params]]
name = "site"
label = "Site name"
kind = "text"
required = false
boot_param = "korp.site"

[[build.installer.params]]
name = "hub_conf"
label = "Hub declaration (public data)"
kind = "text"
required = false
staged_file = "hub.conf"

[build.installer.fixtures]
hub_conf = "wg-hub"
"#,
    )
    .expect("declaration");
    dir
}

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
}

/// The settings the installer CELL's column declares, as the scene's state carries
/// them. The cell hangs off the row of the appliance it LAYS DOWN (`variant =
/// "orten-server"`), as column `installer:<cell>` — the pointer variant's own row
/// carries the kind, not the cell's declared inputs.
fn installer_settings(robot: &RobotSession<'_, SceneApp>) -> (String, Vec<serde_json::Value>) {
    let rows = robot.state_at("/rows").expect("rows");
    for row in rows.as_array().expect("rows is a list") {
        if row["name"] != "orten-server" {
            continue;
        }
        for t in row["targets"].as_array().expect("targets") {
            let name = t["name"].as_str().unwrap_or_default();
            if t["kind"] == "installer" && t["declared"] == true && name.starts_with("installer:") {
                return (
                    name.to_string(),
                    t["settings"].as_array().cloned().unwrap_or_default(),
                );
            }
        }
    }
    panic!("no declared installer:<cell> column on the orten-server row: {rows}");
}

#[test]
fn the_fixture_toggle_is_offered_only_where_the_recipe_declares_it_and_supplies_at_fixture() {
    let dir = staged();
    let mut robot = session(dir.path());

    robot
        .type_into("artefakt", "orten-server")
        .expect("the artefakt field");
    robot.click_label("sök").expect("the sök button");
    robot.run(2);
    robot
        .click_label("orten-server")
        .expect("the appliance row is a real widget");
    robot.run(2);
    let (column, settings) = installer_settings(&robot);
    robot
        .click_label(&column)
        .expect("the installer column is clickable");
    robot.run(3);
    assert_eq!(robot.state_at("/target"), Some(serde_json::json!(column)));

    // ── the DECLARATION reached the scene's state, verbatim ─────────────────
    let hub = settings
        .iter()
        .find(|s| s["key"] == "hub_conf")
        .expect("hub_conf declared");
    let site = settings
        .iter()
        .find(|s| s["key"] == "site")
        .expect("site declared");
    assert_eq!(
        hub["fixture"], "wg-hub",
        "the declared fixture, by its label: {hub}"
    );
    assert_eq!(
        hub["multiline"], true,
        "a staged text file is a multi-line value: {hub}"
    );
    assert_eq!(
        site["fixture"],
        serde_json::Value::Null,
        "no fixture declared for site: {site}"
    );
    assert_eq!(
        site["multiline"], false,
        "a cmdline token is one line: {site}"
    );

    // ── RED: no toggle where no fixture is declared ───────────────────────
    assert!(
        !robot.label_visible(NO_FIXTURE_TOGGLE),
        "a fixture toggle was drawn for `site`, whose recipe declares none — an affordance \
         nobody can honestly use is the same defect as an invented column"
    );

    // ── GREEN: an addressable, unticked checkbox where one IS declared ───
    assert!(
        robot.label_visible(FIXTURE_TOGGLE),
        "the declared fixture has no control at all"
    );
    assert_eq!(
        robot.field_checked(&Target::Label(FIXTURE_TOGGLE.into())),
        Some(false),
        "the fixture control is not a checkbox with a state"
    );
    assert_ne!(
        robot.state_at("/settings/hub_conf"),
        Some(serde_json::json!("@fixture")),
        "@fixture was supplied before anybody asked for it"
    );

    // Tick it: the settings the build is given now carry the CLI's own word.
    robot
        .click_label(FIXTURE_TOGGLE)
        .expect("the fixture checkbox is clickable");
    robot.run(2);
    assert_eq!(
        robot.field_checked(&Target::Label(FIXTURE_TOGGLE.into())),
        Some(true),
        "clicking the checkbox did not change the WIDGET's state"
    );
    assert_eq!(
        robot.state_at("/settings/hub_conf"),
        Some(serde_json::json!("@fixture")),
        "the tick never reached the settings the build would be given"
    );
    assert_eq!(robot.state_at("/can_build"), Some(serde_json::json!(true)));

    // Untick: back to an operator's empty field, not a stale sentinel.
    robot
        .click_label(FIXTURE_TOGGLE)
        .expect("the fixture checkbox is clickable twice");
    robot.run(2);
    assert_eq!(
        robot.state_at("/settings/hub_conf"),
        Some(serde_json::json!(""))
    );
}