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/navgraph_scene.rs

//! **navgraph phase 2 on the scene container** (`.nornir/navgraph-design.md` §6).
//!
//! korp, dwarves and every other scene-based app draw their tabs and panes through
//! `facett_app::scene::render`. The scene tree is DATA, so the renderer declares the
//! navigation order from it: a children pane declares its kids in authored order, a tab
//! set declares `[tabs, *]` (the strip, then the active tab in the wildcard slot) and the
//! strip's headers in draw order. No host writes a `NavOrder` for its tabs and panes.
//!
//! This proves it on a korp-shaped scene (a rail beside a three-tab workspace):
//! 1. the spine (every scene pane, the strip and its headers) shows no `~` and no `?`;
//! 2. the numbers are the authored order: rail `1.1`, workspace `1.2`, strip `1.2.1`,
//!    headers `1.2.1.1..3`, the active tab `1.2.2` whichever tab is active;
//! 3. `Ctrl+G 1.2.1.3 Enter` focuses the third header.
//!
//! ```bash
//! cargo test -p facett-app --test navgraph_scene
//! ```

use egui::{Key, Modifiers};
use egui_kittest::Harness;
use facett_app::scene::{self, Axis, NavState, Pane, Scene, SceneHost};
use facett_core::navgraph::{self, NavKind, NavMode};
use serde_json::Value;

struct Host;
impl SceneHost for Host {
    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"
    }
}

fn korpish() -> Scene {
    Scene::new(
        "navgraph-scene",
        Pane::split(
            "shell",
            Axis::Horizontal,
            0.25,
            vec![
                Pane::slot("rail", "facets").title("Facets"),
                Pane::tabs(
                    "work",
                    0,
                    vec![
                        Pane::slot("cases", "cases").title("Cases"),
                        Pane::slot("map", "map").title("Map"),
                        Pane::slot("jobs", "jobs").title("Jobs"),
                    ],
                ),
            ],
        ),
    )
}

struct St {
    scene: Scene,
    nav: NavState,
}

fn harness() -> Harness<'static, St> {
    facett_core::devid::force_gate(true);
    let scene = korpish();
    let nav = NavState::new(&scene);
    let mut h = Harness::builder().with_size(egui::vec2(1000.0, 700.0)).build_ui_state(
        |ui, st: &mut St| {
            scene::render(&st.scene, &mut st.nav, ui, &mut Host);
        },
        St { scene, nav },
    );
    navgraph::set_mode(&h.ctx, NavMode::Labels);
    h.run_steps(3);
    h
}

/// Is node `i` inside a host-owned pane (phase 3), below the scene's own spine?
fn in_host_pane(g: &navgraph::NavGraph, i: usize) -> bool {
    let mut at = g.nodes()[i].parent;
    while let Some(p) = at {
        if g.nodes()[p as usize].kind == NavKind::Pane {
            return true;
        }
        at = g.nodes()[p as usize].parent;
    }
    false
}

fn number(g: &navgraph::NavGraph, id: &str) -> String {
    g.find_id(id).unwrap_or_else(|| panic!("no node {id}: {}", ids(g))).number.clone()
}

fn ids(g: &navgraph::NavGraph) -> String {
    g.nodes().iter().map(|n| format!("{}={}", n.id, n.number)).collect::<Vec<_>>().join(" ")
}

#[test]
fn the_scene_tree_is_the_declared_order() {
    let h = harness();
    navgraph::with(&h.ctx, |g| {
        assert_eq!(number(g, "shell"), "1");
        assert_eq!(number(g, "shell.rail"), "1.1");
        assert_eq!(number(g, "shell.work"), "1.2");
        assert_eq!(number(g, "shell.work.tabs"), "1.2.1");
        assert_eq!(number(g, "shell.work.tabs.cases"), "1.2.1.1");
        assert_eq!(number(g, "shell.work.tabs.map"), "1.2.1.2");
        assert_eq!(number(g, "shell.work.tabs.jobs"), "1.2.1.3");
        assert_eq!(number(g, "shell.work.cases"), "1.2.2", "the active tab takes the wildcard slot");

        let spine_bad: Vec<String> = g
            .nodes()
            .iter()
            .enumerate()
            .filter(|(i, n)| !n.step.is_declared() && !n.harvested && !in_host_pane(g, *i))
            .map(|(_, n)| format!("{} = {}", n.id, n.number))
            .collect();
        assert!(spine_bad.is_empty(), "the scene spine shows ~ or ?: {spine_bad:?}");
    });
}

#[test]
fn switching_tabs_keeps_the_active_pane_number() {
    let mut h = harness();
    h.state_mut().nav.set_active_tab("work", 2);
    h.run_steps(3);
    navgraph::with(&h.ctx, |g| {
        assert!(g.find_id("shell.work.cases").is_none(), "the inactive tab is not drawn");
        assert_eq!(number(g, "shell.work.jobs"), "1.2.2");
    });
}

#[test]
fn the_chord_focuses_the_third_tab_header() {
    let mut h = harness();
    let want = navgraph::with(&h.ctx, |g| g.find_number("1.2.1.3").and_then(|n| n.widget)).expect("a widget");
    h.key_press_modifiers(Modifiers::CTRL, Key::G);
    h.step();
    for c in "1.2.1.3".chars() {
        h.key_press(if c == '.' { Key::Period } else { Key::from_name(&c.to_string()).unwrap() });
        h.step();
    }
    h.key_press(Key::Enter);
    h.run_steps(3);
    assert_eq!(h.ctx.memory(|m| m.focused()), Some(want), "Ctrl+G 1.2.1.3 did not focus the Jobs header");
}