use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
use tokio::sync::mpsc;
use crate::state::types::{AppMode, NewsBookmark, NewsFeedItem, NewsFeedSource};
use crate::state::{AppState, PackageItem, Source};
use super::handle_install_key;
fn new_app() -> AppState {
AppState::default()
}
fn create_test_package(name: &str, source: Source) -> PackageItem {
PackageItem {
name: name.into(),
version: "1.0.0".into(),
description: String::new(),
source,
popularity: None,
out_of_date: None,
orphaned: false,
}
}
#[test]
fn install_enter_opens_confirm_install() {
let _guard = crate::theme::test_mutex()
.lock()
.expect("Test mutex poisoned");
let orig_home = std::env::var_os("HOME");
let orig_xdg = std::env::var_os("XDG_CONFIG_HOME");
let base = std::env::temp_dir().join(format!(
"pacsea_test_install_{}_{}",
std::process::id(),
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.expect("System time is before UNIX epoch")
.as_nanos()
));
let cfg = base.join(".config").join("pacsea");
let _ = std::fs::create_dir_all(&cfg);
unsafe { std::env::set_var("HOME", base.display().to_string()) };
unsafe { std::env::remove_var("XDG_CONFIG_HOME") };
let settings_path = cfg.join("settings.conf");
std::fs::write(&settings_path, "skip_preflight = false\n")
.expect("Failed to write test settings file");
let mut app = new_app();
app.install_list = vec![create_test_package("rg", Source::Aur)];
let (dtx, _drx) = mpsc::unbounded_channel::<PackageItem>();
let (ptx, _prx) = mpsc::unbounded_channel::<PackageItem>();
let (atx, _arx) = mpsc::unbounded_channel::<PackageItem>();
let _ = handle_install_key(
KeyEvent::new(KeyCode::Enter, KeyModifiers::empty()),
&mut app,
&dtx,
&ptx,
&atx,
);
match app.modal {
crate::state::Modal::Preflight {
ref items,
action,
tab,
summary: _,
summary_scroll: _,
header_chips: _,
dependency_info: _,
dep_selected: _,
dep_tree_expanded: _,
deps_error: _,
file_info: _,
file_selected: _,
file_tree_expanded: _,
files_error: _,
service_info: _,
service_selected: _,
services_loaded: _,
services_error: _,
sandbox_info: _,
sandbox_selected: _,
sandbox_tree_expanded: _,
sandbox_loaded: _,
sandbox_error: _,
selected_optdepends: _,
cascade_mode: _,
cached_reverse_deps_report: _,
} => {
assert_eq!(items.len(), 1);
assert_eq!(action, crate::state::PreflightAction::Install);
assert_eq!(tab, crate::state::PreflightTab::Summary);
}
_ => panic!("Preflight modal not opened"),
}
unsafe {
if let Some(v) = orig_home {
std::env::set_var("HOME", v);
} else {
std::env::remove_var("HOME");
}
if let Some(v) = orig_xdg {
std::env::set_var("XDG_CONFIG_HOME", v);
} else {
std::env::remove_var("XDG_CONFIG_HOME");
}
}
let _ = std::fs::remove_dir_all(&base);
}
#[test]
fn install_enter_bypasses_preflight_with_skip_flag() {
let _guard = crate::theme::test_mutex()
.lock()
.expect("Test mutex poisoned");
let orig_home = std::env::var_os("HOME");
let orig_xdg = std::env::var_os("XDG_CONFIG_HOME");
let base = std::env::temp_dir().join(format!(
"pacsea_test_install_skip_{}_{}",
std::process::id(),
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.expect("System time is before UNIX epoch")
.as_nanos()
));
let cfg = base.join(".config").join("pacsea");
let _ = std::fs::create_dir_all(&cfg);
unsafe { std::env::set_var("HOME", base.display().to_string()) };
unsafe { std::env::remove_var("XDG_CONFIG_HOME") };
let settings_path = cfg.join("settings.conf");
std::fs::write(&settings_path, "skip_preflight = false\n")
.expect("Failed to write test settings file");
assert!(
!crate::theme::settings().skip_preflight,
"skip_preflight unexpectedly true by default"
);
let mut app = new_app();
app.install_list = vec![create_test_package(
"ripgrep",
Source::Official {
repo: "core".into(),
arch: "x86_64".into(),
},
)];
let (dtx, _drx) = mpsc::unbounded_channel::<PackageItem>();
let (ptx, _prx) = mpsc::unbounded_channel::<PackageItem>();
let (atx, _arx) = mpsc::unbounded_channel::<PackageItem>();
let _ = handle_install_key(
KeyEvent::new(KeyCode::Enter, KeyModifiers::empty()),
&mut app,
&dtx,
&ptx,
&atx,
);
match app.modal {
crate::state::Modal::Preflight {
summary: _,
summary_scroll: _,
header_chips: _,
dependency_info: _,
dep_selected: _,
dep_tree_expanded: _,
file_info: _,
file_selected: _,
file_tree_expanded: _,
files_error: _,
service_info: _,
service_selected: _,
services_loaded: _,
cascade_mode: _,
..
} => {}
_ => panic!("Expected Preflight when skip_preflight=false"),
}
unsafe {
if let Some(v) = orig_home {
std::env::set_var("HOME", v);
} else {
std::env::remove_var("HOME");
}
if let Some(v) = orig_xdg {
std::env::set_var("XDG_CONFIG_HOME", v);
} else {
std::env::remove_var("XDG_CONFIG_HOME");
}
}
let _ = std::fs::remove_dir_all(&base);
}
#[test]
fn load_news_bookmark_without_cached_content_clears_loading_flag() {
let mut app = new_app();
app.app_mode = AppMode::News;
app.news_content_loading = true;
app.news_content = Some("stale".into());
app.news_bookmarks.clear();
app.news_results.clear();
app.news_content_cache.clear();
let bookmark = NewsBookmark {
item: NewsFeedItem {
id: "id-1".into(),
date: "2024-01-01".into(),
title: "Example".into(),
summary: None,
url: Some("https://example.com/news".into()),
source: NewsFeedSource::ArchNews,
severity: None,
packages: Vec::new(),
},
content: None,
html_path: None,
};
app.news_bookmarks.push(bookmark);
let last_idx = app.news_bookmarks.len().saturating_sub(1);
app.install_state.select(Some(last_idx));
super::load_news_bookmark(&mut app);
assert!(!app.news_content_loading, "loading flag should reset");
assert!(
app.news_content.is_none(),
"stale content should be cleared when bookmark has no cache, got {:?}",
app.news_content
);
}
#[test]
fn install_delete_removes_item() {
let mut app = new_app();
app.install_list = vec![
create_test_package("rg", Source::Aur),
create_test_package("fd", Source::Aur),
];
app.install_state.select(Some(0));
let (dtx, _drx) = mpsc::unbounded_channel::<PackageItem>();
let (ptx, _prx) = mpsc::unbounded_channel::<PackageItem>();
let (atx, _arx) = mpsc::unbounded_channel::<PackageItem>();
let _ = handle_install_key(
KeyEvent::new(KeyCode::Delete, KeyModifiers::empty()),
&mut app,
&dtx,
&ptx,
&atx,
);
assert_eq!(app.install_list.len(), 1);
}
#[test]
fn install_navigation_down() {
let mut app = new_app();
app.install_list = vec![
create_test_package("rg", Source::Aur),
create_test_package("fd", Source::Aur),
create_test_package("bat", Source::Aur),
];
app.install_state.select(Some(0));
let (dtx, mut drx) = mpsc::unbounded_channel::<PackageItem>();
let (ptx, _prx) = mpsc::unbounded_channel::<PackageItem>();
let (atx, _arx) = mpsc::unbounded_channel::<PackageItem>();
let _ = handle_install_key(
KeyEvent::new(KeyCode::Char('j'), KeyModifiers::empty()),
&mut app,
&dtx,
&ptx,
&atx,
);
assert_eq!(app.install_state.selected(), Some(1));
let _ = drx.try_recv();
}
#[test]
fn install_navigation_up() {
let mut app = new_app();
app.install_list = vec![
create_test_package("rg", Source::Aur),
create_test_package("fd", Source::Aur),
create_test_package("bat", Source::Aur),
];
app.install_state.select(Some(1));
let (dtx, mut drx) = mpsc::unbounded_channel::<PackageItem>();
let (ptx, _prx) = mpsc::unbounded_channel::<PackageItem>();
let (atx, _arx) = mpsc::unbounded_channel::<PackageItem>();
let _ = handle_install_key(
KeyEvent::new(KeyCode::Char('k'), KeyModifiers::empty()),
&mut app,
&dtx,
&ptx,
&atx,
);
assert_eq!(app.install_state.selected(), Some(0));
let _ = drx.try_recv();
}
#[test]
fn install_esc_returns_to_search() {
let mut app = new_app();
app.focus = crate::state::Focus::Install;
let (dtx, mut drx) = mpsc::unbounded_channel::<PackageItem>();
let (ptx, _prx) = mpsc::unbounded_channel::<PackageItem>();
let (atx, _arx) = mpsc::unbounded_channel::<PackageItem>();
let _ = handle_install_key(
KeyEvent::new(KeyCode::Esc, KeyModifiers::empty()),
&mut app,
&dtx,
&ptx,
&atx,
);
assert_eq!(app.focus, crate::state::Focus::Search);
assert!(app.search_normal_mode);
let _ = drx.try_recv();
}
#[test]
fn install_enter_with_empty_list() {
let mut app = new_app();
app.install_list.clear();
let initial_modal = app.modal.clone();
let (dtx, _drx) = mpsc::unbounded_channel::<PackageItem>();
let (ptx, _prx) = mpsc::unbounded_channel::<PackageItem>();
let (atx, _arx) = mpsc::unbounded_channel::<PackageItem>();
let _ = handle_install_key(
KeyEvent::new(KeyCode::Enter, KeyModifiers::empty()),
&mut app,
&dtx,
&ptx,
&atx,
);
match (initial_modal, app.modal) {
(crate::state::Modal::None, crate::state::Modal::None) => {}
_ => panic!("Modal should not change with empty install list"),
}
}
#[test]
fn install_clear_list() {
let mut app = new_app();
app.install_list = vec![
create_test_package("rg", Source::Aur),
create_test_package("fd", Source::Aur),
create_test_package("bat", Source::Aur),
];
app.install_state.select(Some(1));
app.install_list.clear();
app.install_state.select(None);
app.install_dirty = true;
app.install_list_deps.clear();
app.install_list_files.clear();
app.deps_resolving = false;
app.files_resolving = false;
assert!(app.install_list.is_empty());
assert_eq!(app.install_state.selected(), None);
assert!(app.install_dirty);
}
#[test]
fn install_pane_find_mode_entry() {
let mut app = new_app();
assert!(app.pane_find.is_none());
let (dtx, _drx) = mpsc::unbounded_channel::<PackageItem>();
let (ptx, _prx) = mpsc::unbounded_channel::<PackageItem>();
let (atx, _arx) = mpsc::unbounded_channel::<PackageItem>();
let _ = handle_install_key(
KeyEvent::new(KeyCode::Char('/'), KeyModifiers::empty()),
&mut app,
&dtx,
&ptx,
&atx,
);
assert!(app.pane_find.is_some());
assert_eq!(
app.pane_find.as_ref().expect("pane_find should be Some"),
""
);
}
#[test]
fn install_pane_find_mode_cancel() {
let mut app = new_app();
app.pane_find = Some("test".to_string());
let (dtx, _drx) = mpsc::unbounded_channel::<PackageItem>();
let (ptx, _prx) = mpsc::unbounded_channel::<PackageItem>();
let (atx, _arx) = mpsc::unbounded_channel::<PackageItem>();
let _ = handle_install_key(
KeyEvent::new(KeyCode::Esc, KeyModifiers::empty()),
&mut app,
&dtx,
&ptx,
&atx,
);
assert!(app.pane_find.is_none());
}
#[test]
fn install_delete_last_item() {
let mut app = new_app();
app.install_list = vec![create_test_package("rg", Source::Aur)];
app.install_state.select(Some(0));
let (dtx, _drx) = mpsc::unbounded_channel::<PackageItem>();
let (ptx, _prx) = mpsc::unbounded_channel::<PackageItem>();
let (atx, _arx) = mpsc::unbounded_channel::<PackageItem>();
let _ = handle_install_key(
KeyEvent::new(KeyCode::Delete, KeyModifiers::empty()),
&mut app,
&dtx,
&ptx,
&atx,
);
assert!(app.install_list.is_empty());
assert_eq!(app.install_state.selected(), None);
}