use crate::recipe::Recipe;
use crate::recipe::fetch::Index;
use crate::tui::app::{App, Section};
use crate::tui::data::library::LibraryEntry;
use crate::tui::lib_state::View;
use crate::tui::render::harness::{has, screen};
pub(super) fn recipe(stem: &str) -> Recipe {
let path = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
.join("tests/fixtures/recipes/qwen3.6")
.join(format!("{stem}.yaml"));
Recipe::parse(
format!("qwen3.6/{stem}"),
&std::fs::read_to_string(path).expect("fixture"),
)
.expect("parses")
}
pub(super) fn local(id: &str, has_weights: bool) -> LibraryEntry {
LibraryEntry {
id: id.to_string(),
snapshot_dir: Default::default(),
size_bytes: 34_900_000_000,
has_weights,
model_type: "qwen3_6_moe".into(),
quant: "fp8".into(),
layers: 40,
hidden: 4096,
heads: 32,
experts: 128,
context: 65536,
optimized: true,
}
}
pub(super) fn lib(recipes: Vec<Recipe>, locals: Vec<LibraryEntry>) -> App {
let mut a = crate::tui::render::tests::app();
a.section = Section::Library;
a.library = locals;
a.lib.index = Index {
recipes,
..Default::default()
};
a.lib.rebuild(&a.library);
a
}
fn flagship() -> App {
let r = recipe("qwen3.6-35b-a3b-fp8-mtp");
let model = r.model.clone();
lib(vec![r], vec![local(&model, true)])
}
fn downloading(done: u64, total: u64) -> App {
const REPO: &str = "metrale-render-tests/no-such-model";
let mut a = lib(Vec::new(), vec![local(REPO, false)]);
let root = std::env::temp_dir().join("metrale-library-render-tests");
std::fs::create_dir_all(&root).ok();
a.download.start(REPO, root);
let job = a.download.job.as_mut().expect("a job");
job.done = done;
job.total = total;
job.rate_bps = 96_000_000.0;
job.file = Some((3, 85, "model-00003-of-00085.safetensors".into()));
a
}
fn list_pane(rows: &[String], screen_w: u16) -> Vec<String> {
let chrome = crate::tui::render::Chrome::of(ratatui::layout::Size::new(screen_w, 40));
let content_w = screen_w - chrome.sidebar_w - 2;
let end = (chrome.sidebar_w + 1 + content_w * 46 / 100) as usize;
rows.iter().map(|r| r.chars().take(end).collect()).collect()
}
#[test]
fn the_progress_line_sheds_fields_from_the_right_as_the_pane_narrows() {
let a = downloading(4_000_000_000, 34_900_000_000);
let cramped = list_pane(&screen(&a, 120, 40), 120);
assert!(
has(&cramped, " 11%"),
"the percentage survives:\n{cramped:#?}"
);
assert!(has(&cramped, "▓"), "and so does the bar");
assert!(!has(&cramped, "MB/s"), "{cramped:#?}");
assert!(!has(&cramped, "file 3/85"), "{cramped:#?}");
let roomy = screen(&a, 200, 40);
assert!(has(&roomy, "3.7 GB / 32.5 GB"), "{roomy:#?}");
assert!(has(&roomy, "92 MB/s"), "{roomy:#?}");
let wide = list_pane(&screen(&a, 280, 40), 280);
assert!(has(&wide, "file 3/85"), "{wide:#?}");
}
fn model_row(rows: &[String], id: &str) -> String {
rows.iter()
.find(|r| r.contains(id))
.cloned()
.unwrap_or_default()
}
#[test]
fn a_running_download_owns_the_detail_pane_and_marks_its_row_with_a_dot() {
let a = downloading(4_000_000_000, 34_900_000_000);
let rows = screen(&a, 120, 40);
assert!(has(&rows, "DOWNLOADING"), "{rows:#?}");
assert!(has(&rows, "3.7 GB / 32.5 GB"), "{rows:#?}");
assert!(has(&rows, "92 MB/s"), "{rows:#?}");
assert!(has(&rows, "file 3/85"), "{rows:#?}");
let row = model_row(&rows, "metrale-render-tests/no-such-model");
assert!(
row.contains("●"),
"the row glows while its download runs: {row:?}"
);
}
#[test]
fn a_model_not_downloading_draws_no_dot_and_no_download_section() {
let a = flagship();
let rows = screen(&a, 200, 40);
assert!(!has(&rows, "DOWNLOADING"), "{rows:#?}");
let id = a.lib.visible()[0].model.clone();
let row = model_row(&rows, &id);
assert!(!row.contains("●"), "{row:?}");
}
#[test]
fn a_download_the_hub_gave_no_size_for_says_fetching_rather_than_nought_percent() {
let a = downloading(900_000_000, 0);
let rows = screen(&a, 200, 40);
assert!(has(&rows, "fetching"), "{rows:#?}");
assert!(!has(&rows, "0.0%"), "no fraction was knowable:\n{rows:#?}");
}
#[test]
fn a_cancelling_download_says_stopping_instead_of_a_bar_that_keeps_moving() {
let mut a = downloading(4_000_000_000, 34_900_000_000);
a.download.job.as_mut().expect("a job").cancelling = true;
let rows = screen(&a, 200, 40);
assert!(has(&rows, "stopping…"), "{rows:#?}");
assert!(
!has(&rows, "MB/s"),
"and drops the fields that imply progress:\n{rows:#?}"
);
assert!(has(&rows, "x stop the download"), "{rows:#?}");
}
#[test]
fn an_update_badge_appears_only_for_a_mismatch_that_was_actually_confirmed() {
use crate::model_download::stale::Freshness;
let mut a = flagship();
let model = a.lib.visible()[0].model.clone();
a.download
.freshness
.insert(model.clone(), Freshness::Unknown);
assert!(!has(&screen(&a, 200, 40), " update "));
a.download.freshness.insert(
model.clone(),
Freshness::Stale {
local: "abc".into(),
remote: "def".into(),
},
);
let stale = screen(&a, 200, 40);
assert!(has(&stale, " update "), "{stale:#?}");
assert!(has(&stale, "d update"), "and the key that fixes it");
}
#[test]
fn a_freshness_check_in_flight_holds_the_badges_place_so_the_row_cannot_reflow() {
let mut a = flagship();
let model = a.lib.visible()[0].model.clone();
a.download.checking = Some(model);
assert!(has(&screen(&a, 200, 40), "░░░░░░"));
}
#[test]
fn a_search_that_matches_nothing_says_so_rather_than_looking_empty() {
let mut a = flagship();
a.lib.filter = "llama".into();
a.lib.rebuild(&a.library.clone());
let rows = screen(&a, 200, 40);
assert!(has(&rows, "nothing matches this search"), "{rows:#?}");
assert!(
!has(&rows, "press r to fetch recipes"),
"an empty SEARCH is not an empty library:\n{rows:#?}"
);
}
#[test]
fn the_search_field_is_visible_before_anyone_knows_the_key_for_it() {
let a = flagship();
let rows = screen(&a, 200, 40);
assert!(
has(&rows, "⌕ search models — / or click"),
"the idle field must name both ways in:\n{rows:#?}"
);
}
#[test]
fn the_search_field_echoes_what_is_being_typed_and_keeps_it_after() {
let mut a = flagship();
a.lib.filter = "qwen".into();
a.lib.filter_editing = true;
assert!(has(&screen(&a, 200, 40), "⌕ qwen▏"));
a.lib.filter_editing = false;
let rows = screen(&a, 200, 40);
assert!(has(&rows, "⌕ qwen — 1 of 1 · / edits"), "{rows:#?}");
assert!(!has(&rows, "qwen▏"));
}
#[test]
fn an_offline_recipe_index_says_why_and_what_to_do_about_it() {
let mut a = flagship();
a.lib.index.offline = Some("no route to host".into());
let rows = screen(&a, 200, 40);
assert!(has(&rows, "offline"), "the title admits it:\n{rows:#?}");
assert!(
has(&rows, "HTTPS_PROXY"),
"and the body names the fix for THIS cause:\n{rows:#?}"
);
}
#[test]
fn a_checkpoint_with_no_recipe_is_still_listed_and_says_the_flags_are_yours() {
let a = lib(Vec::new(), vec![local("org/unknown-checkpoint", true)]);
let rows = screen(&a, 200, 40);
assert!(has(&rows, "org/unknown-checkpoint"), "{rows:#?}");
assert!(has(&rows, "no recipe"), "{rows:#?}");
assert!(has(&rows, "No recipe covers this checkpoint"), "{rows:#?}");
assert!(has(&rows, "ON DISK"), "what IS known is still shown");
assert!(has(&rows, "qwen3_6_moe"));
}
#[test]
fn the_row_glyph_distinguishes_present_partial_and_absent_weights() {
let r = recipe("qwen3.6-35b-a3b-fp8-mtp");
let model = r.model.clone();
let present = lib(vec![r.clone()], vec![local(&model, true)]);
assert!(has(&screen(&present, 200, 40), "✓ "));
let partial = lib(vec![r.clone()], vec![local(&model, false)]);
let partial_rows = screen(&partial, 200, 40);
assert!(has(&partial_rows, "◐ "), "{partial_rows:#?}");
assert!(
has(&partial_rows, "d resume the download"),
"a half-downloaded model is resumed, not restarted:\n{partial_rows:#?}"
);
let absent = lib(vec![r], Vec::new());
let absent_rows = screen(&absent, 200, 40);
assert!(has(&absent_rows, "· "), "{absent_rows:#?}");
assert!(has(&absent_rows, "weights are not in the local cache"));
assert!(has(&absent_rows, "d download the weights"));
}
#[test]
fn the_settings_form_marks_the_rows_that_were_changed_and_previews_the_command() {
let mut a = flagship();
a.lib.open_cards().expect("opens");
a.lib.open_config().expect("opens");
assert_eq!(a.lib.view, View::Config);
let key = a.lib.config_rows()[0].key.clone();
a.lib.overrides.insert(key.clone(), "4242".into());
let rows = screen(&a, 200, 50);
assert!(has(&rows, "SETTINGS ─ 1 changed"), "{rows:#?}");
assert!(has(&rows, "•"), "the gutter marks the changed row");
assert!(has(&rows, "4242"), "{rows:#?}");
assert!(has(&rows, "COMMAND"), "{rows:#?}");
assert!(
has(&rows, "met serve"),
"a form that hides its output makes the user guess:\n{rows:#?}"
);
}
#[test]
fn a_field_being_edited_shows_its_buffer_and_a_cursor_not_the_stored_value() {
let mut a = flagship();
a.lib.open_cards().expect("opens");
a.lib.open_config().expect("opens");
a.lib.editing = true;
a.lib.edit_buffer = "99991".into();
let rows = screen(&a, 200, 50);
assert!(has(&rows, "99991▏"), "{rows:#?}");
}
#[test]
fn a_validation_error_attaches_to_the_row_that_caused_it() {
let mut a = flagship();
a.lib.open_cards().expect("opens");
a.lib.open_config().expect("opens");
a.lib.error = Some("must be a positive integer".into());
assert!(has(&screen(&a, 200, 50), "must be a positive integer"));
}
#[test]
fn a_card_says_on_its_face_when_enter_will_refuse_it() {
let mut r = recipe("qwen3.6-35b-a3b-fp8-mtp");
r.min_nodes = 2;
let model = r.model.clone();
let mut a = lib(vec![r], vec![local(&model, true)]);
a.lib.open_cards().expect("opens");
let rows = screen(&a, 200, 50);
assert!(
has(
&rows,
"needs 2 nodes — the dashboard starts single-node runs only"
),
"{rows:#?}"
);
assert!(!has(&rows, "⏎ configure and start"), "{rows:#?}");
assert!(has(&rows, "EP=2"), "the topology chip says why:\n{rows:#?}");
}
#[test]
fn a_recipe_for_another_runtime_is_listed_but_never_offered_as_launchable() {
let mut r = recipe("qwen3.6-35b-a3b-fp8-mtp");
r.runtime = Some("vllm".into());
let model = r.model.clone();
let mut a = lib(vec![r], vec![local(&model, true)]);
let list = screen(&a, 200, 50);
assert!(
has(&list, " vllm "),
"it is listed, never hidden:\n{list:#?}"
);
a.lib.open_cards().expect("opens");
let cards = screen(&a, 200, 50);
assert!(
has(&cards, "this runtime cannot be launched from here"),
"{cards:#?}"
);
assert!(has(&cards, "⊘ vllm"), "and the card is marked:\n{cards:#?}");
}
#[test]
fn every_library_pane_survives_narrow_and_short_terminals() {
let mut a = flagship();
for (w, h) in [(20u16, 4u16), (20, 8), (20, 40), (40, 12), (200, 3)] {
assert_eq!(screen(&a, w, h).len(), h as usize, "list at {w}x{h}");
}
a.lib.open_cards().expect("opens");
for (w, h) in [(20u16, 4u16), (20, 8), (40, 12), (200, 3)] {
assert_eq!(screen(&a, w, h).len(), h as usize, "cards at {w}x{h}");
}
a.lib.open_config().expect("opens");
for (w, h) in [(20u16, 4u16), (20, 8), (40, 12), (200, 3)] {
assert_eq!(screen(&a, w, h).len(), h as usize, "config at {w}x{h}");
}
}
#[test]
fn every_weights_state_is_a_word_on_the_row_not_only_a_mark_glyph() {
let r = recipe("qwen3.6-35b-a3b-fp8-mtp");
let model = r.model.clone();
let complete = lib(vec![r.clone()], vec![local(&model, true)]);
let rows = screen(&complete, 200, 50);
assert!(has(&rows, " on disk "), "{rows:#?}");
let partial = lib(vec![r.clone()], vec![local(&model, false)]);
let rows = screen(&partial, 200, 50);
assert!(has(&rows, " partial "), "{rows:#?}");
let absent = lib(vec![r], Vec::new());
let rows = screen(&absent, 200, 50);
assert!(has(&rows, " not downloaded "), "{rows:#?}");
assert!(
!has(&rows, " on disk "),
"absence must not wear the presence badge:\n{rows:#?}"
);
let running = downloading(4_000_000_000, 34_900_000_000);
let rows = screen(&running, 200, 50);
assert!(has(&rows, " downloading "), "{rows:#?}");
}
#[test]
fn a_mixed_list_shows_each_rows_own_state() {
let mut here = recipe("qwen3.6-35b-a3b-fp8-mtp");
here.model = "org/model-here".into();
let mut gone = recipe("qwen3.6-35b-a3b-fp8-mtp");
gone.model = "org/model-absent".into();
let a = lib(vec![here, gone], vec![local("org/model-here", true)]);
let rows = screen(&a, 200, 50);
let here_row = rows
.iter()
.position(|r| r.contains("org/model-here"))
.expect("row");
let gone_row = rows
.iter()
.position(|r| r.contains("org/model-absent"))
.expect("row");
assert!(rows[here_row + 1].contains(" on disk "), "{rows:#?}");
assert!(rows[gone_row + 1].contains(" not downloaded "), "{rows:#?}");
}
#[test]
fn the_settings_form_scrolls_to_keep_the_selected_row_on_screen() {
let mut a = flagship();
a.lib.open_cards().expect("opens");
a.lib.open_config().expect("opens");
let rows_in_form = a.lib.config_rows();
let first_key = rows_in_form.first().expect("has rows").key.clone();
let last_key = rows_in_form.last().expect("has rows").key.clone();
a.lib.row = rows_in_form.len() - 1;
let rows = screen(&a, 100, 16);
assert!(
has(&rows, &last_key),
"the selected (last) row is on screen:\n{rows:#?}"
);
assert!(
!has(&rows, &first_key),
"the top scrolled away to make room:\n{rows:#?}"
);
assert!(
has(&rows, "COMMAND") || has(&rows, "met "),
"the preview stays pinned at the bottom:\n{rows:#?}"
);
a.lib.row = 0;
let rows = screen(&a, 100, 16);
assert!(has(&rows, &first_key), "{rows:#?}");
}