Files
Marcos Paulo eb1245bd20
ci / ci (macos-latest) (push) Has been cancelled
ci / ci (ubuntu-latest) (push) Has been cancelled
ci / ci (windows-latest) (push) Has been cancelled
fix: tab switch freeze (unlinked scan off the UI thread); assistant: vault MCP wiring, preamble, wrapping choices, presets; 23 grammars incl. Twig; Tasks query, Templater include, icon picker
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-21 13:36:33 -03:00

171 lines
5.8 KiB
Rust

//! Tests of `tp.file.include` and of the Bases fallback (spec D3).
use std::collections::BTreeMap;
use notescraft_bases::Value;
use notescraft_templater::{Answers, Ctx, Rendered, 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()
}
/// The notes of the tests, by path.
fn notes() -> BTreeMap<String, String> {
let mut notes = BTreeMap::new();
notes.insert("Note.md".to_owned(), "# Title\n\nIntro.\n\n## Section\n\nSection body.\n\n## Other\n\nOther body.\n".to_owned());
notes.insert("Block.md".to_owned(), "First paragraph.\n\nSecond paragraph with an id ^abc\n\nThird paragraph.\n".to_owned());
notes.insert("Templated.md".to_owned(), "Hello <% tp.file.title %>!\n".to_owned());
notes.insert("A.md".to_owned(), "<% tp.file.include(\"[[A]]\") %>\n".to_owned());
notes
}
/// The tags of the target note.
fn tags() -> Vec<String> {
vec!["alpha".to_owned()]
}
/// The frontmatter of the target note.
fn frontmatter() -> Vec<(String, Value)> {
vec![("project".to_owned(), Value::Str("NotesCraft".to_owned()))]
}
/// A context with the notes of the tests.
fn ctx<'a>(read_note: &'a dyn Fn(&str) -> Option<String>, exists: &'a dyn Fn(&str) -> bool, tags: &'a [String], frontmatter: &'a [(String, Value)]) -> Ctx<'a> {
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,
exists,
}
}
/// Renders a template with the notes of the tests.
fn render(template: &str) -> Rendered {
let notes = notes();
let read_note = |path: &str| notes.get(path).cloned();
let exists = |_: &str| -> bool { true };
let tags = tags();
let frontmatter = frontmatter();
let ctx = ctx(&read_note, &exists, &tags, &frontmatter);
let template = parse(template).expect("the template parses");
template.render(&ctx, &Answers::default())
}
#[test]
fn a_whole_note_is_included() {
let rendered = render("<% tp.file.include(\"[[Note]]\") %>");
assert_eq!(rendered.text, notes()["Note.md"]);
assert_eq!(rendered.errors, Vec::new());
}
#[test]
fn a_note_is_included_without_brackets() {
let rendered = render("<% tp.file.include(\"Note\") %>");
assert_eq!(rendered.text, notes()["Note.md"]);
assert_eq!(rendered.errors, Vec::new());
}
#[test]
fn a_heading_section_is_included() {
let rendered = render("<% tp.file.include(\"[[Note#Section]]\") %>");
assert_eq!(rendered.text, "## Section\n\nSection body.\n\n");
assert_eq!(rendered.errors, Vec::new());
}
#[test]
fn a_block_is_included() {
let rendered = render("<% tp.file.include(\"[[Block#^abc]]\") %>");
assert_eq!(rendered.text, "Second paragraph with an id");
assert_eq!(rendered.errors, Vec::new());
}
#[test]
fn the_templates_inside_an_included_note_are_rendered() {
let rendered = render("<% tp.file.include(\"[[Templated]]\") %>");
assert_eq!(rendered.text, "Hello Note!\n");
assert_eq!(rendered.errors, Vec::new());
}
#[test]
fn a_cycle_stops_with_an_error() {
let rendered = render("<% tp.file.include(\"[[A]]\") %>");
assert_eq!(rendered.errors.len(), 1, "{:?}", rendered.errors);
assert!(rendered.errors[0].message.contains("deeper than 4 levels"), "{}", rendered.errors[0].message);
assert_eq!(rendered.text, "<% tp.file.include(\"[[A]]\") %>\n\n\n\n");
}
#[test]
fn a_missing_note_is_reported() {
let rendered = render("<% tp.file.include(\"[[Missing]]\") %>");
assert_eq!(rendered.errors.len(), 1);
assert!(rendered.errors[0].message.contains("does not exist"), "{}", rendered.errors[0].message);
assert_eq!(rendered.text, "<% tp.file.include(\"[[Missing]]\") %>");
}
#[test]
fn a_missing_section_is_reported() {
let rendered = render("<% tp.file.include(\"[[Note#Nowhere]]\") %>");
assert_eq!(rendered.errors.len(), 1);
assert!(rendered.errors[0].message.contains("is not in the note"), "{}", rendered.errors[0].message);
}
#[test]
fn a_bases_expression_is_evaluated() {
let rendered = render("<% file.name.upper() %>");
assert_eq!(rendered.text, "NOTE.MD");
assert_eq!(rendered.errors, Vec::new());
}
#[test]
fn a_bases_path_reads_the_note_of_the_context() {
let rendered = render("<% file.folder %>|<% file.basename %>");
assert_eq!(rendered.text, "Folder|Note");
assert_eq!(rendered.errors, Vec::new());
}
#[test]
fn a_bases_path_works_inside_a_template_command() {
let rendered = render("<% tp.file.title + file.name %>");
assert_eq!(rendered.text, "NoteNote.md");
assert_eq!(rendered.errors, Vec::new());
}
#[test]
fn the_date_of_the_fallback_is_the_date_of_the_module() {
let fallback = render("<% now().format(\"YYYY\") %>");
let module = render("<% tp.date.now(\"YYYY\") %>");
assert_eq!(fallback.text, "2026");
assert_eq!(fallback.text, module.text);
assert_eq!(fallback.errors, Vec::new());
let dated = render("<% date(now()).format(\"YYYY\") %>");
assert_eq!(dated.text, module.text);
assert_eq!(dated.errors, Vec::new());
}
#[test]
fn a_failing_bases_expression_is_reported() {
let rendered = render("<% file.nope() %>");
assert_eq!(rendered.errors.len(), 1, "{:?}", rendered.errors);
assert_eq!(rendered.text, "<% file.nope() %>");
}
#[test]
fn an_unknown_template_command_is_not_a_bases_expression() {
let rendered = render("<% tp.foo.bar %>");
assert_eq!(rendered.errors.len(), 1);
assert_eq!(rendered.errors[0].message, "tp.foo.bar is not supported");
assert_eq!(rendered.text, "<% tp.foo.bar %>");
}