diff --git a/crates/lpm-cli/src/commands/config/tests.rs b/crates/lpm-cli/src/commands/config/tests.rs index 6c2f7bc0..1900d318 100644 --- a/crates/lpm-cli/src/commands/config/tests.rs +++ b/crates/lpm-cli/src/commands/config/tests.rs @@ -852,7 +852,7 @@ async fn grouped_release_age_save_persists_scope_and_minimum_age_together() { ReleaseAgeSelection::Seconds(CAUTIOUS_RELEASE_AGE_SECS), Some(crate::release_age_config::ReleaseAgePolicy::Strict), false, - "lpm config release-age --set 3d", + "lpm config release-age --set 1d", ) .await .unwrap(); @@ -867,7 +867,7 @@ async fn grouped_release_age_save_persists_scope_and_minimum_age_together() { .and_then(toml::Value::as_str), table.get("registry").and_then(toml::Value::as_str), ), - (Some("259200"), Some("strict"), Some("https://example.test"),) + (Some("86400"), Some("strict"), Some("https://example.test"),) ); } @@ -919,18 +919,15 @@ async fn release_age_policy_wizard_rejects_lower_value_when_force_floor_enabled( } #[test] -fn release_age_wizard_initial_choice_treats_explicit_one_day_as_custom() { +fn release_age_wizard_initial_choice_matches_one_day_and_preserves_three_days_as_custom() { assert_eq!(release_age_initial_choice(None), "default"); assert_eq!(release_age_initial_choice(Some(0)), "off"); assert_eq!( release_age_initial_choice(Some(CAUTIOUS_RELEASE_AGE_SECS)), "cautious" ); - assert_eq!( - release_age_initial_choice(Some(86_400)), - "custom", - "explicit 1d override must stay distinguishable from true default", - ); + assert_eq!(release_age_initial_choice(Some(86_400)), "cautious"); + assert_eq!(release_age_initial_choice(Some(259_200)), "custom"); } // ── sandbox wizard (rework) ───────────────────────── diff --git a/crates/lpm-cli/src/commands/config/wizards/release_age.rs b/crates/lpm-cli/src/commands/config/wizards/release_age.rs index d0b9dea6..4a6438af 100644 --- a/crates/lpm-cli/src/commands/config/wizards/release_age.rs +++ b/crates/lpm-cli/src/commands/config/wizards/release_age.rs @@ -8,7 +8,7 @@ pub(in crate::commands::config) const RELEASE_AGE_POLICY_KEY: &str = pub(in crate::commands::config) const RELEASE_AGE_GUIDED_MENU_LABEL: &str = "Release age configuration"; const DEFAULT_RELEASE_AGE_SECS: u64 = crate::release_age_config::DEFAULT_MIN_RELEASE_AGE_SECS; -pub(in crate::commands::config) const CAUTIOUS_RELEASE_AGE_SECS: u64 = 3 * 24 * 60 * 60; +pub(in crate::commands::config) const CAUTIOUS_RELEASE_AGE_SECS: u64 = 24 * 60 * 60; const RELEASE_AGE_EDITOR_HELP: &str = "Use ↑/↓ to move, ←/→ to change, Enter to save, Esc to cancel."; @@ -136,7 +136,7 @@ pub(in crate::commands::config) async fn run_release_age_wizard( let preset: &str = cliclack::select("How long should LPM wait before allowing newly published packages?") .item("default", "Default (off)", "no cooldown") - .item("cautious", "Cautious (3 days)", "stricter") + .item("cautious", "Cautious (1 day)", "stricter") .item("off", "Off", "always disable the cooldown") .item("custom", "Custom", "enter 12h / 7d / 0") .initial_value(release_age_initial_choice(current)) @@ -236,16 +236,16 @@ fn interact_release_age_editor( Key::ArrowUp | Key::ArrowDown | Key::Char('j') | Key::Char('k') => cursor ^= 1, Key::ArrowLeft | Key::Char('h') => { if cursor == 0 { - settings.step_scope(); - } else { settings.step_age(SelectionStep::Previous); + } else { + settings.step_scope(); } } Key::ArrowRight | Key::Char('l') | Key::Char(' ') => { if cursor == 0 { - settings.step_scope(); - } else { settings.step_age(SelectionStep::Next); + } else { + settings.step_scope(); } } Key::Enter => return Ok(settings), @@ -266,17 +266,17 @@ fn format_release_age_editor_frame(settings: ReleaseAgeEditorSettings, cursor: u push_release_age_editor_row( &mut frame, cursor == 0, - "Release-age scope", - "Apply the release-age cooldown to which dependencies?", - &format_release_age_policy_options(settings.policy), + "Minimum release age", + "How long should LPM wait before allowing newly published packages?", + &format_release_age_options(settings.age_choice), ); frame.push_str("│\n"); push_release_age_editor_row( &mut frame, cursor == 1, - "Minimum release age", - "How long should LPM wait before allowing newly published packages?", - &format_release_age_options(settings.age_choice), + "Release-age scope", + "Apply the release-age cooldown to which dependencies?", + &format_release_age_policy_options(settings.policy), ); frame.push_str("└\n"); frame @@ -325,7 +325,7 @@ fn format_release_age_options(choice: ReleaseAgeChoice) -> String { let mut rendered = String::with_capacity(130); for (candidate, label) in [ (ReleaseAgeChoice::Default, "Default (off)"), - (ReleaseAgeChoice::Cautious, "Cautious (3 days)"), + (ReleaseAgeChoice::Cautious, "Cautious (1 day)"), (ReleaseAgeChoice::Off, "Off"), (ReleaseAgeChoice::Custom, "Custom"), ] { @@ -622,7 +622,7 @@ mod tests { assert!(plain.contains("direct and transitive dependencies")); assert!(plain.contains("Minimum release age")); assert!(plain.contains("● Default (off)")); - assert!(plain.contains("○ Cautious (3 days)")); + assert!(plain.contains("○ Cautious (1 day)")); assert!(plain.contains("○ Off")); assert!(plain.contains("○ Custom")); } @@ -644,6 +644,18 @@ mod tests { ); } + #[test] + fn grouped_editor_preserves_three_day_override_as_custom() { + let settings = ReleaseAgeEditorSettings::from_overrides(Some(259_200), None); + + assert_eq!(settings.age_choice, ReleaseAgeChoice::Custom); + assert_eq!(settings.custom_secs, 259_200); + assert_eq!( + settings.age_choice.selection(settings.custom_secs), + ReleaseAgeSelection::Seconds(259_200) + ); + } + #[test] fn grouped_editor_age_choices_wrap_in_both_directions() { assert_eq!( diff --git a/crates/lpm-cli/tests/config_release_age_wizard.rs b/crates/lpm-cli/tests/config_release_age_wizard.rs index 4f0806d0..62b8c6cd 100644 --- a/crates/lpm-cli/tests/config_release_age_wizard.rs +++ b/crates/lpm-cli/tests/config_release_age_wizard.rs @@ -128,13 +128,14 @@ mod tty { use std::process::{ExitStatus, Stdio}; use std::time::{Duration, Instant}; - fn grouped_release_age_input() -> Vec { + fn grouped_release_age_input(editor_input: &[u8]) -> Vec { let mut input = Vec::with_capacity(96); for _ in 0..9 { input.extend_from_slice(b"\x1b[B"); } input.push(b'\r'); - input.extend_from_slice(b"\x1b[C\x1b[B\x1b[C\r"); + input.extend_from_slice(editor_input); + input.push(b'\r'); for _ in 0..12 { input.extend_from_slice(b"\x1b[B"); } @@ -254,10 +255,36 @@ mod tty { (status, String::from_utf8_lossy(&transcript).into_owned()) } + #[test] + fn guided_config_focuses_minimum_age_first_and_saves_one_day_cautious_preset() { + let (project, lpm_home) = isolated_project(); + let input = grouped_release_age_input(b"\x1b[C"); + + let (status, transcript) = run_guided_config_in_pty(&project, &lpm_home, &input); + + assert!(status.success(), "guided config failed:\n{transcript}"); + let age_position = transcript.find("› Minimum release age").expect(&transcript); + let scope_position = transcript.find("Release-age scope").expect(&transcript); + assert!( + age_position < scope_position, + "minimum age must appear first and have initial focus:\n{transcript}" + ); + assert!( + transcript.contains("● Cautious (1 day)"), + "right arrow on the initial row must select the one-day preset:\n{transcript}" + ); + let cfg = read_lpm_config(&lpm_home); + assert!( + cfg.contains("minimum-release-age-secs = \"86400\"") + && cfg.contains("release-age-policy = \"direct\""), + "changing the first row must persist one day and retain direct scope:\n{cfg}" + ); + } + #[test] fn guided_config_edits_release_age_scope_and_minimum_age_on_one_screen() { let (project, lpm_home) = isolated_project(); - let input = grouped_release_age_input(); + let input = grouped_release_age_input(b"\x1b[C\x1b[B\x1b[C"); let (status, transcript) = run_guided_config_in_pty(&project, &lpm_home, &input); @@ -271,14 +298,35 @@ mod tty { "both release-age controls must appear in the grouped editor:\n{transcript}" ); assert!( - transcript.contains("Saved release age configuration: scope = strict, minimum = 3d"), + transcript.contains("Saved release age configuration: scope = strict, minimum = 1d"), "grouped save summary is missing:\n{transcript}" ); let cfg = read_lpm_config(&lpm_home); assert!( - cfg.contains("minimum-release-age-secs = \"259200\"") + cfg.contains("minimum-release-age-secs = \"86400\"") && cfg.contains("release-age-policy = \"strict\""), "grouped editor must persist both release-age settings:\n{cfg}" ); } + + #[test] + fn guided_config_left_arrow_changes_age_then_scope_after_moving_down() { + let (project, lpm_home) = isolated_project(); + std::fs::write( + lpm_config_path(&lpm_home), + "minimum-release-age-secs = \"0\"\n", + ) + .expect("seed disabled cooldown"); + let input = grouped_release_age_input(b"\x1b[D\x1b[B\x1b[D"); + + let (status, transcript) = run_guided_config_in_pty(&project, &lpm_home, &input); + + assert!(status.success(), "guided config failed:\n{transcript}"); + let cfg = read_lpm_config(&lpm_home); + assert!( + cfg.contains("minimum-release-age-secs = \"86400\"") + && cfg.contains("release-age-policy = \"strict\""), + "left arrow must change the focused age or scope setting:\n{cfg}" + ); + } }