Files
Marcos Paulo aeac86ddd2
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: Bases model, evaluator, function library and map data model
.base model (filters, formulas, properties, views; unknown keys kept in order; limits and invalid-value errors), evaluator with the D5 type rules, lazy if and boolean operators, memoised formulas with cycle errors, depth cap 200, string/number/object/list/date/link/file/regexp functions with lambdas (regex through notescraft-regex), and the map model (Web Mercator, tile ids, viewport tiles, grid clustering, coordinate and colour parsing, OSM attribution constants; Paris z10 is tile 518/352). Bases tasks 2, 5, 6 (Claude Sonnet) and 8 (MiniMax M3.1 Flash, OpenCode Zen free route). Verified: notescraft-bases suites all pass, clippy -D warnings, xtask layers and tree clean. Known gaps: toFixed rounds ties to even, list.unique is quadratic.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-21 02:58:07 -03:00

108 lines
5.8 KiB
Rust

//! File and link functions through a `NoteSource`.
mod common;
use common::{Rows, TestRow, list, n, run, s};
use notescraft_bases::Value;
fn vault() -> Rows {
Rows(vec![
TestRow::new("Books/Dune.md")
.tags(&["#book", "#scifi/space"])
.links(&["Herbert", "Missing"])
.prop("type", list(vec![Value::Link { target: "Novel".into(), display: String::new() }]))
.prop("year", n(1965.0)),
TestRow::new("Books/Deep/Emma.md")
.tags(&["book"])
.links(&["Books/Dune.md"])
.prop("author", Value::Link { target: "Herbert".into(), display: "Frank".into() }),
TestRow::new("People/Herbert.md").tags(&["#author"]).links(&["Dune#Plot"]),
TestRow::new("Types/Novel.md").prop("icon", s("book-open")),
TestRow::new("Root.md"),
])
}
fn at(src: &str, row: &str) -> Value {
let vault = vault();
let found = vault.0.iter().find(|r| r.path == row).expect("row");
run(src, found, &[], Some(&vault), None)
}
#[test]
fn has_tag_matches_nested_tags_and_any_value() {
let dune = "Books/Dune.md";
assert_eq!(at("file.hasTag(\"book\")", dune), Value::Bool(true));
assert_eq!(at("file.hasTag(\"#book\")", dune), Value::Bool(true));
assert_eq!(at("file.hasTag(\"scifi\")", dune), Value::Bool(true));
assert_eq!(at("file.hasTag(\"scifi/space\")", dune), Value::Bool(true));
assert_eq!(at("file.hasTag(\"sci\")", dune), Value::Bool(false));
assert_eq!(at("file.hasTag(\"a\", \"b\", \"book\")", dune), Value::Bool(true));
assert_eq!(at("file.hasTag()", dune), Value::Bool(false));
assert_eq!(at("file.hasTag(\"BOOK\")", "Books/Deep/Emma.md"), Value::Bool(true));
assert!(matches!(at("file.hasTag(1)", dune), Value::Error(_)));
}
#[test]
fn in_folder_matches_descendants() {
assert_eq!(at("file.inFolder(\"Books\")", "Books/Dune.md"), Value::Bool(true));
assert_eq!(at("file.inFolder(\"Books\")", "Books/Deep/Emma.md"), Value::Bool(true));
assert_eq!(at("file.inFolder(\"Books/Deep\")", "Books/Dune.md"), Value::Bool(false));
assert_eq!(at("file.inFolder(\"Book\")", "Books/Dune.md"), Value::Bool(false));
assert_eq!(at("file.inFolder(\"/\")", "Root.md"), Value::Bool(true));
assert_eq!(at("file.inFolder(\"Books/\")", "Books/Dune.md"), Value::Bool(true));
assert!(matches!(at("file.inFolder(1)", "Root.md"), Value::Error(_)));
}
#[test]
fn has_property_and_has_link() {
assert_eq!(at("file.hasProperty(\"year\")", "Books/Dune.md"), Value::Bool(true));
assert_eq!(at("file.hasProperty(\"nope\")", "Books/Dune.md"), Value::Bool(false));
assert_eq!(at("file.hasLink(\"Herbert\")", "Books/Dune.md"), Value::Bool(true));
assert_eq!(at("file.hasLink(link(\"Herbert\"))", "Books/Dune.md"), Value::Bool(true));
assert_eq!(at("file.hasLink(file(\"People/Herbert.md\"))", "Books/Dune.md"), Value::Bool(true));
assert_eq!(at("file.hasLink(\"Types/Novel.md\")", "Books/Dune.md"), Value::Bool(false));
assert_eq!(at("file.hasLink(\"Dune\")", "People/Herbert.md"), Value::Bool(true));
assert_eq!(at("file.hasLink(\"Books/Dune.md\")", "Books/Deep/Emma.md"), Value::Bool(true));
assert_eq!(at("file.hasLink(\"Missing\")", "Books/Dune.md"), Value::Bool(true));
assert!(matches!(at("file.hasLink(1)", "Root.md"), Value::Error(_)));
}
#[test]
fn as_link_and_file_constructor() {
assert_eq!(at("file.asLink()", "Root.md"), Value::Link { target: "Root.md".into(), display: String::new() });
assert_eq!(at("file.asLink(\"Home\")", "Root.md"), Value::Link { target: "Root.md".into(), display: "Home".into() });
assert_eq!(at("file(\"Herbert\")", "Root.md"), Value::File { path: "People/Herbert.md".into() });
assert_eq!(at("file(link(\"[[Novel]]\"))", "Root.md"), Value::File { path: "Types/Novel.md".into() });
assert_eq!(at("file(\"Nope\")", "Root.md"), Value::Null);
assert_eq!(at("file(null)", "Root.md"), Value::Null);
assert_eq!(at("file(file)", "Root.md"), Value::File { path: "Root.md".into() });
assert!(matches!(at("file(1)", "Root.md"), Value::Error(_)));
assert_eq!(at("file.asLink().asFile()", "Root.md"), Value::File { path: "Root.md".into() });
}
#[test]
fn link_functions_and_file_fields_of_other_files() {
assert_eq!(at("list(type)[0].asFile().properties.icon", "Books/Dune.md"), s("book-open"));
assert_eq!(at("type[0].asFile().name", "Books/Dune.md"), s("Novel.md"));
assert_eq!(at("author.asFile().file", "Books/Deep/Emma.md"), Value::File { path: "People/Herbert.md".into() });
assert_eq!(at("link(\"Nope\").asFile()", "Root.md"), Value::Null);
assert_eq!(at("author.linksTo(file(\"Books/Dune.md\"))", "Books/Deep/Emma.md"), Value::Bool(true));
assert_eq!(at("author.linksTo(file(\"Root.md\"))", "Books/Deep/Emma.md"), Value::Bool(false));
assert_eq!(at("link(\"Herbert\").linksTo(file(\"Books/Dune.md\"))", "Root.md"), Value::Bool(true));
assert_eq!(at("link(\"Herbert\").linksTo(\"Missing\")", "Root.md"), Value::Bool(false));
assert_eq!(at("link(\"Nope\").linksTo(file)", "Root.md"), Value::Bool(false));
assert_eq!(at("author == file(\"People/Herbert.md\")", "Books/Deep/Emma.md"), Value::Bool(false));
assert_eq!(at("file.links[0].asFile().basename", "Books/Dune.md"), s("Herbert"));
assert_eq!(at("[file(\"Types/Novel.md\")].map(value.size)", "Root.md"), list(vec![n(0.0)]));
}
#[test]
fn files_without_a_source_answer_false_or_null() {
let row = TestRow::new("a.md").tags(&["x"]);
assert_eq!(run("file.hasTag(\"x\")", &row, &[], None, None), Value::Bool(true));
assert_eq!(run("link(\"B\").asFile()", &row, &[], None, None), Value::Null);
assert_eq!(run("file(\"b.md\")", &row, &[], None, None), Value::File { path: "b.md".into() });
assert_eq!(run("file(\"b.md\").hasTag(\"x\")", &row, &[], None, None), Value::Bool(false));
assert_eq!(run("file(\"b.md\").name", &row, &[], None, None), Value::Null);
}