test(todo): add comprehensive unit tests for todo features

Add tests for:
- @Done insertion at various line positions
- Future task filtering with show_future flag
- Task sorting by moment ascending
- Error message formatting for all new error variants
- Trailing newline preservation
This commit is contained in:
Konstantin Fickel 2026-04-02 18:28:13 +02:00
parent 124a5b7e2a
commit e05e9cfd90
Signed by: kfickel
GPG key ID: A793722F9933C1A5

View file

@ -170,6 +170,26 @@ pub fn run() -> Result<(), StreamdError> {
#[cfg(test)]
mod tests {
use super::*;
use chrono::{Duration, TimeZone};
use indexmap::IndexMap;
fn make_task_shard(moment: chrono::DateTime<Utc>, file: &str) -> LocalizedShard {
let mut location = IndexMap::new();
location.insert("file".to_string(), file.to_string());
location.insert("task".to_string(), "open".to_string());
LocalizedShard {
markers: vec!["Task".to_string()],
tags: vec![],
start_line: 1,
end_line: 1,
moment,
location,
children: vec![],
}
}
#[test]
fn test_insert_done_after_task() {
let line = "Some content @Task with more text";
@ -184,6 +204,13 @@ mod tests {
assert_eq!(result, "Some content @Task @Done");
}
#[test]
fn test_insert_done_only_first_task() {
let line = "Some @Task content @Task again";
let result = line.replacen("@Task", "@Task @Done", 1);
assert_eq!(result, "Some @Task @Done content @Task again");
}
#[test]
fn test_task_count_single() {
let line = "Some content @Task with more text";
@ -201,4 +228,104 @@ mod tests {
let line = "Some content without task marker";
assert_eq!(line.matches("@Task").count(), 0);
}
#[test]
fn test_filter_future_tasks_excludes_future_when_show_future_false() {
let now = Utc::now();
let past = now - Duration::hours(1);
let future = now + Duration::hours(1);
let tasks = vec![
make_task_shard(past, "past.md"),
make_task_shard(future, "future.md"),
];
let filtered: Vec<_> = tasks
.into_iter()
.filter(|shard| shard.moment <= now)
.collect();
assert_eq!(filtered.len(), 1);
assert_eq!(filtered[0].location.get("file").unwrap(), "past.md");
}
#[test]
fn test_filter_future_tasks_includes_all_when_show_future_true() {
let now = Utc::now();
let past = now - Duration::hours(1);
let future = now + Duration::hours(1);
let tasks = vec![
make_task_shard(past, "past.md"),
make_task_shard(future, "future.md"),
];
let filtered: Vec<_> = tasks.into_iter().filter(|_| true).collect();
assert_eq!(filtered.len(), 2);
}
#[test]
fn test_sort_tasks_by_moment_ascending() {
let oldest = Utc.with_ymd_and_hms(2020, 1, 1, 0, 0, 0).unwrap();
let middle = Utc.with_ymd_and_hms(2021, 1, 1, 0, 0, 0).unwrap();
let newest = Utc.with_ymd_and_hms(2022, 1, 1, 0, 0, 0).unwrap();
let mut tasks = vec![
make_task_shard(newest, "newest.md"),
make_task_shard(oldest, "oldest.md"),
make_task_shard(middle, "middle.md"),
];
tasks.sort_by(|a, b| a.moment.cmp(&b.moment));
assert_eq!(tasks[0].location.get("file").unwrap(), "oldest.md");
assert_eq!(tasks[1].location.get("file").unwrap(), "middle.md");
assert_eq!(tasks[2].location.get("file").unwrap(), "newest.md");
}
#[test]
fn test_preserve_trailing_newline() {
let content_with_newline = "line1\nline2\n";
let content_without_newline = "line1\nline2";
assert!(content_with_newline.ends_with('\n'));
assert!(!content_without_newline.ends_with('\n'));
}
#[test]
fn test_invalid_task_number_zero() {
let result = StreamdError::InvalidTaskNumber(0, 5);
assert_eq!(
result.to_string(),
"Invalid task number 0: only 5 tasks available"
);
}
#[test]
fn test_invalid_task_number_exceeds_count() {
let result = StreamdError::InvalidTaskNumber(10, 3);
assert_eq!(
result.to_string(),
"Invalid task number 10: only 3 tasks available"
);
}
#[test]
fn test_multiple_task_markers_error_message() {
let result = StreamdError::MultipleTaskMarkers("/path/file.md".to_string(), 42);
assert_eq!(
result.to_string(),
"Multiple @Task markers found in /path/file.md:42 - cannot auto-insert @Done"
);
}
#[test]
fn test_no_task_marker_error_message() {
let result = StreamdError::NoTaskMarker("/path/file.md".to_string(), 42);
assert_eq!(
result.to_string(),
"No @Task marker found in /path/file.md:42"
);
}
}