diff --git a/romm-tui/src/tui/app/handlers/settings.rs b/romm-tui/src/tui/app/handlers/settings.rs index 521b9c3..d74bb35 100644 --- a/romm-tui/src/tui/app/handlers/settings.rs +++ b/romm-tui/src/tui/app/handlers/settings.rs @@ -47,6 +47,16 @@ impl App { fn persist_settings_screen(&mut self) -> bool { use romm_api::config::persist_user_config; + if self.config_reset_pending_restart { + if let AppScreen::Settings(s) = &mut self.screen { + s.message = Some(( + "Settings were reset. Restart romm-cli before saving again.".to_string(), + MessageTone::Warning, + )); + } + return false; + } + let settings = match &self.screen { AppScreen::Settings(s) => s, _ => return false, @@ -271,11 +281,21 @@ impl App { match key.code { KeyCode::Enter => match settings.confirm.take().unwrap() { crate::tui::screens::settings::SettingsConfirm::Reset => { - let _ = romm_api::config::reset_all_settings(); - settings.message = Some(( - "Settings deleted. Please restart romm-cli.".to_string(), - MessageTone::Warning, - )); + match romm_api::config::reset_all_settings() { + Ok(()) => { + self.config_reset_pending_restart = true; + settings.message = Some(( + "Settings deleted. Please restart romm-cli.".to_string(), + MessageTone::Warning, + )); + } + Err(e) => { + settings.message = Some(( + format!("Failed to reset settings: {e}"), + MessageTone::Error, + )); + } + } } crate::tui::screens::settings::SettingsConfirm::ClearCache => { match romm_api::core::cache::RomCache::clear_file() { diff --git a/romm-tui/src/tui/app/mod.rs b/romm-tui/src/tui/app/mod.rs index f062c42..5ef792c 100644 --- a/romm-tui/src/tui/app/mod.rs +++ b/romm-tui/src/tui/app/mod.rs @@ -71,6 +71,7 @@ pub struct App { pub screen: AppScreen, client: RommClient, config: Config, + config_reset_pending_restart: bool, /// RomM server version from `GET /api/heartbeat` (`SYSTEM.VERSION`), if available. server_version: Option, save_sync_compat: SaveSyncCompatibility, @@ -258,6 +259,7 @@ impl App { ))), client, config, + config_reset_pending_restart: false, server_version, save_sync_compat, metadata_edit_compat, @@ -338,6 +340,9 @@ impl App { } pub(in crate::tui::app) fn persist_tui_layout(&self) { + if self.config_reset_pending_restart { + return; + } let mut config = self.config.clone(); config.auth = auth_for_persist_merge(config.auth); if let Err(e) = romm_api::config::persist_user_config(&config) { diff --git a/romm-tui/src/tui/app/tests.rs b/romm-tui/src/tui/app/tests.rs index 861c616..5398f4e 100644 --- a/romm-tui/src/tui/app/tests.rs +++ b/romm-tui/src/tui/app/tests.rs @@ -32,7 +32,7 @@ use romm_api::types::{Platform, RomList}; use romm_api::update::UpdateStatus; use serde_json::json; use std::path::PathBuf; -use std::sync::{Arc, Mutex}; +use std::sync::{Arc, Mutex, MutexGuard}; use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH}; use tokio::io::{AsyncReadExt, AsyncWriteExt}; use wiremock::matchers::{method, path, query_param}; @@ -1748,6 +1748,34 @@ fn app_on_library() -> App { ) } +struct IsolatedConfigDir { + _guard: MutexGuard<'static, ()>, + dir: PathBuf, +} + +impl IsolatedConfigDir { + fn new(prefix: &str) -> Self { + let guard = romm_api::config::test_env_lock() + .lock() + .unwrap_or_else(|e| e.into_inner()); + let ts = std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap() + .as_nanos(); + let dir = std::env::temp_dir().join(format!("romm-tui-{prefix}-test-{ts}")); + std::fs::create_dir_all(&dir).unwrap(); + std::env::set_var("ROMM_TEST_CONFIG_DIR", &dir); + Self { _guard: guard, dir } + } +} + +impl Drop for IsolatedConfigDir { + fn drop(&mut self) { + std::env::remove_var("ROMM_TEST_CONFIG_DIR"); + let _ = std::fs::remove_dir_all(&self.dir); + } +} + #[tokio::test] async fn settings_theme_preview_reverts_when_leaving_without_save() { std::env::remove_var("NO_COLOR"); @@ -1778,6 +1806,44 @@ async fn settings_theme_preview_reverts_when_leaving_without_save() { assert_eq!(app.theme_id(), saved_theme); } +#[tokio::test] +async fn settings_reset_prevents_later_config_persistence() { + let _env = IsolatedConfigDir::new("settings-reset"); + let mut app = app_on_library(); + + romm_api::config::persist_user_config(&app.config).expect("seed config"); + let config_path = romm_api::config::user_config_json_path().expect("config path"); + assert!(config_path.exists(), "test setup should create config.json"); + + app.screen = AppScreen::Settings(Box::new(SettingsScreen::new( + &app.config, + None, + supported_save_sync_compatibility(), + ))); + if let AppScreen::Settings(settings) = &mut app.screen { + settings.confirm = Some(SettingsConfirm::Reset); + } + + app.handle_key_event(&KeyEvent::new(KeyCode::Enter, KeyModifiers::empty())) + .await + .expect("confirm reset"); + assert!(!config_path.exists(), "reset should remove config.json"); + + app.handle_key_event(&KeyEvent::new(KeyCode::Char('S'), KeyModifiers::empty())) + .await + .expect("save after reset"); + assert!( + !config_path.exists(), + "settings save must not recreate config.json after reset" + ); + + app.persist_tui_layout(); + assert!( + !config_path.exists(), + "layout persistence must not recreate config.json after reset" + ); +} + #[tokio::test] async fn global_slash_toggles_search_overlay() { let mut app = app_on_library();