#![cfg(feature = "viz")]
use std::path::PathBuf;
use std::sync::Mutex;
use nornir::viz::control::{self, VizCommand};
use nornir::viz::UrdrThreadsApp;
use nornir::warehouse::test_results::{status, TestResultRow};
fn mk(run: &str, repo: &str, ts: i64, aspect: &str, name: &str, st: &str) -> TestResultRow {
TestResultRow {
run_id: run.into(),
repo: repo.into(),
suite: repo.into(),
test_name: name.into(),
status: st.into(),
duration_ms: 4.0,
ts_micros: ts,
message: String::new(),
aspect: aspect.into(),
metric: 0.0,
}
}
static CHANNEL_LOCK: Mutex<()> = Mutex::new(());
fn build_app() -> UrdrThreadsApp {
let warehouse_root =
std::env::temp_dir().join(format!("nornir-viz-robot-{}", std::process::id()));
let _ = std::fs::create_dir_all(&warehouse_root);
UrdrThreadsApp::with_repos(
warehouse_root,
"nornir".to_string(),
PathBuf::new(),
vec!["nornir".to_string()],
)
}
fn seed_test_rows(app: &mut UrdrThreadsApp) {
app.inject_test_results_for_test(vec![
mk("nornir-run", "nornir", 300, "build", "build", status::PASS),
mk("nornir-run", "nornir", 300, "unit", "wh::a", status::PASS),
]);
}
fn tick(app: &mut UrdrThreadsApp) {
let ctx = egui::Context::default();
let _ = ctx.run(egui::RawInput::default(), |ctx| app.draw_ui(ctx));
}
#[test]
fn robot_loop_clicks_test_tab_through_the_control_channel() {
let _guard = CHANNEL_LOCK.lock().unwrap_or_else(|p| p.into_inner());
let cmd_file = std::env::temp_dir().join(format!("nornir-viz-cmd-robot-{}.json", std::process::id()));
std::env::set_var("NORNIR_VIZ_CMD", &cmd_file);
let _ = std::fs::remove_file(&cmd_file);
let mut app = build_app();
seed_test_rows(&mut app);
app.apply_command(&VizCommand { tab: Some("Timeline".into()), workspace: None, palette: None });
assert_eq!(app.state_json()["tab"].as_str(), Some("Timeline"), "precondition: on Timeline");
control::write_command(&VizCommand { tab: Some("Test".into()), workspace: None, palette: None })
.expect("write viz command");
assert!(cmd_file.exists(), "command file written by viz.click");
tick(&mut app);
assert!(!cmd_file.exists(), "the viz consumed (removed) the command file");
let state = app.state_json();
assert_eq!(state["tab"].as_str(), Some("Test"), "click switched to the Test tab");
let tabs: Vec<&str> = state["all_tabs"].as_array().unwrap().iter().filter_map(|t| t.as_str()).collect();
assert!(tabs.contains(&"Test"), "Test present in all_tabs");
let test = &state["test"];
assert!(test["error"].is_null(), "test pane loaded without error");
let runs = test["runs"].as_array().expect("test.runs array");
assert!(!runs.is_empty(), "the seeded run is rendered, got {runs:?}");
assert_eq!(runs[0]["run_id"].as_str(), Some("nornir-run"), "the seeded run id surfaces");
let grid_repos: Vec<&str> = test["grid"]["repos"].as_array().unwrap().iter().filter_map(|r| r.as_str()).collect();
assert_eq!(grid_repos, vec!["nornir"], "the grid renders the seeded repo row");
let trail: Vec<String> = state["action_log"]["recent"].as_array().unwrap()
.iter().filter_map(|e| e.as_str().map(String::from)).collect();
assert!(
trail.iter().any(|l| l.contains("viz.click tab → Test")),
"the viz.click drive is in the action trail: {trail:?}"
);
let _ = std::fs::remove_file(&cmd_file);
}
#[test]
fn robot_loop_switches_workspace_via_command() {
let _guard = CHANNEL_LOCK.lock().unwrap_or_else(|p| p.into_inner());
let cmd_file = std::env::temp_dir().join(format!("nornir-viz-cmd-ws-{}.json", std::process::id()));
std::env::set_var("NORNIR_VIZ_CMD", &cmd_file);
let _ = std::fs::remove_file(&cmd_file);
let mut app = build_app();
assert_eq!(app.state_json()["workspace_picker"]["selected"].as_str(), Some("nornir"));
control::write_command(&VizCommand { tab: None, workspace: Some("korp".into()), palette: None })
.expect("write viz command");
tick(&mut app);
let state = app.state_json();
assert_eq!(
state["workspace_picker"]["selected"].as_str(),
Some("korp"),
"viz.click switched the selected workspace"
);
let _ = std::fs::remove_file(&cmd_file);
}