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>
455 lines
24 KiB
Rust
455 lines
24 KiB
Rust
//! Integration tests of the settings document: the `craft-settings` store
|
|
//! round trip, recovery of a damaged file, newer schema files, every repair
|
|
//! (SPEC 3, 4.1, 6 item 14). The commands are covered in
|
|
//! [`settings_commands`](super); both run against a throw-away `config.toml`
|
|
//! folder the session points at, so nothing here touches a real config or a
|
|
//! real vault.
|
|
|
|
use notescraft_engine::{AppSettings, Error, SCHEMA_VERSION};
|
|
|
|
use common::{TempVault, temp_config};
|
|
|
|
mod common;
|
|
|
|
/// A temp vault with its own settings folder to load and save against.
|
|
struct Fix {
|
|
config: TempVault,
|
|
}
|
|
|
|
impl Fix {
|
|
#[must_use]
|
|
fn new(name: &str) -> Self {
|
|
Self { config: temp_config(name) }
|
|
}
|
|
}
|
|
|
|
/// The `config.toml` inside the settings folder.
|
|
fn config_file(fix: &Fix) -> std::path::PathBuf {
|
|
fix.config.path().join("config.toml")
|
|
}
|
|
|
|
/// (settings, notes) from the settings folder of `fix`.
|
|
fn load_from(config: &TempVault) -> (AppSettings, Vec<String>) {
|
|
notescraft_engine::load_at(config.path())
|
|
}
|
|
|
|
/// Defaults survive the TOML round trip with no notes at all.
|
|
#[test]
|
|
fn defaults_round_trip() {
|
|
let fix = Fix::new("round-trip");
|
|
let before = AppSettings::default();
|
|
notescraft_engine::save_at(fix.config.path(), &before).expect("save the defaults");
|
|
assert!(config_file(&fix).exists(), "the store writes the file even for defaults");
|
|
|
|
let (settings, notes) = load_from(&fix.config);
|
|
assert_eq!(settings, before);
|
|
assert_eq!(notes, &[] as &[String], "{notes:?}");
|
|
}
|
|
|
|
/// A damaged `config.toml` is moved aside, defaults are in use and the note
|
|
/// says where the bytes went.
|
|
#[test]
|
|
fn a_corrupt_file_is_recovered_with_a_note() {
|
|
let fix = Fix::new("corrupt");
|
|
let broken = "this is = not toml ][";
|
|
std::fs::write(config_file(&fix), broken).expect("write the broken file");
|
|
|
|
let (settings, notes) = load_from(&fix.config);
|
|
assert_eq!(settings, AppSettings::default());
|
|
assert_eq!(notes.len(), 1, "{notes:?}");
|
|
assert!(notes[0].starts_with("the damaged config.toml was kept at "), "{}", notes[0]);
|
|
assert!(notes[0].contains("and the defaults are in use"), "{}", notes[0]);
|
|
|
|
let kept: Vec<_> = std::fs::read_dir(fix.config.path())
|
|
.unwrap_or_else(|error| panic!("read the config dir: {error}"))
|
|
.filter_map(Result::ok)
|
|
.map(|entry| entry.file_name().to_string_lossy().into_owned())
|
|
.collect();
|
|
assert!(kept.iter().any(|name| name.starts_with("config.broken-")), "kept = {kept:?}, the corrupt bytes are kept");
|
|
}
|
|
|
|
/// A new `schema_version` file from a newer build is read (its known sections
|
|
/// are in use) but never overwritten.
|
|
#[test]
|
|
fn a_newer_schema_file_is_read_but_not_saved() {
|
|
let fix = Fix::new("newer");
|
|
std::fs::write(config_file(&fix), b"schema_version = 2\n\n[session]\ndaily_folder = \"Notes\"\n\n[hotkeys]\n\"note.read\" = \"Mod-r\"\n")
|
|
.expect("write the newer file");
|
|
|
|
let (settings, notes) = load_from(&fix.config);
|
|
assert_eq!(settings.session.daily_folder, "Notes", "known keys are still usable");
|
|
assert_eq!(settings.hotkeys.get("note.read").map(String::as_str), Some("Mod-r"));
|
|
let joined = notes.join("\n");
|
|
assert!(joined.contains("config.toml is read-only: config.toml has schema version 2"), "{joined}");
|
|
|
|
let refused = notescraft_engine::save_at(fix.config.path(), &AppSettings::default());
|
|
assert!(matches!(refused, Err(Error::Settings(ref message)) if message.contains("read-only")), "{refused:?}");
|
|
let text = std::fs::read_to_string(config_file(&fix)).unwrap_or_default();
|
|
assert!(text.starts_with("schema_version = 2"), "the newer file is untouched: {text}");
|
|
}
|
|
|
|
/// A file one version older runs the (empty) migration hook per step and gets
|
|
/// the migration note.
|
|
#[test]
|
|
fn an_older_schema_file_migrates() {
|
|
let fix = Fix::new("migrate");
|
|
std::fs::write(config_file(&fix), b"schema_version = 0\n\n[session]\ntheme = \"latte\"\n").expect("write the old file");
|
|
|
|
let (settings, notes) = load_from(&fix.config);
|
|
assert_eq!(settings.session.theme, "latte");
|
|
assert!(notes.iter().any(|note| note.contains("config.toml was migrated from schema version 0")), "{notes:?}");
|
|
// The migrated document is written back at the current version, too.
|
|
notescraft_engine::save_at(fix.config.path(), &settings).expect("the migrated file can be saved over");
|
|
let text = std::fs::read_to_string(config_file(&fix)).unwrap_or_default();
|
|
assert!(text.contains(&format!("schema_version = {SCHEMA_VERSION}")), "{text}");
|
|
}
|
|
|
|
/// A section that parses as TOML but does not fit the document is replaced by
|
|
/// its defaults and reported, while the other sections stay.
|
|
#[test]
|
|
fn a_broken_section_is_reset_with_a_note() {
|
|
let fix = Fix::new("broken-section");
|
|
std::fs::write(config_file(&fix), b"schema_version = 1\n\nhotkeys = \"oops\"\n\n[session]\ntheme = \"latte\"\n").expect("write");
|
|
|
|
let (settings, notes) = load_from(&fix.config);
|
|
assert_eq!(settings.hotkeys, std::collections::BTreeMap::new(), "the broken section is at its defaults");
|
|
assert_eq!(settings.session.theme, "latte", "the other sections survive");
|
|
assert!(notes.iter().any(|note| note.starts_with("the `hotkeys` section was replaced by its defaults")), "{notes:?}");
|
|
}
|
|
|
|
/// A hand-written file with bad values is repaired on load, the notes say
|
|
/// what was repaired, and the next save stores the repaired document.
|
|
#[test]
|
|
fn bad_values_in_a_file_are_repaired_with_notes() {
|
|
let fix = Fix::new("repairs");
|
|
let big = "x".repeat(notescraft_engine::MAX_UI_BYTES * 2);
|
|
let document = format!(
|
|
"schema_version = 1\n\nui = {{ theme = \"latte\", blob = \"{big}\" }}\n\nrecent_vaults = [\"b\", \"b\", \"\", \"a\"]\n\n[session]\ndaily_folder = \"../up\"\n\n[hotkeys]\n\"bad key\" = \"Mod-r\"\n\"ok\" = \"not ok!!\"\n"
|
|
);
|
|
std::fs::write(config_file(&fix), document).expect("write the file with repairs");
|
|
|
|
let (settings, notes) = load_from(&fix.config);
|
|
let joined = notes.join("\n");
|
|
assert!(joined.contains("session.daily_folder"), "{joined}");
|
|
assert!(joined.contains("ui: the opaque ui state is dropped"), "{joined}");
|
|
assert!(joined.contains("recent_vaults: kept"), "{joined}");
|
|
assert!(joined.contains("hotkeys: dropped"), "{joined}");
|
|
assert_eq!(settings.session.daily_folder, "Daily");
|
|
assert_eq!(settings.ui, AppSettings::default().ui);
|
|
assert_eq!(settings.recent_vaults, ["b", "a"].iter().map(|s| (*s).to_owned()).collect::<Vec<String>>());
|
|
assert_eq!(settings.hotkeys, std::collections::BTreeMap::new());
|
|
|
|
notescraft_engine::save_at(fix.config.path(), &settings).expect("save");
|
|
let (again, again_notes) = load_from(&fix.config);
|
|
assert_eq!(again, settings, "the repaired values stay over a save");
|
|
// The raw keys craft-settings preserves (hand edits survive a merge) are
|
|
// still in the file, so they are repaired again, with the same notes.
|
|
let wanted: Vec<&String> = again_notes.iter().filter(|note| note.starts_with("ui:") || note.starts_with("hotkeys:")).collect();
|
|
assert_eq!(wanted.len(), 2, "{again_notes:?}");
|
|
}
|
|
|
|
/// The recents list keeps its order through the file round trip.
|
|
#[test]
|
|
fn recents_keep_their_order_through_the_file() {
|
|
let fix = Fix::new("recents");
|
|
let settings = AppSettings { recent_vaults: ["zed", "old", "new"].iter().map(|s| (*s).to_owned()).collect(), ..AppSettings::default() };
|
|
notescraft_engine::save_at(fix.config.path(), &settings).expect("save");
|
|
|
|
let (loaded, notes) = load_from(&fix.config);
|
|
assert_eq!(notes, &[] as &[String], "{notes:?}");
|
|
assert_eq!(loaded.recent_vaults, settings.recent_vaults, "newest first, in order");
|
|
}
|
|
|
|
/// The attachment cap lives in the nested `session.files` group and is set and
|
|
/// repaired like every other session setting (SPEC 9).
|
|
#[test]
|
|
fn the_attachment_cap_is_a_session_files_setting() {
|
|
use notescraft_engine::{FilesConfig, Session, SessionConfig};
|
|
|
|
let vault = temp_config("attach-cap-setting");
|
|
let mut session = Session::open(vault.path()).expect("open");
|
|
session.use_settings_dir(temp_config("attach-cap-cfg").path());
|
|
assert_eq!(session.config().files.max_attachment_mib, 64, "the default");
|
|
|
|
let mut registry = notescraft_engine::Registry::new();
|
|
notescraft_engine::register_core(&mut registry).expect("register");
|
|
let set = |session: &mut Session, key: &str, value: i64| {
|
|
let params = serde_json::json!({ "key": key, "value": value });
|
|
registry.run("settings.set", session, ¶ms)
|
|
};
|
|
|
|
let ok = set(&mut session, "session.files.max_attachment_mib", 128);
|
|
assert!(ok.is_ok(), "{ok:?}");
|
|
assert_eq!(session.config().files.max_attachment_mib, 128, "applied to the live session");
|
|
|
|
// 0 is refused at the config boundary...
|
|
let config = SessionConfig { files: FilesConfig { max_attachment_mib: 0, ..FilesConfig::default() }, ..SessionConfig::default() };
|
|
assert!(config.validate().is_err(), "0 is refused");
|
|
|
|
// ...and repaired to the default with a note through the settings document.
|
|
let repaired = set(&mut session, "session.files.max_attachment_mib", 0);
|
|
assert!(repaired.is_ok(), "{repaired:?}");
|
|
assert_eq!(session.config().files.max_attachment_mib, 64, "repaired to the default");
|
|
let notes = repaired
|
|
.unwrap_or_default()
|
|
.get("notes")
|
|
.and_then(serde_json::Value::as_array)
|
|
.map(|notes| notes.iter().filter_map(|note| note.as_str()).collect::<Vec<&str>>().join("; "))
|
|
.unwrap_or_default();
|
|
assert!(notes.contains("session.files.max_attachment_mib"), "{notes}");
|
|
}
|
|
|
|
/// The knowledge settings live in the nested `session.knowledge` group: a value
|
|
/// round-trips through `settings.set` and an out-of-range one is refused.
|
|
#[test]
|
|
fn knowledge_settings_round_trip_through_settings_set() {
|
|
use notescraft_engine::Session;
|
|
|
|
let vault = temp_config("knowledge-setting");
|
|
let mut session = Session::open(vault.path()).expect("open");
|
|
session.use_settings_dir(temp_config("knowledge-cfg").path());
|
|
assert_eq!(session.config().knowledge.passages, 6, "the default");
|
|
|
|
let mut registry = notescraft_engine::Registry::new();
|
|
notescraft_engine::register_core(&mut registry).expect("register");
|
|
let set = |session: &mut Session, key: &str, value: serde_json::Value| {
|
|
let params = serde_json::json!({ "key": key, "value": value });
|
|
registry.run("settings.set", session, ¶ms)
|
|
};
|
|
|
|
let ok = set(&mut session, "session.knowledge.passages", serde_json::json!(12));
|
|
assert!(ok.is_ok(), "{ok:?}");
|
|
assert_eq!(session.config().knowledge.passages, 12, "applied to the live session");
|
|
|
|
// 0 is never applied: the settings document repairs the section to its defaults and says so.
|
|
let repaired = set(&mut session, "session.knowledge.passages", serde_json::json!(0));
|
|
assert!(repaired.is_ok(), "{repaired:?}");
|
|
assert_eq!(session.config().knowledge.passages, 6, "repaired to the default, not clamped to 1");
|
|
let notes = repaired.unwrap_or_default().to_string();
|
|
assert!(notes.contains("knowledge"), "{notes}");
|
|
}
|
|
|
|
/// `session.lint.*` and `editor.blocks.*` are settings paths that apply to the live session.
|
|
#[test]
|
|
fn lint_and_block_settings_round_trip_through_settings_set() {
|
|
use notescraft_engine::Session;
|
|
|
|
let vault = temp_config("lint-setting");
|
|
let mut session = Session::open(vault.path()).expect("open");
|
|
session.use_settings_dir(temp_config("lint-cfg").path());
|
|
assert!(session.config().lint.learn_style, "the default");
|
|
|
|
let mut registry = notescraft_engine::Registry::new();
|
|
notescraft_engine::register_core(&mut registry).expect("register");
|
|
let set = |session: &mut Session, key: &str, value: serde_json::Value| {
|
|
let params = serde_json::json!({ "key": key, "value": value });
|
|
registry.run("settings.set", session, ¶ms)
|
|
};
|
|
assert!(set(&mut session, "session.lint.learn_style", serde_json::json!(false)).is_ok());
|
|
assert!(!session.config().lint.learn_style);
|
|
assert!(session.config().lint.learn_list_marker && session.config().lint.learn_date_format, "the aspects default on");
|
|
assert!(set(&mut session, "session.lint.learn_list_marker", serde_json::json!(false)).is_ok());
|
|
assert!(set(&mut session, "session.lint.learn_date_format", serde_json::json!(false)).is_ok());
|
|
assert!(!session.config().lint.learn_list_marker && !session.config().lint.learn_date_format);
|
|
assert!(set(&mut session, "editor.blocks.grip", serde_json::json!(false)).is_ok());
|
|
assert!(set(&mut session, "editor.blocks.nope", serde_json::json!(true)).is_err(), "unknown key refused");
|
|
}
|
|
|
|
/// A session with its own config folder and a registry holding the core commands.
|
|
fn preview_session(name: &str) -> (common::TempVault, notescraft_engine::Registry<notescraft_engine::Session>, notescraft_engine::Session) {
|
|
let vault = temp_config(name);
|
|
let config = temp_config(&format!("{name}-cfg"));
|
|
let mut session = notescraft_engine::Session::open(vault.path()).expect("open the vault");
|
|
session.use_settings_dir(config.path());
|
|
let mut registry = notescraft_engine::Registry::new();
|
|
notescraft_engine::register_core(&mut registry).expect("register the core commands");
|
|
(config, registry, session)
|
|
}
|
|
|
|
/// The two preview switches as the document holds them.
|
|
fn saved_previews(config: &common::TempVault) -> (bool, bool) {
|
|
let (settings, notes) = load_from(config);
|
|
assert_eq!(notes, &[] as &[String], "{notes:?}");
|
|
(settings.editor.previews.colors, settings.editor.previews.named_colors)
|
|
}
|
|
|
|
/// `editor.previews.*` set, save, come back out of `config.toml` and refuse an
|
|
/// ill-typed value without touching the file.
|
|
#[test]
|
|
fn preview_settings_round_trip_through_settings_set() {
|
|
let defaults = notescraft_engine::PreviewSettings::default();
|
|
assert!(defaults.colors, "color swatches are on by default");
|
|
assert!(!defaults.named_colors, "named colors are off by default");
|
|
|
|
let (config, registry, mut session) = preview_session("previews-set");
|
|
let set = |session: &mut notescraft_engine::Session, key: &str, value: serde_json::Value| {
|
|
let params = serde_json::json!({ "key": key, "value": value });
|
|
registry.run("settings.set", session, ¶ms)
|
|
};
|
|
|
|
assert!(set(&mut session, "editor.previews.colors", serde_json::json!(false)).is_ok());
|
|
assert!(set(&mut session, "editor.previews.named_colors", serde_json::json!(true)).is_ok());
|
|
assert_eq!(saved_previews(&config), (false, true), "both switches survive the file");
|
|
|
|
let got = registry.run("settings.get", &mut session, &serde_json::json!({})).expect("get");
|
|
let previews = got.get("settings").and_then(|settings| settings.get("editor")).and_then(|editor| editor.get("previews")).cloned();
|
|
assert_eq!(previews, Some(serde_json::json!({ "colors": false, "named_colors": true })), "{got}");
|
|
|
|
let before = std::fs::read(config.path().join("config.toml")).expect("read the config file");
|
|
for (key, value) in [("editor.previews.colors", serde_json::json!("yes")), ("editor.previews.named_colors", serde_json::json!(3))] {
|
|
let refused = set(&mut session, key, value);
|
|
assert!(refused.is_err(), "`{key}` with the wrong type is refused");
|
|
}
|
|
let after = std::fs::read(config.path().join("config.toml")).expect("read the config file again");
|
|
assert_eq!(before, after, "a refused value leaves config.toml byte-identical");
|
|
}
|
|
|
|
/// A `config.toml` written before the switches existed loads with the defaults.
|
|
#[test]
|
|
fn preview_settings_default_for_older_config_files() {
|
|
let fix = Fix::new("previews-old");
|
|
std::fs::write(config_file(&fix), b"schema_version = 1\n\n[editor]\nvim = false\n\n[editor.blocks]\ngrip = false\n").expect("write the old file");
|
|
|
|
let (settings, notes) = load_from(&fix.config);
|
|
assert_eq!(notes, &[] as &[String], "{notes:?}");
|
|
assert_eq!(settings.editor.previews, notescraft_engine::PreviewSettings::default(), "a file without the group gets the defaults");
|
|
assert_eq!(saved_previews(&fix.config), (true, false));
|
|
assert_eq!(AppSettings::default().editor.previews, notescraft_engine::PreviewSettings::default());
|
|
}
|
|
|
|
/// `settings.reset editor.previews` puts both switches back.
|
|
#[test]
|
|
fn settings_reset_restores_the_preview_defaults() {
|
|
let (config, registry, mut session) = preview_session("previews-reset");
|
|
for (key, value) in [("editor.previews.colors", serde_json::json!(false)), ("editor.previews.named_colors", serde_json::json!(true))] {
|
|
let params = serde_json::json!({ "key": key, "value": value });
|
|
assert!(registry.run("settings.set", &mut session, ¶ms).is_ok(), "{key}");
|
|
}
|
|
assert_eq!(saved_previews(&config), (false, true));
|
|
|
|
let params = serde_json::json!({ "key": "editor.previews.named_colors" });
|
|
assert!(registry.run("settings.reset", &mut session, ¶ms).is_ok());
|
|
assert_eq!(saved_previews(&config), (false, false), "one key resets to its own default");
|
|
|
|
for (key, value) in [("editor.previews.colors", serde_json::json!(false)), ("editor.previews.named_colors", serde_json::json!(true))] {
|
|
let params = serde_json::json!({ "key": key, "value": value });
|
|
assert!(registry.run("settings.set", &mut session, ¶ms).is_ok(), "{key}");
|
|
}
|
|
let params = serde_json::json!({ "key": "editor.previews" });
|
|
assert!(registry.run("settings.reset", &mut session, ¶ms).is_ok());
|
|
assert_eq!(saved_previews(&config), (true, false), "the whole group resets too");
|
|
}
|
|
|
|
/// The `session.files` group as it sits in `config.toml`.
|
|
fn saved_files(config: &common::TempVault) -> serde_json::Value {
|
|
let (settings, notes) = load_from(config);
|
|
assert_eq!(notes, &[] as &[String], "{notes:?}");
|
|
serde_json::to_value(&settings).expect("serialize settings").pointer("/session/files").cloned().unwrap_or_default()
|
|
}
|
|
|
|
/// The new-note folder, the link format, the link path, the deleted-file
|
|
/// behaviour and the hidden-file switch live in `session.files`, default the
|
|
/// way the design says, and set, save and reset like every other key.
|
|
#[test]
|
|
fn new_note_and_link_settings_round_trip_through_settings_set() {
|
|
let defaults = serde_json::to_value(notescraft_engine::FilesConfig::default()).expect("the default files section");
|
|
assert_eq!(defaults.pointer("/new_note_folder"), Some(&serde_json::json!("")));
|
|
assert_eq!(defaults.pointer("/link_format"), Some(&serde_json::json!("wikilink")));
|
|
assert_eq!(defaults.pointer("/link_path"), Some(&serde_json::json!("shortest")));
|
|
assert_eq!(defaults.pointer("/deleted_files"), Some(&serde_json::json!("trash")));
|
|
assert_eq!(defaults.pointer("/show_hidden_files"), Some(&serde_json::json!(false)));
|
|
|
|
let (config, registry, mut session) = preview_session("files-set");
|
|
let set = |session: &mut notescraft_engine::Session, key: &str, value: serde_json::Value| {
|
|
let params = serde_json::json!({ "key": key, "value": value });
|
|
registry.run("settings.set", session, ¶ms)
|
|
};
|
|
let changed = [
|
|
("session.files.new_note_folder", serde_json::json!("Notes/Inbox")),
|
|
("session.files.link_format", serde_json::json!("markdown")),
|
|
("session.files.link_path", serde_json::json!("relative")),
|
|
("session.files.deleted_files", serde_json::json!("permanent")),
|
|
("session.files.show_hidden_files", serde_json::json!(true)),
|
|
];
|
|
for (key, value) in &changed {
|
|
assert!(set(&mut session, key, value.clone()).is_ok(), "{key} must be settable");
|
|
let field = key.trim_start_matches("session.files.");
|
|
assert_eq!(saved_files(&config).pointer(&format!("/{field}")), Some(value), "{key} in config.toml");
|
|
}
|
|
|
|
for (key, _) in &changed {
|
|
let params = serde_json::json!({ "key": key });
|
|
assert!(registry.run("settings.reset", &mut session, ¶ms).is_ok(), "{key} must be resettable");
|
|
let field = key.trim_start_matches("session.files.");
|
|
assert_eq!(saved_files(&config).pointer(&format!("/{field}")), defaults.pointer(&format!("/{field}")), "{key} back to its default");
|
|
}
|
|
for (key, value) in &changed {
|
|
let params = serde_json::json!({ "key": key, "value": value.clone() });
|
|
assert!(registry.run("settings.set", &mut session, ¶ms).is_ok(), "{key} must be settable again");
|
|
}
|
|
let params = serde_json::json!({ "key": "session.files" });
|
|
assert!(registry.run("settings.reset", &mut session, ¶ms).is_ok());
|
|
assert_eq!(saved_files(&config), defaults, "the whole group resets too");
|
|
}
|
|
|
|
/// A `config.toml` written before these keys existed loads with the defaults.
|
|
#[test]
|
|
fn file_settings_default_for_older_config_files() {
|
|
let fix = Fix::new("files-old");
|
|
std::fs::write(config_file(&fix), b"schema_version = 1\n\n[session]\ndaily_folder = \"Notes\"\n\n[session.files]\nmax_attachment_mib = 8\n")
|
|
.expect("write the old file");
|
|
|
|
let (settings, notes) = load_from(&fix.config);
|
|
assert_eq!(notes, &[] as &[String], "{notes:?}");
|
|
assert_eq!(settings.session.files.max_attachment_mib, 8, "the keys the file has are kept");
|
|
assert_eq!(settings.session.files, notescraft_engine::FilesConfig { max_attachment_mib: 8, ..notescraft_engine::FilesConfig::default() });
|
|
}
|
|
|
|
/// An unusable new-note folder is repaired to the vault root with a note, a
|
|
/// wrong type and an unknown enum name are refused, and a refused set leaves
|
|
/// `config.toml` byte-identical.
|
|
#[test]
|
|
fn file_settings_repair_and_refuse_bad_values() {
|
|
let fix = Fix::new("files-bad-folder");
|
|
std::fs::write(config_file(&fix), b"schema_version = 1\n\n[session]\n\n[session.files]\nnew_note_folder = \"../up\"\n").expect("write the bad folder");
|
|
let (settings, notes) = load_from(&fix.config);
|
|
assert_eq!(settings.session.files.new_note_folder, "", "a folder that escapes the vault is cleared");
|
|
let joined = notes.join("\n");
|
|
assert!(joined.contains("session.files.new_note_folder"), "{joined}");
|
|
|
|
let (config, registry, mut session) = preview_session("files-refusals");
|
|
let set = |session: &mut notescraft_engine::Session, key: &str, value: serde_json::Value| {
|
|
let params = serde_json::json!({ "key": key, "value": value });
|
|
registry.run("settings.set", session, ¶ms)
|
|
};
|
|
assert!(set(&mut session, "session.files.link_path", serde_json::json!("absolute")).is_ok(), "one good value first");
|
|
let before = std::fs::read(config.path().join("config.toml")).expect("read the config file");
|
|
|
|
for (key, value, code) in [
|
|
("session.files.new_note_folder", serde_json::json!(["Notes"]), "invalid_value"),
|
|
("session.files.link_format", serde_json::json!("WikiLink"), "invalid_value"),
|
|
("session.files.link_path", serde_json::json!("shortest "), "invalid_value"),
|
|
("session.files.deleted_files", serde_json::json!(true), "invalid_value"),
|
|
("session.files.show_hidden_files", serde_json::json!("no"), "invalid_value"),
|
|
("session.files.nope", serde_json::json!(true), "unknown_key"),
|
|
] {
|
|
let result = set(&mut session, key, value.clone());
|
|
assert!(matches!(result, Err(Error::Command { ref source, .. }) if source.code == code), "`{key}` = {value} must fail with {code}, got {result:?}");
|
|
}
|
|
let after = std::fs::read(config.path().join("config.toml")).expect("read the config file again");
|
|
assert_eq!(before, after, "a refused value leaves config.toml byte-identical");
|
|
assert_eq!(saved_files(&config).pointer("/link_path"), Some(&serde_json::json!("absolute")), "the last good value survives");
|
|
}
|
|
|
|
/// A new-note folder that escapes the vault is repaired to the vault root
|
|
/// through `settings.set`, with the note that says so.
|
|
#[test]
|
|
fn a_bad_new_note_folder_is_cleared_through_settings_set() {
|
|
let (config, registry, mut session) = preview_session("files-repair-set");
|
|
let params = serde_json::json!({ "key": "session.files.new_note_folder", "value": ".obsidian" });
|
|
let result = registry.run("settings.set", &mut session, ¶ms).expect("the set succeeds, repaired");
|
|
let notes = result.to_string();
|
|
assert!(notes.contains("session.files.new_note_folder"), "{notes}");
|
|
assert_eq!(saved_files(&config).pointer("/new_note_folder"), Some(&serde_json::json!("")));
|
|
}
|