Files
Marcos Paulo bc0b258e04 feat: engine and core crates: comment sidecars hidden and removed with the note, e2e comments test, extension tables, annotate text and arrow items
Sidecars (*.comments.json) stay out of the index and listings and follow rename and delete of their note; end-to-end comments test over a stub server (Ana comments and mentions Bob, Bob resolves, second sync changes nothing); viewer extension tables gain ico, ogv, 3gp, opus, aac; annotate crate: Edit::Replace for undoable text moves and edits, line items carry an arrow flag; syntax test fixed (TeX is a bundled grammar). Built by MiniMax M3.1 Flash and Claude Sonnet; verified with the full cargo xtask ci on the combined tree (4422 tests, clippy -D warnings, layers, tree, i18n).

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

84 lines
3.6 KiB
Rust

//! The life of the sidecar that holds the comments of a note (D1): it follows
//! the note when it moves and it goes when the note goes.
mod common;
use common::{seed, temp_vault};
use notescraft_engine::{Registry, Session, register_core};
use serde_json::{Value, json};
/// The note the tests comment on.
const NOTE: &str = "# Note\n\nThis always works, except on slow disks.\n";
/// The quote the tests anchor their thread to.
const QUOTE: &str = "This always works";
/// A registry, a session and a vault with one note.
fn fixture(name: &str) -> (Registry<Session>, Session, common::TempVault) {
let vault = temp_vault(name);
seed(vault.path(), &[("note.md", NOTE)]);
let mut reg = Registry::new();
register_core(&mut reg).expect("register the core commands");
let session = Session::open(vault.path()).expect("open the vault");
(reg, session, vault)
}
/// Adds a thread on the quote of the note.
fn add(reg: &Registry<Session>, session: &mut Session) {
let start = NOTE.find(QUOTE).expect("the quote is in the note");
reg.run(
"comment.add",
session,
&json!({ "path": "note.md", "quote": QUOTE, "body": "Can we soften this claim?", "start": start, "end": start + QUOTE.len() }),
)
.expect("comment.add");
}
/// The threads of the sidecar of `path`, as the file holds them.
fn threads_on_disk(vault: &common::TempVault, path: &str) -> Vec<Value> {
let bytes = std::fs::read(vault.path().join(format!("{path}.comments.json"))).expect("the sidecar of the note");
let parsed: Value = serde_json::from_slice(&bytes).expect("the sidecar is JSON");
parsed.get("comments").and_then(Value::as_array).expect("the threads of the note").clone()
}
/// A renamed note takes its sidecar with it.
#[test]
fn a_renamed_note_keeps_its_sidecar() {
let (reg, mut session, vault) = fixture("rename");
add(&reg, &mut session);
reg.run("note.rename", &mut session, &json!({ "from": "note.md", "to": "moved.md" })).expect("note.rename");
assert!(!vault.path().join("note.md.comments.json").exists(), "the old sidecar is gone");
let threads = threads_on_disk(&vault, "moved.md");
assert_eq!(threads.len(), 1, "the threads follow the note");
assert_eq!(threads.first().and_then(|thread| thread.get("body")), Some(&json!("Can we soften this claim?")));
let bytes = std::fs::read(vault.path().join("moved.md.comments.json")).expect("the sidecar of the moved note");
let parsed: Value = serde_json::from_slice(&bytes).expect("the sidecar is JSON");
assert_eq!(parsed.get("note"), Some(&json!("moved.md")), "the sidecar points at the new path");
}
/// A deleted note takes its sidecar with it.
#[test]
fn a_deleted_note_removes_its_sidecar() {
let (reg, mut session, vault) = fixture("delete");
add(&reg, &mut session);
assert!(vault.path().join("note.md.comments.json").exists(), "the sidecar is there before the delete");
reg.run("note.delete", &mut session, &json!({ "path": "note.md" })).expect("note.delete");
assert!(!vault.path().join("note.md").exists(), "the note is gone");
assert!(!vault.path().join("note.md.comments.json").exists(), "the sidecar went with it");
}
/// A note without threads has no sidecar, and deleting it is not an error.
#[test]
fn a_note_without_threads_has_no_sidecar() {
let (reg, mut session, vault) = fixture("empty");
reg.run("note.delete", &mut session, &json!({ "path": "note.md" })).expect("note.delete");
assert!(!vault.path().join("note.md").exists(), "the note is gone");
assert!(!vault.path().join("note.md.comments.json").exists(), "and there was no sidecar to go");
}