| 1 | from pathlib import Path |
| 2 | import runpy |
| 3 | import shutil |
| 4 | from tempfile import TemporaryDirectory |
| 5 | import unittest |
| 6 | import uuid |
| 7 | import xml.etree.ElementTree as ET |
| 8 | |
| 9 | from native_xml import ns |
| 10 | |
| 11 | ROOT = Path(__file__).resolve().parent.parent |
| 12 | FIXTURE = ROOT / 'corpus/notebook-management' |
| 13 | compare = runpy.run_path(str(ROOT / 'tools/verify-document.py'))['compare'] |
| 14 | |
| 15 | |
| 16 | def file_identity(path): |
| 17 | return uuid.UUID(bytes_le=path.read_bytes()[16:32]) |
| 18 | |
| 19 | |
| 20 | def tree(node): |
| 21 | """Sections, groups and pages as OneNote lists them, with what the app set on each.""" |
| 22 | out = [] |
| 23 | for child in node: |
| 24 | tag = child.tag.rsplit('}', 1)[-1] |
| 25 | if tag in ('Section', 'SectionGroup'): |
| 26 | out.append((tag, child.get('name'), child.get('color'), tree(child))) |
| 27 | elif tag == 'Page': |
| 28 | out.append((tag, child.get('name'), child.get('pageLevel'), child.get('isInRecycleBin'))) |
| 29 | return out |
| 30 | |
| 31 | |
| 32 | def page(title): |
| 33 | for path in sorted((FIXTURE / 'cold/read').glob('page-*.xml')): |
| 34 | root = ET.parse(path).getroot() |
| 35 | if root.get('name') == title: |
| 36 | return root |
| 37 | raise AssertionError(f'No page titled {title!r}') |
| 38 | |
| 39 | |
| 40 | class NotebookManagementTest(unittest.TestCase): |
| 41 | def test_the_managed_notebook_reopens_natively_as_written(self): |
| 42 | with TemporaryDirectory() as temporary: |
| 43 | read = Path(temporary) / 'read' |
| 44 | shutil.copytree(FIXTURE / 'cold/read', read) |
| 45 | compare(FIXTURE / 'candidate', read) |
| 46 | for candidate in sorted((FIXTURE / 'candidate').rglob('*.one*')): |
| 47 | relative = candidate.relative_to(FIXTURE / 'candidate') |
| 48 | self.assertEqual(file_identity(FIXTURE / 'cold/notebook' / relative), |
| 49 | file_identity(candidate), str(relative)) |
| 50 | |
| 51 | def test_sections_groups_and_pages_sit_where_the_app_put_them(self): |
| 52 | hierarchy = ET.parse(FIXTURE / 'cold/read/hierarchy.xml').getroot() |
| 53 | self.assertEqual(tree(hierarchy), [ |
| 54 | ('Section', 'New Section 1', '#8AA8E4', [ |
| 55 | ('Page', 'Garden plan', '1', None), |
| 56 | ('Page', 'Ivy page', '1', None), |
| 57 | ('Page', 'Teal page', '1', None), |
| 58 | ('Page', 'Subpage', '2', None), |
| 59 | ('Page', 'Meeting page', '1', None), |
| 60 | ]), |
| 61 | ('SectionGroup', 'Archive', None, [ |
| 62 | ('Section', 'Inner', '#8AA8E4', [('Page', 'Untitled page', '1', None)]), |
| 63 | ('Section', 'Kitchen', '#F6B078', [ |
| 64 | ('Page', 'Untitled page', '1', None), |
| 65 | ('Page', 'Moved page', '1', None), |
| 66 | ]), |
| 67 | ]), |
| 68 | ('SectionGroup', 'OneNote_RecycleBin', None, [ |
| 69 | ('Section', 'Deleted Pages', '#E1E1E1', [('Page', 'Deleted page', '1', 'true')]), |
| 70 | ('Section', 'Gone', '#8AA8E4', [('Page', 'Untitled page', '1', 'true')]), |
| 71 | ('Section', 'Binned', '#D5A4BB', [('Page', 'Untitled page', '1', 'true')]), |
| 72 | ]), |
| 73 | ]) |
| 74 | bin = hierarchy.find("one:SectionGroup[@name='OneNote_RecycleBin']", ns) |
| 75 | self.assertEqual(bin.get('isRecycleBin'), 'true') |
| 76 | deleted = bin.find("one:Section[@name='Deleted Pages']", ns) |
| 77 | self.assertEqual(deleted.get('isDeletedPages'), 'true') |
| 78 | |
| 79 | def test_pages_keep_their_date_colour_and_template_art(self): |
| 80 | teal = page('Teal page') |
| 81 | self.assertEqual(teal.find('one:PageSettings', ns).get('color'), '#D4F9F2') |
| 82 | # The title's second outline holds the date and the time. |
| 83 | title = teal.find('one:Title', ns) |
| 84 | texts = [t.text for t in title.iter('{%s}T' % ns['one'])] |
| 85 | self.assertEqual(len(texts), 1) |
| 86 | ivy = page('Ivy page') |
| 87 | images = ivy.findall('one:Image', ns) |
| 88 | self.assertEqual([image.get('backgroundImage') for image in images], ['true']) |
| 89 | self.assertEqual(page('Garden plan').find('one:PageSettings', ns).get('color'), 'automatic') |
| 90 | |
| 91 | def test_meeting_notes_content_reads_back_as_onenote_writes_it(self): |
| 92 | meeting = page('Meeting page') |
| 93 | self.assertEqual(len(meeting.findall('one:Image', ns)), 1) |
| 94 | outlines = meeting.findall('one:Outline', ns) |
| 95 | self.assertEqual(len(outlines), 4) |
| 96 | texts = [t.text or '' for t in meeting.iter('{%s}T' % ns['one'])] |
| 97 | for text in ('Agenda', 'Action Items', 'Important Dates', 'Meeting Details', 'Attendees:', 'Next Meeting'): |
| 98 | self.assertTrue(any(text in line for line in texts), text) |
| 99 | self.assertEqual(len(meeting.findall('.//one:Tag', ns)), 1) |
| 100 | self.assertEqual(len(meeting.findall('.//one:Number', ns)), 1) |
| 101 | self.assertEqual(len(meeting.findall('.//one:Bullet', ns)), 11) |
| 102 | |
| 103 | |
| 104 | if __name__ == '__main__': |
| 105 | unittest.main() |