230 lines
10 KiB
Rust
230 lines
10 KiB
Rust
//! Integration tests of the property maintenance commands: renaming a key
|
|
//! in every note, the conflicts, the dry run, the values of a key and the
|
|
//! keys of the vault.
|
|
|
|
mod common;
|
|
|
|
use common::{on_disk, seed, temp_vault};
|
|
use notescraft_engine::{Error, Registry, Session, register_core};
|
|
use serde_json::{Value, json};
|
|
|
|
/// A registry with every core command.
|
|
fn registry() -> Registry<Session> {
|
|
let mut reg = Registry::new();
|
|
register_core(&mut reg).expect("registry");
|
|
reg
|
|
}
|
|
|
|
/// The code of a command failure, when the outcome is one.
|
|
fn failure_code(outcome: &Result<Value, Error>) -> Option<String> {
|
|
match outcome {
|
|
Err(Error::Command { source, .. }) => Some(source.code.clone()),
|
|
_ => None,
|
|
}
|
|
}
|
|
|
|
/// The paths of a report array, empty when it is not there.
|
|
fn paths(report: &Value, name: &str) -> Vec<String> {
|
|
report.get(name).and_then(Value::as_array).map(|rows| rows.iter().filter_map(|row| row.as_str().map(str::to_owned)).collect()).unwrap_or_default()
|
|
}
|
|
|
|
/// A key is renamed in every note that holds it; a note that already holds
|
|
/// the new key is a conflict and is left alone.
|
|
#[test]
|
|
fn a_key_is_renamed_in_every_note() {
|
|
let vault = temp_vault("prop-rename");
|
|
seed(
|
|
vault.path(),
|
|
&[
|
|
("a.md", "---\nstatus: open\n---\nbody a\n"),
|
|
("b.md", "---\nstatus: closed\n---\nbody b\n"),
|
|
("c.md", "---\nstatus: open\n---\nbody c\n"),
|
|
("d.md", "---\nstatus: open\n---\nbody d\n"),
|
|
("e.md", "---\nstatus: open\nstate: yes\n---\nbody e\n"),
|
|
("f.md", "no frontmatter here\n"),
|
|
],
|
|
);
|
|
let mut session = Session::open(vault.path()).expect("session");
|
|
let reg = registry();
|
|
|
|
let report = reg.run("properties.rename_all", &mut session, &json!({ "from": "status", "to": "state" })).expect("rename");
|
|
assert_eq!(report.get("renamed"), Some(&json!(4)), "{report}");
|
|
assert_eq!(paths(&report, "notes"), vec![String::from("a.md"), String::from("b.md"), String::from("c.md"), String::from("d.md")], "{report}");
|
|
assert_eq!(paths(&report, "conflicts"), vec![String::from("e.md")], "{report}");
|
|
assert_eq!(on_disk(vault.path(), "a.md"), "---\nstate: open\n---\nbody a\n");
|
|
assert_eq!(on_disk(vault.path(), "e.md"), "---\nstatus: open\nstate: yes\n---\nbody e\n", "the conflict is left alone");
|
|
assert_eq!(on_disk(vault.path(), "f.md"), "no frontmatter here\n", "a note without the key is left alone");
|
|
}
|
|
|
|
/// The body of a note survives the rename, byte for byte.
|
|
#[test]
|
|
fn the_body_of_a_note_is_untouched() {
|
|
let vault = temp_vault("prop-body");
|
|
let body = "# Title\n\nA rule ---\n\nstatus: not a key\n";
|
|
seed(vault.path(), &[("a.md", &format!("---\nstatus: open\n---\n{body}"))]);
|
|
let mut session = Session::open(vault.path()).expect("session");
|
|
let reg = registry();
|
|
|
|
reg.run("properties.rename_all", &mut session, &json!({ "from": "status", "to": "state" })).expect("rename");
|
|
assert_eq!(on_disk(vault.path(), "a.md"), format!("---\nstate: open\n---\n{body}"));
|
|
}
|
|
|
|
/// A dry run reports what would change and writes nothing at all.
|
|
#[test]
|
|
fn a_dry_run_changes_no_file() {
|
|
let vault = temp_vault("prop-dry-run");
|
|
seed(vault.path(), &[("a.md", "---\nstatus: open\n---\nbody\n")]);
|
|
let mut session = Session::open(vault.path()).expect("session");
|
|
let reg = registry();
|
|
let before = on_disk(vault.path(), "a.md");
|
|
|
|
let report = reg.run("properties.rename_all", &mut session, &json!({ "from": "status", "to": "state", "dry_run": true })).expect("dry run");
|
|
assert_eq!(report.get("renamed"), Some(&json!(1)), "{report}");
|
|
assert_eq!(report.get("dry_run"), Some(&json!(true)), "{report}");
|
|
assert_eq!(on_disk(vault.path(), "a.md"), before, "the dry run wrote nothing");
|
|
|
|
reg.run("properties.rename_all", &mut session, &json!({ "from": "status", "to": "state" })).expect("rename");
|
|
assert_eq!(on_disk(vault.path(), "a.md"), "---\nstate: open\n---\nbody\n");
|
|
}
|
|
|
|
/// The values of a key are counted, and the items of a list count one by
|
|
/// one.
|
|
#[test]
|
|
fn the_values_of_a_key_are_counted() {
|
|
let vault = temp_vault("prop-values");
|
|
seed(
|
|
vault.path(),
|
|
&[
|
|
("a.md", "---\nlabels: [work, home]\nstatus: open\n---\n"),
|
|
("b.md", "---\nlabels: [work]\nstatus: closed\n---\n"),
|
|
("c.md", "---\nlabels:\n - work\n - away\nstatus: open\n---\n"),
|
|
],
|
|
);
|
|
let mut session = Session::open(vault.path()).expect("session");
|
|
let reg = registry();
|
|
|
|
let report = reg.run("properties.values", &mut session, &json!({ "key": "labels" })).expect("values");
|
|
assert_eq!(report.get("type"), Some(&json!("list")), "{report}");
|
|
assert_eq!(report.get("total_notes"), Some(&json!(3)), "{report}");
|
|
assert_eq!(
|
|
report.get("values"),
|
|
Some(&json!([
|
|
{ "value": "work", "count": 3 },
|
|
{ "value": "away", "count": 1 },
|
|
{ "value": "home", "count": 1 },
|
|
])),
|
|
"{report}"
|
|
);
|
|
|
|
let scalars = reg.run("properties.values", &mut session, &json!({ "key": "status" })).expect("values");
|
|
assert_eq!(scalars.get("type"), Some(&json!("text")), "{scalars}");
|
|
assert_eq!(scalars.get("values"), Some(&json!([{ "value": "open", "count": 2 }, { "value": "closed", "count": 1 }])), "{scalars}");
|
|
|
|
let limited = reg.run("properties.values", &mut session, &json!({ "key": "labels", "limit": 1 })).expect("values");
|
|
assert_eq!(limited.get("values"), Some(&json!([{ "value": "work", "count": 3 }])), "{limited}");
|
|
}
|
|
|
|
/// Every key of the vault is listed, the most used first.
|
|
#[test]
|
|
fn the_keys_of_the_vault_are_sorted_by_count() {
|
|
let vault = temp_vault("prop-keys");
|
|
seed(vault.path(), &[("a.md", "---\nstatus: open\nlabels: [work]\n---\n"), ("b.md", "---\nstatus: closed\n---\n"), ("c.md", "---\nstatus: open\n---\n")]);
|
|
let mut session = Session::open(vault.path()).expect("session");
|
|
let reg = registry();
|
|
|
|
let report = reg.run("properties.keys", &mut session, &json!({})).expect("keys");
|
|
assert_eq!(
|
|
report.get("keys"),
|
|
Some(&json!([
|
|
{ "key": "status", "type": "text", "count": 3 },
|
|
{ "key": "labels", "type": "list", "count": 1 },
|
|
])),
|
|
"{report}"
|
|
);
|
|
}
|
|
|
|
/// A property name may hold spaces.
|
|
#[test]
|
|
fn a_key_with_spaces_works() {
|
|
let vault = temp_vault("prop-spaces");
|
|
seed(vault.path(), &[("a.md", "---\nmy key: value\n---\nbody\n")]);
|
|
let mut session = Session::open(vault.path()).expect("session");
|
|
let reg = registry();
|
|
|
|
let report = reg.run("properties.rename_all", &mut session, &json!({ "from": "my key", "to": "other key" })).expect("rename");
|
|
assert_eq!(report.get("renamed"), Some(&json!(1)), "{report}");
|
|
assert_eq!(on_disk(vault.path(), "a.md"), "---\nother key: value\n---\nbody\n");
|
|
}
|
|
|
|
/// A name that cannot be a frontmatter key is refused, on both sides of the
|
|
/// rename.
|
|
#[test]
|
|
fn an_invalid_key_is_refused() {
|
|
let vault = temp_vault("prop-invalid");
|
|
seed(vault.path(), &[("a.md", "---\nstatus: open\n---\n")]);
|
|
let mut session = Session::open(vault.path()).expect("session");
|
|
let reg = registry();
|
|
|
|
for key in ["", "a:b", "a\nb"] {
|
|
let from = reg.run("properties.rename_all", &mut session, &json!({ "from": key, "to": "state" }));
|
|
assert_eq!(failure_code(&from), Some(String::from("invalid_params")), "`{key}` must be refused");
|
|
let to = reg.run("properties.rename_all", &mut session, &json!({ "from": "status", "to": key }));
|
|
assert_eq!(failure_code(&to), Some(String::from("invalid_params")), "`{key}` must be refused");
|
|
let values = reg.run("properties.values", &mut session, &json!({ "key": key }));
|
|
assert_eq!(failure_code(&values), Some(String::from("invalid_params")), "`{key}` must be refused");
|
|
}
|
|
assert_eq!(on_disk(vault.path(), "a.md"), "---\nstatus: open\n---\n", "nothing was written");
|
|
}
|
|
|
|
/// `.obsidian/types.json` is read-only: the report says the old key had a
|
|
/// type there and the file is untouched.
|
|
#[test]
|
|
fn the_types_file_is_read_only() {
|
|
let vault = temp_vault("prop-types");
|
|
seed(vault.path(), &[("a.md", "---\nstatus: open\n---\n"), (".obsidian/types.json", "{\"types\":{\"status\":\"text\"}}")]);
|
|
let mut session = Session::open(vault.path()).expect("session");
|
|
let reg = registry();
|
|
|
|
let report = reg.run("properties.rename_all", &mut session, &json!({ "from": "status", "to": "state" })).expect("rename");
|
|
assert_eq!(report.get("types_json_note"), Some(&json!(true)), "{report}");
|
|
assert_eq!(on_disk(vault.path(), ".obsidian/types.json"), "{\"types\":{\"status\":\"text\"}}", "the types file is never edited");
|
|
}
|
|
|
|
/// A note open in the editor gets one undoable edit, the disk waits.
|
|
#[test]
|
|
fn an_open_buffer_gets_one_undoable_edit() {
|
|
let vault = temp_vault("prop-buffer");
|
|
seed(vault.path(), &[("a.md", "---\nstatus: open\n---\nbody\n")]);
|
|
let mut session = Session::open(vault.path()).expect("session");
|
|
session.open_buffer("a.md").expect("buffer");
|
|
let reg = registry();
|
|
|
|
reg.run("properties.rename_all", &mut session, &json!({ "from": "status", "to": "state" })).expect("rename");
|
|
let buffer = session.buffer("a.md").expect("open buffer");
|
|
assert_eq!(buffer.text(), "---\nstate: open\n---\nbody\n");
|
|
assert!(buffer.is_dirty(), "the buffer edit marks the note dirty");
|
|
assert!(buffer.can_undo(), "one undo step covers the rewrite");
|
|
assert_eq!(on_disk(vault.path(), "a.md"), "---\nstatus: open\n---\nbody\n", "the disk still holds what was saved last");
|
|
|
|
let edit = session.buffer_mut("a.md").expect("buffer");
|
|
edit.undo().expect("undo");
|
|
assert_eq!(edit.text(), "---\nstatus: open\n---\nbody\n", "undo takes the rename back");
|
|
}
|
|
|
|
/// A read-only session refuses the write and keeps the two reads.
|
|
#[test]
|
|
fn a_read_only_session_refuses_the_write_only() {
|
|
let vault = temp_vault("prop-read-only");
|
|
seed(vault.path(), &[("a.md", "---\nstatus: open\n---\n")]);
|
|
let mut session = Session::open_read_only(vault.path()).expect("read-only session");
|
|
let reg = registry();
|
|
|
|
assert!(!reg.is_enabled("properties.rename_all", &session));
|
|
assert!(reg.is_enabled("properties.values", &session));
|
|
assert!(reg.is_enabled("properties.keys", &session));
|
|
assert!(matches!(reg.run("properties.rename_all", &mut session, &json!({ "from": "status", "to": "state" })), Err(Error::Disabled(_))));
|
|
assert!(reg.run("properties.values", &mut session, &json!({ "key": "status" })).is_ok());
|
|
assert!(reg.run("properties.keys", &mut session, &json!({})).is_ok());
|
|
assert_eq!(on_disk(vault.path(), "a.md"), "---\nstatus: open\n---\n");
|
|
}
|