ek-ui 0.9.21

ek 生态常驻主界面(egui/eframe):插件即界面——观测页 + 日志页,车头与 UI 同插件
Documentation
//! 有界面面**全流程实跑**(我操作,kittest 驱动真实 StageApp + 真实插件壳)。
//!
//! 链路口(全产品路,不造手搭壳;0.9.10 一钮收口后):
//!   配置面首屏 → 点「启动运行」(先按勾选装配,再起跑)→ 主界面登场 + 锁置位
//!   → 关窗收尾。
//! 参数面/档案包/导出启动清单 = 0.9.10 用户裁定连功能铲去,对应流程步随退场。
//! 口令铁律:本判据不碰密码面(密库引用原样过手,永不读值)。
//!
//! 跑法(开发版,零打包):`cargo test --test ui_stage_flow -- --nocapture`

use egui_kittest::Harness;
use egui_kittest::kittest::Queryable;
use ek_core::main_engine::MainEngine;
use ek_ui::plugin::UI_NAME;
use ek_ui::runtime::{Stage, StageApp, ui_kinds};
use ek_ui::state::UiShared;
use std::cell::RefCell;
use std::rc::Rc;
use std::sync::Arc;

fn temp_store(tag: &str) -> ek_cfg::Store {
    let root = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
        .join("target")
        .join("tmp")
        .join(format!("flow-{}", tag));
    let _ = std::fs::remove_dir_all(&root);
    // 缺省根同钉(2026-10-29 全链闸抓到):传进 `new_setup` 的 store 只到 cfg 收件壳,
    // UI 插件自己的 `ui` 段读写走 `ui_cfg_store()`(= `Store::eco()`),不吃这个参数。
    // 不钉这一下,本判据装配一次就把**真档案根**的 `ui` 段重写一遍(值未变、rev/mtime 变了)。
    ek_cfg::set_default_root_for_tests(root.clone());
    ek_cfg::Store::with_root(root)
}

#[test]
#[allow(non_snake_case)]
fn U_S_FLOW_full_desktop_walk() {
    let engine = Arc::new(MainEngine::new(None));
    let shared = Arc::new(UiShared::new());
    let store = temp_store("flow");
    let app = StageApp::new_setup(
        Arc::clone(&engine),
        Arc::clone(&shared),
        ui_kinds(&shared, true),
        Some(store),
        None,
    );
    let cell = Rc::new(RefCell::new(app));
    let mut h = Harness::new_ui_state(
        {
            let c = Rc::clone(&cell);
            move |ui, ()| {
                c.borrow_mut().render_setup_panel(ui);
            }
        },
        (),
    );
    let _ = h.run();

    // ① 首屏 = 配置面:无主界面菜单,有启动钮;tq 摘掉(判据不起侧车)
    assert!(
        h.query_by_label_contains("运维").is_none(),
        "首屏不许出主界面"
    );
    assert!(h.query_by_label("启动运行").is_some());
    cell.borrow_mut().set_selected("tq", false);
    let _ = h.run();

    // ② 启动运行(一钮两义:装配 → 起跑)→ 装配落壳 + 转运行相 + 锁置位
    h.get_by_label("启动运行").click_accesskit();
    let _ = h.run();
    {
        let a = cell.borrow();
        assert!(a.assembled(), "启动钮要先装配落壳");
        assert_eq!(a.stage(), Stage::Running, "装配成功即起跑转运行相");
    }
    let names = engine.plugin_names();
    println!("[flow] 装配在册:{}", names.join(","));
    assert!(names.iter().any(|n| n == "dm") && names.iter().any(|n| n == "ctp"));
    // 步骤 1 判据钉(桌面相注入):勾选清单必含 cfg(系统注入,不吃勾选面)。
    assert!(names.iter().any(|n| n == "cfg"), "装配清单必含 cfg: {names:?}");
    assert!(shared.assembly_locked(), "全壳 Running 要置锁");
    assert!(!shared.is_stop());

    // ③ 主界面真的画得出(菜单在场)
    let mut h2 = Harness::new_ui_state(
        {
            let shared = Arc::clone(&shared);
            move |ui, ()| {
                ek_ui::render_body(ui.ctx(), &shared);
            }
        },
        (),
    );
    let _ = h2.run();
    assert!(
        h2.query_by_label_contains("运维").is_some(),
        "运行相主界面要在场"
    );

    // ④ 勾选器在锁定期不可动(装配态与锁位扛住帧推进)
    let _ = h.run();
    {
        let a = cell.borrow();
        assert!(a.picked().iter().any(|n| n == UI_NAME), "ui 行恒勾");
    }
    assert!(shared.assembly_locked(), "锁位扛帧");

    // ⑤ 收尾:运行相关窗口径(引擎收尾不炸)
    drop(h2);
    drop(h);
    engine.close_all(std::time::Duration::from_secs(3));
    println!("[flow] 收尾后在册:{}", engine.plugin_names().join(","));
    let _ = std::fs::remove_dir_all(
        std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
            .join("target")
            .join("tmp"),
    );
}