Files
Marcos Paulo 6ed829e5bd
ci / ci (macos-latest) (push) Has been cancelled
ci / ci (ubuntu-latest) (push) Has been cancelled
ci / ci (windows-latest) (push) Has been cancelled
feat: note scan into tasks, Templater inputs, note banner and title icon (MiniMax M3.1 Flash); agy feature-gap research
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-21 12:07:28 -03:00

142 lines
4.3 KiB
Rust

//! Tests of the scan of a note.
use notescraft_tasks::{ScanCfg, Task, scan_note};
/// Scans a note with the default configuration.
fn scan(text: &str) -> Vec<Task> {
scan_note("notes/n.md", text, &ScanCfg::default())
}
/// The descriptions of the tasks of a note.
fn descriptions(tasks: &[Task]) -> Vec<&str> {
tasks.iter().map(|task| task.description.as_str()).collect()
}
#[test]
fn tasks_in_a_fence_are_ignored() {
let text = "- [ ] first\n```\n- [ ] hidden\n```\n- [ ] last\n";
let tasks = scan(text);
assert_eq!(descriptions(&tasks), vec!["first", "last"]);
assert_eq!(tasks[0].line, 0);
assert_eq!(tasks[1].line, 4);
}
#[test]
fn an_unclosed_fence_hides_the_rest_of_the_note() {
let text = "- [ ] first\n```\n- [ ] hidden\n";
let tasks = scan(text);
assert_eq!(descriptions(&tasks), vec!["first"]);
}
#[test]
fn frontmatter_lines_are_ignored() {
let text = "---\ntags: [work]\nstatus: open\n---\n- [ ] real\n";
let tasks = scan(text);
assert_eq!(descriptions(&tasks), vec!["real"]);
assert_eq!(tasks[0].line, 4);
}
#[test]
fn a_note_without_frontmatter_starts_at_the_first_line() {
let tasks = scan("- [ ] real\n");
assert_eq!(tasks[0].line, 0);
}
#[test]
fn the_heading_is_tracked_across_levels() {
let text = "# One\n## Two\n### Three\n- [ ] a\n## Four\n- [ ] b\n# Five\n- [ ] c\n";
let tasks = scan(text);
assert_eq!(tasks.len(), 3);
assert_eq!(tasks[0].heading.as_deref(), Some("Three"));
assert_eq!(tasks[1].heading.as_deref(), Some("Four"));
assert_eq!(tasks[2].heading.as_deref(), Some("Five"));
}
#[test]
fn a_task_without_a_heading_above_has_none() {
let tasks = scan("- [ ] a\n");
assert_eq!(tasks[0].heading, None);
}
#[test]
fn a_nested_task_knows_its_parent() {
let text = "- [ ] parent\n - [ ] child\n - [ ] grandchild\n- [ ] sibling\n";
let tasks = scan(text);
assert_eq!(tasks.len(), 4);
assert_eq!(tasks[0].parent_line, None);
assert_eq!(tasks[1].parent_line, Some(0));
assert_eq!(tasks[2].parent_line, Some(1));
assert_eq!(tasks[3].parent_line, None);
}
#[test]
fn a_child_of_a_filtered_out_task_has_no_parent() {
let cfg = ScanCfg { global_filter: Some("#task".to_owned()) };
let text = "- [ ] parent\n - [ ] child #task\n";
let tasks = scan_note("n.md", text, &cfg);
assert_eq!(tasks.len(), 1);
assert_eq!(tasks[0].parent_line, None);
}
#[test]
fn the_global_filter_selects_the_lines() {
let cfg = ScanCfg { global_filter: Some("#task".to_owned()) };
let text = "- [ ] a #task\n- [ ] b\n- [ ] c #task\n";
let tasks = scan_note("n.md", text, &cfg);
assert_eq!(descriptions(&tasks), vec!["a", "c"]);
}
#[test]
fn without_a_filter_every_checkbox_is_a_task() {
let text = "- [ ] a #task\n- [ ] b\n";
let tasks = scan(text);
assert_eq!(descriptions(&tasks), vec!["a #task", "b"]);
}
#[test]
fn the_filter_stays_in_the_source_line() {
let cfg = ScanCfg { global_filter: Some("#task".to_owned()) };
let tasks = scan_note("n.md", "- [ ] a #task\n", &cfg);
assert_eq!(tasks[0].original, "- [ ] a #task");
assert_eq!(tasks[0].description, "a");
}
#[test]
fn a_note_with_many_tasks_is_capped() {
let mut text = String::new();
for number in 0..1500 {
text.push_str("- [ ] task ");
text.push_str(&number.to_string());
text.push('\n');
}
let tasks = scan(&text);
assert_eq!(tasks.len(), 1000);
assert_eq!(tasks[999].description, "task 999");
}
#[test]
fn carriage_returns_are_not_part_of_the_original() {
let text = "- [ ] a\r\n- [ ] b\r\n";
let tasks = scan(text);
assert_eq!(tasks.len(), 2);
assert_eq!(tasks[0].original, "- [ ] a");
assert_eq!(tasks[1].original, "- [ ] b");
assert!(tasks.iter().all(|task| !task.original.contains('\r')));
}
#[test]
fn the_path_and_the_line_are_filled() {
let tasks = scan_note("notes/deep/n.md", "text\n- [ ] a\n", &ScanCfg::default());
assert_eq!(tasks[0].path, "notes/deep/n.md");
assert_eq!(tasks[0].line, 1);
}
#[test]
fn the_signifiers_of_a_task_are_read() {
let tasks = scan("- [ ] a 📅 2026-10-20 🔺\n");
assert_eq!(tasks[0].description, "a");
assert_eq!(tasks[0].priority, notescraft_tasks::Priority::Highest);
let due = tasks[0].due.map(|date| format!("{:04}-{:02}-{:02}", date.year(), date.month(), date.day()));
assert_eq!(due, Some("2026-10-20".to_owned()));
}