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
3.1 KiB
Rust

//! Shared fixtures of the evaluator tests: a `Vec` of rows behind `NoteSource`.
#![allow(dead_code)] // shared test helpers: each test file uses only some of them
use notescraft_bases::{Env, Formulas, NoteSource, RowRef, Value, eval};
/// 2024-12-01 12:00:00 UTC.
pub const NOW: i64 = 1_733_054_400_000;
pub struct TestRow {
pub path: String,
pub size: u64,
pub ctime: i64,
pub mtime: i64,
pub tags: Vec<String>,
pub links: Vec<String>,
pub props: Vec<(String, Value)>,
}
impl TestRow {
pub fn new(path: &str) -> Self {
Self { path: path.into(), size: 0, ctime: 0, mtime: 0, tags: vec![], links: vec![], props: vec![] }
}
pub fn prop(mut self, key: &str, value: Value) -> Self {
self.props.push((key.into(), value));
self
}
pub fn tags(mut self, tags: &[&str]) -> Self {
self.tags = tags.iter().map(|t| (*t).to_string()).collect();
self
}
pub fn links(mut self, links: &[&str]) -> Self {
self.links = links.iter().map(|t| (*t).to_string()).collect();
self
}
pub fn row(&self) -> RowRef<'_> {
RowRef {
size: self.size,
ctime_ms: self.ctime,
mtime_ms: self.mtime,
tags: &self.tags,
links: &self.links,
props: &self.props,
..RowRef::new(&self.path)
}
}
}
pub struct Rows(pub Vec<TestRow>);
impl NoteSource for Rows {
fn len(&self) -> usize {
self.0.len()
}
fn row(&self, i: usize) -> Option<RowRef<'_>> {
self.0.get(i).map(TestRow::row)
}
fn find(&self, path: &str) -> Option<RowRef<'_>> {
self.0.iter().find(|r| r.path == path).map(TestRow::row)
}
fn resolve_link(&self, target: &str, _from: &str) -> Option<String> {
let with_md = [target, ".md"].concat();
let found = self.0.iter().find(|r| r.path == target || r.path == with_md || r.row().basename() == target)?;
Some(found.path.clone())
}
}
pub fn n(x: f64) -> Value {
Value::Number(x)
}
pub fn s(x: &str) -> Value {
Value::Str(x.into())
}
pub fn list(items: Vec<Value>) -> Value {
Value::List(items)
}
/// Evaluates `src` over `row` with the formulas, `this` and the source of the arguments.
pub fn run(src: &str, row: &TestRow, formulas: &[(&str, &str)], source: Option<&Rows>, this: Option<&TestRow>) -> Value {
let expr = notescraft_bases::expr::parse_expr(src).unwrap_or_else(|e| panic!("`{src}` does not parse: {e}"));
let formulas: Vec<(String, String)> = formulas.iter().map(|(k, v)| ((*k).to_string(), (*v).to_string())).collect();
let mut env = Env::new(NOW, 7);
env.this = this.map(TestRow::row);
env.source = source.map(|r| r as &dyn NoteSource);
eval(&expr, &env, &row.row(), &Formulas::new(&formulas))
}
/// Evaluates `src` over an empty row.
pub fn ev(src: &str) -> Value {
run(src, &TestRow::new("Folder/Note.md"), &[], None, None)
}
/// Asserts every case, naming the source on a failure.
pub fn cases(table: &[(&str, Value)]) {
for (src, want) in table {
assert_eq!(&ev(src), want, "{src}");
}
}
pub fn is_error(value: &Value) -> bool {
matches!(value, Value::Error(_))
}