use crate::models::{ChecklistItem, Priority, Project, Task};
pub fn format_project_list(projects: &[Project]) -> String {
if projects.is_empty() {
return "No projects found.".to_string();
}
let mut output = String::from("Projects:\n");
for project in projects {
output.push_str(&format_project_line(project));
output.push('\n');
}
output.push_str(&format!("\nTotal: {} project(s)", projects.len()));
output
}
fn format_project_line(project: &Project) -> String {
if project.is_inbox() {
format!("- [{}] Inbox", project.id)
} else if project.color.is_empty() {
format!("- [{}] {}", project.id, project.name)
} else {
format!("- [{}] {} ({})", project.id, project.name, project.color)
}
}
pub fn format_project_details(project: &Project) -> String {
let mut output = String::new();
output.push_str(&format!("Project: {}\n", project.id));
output.push_str(&format!("Name: {}\n", project.name));
if !project.color.is_empty() {
output.push_str(&format!("Color: {}\n", project.color));
}
output.push_str(&format!("View Mode: {}\n", project.view_mode));
output.push_str(&format!("Kind: {}\n", project.kind));
output.push_str(&format!(
"Closed: {}\n",
if project.closed { "yes" } else { "no" }
));
if let Some(ref group_id) = project.group_id {
output.push_str(&format!("Group ID: {}\n", group_id));
}
output
}
pub fn format_task_list(tasks: &[Task]) -> String {
if tasks.is_empty() {
return "No tasks found.".to_string();
}
let mut output = String::from("Tasks:\n");
for task in tasks {
output.push_str(&format_task_line(task));
output.push('\n');
}
output.push_str(&format!("\nTotal: {} task(s)", tasks.len()));
output
}
fn format_task_line(task: &Task) -> String {
let status_marker = if task.is_complete() { "[x]" } else { "[ ]" };
let priority = format_priority_marker(&task.priority);
let due = task
.due_date
.map(|d| format!(" (due: {})", d.format("%Y-%m-%d")))
.unwrap_or_default();
format!("{} {} {}{}", status_marker, priority, task.title, due)
}
fn format_priority_marker(priority: &Priority) -> &'static str {
match priority {
Priority::None => " ",
Priority::Low => "[L]",
Priority::Medium => "[M]",
Priority::High => "[H]",
}
}
pub fn format_task_details(task: &Task) -> String {
let mut output = String::new();
output.push_str(&format!("Task: {}\n", task.id));
output.push_str(&format!("Title: {}\n", task.title));
output.push_str(&format!("Project: {}\n", task.project_id));
output.push_str(&format!("Status: {}\n", task.status));
output.push_str(&format!("Priority: {}\n", task.priority));
if let Some(ref due) = task.due_date {
output.push_str(&format!("Due: {} UTC\n", due.format("%Y-%m-%d %H:%M:%S")));
}
if let Some(ref start) = task.start_date {
output.push_str(&format!(
"Start: {} UTC\n",
start.format("%Y-%m-%d %H:%M:%S")
));
}
if task.is_all_day {
output.push_str("All Day: yes\n");
}
if !task.content.is_empty() {
output.push_str(&format!("Content: {}\n", task.content));
}
if !task.tags.is_empty() {
output.push_str(&format!("Tags: {}\n", task.tags.join(", ")));
}
if !task.time_zone.is_empty() {
output.push_str(&format!("Timezone: {}\n", task.time_zone));
}
if !task.items.is_empty() {
output.push_str(&format!("\nSubtasks ({}):\n", task.items.len()));
for item in &task.items {
output.push_str(&format_subtask_line(item));
output.push('\n');
}
}
output
}
pub fn format_subtask_list(subtasks: &[ChecklistItem]) -> String {
if subtasks.is_empty() {
return "No subtasks found.".to_string();
}
let mut output = String::from("Subtasks:\n");
for subtask in subtasks {
output.push_str(&format_subtask_line(subtask));
output.push('\n');
}
output.push_str(&format!("\nTotal: {} subtask(s)", subtasks.len()));
output
}
fn format_subtask_line(subtask: &ChecklistItem) -> String {
let status_marker = if subtask.is_complete() { "[x]" } else { "[ ]" };
format!(" {} {}", status_marker, subtask.title)
}
pub fn format_success(message: &str) -> String {
format!("OK: {}", message)
}
pub fn format_success_with_id(message: &str, id: &str) -> String {
format!("OK: {}\n ID: {}", message, id)
}
#[allow(dead_code)] pub fn format_error(message: &str) -> String {
format!("Error: {}", message)
}
pub fn format_version(name: &str, version: &str) -> String {
format!("{} {}", name, version)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::models::{Priority, Status};
use chrono::Utc;
fn sample_project() -> Project {
Project {
id: "proj123".to_string(),
name: "Work".to_string(),
color: "#FF5733".to_string(),
sort_order: 0,
closed: false,
group_id: None,
view_mode: "list".to_string(),
permission: None,
kind: "TASK".to_string(),
}
}
fn sample_task() -> Task {
Task {
id: "task123".to_string(),
project_id: "proj123".to_string(),
title: "Complete report".to_string(),
is_all_day: false,
completed_time: None,
content: "Finish the quarterly report".to_string(),
due_date: None,
items: vec![],
priority: Priority::Medium,
reminders: vec![],
repeat_flag: None,
sort_order: 0,
start_date: None,
status: Status::Normal,
time_zone: "UTC".to_string(),
tags: vec!["work".to_string(), "urgent".to_string()],
}
}
#[test]
fn test_format_project_list() {
let projects = vec![sample_project(), Project::inbox()];
let output = format_project_list(&projects);
assert!(output.contains("Projects:"));
assert!(output.contains("[proj123] Work (#FF5733)"));
assert!(output.contains("[inbox] Inbox"));
assert!(output.contains("Total: 2 project(s)"));
}
#[test]
fn test_format_empty_project_list() {
let output = format_project_list(&[]);
assert_eq!(output, "No projects found.");
}
#[test]
fn test_format_project_details() {
let project = sample_project();
let output = format_project_details(&project);
assert!(output.contains("Project: proj123"));
assert!(output.contains("Name: Work"));
assert!(output.contains("Color: #FF5733"));
assert!(output.contains("View Mode: list"));
}
#[test]
fn test_format_task_list() {
let mut task = sample_task();
task.due_date = Some(Utc::now());
let tasks = vec![task];
let output = format_task_list(&tasks);
assert!(output.contains("Tasks:"));
assert!(output.contains("[ ] [M] Complete report"));
assert!(output.contains("Total: 1 task(s)"));
}
#[test]
fn test_format_empty_task_list() {
let output = format_task_list(&[]);
assert_eq!(output, "No tasks found.");
}
#[test]
fn test_format_task_details() {
let task = sample_task();
let output = format_task_details(&task);
assert!(output.contains("Task: task123"));
assert!(output.contains("Title: Complete report"));
assert!(output.contains("Status: incomplete"));
assert!(output.contains("Priority: medium"));
assert!(output.contains("Tags: work, urgent"));
}
#[test]
fn test_format_success() {
let output = format_success("Task created successfully");
assert_eq!(output, "OK: Task created successfully");
}
#[test]
fn test_format_success_with_id() {
let output = format_success_with_id("Task created", "task123");
assert!(output.contains("OK: Task created"));
assert!(output.contains("ID: task123"));
}
#[test]
fn test_format_error() {
let output = format_error("Task not found");
assert_eq!(output, "Error: Task not found");
}
#[test]
fn test_format_version() {
let output = format_version("tickrs", "0.1.0");
assert_eq!(output, "tickrs 0.1.0");
}
#[test]
fn test_format_priority_markers() {
assert_eq!(format_priority_marker(&Priority::None), " ");
assert_eq!(format_priority_marker(&Priority::Low), "[L]");
assert_eq!(format_priority_marker(&Priority::Medium), "[M]");
assert_eq!(format_priority_marker(&Priority::High), "[H]");
}
#[test]
fn test_format_subtask_list() {
let subtasks = vec![
ChecklistItem {
id: "sub1".to_string(),
title: "Step 1".to_string(),
status: 0,
completed_time: 0,
is_all_day: false,
sort_order: 0,
start_date: None,
time_zone: "UTC".to_string(),
},
ChecklistItem {
id: "sub2".to_string(),
title: "Step 2".to_string(),
status: 1,
completed_time: 0,
is_all_day: false,
sort_order: 1,
start_date: None,
time_zone: "UTC".to_string(),
},
];
let output = format_subtask_list(&subtasks);
assert!(output.contains("Subtasks:"));
assert!(output.contains("[ ] Step 1"));
assert!(output.contains("[x] Step 2"));
assert!(output.contains("Total: 2 subtask(s)"));
}
}