5e8020aa38
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>
171 lines
7.4 KiB
Rust
171 lines
7.4 KiB
Rust
//! Lazy open: names first, notes indexed in time-boxed steps, the same index in the end.
|
|
mod common;
|
|
use std::time::Duration;
|
|
|
|
use common::{linked_vault, seed, temp_vault};
|
|
use notescraft_engine::{IndexProgress, Registry, Session, register_core};
|
|
use serde_json::Value;
|
|
|
|
const GENEROUS: Duration = Duration::from_secs(30);
|
|
|
|
fn run_to_end(session: &mut Session) -> IndexProgress {
|
|
let mut progress = session.index_progress();
|
|
while !progress.finished {
|
|
progress = session.index_step(GENEROUS);
|
|
}
|
|
progress
|
|
}
|
|
|
|
fn run(session: &mut Session, id: &str) {
|
|
let mut registry = Registry::new();
|
|
register_core(&mut registry).expect("register");
|
|
registry.run(id, session, &Value::Null).unwrap_or_else(|error| panic!("{id}: {error:?}"));
|
|
}
|
|
|
|
#[test]
|
|
fn a_lazy_session_has_the_file_list_but_no_parsed_notes() {
|
|
let vault = linked_vault("lazy_empty", 5);
|
|
seed(vault.path(), &[("pic.png", "x")]);
|
|
let session = Session::open_lazy(vault.path()).expect("lazy");
|
|
let progress = session.index_progress();
|
|
assert_eq!((progress.total, progress.done, progress.finished), (5, 0, false));
|
|
assert!(session.index().is_empty(), "no note is parsed before the first step");
|
|
assert!(session.index().files().contains(&"pic.png"), "attachments are registered at open");
|
|
}
|
|
|
|
#[test]
|
|
fn a_zero_budget_still_indexes_one_note_per_step() {
|
|
let vault = linked_vault("lazy_budget", 4);
|
|
let mut session = Session::open_lazy(vault.path()).expect("lazy");
|
|
for expected in 1..=3 {
|
|
let progress = session.index_step(Duration::ZERO);
|
|
assert_eq!((progress.done, progress.finished), (expected, false));
|
|
assert_eq!(session.index().len(), expected);
|
|
}
|
|
let progress = session.index_step(Duration::ZERO);
|
|
assert_eq!((progress.done, progress.total), (4, 4));
|
|
assert!(progress.finished);
|
|
}
|
|
|
|
#[test]
|
|
fn steps_after_the_end_change_nothing() {
|
|
let vault = linked_vault("lazy_idempotent", 3);
|
|
let mut session = Session::open_lazy(vault.path()).expect("lazy");
|
|
let end = run_to_end(&mut session);
|
|
assert_eq!(session.index_step(GENEROUS), end);
|
|
assert_eq!(session.index_step(Duration::ZERO), end);
|
|
}
|
|
|
|
#[test]
|
|
fn lazy_open_plus_all_steps_builds_the_same_index_as_eager_open() {
|
|
let vault = linked_vault("lazy_same", 12);
|
|
seed(
|
|
vault.path(),
|
|
&[("sub/Extra.md", "---\naliases: [Spark]\n---\n# H\n[[Missing]] [[spark]] #deep/tag zebra word\n"), ("pic.png", "x"), ("notes/bad-utf8.md", "ok")],
|
|
);
|
|
std::fs::write(vault.path().join("notes/bad-utf8.md"), [0xFF, 0xFE, 0x00]).expect("write bad bytes");
|
|
let eager = Session::open(vault.path()).expect("eager");
|
|
drop(eager);
|
|
let mut lazy = Session::open_lazy(vault.path()).expect("lazy");
|
|
let progress = run_to_end(&mut lazy);
|
|
let eager = Session::open_read_only(vault.path()).expect("eager again");
|
|
assert_eq!(progress.done, progress.total);
|
|
assert_eq!(lazy.index().notes(), eager.index().notes());
|
|
assert_eq!(lazy.index().files(), eager.index().files());
|
|
assert_eq!(lazy.index().tags().len(), eager.index().tags().len());
|
|
assert_eq!(lazy.index().unresolved().len(), eager.index().unresolved().len());
|
|
assert_eq!(lazy.index().backlinks("note-03.md").len(), eager.index().backlinks("note-03.md").len());
|
|
assert_eq!(lazy.diagnostics().len(), eager.diagnostics().len(), "the unreadable note is a diagnostic in both");
|
|
let hits = |session: &Session| session.search("zebra", 10).expect("search").into_iter().map(|hit| hit.path).collect::<Vec<_>>();
|
|
assert_eq!(hits(&lazy), hits(&eager));
|
|
assert_eq!(hits(&lazy), vec!["sub/Extra.md".to_owned()]);
|
|
}
|
|
|
|
#[test]
|
|
fn an_empty_vault_finishes_on_the_first_step() {
|
|
let vault = temp_vault("lazy_empty_vault");
|
|
let mut session = Session::open_lazy(vault.path()).expect("lazy");
|
|
let progress = session.index_step(GENEROUS);
|
|
assert_eq!((progress.total, progress.done, progress.finished), (0, 0, true));
|
|
}
|
|
|
|
#[test]
|
|
fn pause_stops_the_steps_and_resume_continues_without_rereading() {
|
|
let vault = linked_vault("lazy_pause", 6);
|
|
let mut session = Session::open_lazy(vault.path()).expect("lazy");
|
|
session.index_step(Duration::ZERO);
|
|
run(&mut session, "index.pause");
|
|
let paused = session.index_step(GENEROUS);
|
|
assert!(paused.paused && !paused.finished);
|
|
assert_eq!(paused.done, 1);
|
|
assert_eq!(session.index().len(), 1);
|
|
run(&mut session, "index.resume");
|
|
let end = run_to_end(&mut session);
|
|
assert_eq!((end.done, end.total, end.paused), (6, 6, false));
|
|
assert_eq!(session.index().len(), 6);
|
|
}
|
|
|
|
#[test]
|
|
fn the_newest_notes_are_read_first() {
|
|
let vault = temp_vault("lazy_order");
|
|
seed(vault.path(), &[("a-old.md", "old"), ("b-new.md", "new"), ("c-mid.md", "mid")]);
|
|
let now = std::time::SystemTime::now();
|
|
for (name, age) in [("a-old.md", 300), ("c-mid.md", 200), ("b-new.md", 100)] {
|
|
let file = std::fs::File::options().write(true).open(vault.path().join(name)).expect("open");
|
|
file.set_modified(now - Duration::from_secs(age)).expect("set mtime");
|
|
}
|
|
let mut session = Session::open_lazy(vault.path()).expect("lazy");
|
|
session.index_step(Duration::ZERO);
|
|
assert_eq!(session.index().notes(), vec!["b-new.md"]);
|
|
session.index_step(Duration::ZERO);
|
|
assert_eq!(session.index().notes(), vec!["b-new.md", "c-mid.md"]);
|
|
}
|
|
|
|
#[test]
|
|
fn opening_a_buffer_indexes_that_note_at_once() {
|
|
let vault = linked_vault("lazy_open_buffer", 5);
|
|
let mut session = Session::open_lazy(vault.path()).expect("lazy");
|
|
session.open_buffer("note-03.md").expect("buffer");
|
|
assert!(session.index().note("note-03.md").is_some());
|
|
let progress = session.index_progress();
|
|
assert_eq!((progress.done, progress.finished), (1, false));
|
|
let end = run_to_end(&mut session);
|
|
assert_eq!((end.done, end.total), (5, 5), "the note is not counted twice");
|
|
assert_eq!(session.index().len(), 5);
|
|
}
|
|
|
|
#[test]
|
|
fn a_note_deleted_before_its_turn_is_skipped_quietly() {
|
|
let vault = linked_vault("lazy_deleted", 3);
|
|
let mut session = Session::open_lazy(vault.path()).expect("lazy");
|
|
std::fs::remove_file(vault.path().join("note-01.md")).expect("remove");
|
|
let end = run_to_end(&mut session);
|
|
assert_eq!(end.done, 3);
|
|
assert!(session.diagnostics().is_empty(), "{:?}", session.diagnostics());
|
|
assert!(session.index().note("note-01.md").is_none());
|
|
}
|
|
|
|
#[test]
|
|
fn renames_and_moves_are_refused_until_the_index_is_complete() {
|
|
let vault = linked_vault("lazy_gate", 4);
|
|
seed(vault.path(), &[("dir/inner.md", "[[note-00]]")]);
|
|
let mut session = Session::open_lazy(vault.path()).expect("lazy");
|
|
let error = session.rename_note("note-00.md", "renamed.md", true).expect_err("refused");
|
|
assert_eq!(error.code(), "indexing");
|
|
assert!(error.to_string().contains("still indexing"), "{error}");
|
|
assert_eq!(session.rename_folder("dir", "moved", true).expect_err("refused").code(), "indexing");
|
|
assert!(vault.path().join("note-00.md").exists(), "nothing was renamed");
|
|
run_to_end(&mut session);
|
|
session.rename_note("note-00.md", "renamed.md", true).expect("rename after the scan");
|
|
session.rename_folder("dir", "moved", true).expect("move after the scan");
|
|
}
|
|
|
|
#[test]
|
|
fn search_reports_partial_results_until_the_scan_ends() {
|
|
let vault = linked_vault("lazy_partial", 3);
|
|
let mut session = Session::open_lazy(vault.path()).expect("lazy");
|
|
assert!(session.search_partial());
|
|
run_to_end(&mut session);
|
|
assert!(!session.search_partial());
|
|
}
|