use egui_kittest::kittest::Queryable;
use ek_core::Configurable;
use ek_core::cfg_face::CfgHost;
use ek_core::main_engine::MainEngine;
use ek_core::plugin::Plugin;
use ek_core::section::{Field, SectionMap};
use ek_ui::faces::{self, CfgBridge, FaceStates};
use ek_ui::pluginwin::{self, WinState};
use ek_ui::state::UiShared;
use std::sync::Arc;
fn temp_host(tag: &str) -> (Arc<UiShared>, ek_cfg::Store, std::path::PathBuf) {
let root = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
.join("target")
.join("tmp")
.join(format!("u-{}", tag));
let _ = std::fs::remove_dir_all(&root);
let cfg = ek_cfg::Store::with_root(root.clone());
let shared = Arc::new(UiShared::new());
(shared, cfg, root)
}
struct Bridge {
cfg: ek_cfg::Store,
shared: Arc<UiShared>,
}
impl CfgHost for Bridge {
fn cfg_save(&self, ns: &str, map: &SectionMap) -> Result<(), String> {
self.cfg.store(ns, map).map_err(|e| e.to_string())
}
fn cfg_load(&self, ns: &str) -> Result<Option<SectionMap>, String> {
match self.cfg.load(ns) {
Ok(ek_cfg::LoadOutcome::Loaded { map, .. }) => Ok(Some(map)),
Ok(ek_cfg::LoadOutcome::Fresh) => Ok(None),
Err(e) => Err(e.to_string()),
}
}
fn log(&self, line: &str) {
self.shared.log_line(line);
}
}
fn demo_engine(tag: &str) -> Arc<MainEngine> {
let _ = tag;
let engine = Arc::new(MainEngine::new(None));
engine
.add_plugin(Arc::new(ek_dm::plugin::DmPlugin::new(
ek_dm::plugin::DmConfig::minute("dm"),
)))
.expect("add dm");
engine
.add_plugin(Arc::new(ek_oms::plugin::OmsPlugin::new()))
.expect("add oms");
engine
}
#[test]
fn u_w1_mount_table_lists_only_configurable_plugins() {
let engine = demo_engine("w1");
let mut m = faces::mounted(&engine);
m.sort();
let names: Vec<&str> = m.iter().map(|(n, _)| n.as_str()).collect();
assert_eq!(names, vec!["dm", "oms"], "只列装配且有面的插件:{:?}", m);
}
#[test]
fn u_w2_faces_dispatch_renders_without_panic() {
let engine = demo_engine("w2");
let shared = Arc::new(UiShared::new());
let mut harness = egui_kittest::Harness::new_ui_state(
move |ui, ()| {
let mut fs = FaceStates::new();
let host = CfgBridge {
cfg: None,
shared: Arc::clone(&shared),
};
for (name, _title) in faces::mounted(&engine) {
faces::render_face(ui, &engine, &name, &host, &mut fs);
}
},
(),
);
let _frames = harness.run();
let dm_face = harness.query_by_label_contains("符号窗盘点").is_some();
let oms_face = harness.query_by_label_contains("刷新管线读数").is_some();
assert!(
dm_face && oms_face,
"面控件缺席:dm={dm_face} oms={oms_face}"
);
}
#[test]
fn u_w3_save_writes_temp_root_and_applies_live() {
let (shared, cfg, root) = temp_host("w3");
let engine = demo_engine("w3c");
let dm = engine
.get_typed::<ek_dm::plugin::DmPlugin>("dm")
.expect("dm shell");
let host = Bridge {
cfg,
shared: Arc::clone(&shared),
};
let map = SectionMap::new(1).set("intervals", Field::plain("60,300"));
host.cfg_save("dm", &map).expect("save temp root");
let rep = dm.apply_config(&map).expect("apply");
assert!(
rep.adopted.iter().any(|k| k == "intervals"),
"{:?}",
rep.adopted
);
assert_eq!(dm.intervals_snapshot(), vec![60, 300], "即时在册");
assert!(root.join("dm.ini").exists(), "临时根落了档案");
let _ = std::fs::remove_dir_all(&root);
}
#[test]
fn u_w4_bundle_export_import_roundtrip() {
let (_shared, cfg, root) = temp_host("w4");
let map = SectionMap::new(1).set("rate_limit", Field::plain("5"));
cfg.store("oms", &map).expect("seed oms section");
let m2 = SectionMap::new(1).set("theme", Field::plain("dark"));
cfg.store("ui", &m2).expect("seed ui section");
let out = root.join("bundle");
let (copied, skipped) = pluginwin::export_bundle(&root, &out).expect("export");
assert_eq!(copied, 2, "两段档案要整批走(跳过 {skipped})");
let (shared2, cfg2, root2) = temp_host("w4b");
let engine = demo_engine("w4c");
let host2 = Bridge {
cfg: cfg2,
shared: Arc::clone(&shared2),
};
let lines = pluginwin::import_bundle(&host2, &out, &|ns, sec| {
let line = match ns {
"oms" => {
let p = engine.get_typed::<ek_oms::plugin::OmsPlugin>(ns)?;
p.as_configurable()?.apply_config(sec).ok()?
}
_ => return None,
};
Some(format!(
"受理 采纳{}/待重启{}",
line.adopted.len(),
line.deferred.len()
))
});
let joined = lines.join(" | ");
assert!(joined.contains("oms:收编 1 字段"), "{joined}");
assert!(joined.contains("受理 采纳1"), "oms 段要过受理面:{joined}");
assert!(
joined.contains("ui:收编 1 字段"),
"ui 段照收编(宿主自持):{joined}"
);
assert!(
joined.contains("ekapp add-account"),
"口令引导行必须在:{joined}"
);
let _ = std::fs::remove_dir_all(&root);
let _ = std::fs::remove_dir_all(&root2);
}
#[test]
fn u_w5_generic_window_rows_from_view_when_no_schema() {
let (shared, cfg, root) = temp_host("w5");
let spec = ek_tq::plugin::TqPluginSpec::new(ek_tq::bridge::BridgeConfig::default());
let tq = ek_tq::plugin::TqPlugin::new(spec);
let map = SectionMap::new(1)
.set("url", Field::plain("ws://127.0.0.1:7709"))
.set("user", Field::secret("secret://tq/user"));
tq.apply_config(&map).expect("seed tq section");
let host = Bridge {
cfg,
shared: Arc::clone(&shared),
};
host.cfg_save("tq", &map).expect("save tq root");
let mut harness = egui_kittest::Harness::new_ui_state(
move |ui, ()| {
let mut st = WinState::new();
pluginwin::render_window(ui, &tq, &host, &mut st);
},
(),
);
let _h = harness.run();
let _ = std::fs::remove_dir_all(&root);
}