//! 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, 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: &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 { 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(®, &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(®, &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"); }