use super::*;
#[test]
fn nav_rows_include_subsections_in_sidebar_order() {
let labels: Vec<String> = App::nav_rows()
.iter()
.map(|(s, i)| match s.subs().get(*i) {
Some(sub) => format!("{}/{}", s.label(), sub),
None => s.label().to_string(),
})
.collect();
assert_eq!(
labels,
[
"Main/Overview",
"Main/Kernels",
"Stats",
"Network",
"Library",
"Benchmarks/Suite",
"Benchmarks/History",
"Terminal/Ops",
"Terminal/Chat",
"Help/Guide",
"Help/Report Issue",
]
);
}
#[test]
fn digit_keys_match_the_sidebar_order() {
use clap::Parser;
use crossterm::event::{KeyCode, KeyEvent};
let mut app = App::new(crate::cli::ServeArgs::parse_from(["met", "some/model"]));
for (i, section) in Section::ALL.iter().enumerate() {
let digit = char::from_digit(i as u32 + 1, 10).expect("<=9 sections");
app.on_key(KeyEvent::from(KeyCode::Char(digit)));
assert_eq!(
app.section,
*section,
"key {digit} must select {}",
section.label()
);
}
}
#[test]
fn benchmarks_declares_both_of_its_subsections() {
assert_eq!(Section::Benchmarks.subs(), &["Suite", "History"]);
assert_eq!(
Section::Benchmarks.icon().chars().count(),
1,
"sidebar is 1 cell wide"
);
}
#[test]
fn every_section_is_reachable() {
let rows = App::nav_rows();
for s in Section::ALL {
assert!(
rows.iter().any(|(r, _)| *r == s),
"{} unreachable",
s.label()
);
}
}
#[test]
fn chat_scroll_returns_to_follow_at_the_bottom() {
let mut c = crate::tui::chat::ChatState::default();
assert_eq!(c.scroll, None, "starts following");
c.scroll_by(3);
assert_eq!(c.scroll, Some(3));
c.scroll_by(-1);
assert_eq!(c.scroll, Some(2));
c.scroll_by(-5);
assert_eq!(c.scroll, None, "overshoot resumes follow");
c.scroll_by(10);
c.follow();
assert_eq!(c.scroll, None);
}
#[test]
fn the_watchdog_command_toggles_the_running_run_not_a_process_global() {
use clap::Parser as _;
let mut app = App::new(crate::cli::ServeArgs::parse_from(["met", "some/model"]));
let levers = std::sync::Arc::new(crate::scheduler::levers::SchedLevers::from_env(
None, None, None,
));
let other = std::sync::Arc::new(crate::scheduler::levers::SchedLevers::from_env(
None, None, None,
));
app.run = Some(crate::tui::RunHandles {
levers: levers.clone(),
snapshot: std::sync::Arc::new(metrale_speculative::snapshot::SnapshotCell::default()),
});
crate::tui::commands::execute("/watchdog on", &mut app);
assert!(levers.loop_watchdog(), "the attached run is armed");
assert!(!other.loop_watchdog(), "another run is untouched");
crate::tui::commands::execute("/watchdog off", &mut app);
assert!(!levers.loop_watchdog());
}
#[test]
fn the_watchdog_command_says_so_when_no_run_is_attached() {
use clap::Parser as _;
let mut app = App::new(crate::cli::ServeArgs::parse_from(["met", "some/model"]));
crate::tui::commands::execute("/watchdog on", &mut app);
assert!(
app.ops.output.iter().any(|l| l.contains("no run yet")),
"got {:?}",
app.ops.output
);
}
#[test]
fn a_no_argument_boot_opens_the_library_rather_than_an_empty_main() {
use clap::Parser as _;
let mut args = crate::cli::ServeArgs::parse_from(["met", "m"]);
args.model = None;
let app = App::new(args);
assert!(app.awaiting_model);
assert_eq!(app.section, Section::Library);
}
#[test]
fn a_boot_with_a_model_still_opens_main() {
use clap::Parser as _;
let args = crate::cli::ServeArgs::parse_from(["met", "org/m"]);
let app = App::new(args);
assert!(!app.awaiting_model, "a model was named");
assert_eq!(app.section, Section::Main);
}
#[test]
fn launching_from_the_library_stops_claiming_there_is_no_model() {
use clap::Parser as _;
let mut args = crate::cli::ServeArgs::parse_from(["met", "m"]);
args.model = None;
let mut app = App::new(args);
assert!(app.awaiting_model);
app.launch_selected_recipe();
assert!(app.awaiting_model, "a refused launch loaded nothing");
}
#[test]
fn changing_section_asks_for_a_full_repaint() {
use clap::Parser as _;
let mut app = App::new(crate::cli::ServeArgs::parse_from(["met", "m"]));
app.repaint = false;
app.jump(Section::Library);
assert!(app.repaint, "a real change repaints");
app.repaint = false;
app.jump(Section::Library);
assert!(
!app.repaint,
"jumping to the section already shown does not"
);
}
#[test]
fn the_kernel_table_is_not_built_before_a_model_exists() {
use clap::Parser as _;
let mut args = crate::cli::ServeArgs::parse_from(["met", "m"]);
args.model = None;
let mut app = App::new(args);
app.progress.ready = true;
app.on_tick();
assert!(app.kernels.is_none(), "nothing to describe yet");
assert!(app.kernels_for.is_none());
}
#[test]
fn the_kernel_table_is_built_once_per_model() {
use clap::Parser as _;
let mut app = App::new(crate::cli::ServeArgs::parse_from(["met", "org/a"]));
app.progress.ready = true;
app.on_tick();
assert_eq!(
app.kernels_for.as_deref(),
Some("org/a"),
"built for the model"
);
let built = app.kernels.is_some();
assert!(built);
app.on_tick();
assert_eq!(app.kernels_for.as_deref(), Some("org/a"));
}
#[test]
fn the_wheel_scrolls_every_section_that_has_anything_to_scroll() {
use crate::tui::app::{MainSub, TermSub};
use crate::tui::section::Section;
let mut a = App::new(clap::Parser::parse_from(["met", "m"]));
a.section = Section::Main;
a.main_sub = MainSub::Overview;
a.log_scroll_max.set(100);
a.scroll(-3);
assert_eq!(a.log_scroll, Some(3), "wheel-up enters history");
a.scroll(3);
assert_eq!(a.log_scroll, None, "wheel-down returns to following newest");
a.scroll(3);
assert_eq!(a.log_scroll, None, "and cannot scroll past the newest line");
a.main_sub = MainSub::Kernels;
a.kernel_scroll_max.set(100);
a.scroll(3);
assert_eq!(a.kernel_scroll, 3);
a.scroll(-99);
assert_eq!(a.kernel_scroll, 0, "clamped, not wrapped into a huge usize");
a.section = Section::Terminal;
a.term_sub = TermSub::Chat;
a.chat_scroll_max.set(100);
a.scroll(-3);
assert_eq!(a.chat.scroll, Some(3));
for s in [Section::Stats, Section::Network] {
a.section = s;
a.scroll(3);
a.scroll(-3);
}
}
#[test]
fn scrolling_stops_at_the_limits_instead_of_running_away() {
use crate::tui::app::{MainSub, TermSub};
use crate::tui::section::Section;
let mut a = App::new(clap::Parser::parse_from(["met", "m"]));
a.section = Section::Main;
a.main_sub = MainSub::Overview;
a.log_scroll_max.set(10);
for _ in 0..50 {
a.scroll(-3);
}
assert_eq!(a.log_scroll, Some(10), "clamped at the oldest line");
for _ in 0..4 {
a.scroll(3);
}
assert_eq!(a.log_scroll, None, "back to following the newest line");
a.log_scroll_max.set(0);
a.scroll(-9);
assert_eq!(a.log_scroll, None);
a.main_sub = MainSub::Kernels;
a.kernel_scroll_max.set(5);
for _ in 0..20 {
a.scroll(3);
}
assert_eq!(a.kernel_scroll, 5, "cannot scroll past the last row");
for _ in 0..20 {
a.scroll(-3);
}
assert_eq!(a.kernel_scroll, 0, "nor above the first");
a.section = Section::Terminal;
a.term_sub = TermSub::Chat;
a.chat_scroll_max.set(4);
for _ in 0..20 {
a.scroll(-3);
}
assert_eq!(a.chat.scroll, Some(4), "clamped at the oldest message");
}
#[test]
fn error_toasts_outlive_info_toasts_but_still_leave() {
use clap::Parser;
let mut a = App::new(crate::cli::ServeArgs::parse_from(["met", "some/model"]));
a.toast("plain note", false);
a.toast("something failed", true);
let aged = Instant::now() - std::time::Duration::from_secs(6);
for t in &mut a.toasts {
t.at = aged;
}
a.on_tick();
assert_eq!(a.toasts.len(), 1, "at 6s the info toast is gone");
assert!(a.toasts[0].error);
a.toasts[0].at = Instant::now() - std::time::Duration::from_secs(13);
a.on_tick();
assert!(a.toasts.is_empty(), "at 13s the error leaves too");
}