//! The note composer: `note.extract` and `note.merge`. mod common; use common::{temp_config, temp_vault}; use notescraft_engine::{Registry, Session, register_core}; use serde_json::json; fn open(name: &str, files: &[(&str, &str)]) -> (Registry, Session, common::TempVault, common::TempVault) { let vault = temp_vault(name); for (path, text) in files { std::fs::write(vault.path().join(path), text).expect("seed"); } let mut registry = Registry::new(); register_core(&mut registry).expect("register"); let mut session = Session::open(vault.path()).expect("open"); let config = temp_config(name); session.use_settings_dir(config.path()); (registry, session, vault, config) } #[test] fn extract_moves_the_passage_into_a_new_note_and_leaves_a_link() { let (registry, mut session, vault, _config) = open("extract", &[("src.md", "intro\nthe passage\noutro\n")]); let out = registry.run("note.extract", &mut session, &json!({"path": "src.md", "start": 6, "end": 18, "to": "Passage.md"})).expect("extract"); assert_eq!(out["link"], "[[Passage]]"); assert_eq!(std::fs::read_to_string(vault.path().join("Passage.md")).expect("new"), "the passage\n"); assert_eq!(std::fs::read_to_string(vault.path().join("src.md")).expect("src"), "intro\n[[Passage]]\noutro\n"); assert!(session.index().resolve("src.md", "Passage").is_resolved(), "the link resolves"); } #[test] fn extract_refuses_bad_ranges_and_taken_names_without_changing_anything() { let (registry, mut session, vault, _config) = open("extract-bad", &[("src.md", "héllo world\n"), ("taken.md", "x\n")]); for params in [ json!({"path": "src.md", "start": 2, "end": 5, "to": "n.md"}), json!({"path": "src.md", "start": 3, "end": 3, "to": "n.md"}), json!({"path": "src.md", "start": 0, "end": 999, "to": "n.md"}), json!({"path": "src.md", "start": 0, "end": 5, "to": "taken.md"}), json!({"path": "src.md", "start": 0, "end": 5, "to": "../escape.md"}), ] { assert!(registry.run("note.extract", &mut session, ¶ms).is_err(), "{params}"); } assert_eq!(std::fs::read_to_string(vault.path().join("src.md")).expect("src"), "héllo world\n"); assert_eq!(std::fs::read_to_string(vault.path().join("taken.md")).expect("taken"), "x\n"); assert!(!vault.path().join("n.md").exists()); } #[test] fn extract_goes_through_an_open_buffer() { let (registry, mut session, _vault, _config) = open("extract-buffer", &[("src.md", "keep this\ncut me\n")]); session.open_buffer("src.md").expect("buffer"); registry.run("note.extract", &mut session, &json!({"path": "src.md", "start": 10, "end": 17, "to": "Cut.md"})).expect("extract"); assert_eq!(session.buffer("src.md").map(|buffer| buffer.text().to_owned()).as_deref(), Some("keep this\n[[Cut]]\n")); assert!(session.buffer("src.md").is_some_and(notescraft_engine::Buffer::can_undo), "one undo step"); } #[test] fn merge_appends_the_note_and_trashes_it() { let (registry, mut session, vault, _config) = open("merge", &[("a.md", "# A\nbody\n"), ("b.md", "# B\n")]); registry.run("note.merge", &mut session, &json!({"from": "b.md", "into": "a.md"})).expect("merge"); assert_eq!(std::fs::read_to_string(vault.path().join("a.md")).expect("a"), "# A\nbody\n\n# B\n"); assert!(!vault.path().join("b.md").exists()); assert!(registry.run("note.merge", &mut session, &json!({"from": "a.md", "into": "a.md"})).is_err(), "not into itself"); assert!(registry.run("note.merge", &mut session, &json!({"from": "nope.md", "into": "a.md"})).is_err()); assert_eq!(std::fs::read_to_string(vault.path().join("a.md")).expect("a"), "# A\nbody\n\n# B\n", "a failed merge changes nothing"); } #[test] fn a_deleted_note_comes_back_from_the_trash_and_is_indexed_again() { let (registry, mut session, vault, _config) = open("trash-cmd", &[("keep.md", "# Keep\n[[gone]]\n"), ("gone.md", "# Gone\n")]); registry.run("note.delete", &mut session, &json!({"path": "gone.md"})).expect("delete"); assert!(!session.index().resolve("keep.md", "gone").is_resolved()); let listed = registry.run("trash.list", &mut session, &json!({})).expect("list"); let item = &listed["items"][0]; assert_eq!(item["from"], "gone.md"); let id = item["id"].as_str().expect("id").to_owned(); assert_eq!(registry.run("trash.restore", &mut session, &json!({"id": id})).expect("restore")["path"], "gone.md"); assert!(vault.path().join("gone.md").exists()); assert!(session.index().resolve("keep.md", "gone").is_resolved(), "the link resolves again"); assert!(registry.run("trash.restore", &mut session, &json!({"id": "1/nothing.md"})).is_err()); registry.run("note.delete", &mut session, &json!({"path": "gone.md"})).expect("delete again"); registry.run("trash.empty", &mut session, &json!({})).expect("empty"); assert_eq!(registry.run("trash.list", &mut session, &json!({})).expect("list")["items"].as_array().map(Vec::len), Some(0)); }