diff --git a/crates/notebook/src/session.rs b/crates/notebook/src/session.rs index cee67f696a786d7eba414ff01ec6da746bdd34e3..ad7371f978f0a60d002f66cf3f1622d1b489f1ee 100644 --- a/crates/notebook/src/session.rs +++ b/crates/notebook/src/session.rs @@ -817,13 +817,20 @@ impl Notebook { self.storage.place(&placed, ancestor, &target)?; } if let Some(identity) = identity { - self.edit_toc( - folder, - &[onestore::TocEdit::Rename { + let (toc, _) = self.toc(folder)?; + let edit = if lists(&self.storage.read(&toc)?, identity)? { + onestore::TocEdit::Rename { identity, filename: target, - }], - )?; + } + } else { + onestore::TocEdit::Add { + identity, + filename: target, + group: !filename.to_ascii_lowercase().ends_with(".one"), + } + }; + self.commit_toc(&toc, &[edit])?; } self.refresh()?; Ok(renamed) diff --git a/crates/notebook/src/session/tests.rs b/crates/notebook/src/session/tests.rs index 4cda501114d64f6b1e83eda52cc8bdc1700e3525..e7b57b11c19e252624f3c95d9dcad4dd70e0a33a 100644 --- a/crates/notebook/src/session/tests.rs +++ b/crates/notebook/src/session/tests.rs @@ -19,6 +19,60 @@ fn cached_sections_refuse_links_outside_the_notebook_and_dangling_links() { assert!(notebook.section(path, || {}).is_err()); } +#[test] +fn renaming_an_unlisted_section_creates_its_toc_entry_and_preserves_its_neighbor() { + let temporary = tempfile::tempdir().unwrap(); + let folder = temporary.path().join("notebook"); + let cache = temporary.path().join("cache"); + let page = PageCreation::new(None, Some("Garden plan"), "Fixture").unwrap(); + let mut notebook = Notebook::create(&folder, &cache, Notebook::NEW_COLOR, &page).unwrap(); + let original = notebook.catalog().sections[0].path.clone(); + let other = notebook.create_section("", "Other", &page).unwrap(); + let identity = notebook.section_path(&original).unwrap().file_id; + let neighbor = notebook.section_path(&other).unwrap().file_id; + drop(notebook); + fs::remove_file(folder.join(TOC)).unwrap(); + let mut notebook = Notebook::open(&folder, &cache).unwrap(); + assert!(notebook.catalog().toc.is_none()); + assert_eq!(notebook.catalog().sections.len(), 2); + assert_eq!( + notebook.rename(&original, "Delayed section").unwrap(), + "Delayed section.one" + ); + assert!(!folder.join(&original).exists()); + let check = |notebook: &Notebook| { + assert_eq!(notebook.catalog().sections.len(), 2); + assert!(notebook.section_path(&original).is_err()); + assert_eq!( + notebook + .section_path("Delayed section.one") + .unwrap() + .file_id, + identity + ); + assert_eq!(notebook.section_path(&other).unwrap().file_id, neighbor); + let toc = notebook.read_toc("").unwrap(); + assert!(lists(&toc, identity).unwrap()); + assert_eq!( + stored_pages(¬ebook.read_section("Delayed section.one").unwrap()).unwrap()[0] + .page + .title, + "Garden plan" + ); + }; + check(¬ebook); + drop(notebook); + check(&Notebook::open(&folder, temporary.path().join("cold-cache")).unwrap()); + if let Some(export) = std::env::var_os("SNOWBOUND_RENAME_EXPORT") { + let export = Path::new(&export); + fs::create_dir_all(export).unwrap(); + for entry in fs::read_dir(&folder).unwrap() { + let entry = entry.unwrap(); + fs::copy(entry.path(), export.join(entry.file_name())).unwrap(); + } + } +} + /// A mounted notebook where another writer commits `revision` to the section file just /// before Snowbound's rewritten file takes its place. struct Racing {