diff --git a/src/config.rs b/src/config.rs index a8a83ab..fb488ea 100644 --- a/src/config.rs +++ b/src/config.rs @@ -192,4 +192,53 @@ mod tests { "expected field path in error, got: {message}" ); } + + #[test] + fn join_relative_keeps_relative_paths() { + assert_eq!( + join_relative(Path::new("/base"), Path::new("sub/x.py")), + PathBuf::from("/base/sub/x.py") + ); + } + + #[test] + fn join_relative_strips_leading_slash() { + assert_eq!( + join_relative(Path::new("/base"), Path::new("/sub/x.py")), + PathBuf::from("/base/sub/x.py") + ); + } + + #[test] + fn absolute_entry_points_resolve_relative_to_dir() { + // A leading slash on an entry point is treated as relative to `dir`, + // not as an absolute path. + let json = r#"{ + "deployments_base": "/apps", + "default_venv_name": ".venv", + "default_gui_file": "src/gui.py", + "default_server_file": "src/server.py", + "map": { "x10sa": { "dir": "d", "gui_file": "/abs/gui.py" } } + }"#; + + let config = load_config(json).unwrap(); + assert_eq!( + config.entries["x10sa"].gui_file, + PathBuf::from("/apps/d/abs/gui.py") + ); + } + + #[test] + fn custom_venv_overrides_default() { + let config = load_config(SAMPLE).unwrap(); + // x10sa sets "venv": "pxii"; x06da falls back to ".venv". + assert_eq!( + config.entries["x10sa"].python, + PathBuf::from("/apps/aaredaq-pxii/pxii/bin/python") + ); + assert_eq!( + config.entries["x06da"].python, + PathBuf::from("/apps/aaredaq-pxiii/.venv/bin/python") + ); + } } diff --git a/src/tui/custom.rs b/src/tui/custom.rs index 29d35b3..c50e8f2 100644 --- a/src/tui/custom.rs +++ b/src/tui/custom.rs @@ -155,3 +155,54 @@ fn dir_list<'a>(title: &'a str, items: &'a [String], focused: bool) -> List<'a> .highlight_style(Style::default().add_modifier(Modifier::BOLD | Modifier::REVERSED)) .highlight_symbol("> ") } + +#[cfg(test)] +mod tests { + use super::*; + + fn items(n: usize) -> Vec { + (0..n).map(|i| i.to_string()).collect() + } + + #[test] + fn initial_state_selects_first_when_nonempty() { + assert_eq!(initial_state(&items(3)).selected(), Some(0)); + } + + #[test] + fn initial_state_selects_nothing_when_empty() { + assert_eq!(initial_state(&items(0)).selected(), None); + } + + #[test] + fn select_relative_moves_within_bounds() { + let items = items(3); + let mut state = initial_state(&items); + select_relative(&items, &mut state, 1); + assert_eq!(state.selected(), Some(1)); + } + + #[test] + fn select_relative_wraps_forward_past_end() { + let items = items(3); + let mut state = ListState::default(); + state.select(Some(2)); + select_relative(&items, &mut state, 1); + assert_eq!(state.selected(), Some(0)); + } + + #[test] + fn select_relative_wraps_backward_past_start() { + let items = items(3); + let mut state = initial_state(&items); + select_relative(&items, &mut state, -1); + assert_eq!(state.selected(), Some(2)); + } + + #[test] + fn select_relative_on_empty_is_noop() { + let mut state = ListState::default(); + select_relative(&items(0), &mut state, 1); + assert_eq!(state.selected(), None); + } +}