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
5use canvas::{document::TextPosition, editor::CanvasEditor, layout::TextEngine};
6use onestore::{
7 Arena, Section, Store,
8 op::{Edit, Op},
9 page::{Page, PageObject, ParagraphContent},
10};
11
12const ALONE: &[u8] = include_bytes!("../../../corpus/object-outline/native/notebook/Alone.one");
13
14/// The body outline's paragraphs: an object's kind, or text.
15fn 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]
36fn 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}