Files
Marcos Paulo 5e8020aa38
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: Obsidian basics, editor previews, graph rework, query blocks, stacked tabs, pop-out windows
Editor previews (colour swatches, arrows, embeds), the 18 obsidian-basics tasks (find and replace
with regex, back/forward, auto-pair, footnotes, folding, search operators, split view, PDF/audio/
video embeds, spelling, drag and drop, pinned tabs, settings) and round 2: ```query blocks, stacked
tabs, pop-out windows, tasks ticked inside embeds, text around an embed, embeds that follow edits,
block menu shortcut. New crates: notescraft-regex (linear-time engine) and notescraft-syntax.

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

63 lines
3.3 KiB
Rust

//! The new search operators through the `search` command: `section:`,
//! `block:`, `task:`, `task-todo:`, `task-done:` and the `match_case`
//! param, over a real vault and a real session.
mod common;
use common::{seed, temp_vault};
use notescraft_engine::{Registry, Session, register_core};
use serde_json::{Value, json};
/// A registry plus a session on a vault with one note per shape.
fn fixture(name: &str) -> (Registry<Session>, Session, common::TempVault) {
let vault = temp_vault(name);
seed(
vault.path(),
&[
("sections.md", "# Alpha\n\nalpha text here\n\n# Beta\n\nbeta text\n"),
("tasks.md", "# Tasks\n\n- [ ] ship the release\n- [x] ship the docs\n"),
("blocks.md", "alpha alone\n\nship the release\n"),
],
);
let mut registry = Registry::new();
register_core(&mut registry).expect("register");
let session = Session::open(vault.path()).expect("open");
(registry, session, vault)
}
/// The paths a query returns, through the `search` command.
fn hits(registry: &Registry<Session>, session: &mut Session, params: &Value) -> Vec<String> {
let result = registry.run("search", session, params).expect("search");
let list = result.get("hits").and_then(Value::as_array).map_or_else(Vec::new, Clone::clone);
list.iter().map(|hit| hit.get("path").and_then(Value::as_str).unwrap_or_default().to_string()).collect()
}
/// The operators reach the engine unchanged.
#[test]
fn the_structural_operators_run_through_the_command() {
let (registry, mut session, _vault) = fixture("operators");
assert_eq!(hits(&registry, &mut session, &json!({ "query": "section:(alpha text)" })), vec!["sections.md"]);
assert_eq!(hits(&registry, &mut session, &json!({ "query": "section:(alpha beta)" })), Vec::<String>::new());
assert_eq!(hits(&registry, &mut session, &json!({ "query": "block:(ship the release)" })), vec!["tasks.md", "blocks.md"]);
assert_eq!(hits(&registry, &mut session, &json!({ "query": "task:release" })), vec!["tasks.md"]);
assert_eq!(hits(&registry, &mut session, &json!({ "query": "task-todo:" })), vec!["tasks.md"]);
assert_eq!(hits(&registry, &mut session, &json!({ "query": "task-done:docs" })), vec!["tasks.md"]);
assert_eq!(hits(&registry, &mut session, &json!({ "query": "task:release path:blocks/" })), Vec::<String>::new());
}
/// `match_case` is the param form of the `match-case:` operator.
#[test]
fn the_match_case_param_narrows_the_result() {
let (registry, mut session, _vault) = fixture("match-case");
assert_eq!(hits(&registry, &mut session, &json!({ "query": "ALPHA" })), vec!["sections.md", "blocks.md"]);
assert_eq!(hits(&registry, &mut session, &json!({ "query": "ALPHA", "match_case": true })), Vec::<String>::new());
assert_eq!(hits(&registry, &mut session, &json!({ "query": "Alpha", "match_case": true })), vec!["sections.md"]);
assert_eq!(hits(&registry, &mut session, &json!({ "query": "match-case: Alpha" })), vec!["sections.md"]);
assert_eq!(hits(&registry, &mut session, &json!({ "query": "alpha", "match_case": false })), vec!["sections.md", "blocks.md"]);
// A malformed query is still refused, switch or not.
assert!(registry.run("search", &mut session, &json!({ "query": "match-case:", "match_case": true })).is_err());
}