| 1 | //! An outline holding only a picture or a file edits like any other: a click beside the object |
| 2 | //! puts the caret in a paragraph after it, stored only once typed in, as OneNote 2010 adds one |
| 3 | //! (`corpus/object-outline`). |
| 4 | |
| 5 | use canvas::{document::TextPosition, editor::CanvasEditor, layout::TextEngine}; |
| 6 | use onestore::{ |
| 7 | Arena, Section, Store, |
| 8 | op::{Edit, Op}, |
| 9 | page::{Page, PageObject, ParagraphContent}, |
| 10 | }; |
| 11 | |
| 12 | const ALONE: &[u8] = include_bytes!("../../../corpus/object-outline/native/notebook/Alone.one"); |
| 13 | |
| 14 | /// The body outline's paragraphs: an object's kind, or text. |
| 15 | fn body(page: &Page) -> Vec<String> { |
| 16 | page.objects |
| 17 | .iter() |
| 18 | .find_map(|object| match object { |
| 19 | PageObject::Outline(outline) => Some(&outline.paragraphs), |
| 20 | _ => None, |
| 21 | }) |
| 22 | .unwrap() |
| 23 | .iter() |
| 24 | .map(|node| match &node.content { |
| 25 | ParagraphContent::Text(text) => text.text.text().to_owned(), |
| 26 | ParagraphContent::Image(_) => "picture".into(), |
| 27 | ParagraphContent::Attachment(_) => "file".into(), |
| 28 | _ => "other".into(), |
| 29 | }) |
| 30 | .collect() |
| 31 | } |
| 32 | |
| 33 | /// `SNOWBOUND_OBJECT_OUTLINE_EXPORT` names a new directory receiving the candidate for a cold |
| 34 | /// reopen. |
| 35 | #[test] |
| 36 | fn typing_beside_an_outlines_only_object_adds_a_paragraph() { |
| 37 | let arena = Arena::default(); |
| 38 | let mut section = Section::open(&arena, ALONE.to_vec()).unwrap(); |
| 39 | let pages = section.pages().unwrap(); |
| 40 | let mut engine = TextEngine::default(); |
| 41 | for (index, (title, object)) in [("File alone", "file"), ("Picture alone", "picture")] |
| 42 | .into_iter() |
| 43 | .enumerate() |
| 44 | { |
| 45 | let space = pages.iter().find(|page| page.1 == title).unwrap().0; |
| 46 | let page = section.page(space).unwrap(); |
| 47 | assert_eq!(body(&page), [object]); |
| 48 | let outline = page |
| 49 | .objects |
| 50 | .iter() |
| 51 | .find_map(|object| match object { |
| 52 | PageObject::Outline(outline) => Some(outline.id), |
| 53 | _ => None, |
| 54 | }) |
| 55 | .unwrap(); |
| 56 | let mut editor = CanvasEditor::from_page(page.clone(), &mut engine).unwrap(); |
| 57 | assert!(editor.has_page_outline(outline)); |
| 58 | // The paragraph the caret stands in is not stored until typed in. |
| 59 | assert_eq!(editor.page().unwrap(), page); |
| 60 | editor.focus_outline(outline).unwrap(); |
| 61 | let at = TextPosition { |
| 62 | paragraph: 0, |
| 63 | offset: 0, |
| 64 | }; |
| 65 | editor.select([at; 2].into()).unwrap(); |
| 66 | assert!(editor.take_ops().unwrap().is_empty()); |
| 67 | let mut publish = |editor: &mut CanvasEditor, step: u64| { |
| 68 | let ops = editor |
| 69 | .take_ops() |
| 70 | .unwrap() |
| 71 | .into_iter() |
| 72 | .map(|op| Op::Page { space, op }) |
| 73 | .collect(); |
| 74 | let at = 134_000_000_000_000_000 + (index as u64 * 4 + step) * 10_000_000; |
| 75 | section.apply("Author", &Edit { at, ops }).unwrap(); |
| 76 | body(&section.page(space).unwrap()) |
| 77 | }; |
| 78 | editor.insert(&mut engine, "abc").unwrap(); |
| 79 | assert_eq!(publish(&mut editor, 0), [object, "abc"]); |
| 80 | // Undoing the typing removes the paragraph again, as OneNote 2010's undo does. |
| 81 | assert!(editor.undo(&mut engine).unwrap()); |
| 82 | assert_eq!(publish(&mut editor, 1), [object]); |
| 83 | assert!(editor.redo(&mut engine).unwrap()); |
| 84 | assert_eq!(publish(&mut editor, 2), [object, "abc"]); |
| 85 | } |
| 86 | section.seal().unwrap(); |
| 87 | let written = section.image(); |
| 88 | if let Some(directory) = std::env::var_os("SNOWBOUND_OBJECT_OUTLINE_EXPORT") { |
| 89 | let directory = std::path::PathBuf::from(directory); |
| 90 | std::fs::create_dir(&directory).unwrap(); |
| 91 | std::fs::write(directory.join("Alone.one"), &written).unwrap(); |
| 92 | let file_id = Store::parse(&written).unwrap().header.file_id; |
| 93 | std::fs::write( |
| 94 | directory.join("Open Notebook.onetoc2"), |
| 95 | onestore::create_table_of_contents("Open Notebook.onetoc2", &[("Alone.one", file_id)]) |
| 96 | .unwrap(), |
| 97 | ) |
| 98 | .unwrap(); |
| 99 | } |
| 100 | } |