use anyhow::{Context, Result};
use dialoguer::Confirm;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum UpdateAction {
UpdateNow,
RemindLater,
Skip,
}
pub fn get_update_notification(
current_version: &str,
latest_version: &str,
release_notes: Option<&str>,
) -> String {
let mut message = format!(
"\n{} Update Available: {} → {}\n",
emoji::alert(),
current_version,
latest_version
);
if let Some(notes) = release_notes {
message.push_str(&format!("\n{}\n", format_release_notes(notes)));
}
message
}
fn format_release_notes(notes: &str) -> String {
let lines: Vec<&str> = notes.lines().collect();
lines
.iter()
.map(|line| {
if line.is_empty() {
String::new()
} else {
format!(" {}", line)
}
})
.collect::<Vec<_>>()
.join("\n")
}
pub fn prompt_user_for_update(current_version: &str, latest_version: &str) -> Result<UpdateAction> {
let message = format!(
"Update available: {} → {}. Update now? [Y/n]",
current_version, latest_version
);
let confirmed = Confirm::new()
.with_prompt(&message)
.default(false) .interact()
.context("Failed to get user input")?;
if confirmed {
Ok(UpdateAction::UpdateNow)
} else {
Ok(UpdateAction::RemindLater)
}
}
pub fn prompt_user_for_update_with_notes(
current_version: &str,
latest_version: &str,
release_notes: Option<&str>,
) -> Result<UpdateAction> {
let notification = get_update_notification(current_version, latest_version, release_notes);
println!("{}", notification);
prompt_user_for_update(current_version, latest_version)
}
pub fn get_update_success_message(from_version: &str, to_version: &str) -> String {
format!(
"\n{} Update complete: {} → {}\n",
emoji::success(),
from_version,
to_version
)
}
pub fn get_update_failure_message(error: &str) -> String {
format!("\n{} Update failed: {}\n", emoji::error(), error)
}
pub fn get_up_to_date_message(version: &str) -> String {
format!(
"\n{} Already running latest version: {}\n",
emoji::check(),
version
)
}
mod emoji {
pub fn alert() -> &'static str {
">>>"
}
pub fn success() -> &'static str {
">>>"
}
pub fn error() -> &'static str {
"!!!"
}
pub fn check() -> &'static str {
">>>"
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_get_update_notification_basic() {
let message = get_update_notification("1.0.0", "1.1.0", None);
assert!(message.contains("1.0.0"));
assert!(message.contains("1.1.0"));
assert!(message.contains("Update Available"));
}
#[test]
fn test_get_update_notification_with_notes() {
let notes = "Bug fixes\nPerformance improvements";
let message = get_update_notification("1.0.0", "1.1.0", Some(notes));
assert!(message.contains("Bug fixes"));
assert!(message.contains("Performance improvements"));
assert!(message.contains(" ")); }
#[test]
fn test_format_release_notes_empty() {
let notes = "";
let formatted = format_release_notes(notes);
assert_eq!(formatted, "");
}
#[test]
fn test_format_release_notes_single_line() {
let notes = "Bug fixes";
let formatted = format_release_notes(notes);
assert_eq!(formatted, " Bug fixes");
}
#[test]
fn test_format_release_notes_multiple_lines() {
let notes = "Bug fixes\nPerformance improvements\nNew features";
let formatted = format_release_notes(notes);
assert!(formatted.contains(" Bug fixes"));
assert!(formatted.contains(" Performance improvements"));
assert!(formatted.contains(" New features"));
}
#[test]
fn test_format_release_notes_with_empty_lines() {
let notes = "Bug fixes\n\nPerformance improvements";
let formatted = format_release_notes(notes);
assert!(formatted.contains(" Bug fixes"));
assert!(formatted.contains(" Performance improvements"));
}
#[test]
fn test_get_update_success_message() {
let message = get_update_success_message("1.0.0", "1.1.0");
assert!(message.contains("1.0.0"));
assert!(message.contains("1.1.0"));
assert!(message.contains("Update complete"));
}
#[test]
fn test_get_update_failure_message() {
let message = get_update_failure_message("Network error");
assert!(message.contains("Network error"));
assert!(message.contains("Update failed"));
}
#[test]
fn test_get_up_to_date_message() {
let message = get_up_to_date_message("1.0.0");
assert!(message.contains("1.0.0"));
assert!(message.contains("Already running latest version"));
}
#[test]
fn test_prompt_user_for_update_default_value() {
let current = "1.0.0";
let latest = "1.1.0";
let _ = format!(
"Update available: {} → {}. Update now? [Y/n]",
current, latest
);
}
#[test]
fn test_update_action_variants() {
let _actions = [
UpdateAction::UpdateNow,
UpdateAction::RemindLater,
UpdateAction::Skip,
];
assert_ne!(UpdateAction::UpdateNow, UpdateAction::RemindLater);
assert_ne!(UpdateAction::UpdateNow, UpdateAction::Skip);
assert_ne!(UpdateAction::RemindLater, UpdateAction::Skip);
}
#[test]
fn test_emoji_helpers() {
let alert = emoji::alert();
let success = emoji::success();
let error = emoji::error();
let check = emoji::check();
assert!(!alert.is_empty());
assert!(!success.is_empty());
assert!(!error.is_empty());
assert!(!check.is_empty());
}
#[test]
fn test_get_update_notification_formatting() {
let message = get_update_notification("1.0.0", "1.1.0", Some("Notes"));
assert!(message.contains('\n'));
assert!(message.contains(">>>"));
}
#[test]
fn test_get_up_to_date_message_formatting() {
let message = get_up_to_date_message("1.0.0");
assert!(message.contains('\n'));
assert!(message.contains(">>>"));
}
#[test]
fn test_update_failure_message_formatting() {
let message = get_update_failure_message("Test error");
assert!(message.contains('\n'));
assert!(message.contains("!!!"));
}
}