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

157 lines
5.6 KiB
Rust

//! Tests of the questions a template asks before it renders (spec D6).
use std::collections::BTreeMap;
use notescraft_templater::{Answers, Ctx, InputKind, parse};
use notescraft_templates::{Date, DateTime, Weekday};
/// The reference moment of the tests: Tuesday, 2026-10-13 15:04:05.
fn now() -> DateTime {
DateTime::new(Date::new(2026, 10, 13).unwrap(), 15, 4, 5).unwrap()
}
/// Reads a note of the vault.
fn read_note(path: &str) -> Option<String> {
if path == "missing.md" {
return None;
}
Some("body".to_owned())
}
/// Answers whether a path exists in the vault.
fn exists(_: &str) -> bool {
true
}
/// The context of the tests.
fn test_ctx() -> Ctx<'static> {
Ctx {
title: "Note",
folder: "Folder",
path: "Folder/Note.md",
absolute_path: Some("/vault/Folder/Note.md"),
now: now(),
week_start: Weekday::Monday,
created: Some(now()),
modified: Some(now()),
tags: &[],
frontmatter: &[],
selection: "",
clipboard: "",
read_note: &read_note,
exists: &exists,
}
}
#[test]
fn the_questions_are_asked_in_document_order() {
let template = parse(
r#"<% tp.system.prompt("Name") %>
<% tp.system.suggester(["a", "b"], ["1", "2"]) %>
<% tp.system.prompt("Other", "def") %>"#,
)
.unwrap();
let inputs = template.inputs(&test_ctx());
assert_eq!(inputs.len(), 3);
assert_eq!(inputs[0].id, 0);
assert_eq!(inputs[0].kind, InputKind::Prompt { text: "Name".to_owned(), default: String::new(), multiline: false });
assert_eq!(inputs[1].id, 1);
assert_eq!(
inputs[1].kind,
InputKind::Suggester { labels: vec!["a".to_owned(), "b".to_owned()], values: vec!["1".to_owned(), "2".to_owned()], placeholder: String::new() }
);
assert_eq!(inputs[2].id, 2);
assert_eq!(inputs[2].kind, InputKind::Prompt { text: "Other".to_owned(), default: "def".to_owned(), multiline: false });
assert_eq!(template.input_errors(&test_ctx()), Vec::new());
}
#[test]
fn a_multiline_prompt_is_reported() {
let template = parse(r#"<% tp.system.prompt("Notes", "", false, true) %>"#).unwrap();
let inputs = template.inputs(&test_ctx());
assert_eq!(inputs.len(), 1);
assert_eq!(inputs[0].kind, InputKind::Prompt { text: "Notes".to_owned(), default: String::new(), multiline: true });
}
#[test]
fn the_answers_are_written_in_the_output() {
let template = parse(r#"<% tp.system.prompt("Name") %>-<% tp.system.suggester(["a"], ["1"]) %>"#).unwrap();
let answers = Answers(BTreeMap::from([(0, Some("Ada".to_owned())), (1, Some("1".to_owned()))]));
let rendered = template.render(&test_ctx(), &answers);
assert_eq!(rendered.text, "Ada-1");
assert_eq!(rendered.errors, Vec::new());
}
#[test]
fn a_command_without_an_answer_uses_the_default() {
let template = parse(r#"<% tp.system.prompt("Name", "Ada") %>!"#).unwrap();
let rendered = template.render(&test_ctx(), &Answers::default());
assert_eq!(rendered.text, "Ada!");
assert_eq!(rendered.errors, Vec::new());
}
#[test]
fn a_cancelled_prompt_with_throw_aborts_the_render() {
let template = parse(r#"before <% tp.system.prompt("Name", "", true) %> after"#).unwrap();
let answers = Answers(BTreeMap::from([(0, None)]));
let rendered = template.render(&test_ctx(), &answers);
assert_eq!(rendered.text, "");
assert_eq!(rendered.errors.len(), 1);
assert_eq!(rendered.errors[0].message, "cancelled");
}
#[test]
fn a_cancelled_prompt_without_throw_is_an_empty_answer() {
let template = parse(r#"before <% tp.system.prompt("Name", "", false) %> after"#).unwrap();
let answers = Answers(BTreeMap::from([(0, None)]));
let rendered = template.render(&test_ctx(), &answers);
assert_eq!(rendered.text, "before after");
assert_eq!(rendered.errors, Vec::new());
}
#[test]
fn a_cancelled_suggester_with_throw_aborts_the_render() {
let template = parse(r#"<% tp.system.suggester(["a"], ["1"], true) %>"#).unwrap();
let answers = Answers(BTreeMap::from([(0, None)]));
let rendered = template.render(&test_ctx(), &answers);
assert_eq!(rendered.text, "");
assert_eq!(rendered.errors.len(), 1);
assert_eq!(rendered.errors[0].message, "cancelled");
}
#[test]
fn a_prompt_argument_is_built_from_the_context() {
let template = parse(r#"<% tp.system.prompt(tp.file.title + " notes") %>"#).unwrap();
let inputs = template.inputs(&test_ctx());
assert_eq!(inputs.len(), 1);
assert_eq!(inputs[0].kind, InputKind::Prompt { text: "Note notes".to_owned(), default: String::new(), multiline: false });
}
#[test]
fn a_prompt_that_cannot_be_built_is_reported() {
let template = parse(r#"<% tp.system.prompt(tp.system.prompt("inner")) %>"#).unwrap();
assert_eq!(template.inputs(&test_ctx()), Vec::new());
let errors = template.input_errors(&test_ctx());
assert_eq!(errors.len(), 1);
assert_eq!(errors[0].line, 1);
}
#[test]
fn the_id_of_a_question_is_the_index_of_its_command() {
let template = parse(r#"<% tp.file.title %><% tp.system.prompt("Name") %>"#).unwrap();
let inputs = template.inputs(&test_ctx());
assert_eq!(inputs.len(), 1);
assert_eq!(inputs[0].id, 1);
let answers = Answers(BTreeMap::from([(1, Some("Ada".to_owned()))]));
let rendered = template.render(&test_ctx(), &answers);
assert_eq!(rendered.text, "NoteAda");
assert_eq!(rendered.errors, Vec::new());
}
#[test]
fn a_command_that_is_not_a_question_is_not_asked() {
let template = parse(r"<% tp.file.title %><% tp.system.clipboard() %>").unwrap();
assert_eq!(template.inputs(&test_ctx()), Vec::new());
assert_eq!(template.input_errors(&test_ctx()), Vec::new());
}