use crate::productivity::CompletionResult;
use std::io::IsTerminal;
pub fn should_celebrate(no_progress: bool) -> bool {
should_celebrate_impl(no_progress, std::io::stdout().is_terminal())
}
fn should_celebrate_impl(no_progress: bool, is_terminal: bool) -> bool {
if no_progress {
return false;
}
if !is_terminal {
return false;
}
true
}
pub fn celebrate_task_completion(
task_id: &str,
task_title: &str,
result: &CompletionResult,
) -> String {
if let Some(milestone) = result.milestone_achieved {
return celebrate_milestone(task_id, task_title, milestone, result.new_streak);
}
if result.streak_updated && result.new_streak > 1 {
celebrate_streak(task_id, task_title, result.new_streak)
} else {
celebrate_standard(task_id, task_title)
}
}
pub fn celebrate_standard(task_id: &str, task_title: &str) -> String {
format!(
r#"
{}
Task {} completed!
{}
"#,
art::SPARKLES,
task_id,
task_title
)
}
fn celebrate_streak(task_id: &str, task_title: &str, streak: u32) -> String {
format!(
r#"
{}
Task {} completed!
{}
{} {}-day streak!
"#,
art::SPARKLES,
task_id,
task_title,
art::FIRE,
streak
)
}
fn celebrate_milestone(task_id: &str, task_title: &str, milestone: u64, streak: u32) -> String {
let streak_text = if streak > 1 {
format!("\n {} {}-day streak!", art::FIRE, streak)
} else {
String::new()
};
format!(
r#"
{}
Task {} completed!
{}
Milestone reached: {} tasks completed!{}
"#,
art::milestone_banner(milestone),
task_id,
task_title,
milestone,
streak_text
)
}
pub fn celebrate_session_summary(tasks_completed: usize, duration_seconds: i64) -> String {
let duration_text = if duration_seconds < 60 {
format!("{}s", duration_seconds)
} else if duration_seconds < 3600 {
format!("{}m", duration_seconds / 60)
} else {
format!(
"{}h {}m",
duration_seconds / 3600,
(duration_seconds % 3600) / 60
)
};
let message = match tasks_completed {
0 => "No tasks completed this session.".to_string(),
1 => "1 task completed!".to_string(),
n => format!("{} tasks completed!", n),
};
format!(
r#"
{}
Session Complete!
{}
Time: {}
"#,
art::SESSION_END,
message,
duration_text
)
}
pub mod art {
pub use crate::constants::symbols::{CHECKMARK, FIRE, SPARKLES, STAR};
pub const SESSION_END: &str = r#"
╔═══════════════════════════════════════╗
║ SESSION COMPLETE ║
╚═══════════════════════════════════════╝
"#;
pub fn milestone_banner(threshold: u64) -> String {
let threshold_str = threshold.to_string();
let padding = (33usize.saturating_sub(threshold_str.len())) / 2;
let left_pad = " ".repeat(padding);
let right_pad = " ".repeat(
33usize
.saturating_sub(threshold_str.len())
.saturating_sub(padding),
);
format!(
r#"
╔═══════════════════════════════════════╗
║{}🎉 MILESTONE: {}{}🎉║
╚═══════════════════════════════════════╝
"#,
left_pad, threshold_str, right_pad
)
}
pub fn streak_fire(streak: u32) -> String {
format!("{} {}-day streak!", FIRE, streak)
}
pub fn celebration_stars() -> &'static str {
r#"
. * . * .
* . ★ . *
. * . * .
"#
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_should_celebrate_respects_no_progress() {
assert!(!should_celebrate_impl(true, true));
}
#[test]
fn test_should_celebrate_requires_terminal() {
assert!(!should_celebrate_impl(false, false));
}
#[test]
fn test_should_celebrate_true_when_tty_and_progress_enabled() {
assert!(should_celebrate_impl(false, true));
}
#[test]
fn test_celebrate_standard_includes_task_info() {
let result = celebrate_standard("RQ-0001", "Test task");
assert!(result.contains("RQ-0001"));
assert!(result.contains("Test task"));
assert!(result.contains("completed"));
}
#[test]
fn test_celebrate_streak_includes_streak_count() {
let result = celebrate_streak("RQ-0001", "Test task", 5);
assert!(result.contains("5-day streak"));
}
#[test]
fn test_celebrate_milestone_includes_threshold() {
let result = celebrate_milestone("RQ-0001", "Test task", 100, 3);
assert!(result.contains("100"));
assert!(result.contains("Milestone"));
assert!(result.contains("3-day streak"));
}
#[test]
fn test_milestone_banner_format() {
let banner = art::milestone_banner(100);
assert!(banner.contains("100"));
assert!(banner.contains("MILESTONE"));
}
#[test]
fn test_celebrate_task_completion_with_milestone() {
let completion_result = CompletionResult {
milestone_achieved: Some(10),
streak_updated: true,
new_streak: 3,
total_completed: 10,
};
let result = celebrate_task_completion("RQ-0010", "Milestone task", &completion_result);
assert!(result.contains("Milestone"));
assert!(result.contains("10"));
assert!(result.contains("3-day streak"));
}
#[test]
fn test_celebrate_task_completion_with_streak() {
let completion_result = CompletionResult {
milestone_achieved: None,
streak_updated: true,
new_streak: 5,
total_completed: 15,
};
let result = celebrate_task_completion("RQ-0015", "Streak task", &completion_result);
assert!(result.contains("5-day streak"));
assert!(!result.contains("Milestone"));
}
#[test]
fn test_celebrate_session_summary() {
let result = celebrate_session_summary(5, 3665);
assert!(result.contains("5 tasks completed"));
assert!(result.contains("1h 1m"));
}
#[test]
fn test_celebrate_session_summary_single_task() {
let result = celebrate_session_summary(1, 45);
assert!(result.contains("1 task completed"));
assert!(result.contains("45s"));
}
#[test]
fn test_celebrate_session_summary_no_tasks() {
let result = celebrate_session_summary(0, 0);
assert!(result.contains("No tasks completed"));
}
}