Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 5 additions & 8 deletions crates/lpm-cli/src/commands/config/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -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"),)
);
}

Expand Down Expand Up @@ -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) ─────────────────────────
Expand Down
40 changes: 26 additions & 14 deletions crates/lpm-cli/src/commands/config/wizards/release_age.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.";

Expand Down Expand Up @@ -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))
Expand Down Expand Up @@ -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),
Expand All @@ -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
Expand Down Expand Up @@ -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"),
] {
Expand Down Expand Up @@ -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"));
}
Expand All @@ -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!(
Expand Down
58 changes: 53 additions & 5 deletions crates/lpm-cli/tests/config_release_age_wizard.rs
Original file line number Diff line number Diff line change
Expand Up @@ -128,13 +128,14 @@ mod tty {
use std::process::{ExitStatus, Stdio};
use std::time::{Duration, Instant};

fn grouped_release_age_input() -> Vec<u8> {
fn grouped_release_age_input(editor_input: &[u8]) -> Vec<u8> {
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");
}
Expand Down Expand Up @@ -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);

Expand All @@ -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}"
);
}
}