import Note from '../../models/Note'; import Tag from '../../models/Tag'; import time from '../../time'; import { setupDatabaseAndSynchronizer, supportDir, switchClient } from '../../testing/test-utils'; import { ImportModuleOutputFormat, ImportOptions } from './types'; import InteropService from './InteropService'; import Folder from '../../models/Folder'; import { NoteEntity } from '../database/types'; async function importNote(path: string) { const folder = await Folder.save({}); const importOptions: ImportOptions = { path: path, format: 'md_frontmatter', destinationFolderId: folder.id, outputFormat: ImportModuleOutputFormat.Markdown, }; await InteropService.instance().import(importOptions); const allNotes = await Note.all(); return allNotes[0]; } const importTestFile = async (name: string): Promise => { return importNote(`${supportDir}/test_notes/yaml/${name}`); }; describe('InteropService_Importer_Md_frontmatter: importMetadata', () => { beforeEach(async () => { await setupDatabaseAndSynchronizer(1); await switchClient(1); }); it('should import file and set all metadata correctly', async () => { const note = await importTestFile('full.md'); const format = 'DD/MM/YYYY HH:mm'; expect(note.title).toBe('Test Note Title'); expect(time.formatMsToLocal(note.user_updated_time, format)).toBe('01/05/2019 16:54'); expect(time.formatMsToLocal(note.user_created_time, format)).toBe('01/05/2019 16:54'); expect(note.source_url).toBe('https://joplinapp.org'); expect(note.author).toBe('Joplin'); expect(note.latitude).toBe('37.08402100'); expect(note.longitude).toBe('-94.51350100'); expect(note.altitude).toBe('0.0000'); expect(note.is_todo).toBe(1); expect(note.todo_completed).toBe(0); expect(time.formatMsToLocal(note.todo_due, format)).toBe('22/08/2021 00:00'); expect(note.body).toBe('This is the note body\n'); const tags = await Tag.tagsByNoteId(note.id); expect(tags.length).toBe(3); const tagTitles = tags.map(tag => tag.title); expect(tagTitles).toContain('joplin'); expect(tagTitles).toContain('note'); expect(tagTitles).toContain('pencil'); }); it('should only import data from the first yaml block', async () => { const note = await importTestFile('split.md'); expect(note.title).toBe('xxx'); expect(note.author).not.toBe('xxx'); expect(note.body).toBe('---\nauthor: xxx\n---\n\nnote body\n'); }); it('should only import, duplicate notes and tags are not created', async () => { const note = await importTestFile('duplicates.md'); expect(note.title).toBe('ddd'); const itemIds = await Note.linkedItemIds(note.body); expect(itemIds.length).toBe(1); const tags = await Tag.tagsByNoteId(note.id); expect(tags.length).toBe(1); }); it('should not import items as numbers', async () => { const note = await importTestFile('numbers.md'); expect(note.title).toBe('001'); expect(note.body).toBe('note body\n'); }); it('should normalize whitespace and load correctly', async () => { const note = await importTestFile('normalize.md'); expect(note.title).toBe('norm'); expect(note.body).toBe('note body\n'); const tags = await Tag.tagsByNoteId(note.id); expect(tags.length).toBe(3); }); it('should load unquoted special forms correctly', async () => { const note = await importTestFile('unquoted.md'); expect(note.title).toBe('Unquoted'); expect(note.body).toBe('note body\n'); expect(note.longitude).toBe('-94.51350100'); expect(note.is_todo).toBe(1); expect(note.todo_completed).toBe(0); }); it('should load notes with newline in the title', async () => { const note = await importTestFile('title_newline.md'); expect(note.title).toBe('First\nSecond'); }); it('should import dates (without time) correctly', async () => { const note = await importTestFile('short_date.md'); const format = 'YYYY-MM-DD HH:mm'; expect(time.formatMsToLocal(note.user_updated_time, format)).toBe('2021-01-01 00:00'); expect(time.formatMsToLocal(note.user_created_time, format)).toBe('2017-01-01 00:00'); }); it('should load tags even with the inline syntax', async () => { const note = await importTestFile('inline_tags.md'); expect(note.title).toBe('Inline Tags'); const tags = await Tag.tagsByNoteId(note.id); expect(tags.length).toBe(2); }); it('should import r-markdown files correctly and set what metadata it can', async () => { const note = await importTestFile('r-markdown.md'); const format = 'YYYY-MM-DD HH:mm'; expect(note.title).toBe('YAML metadata for R Markdown with examples'); expect(time.formatMsToLocal(note.user_updated_time, format)).toBe('2021-06-10 00:00'); expect(time.formatMsToLocal(note.user_created_time, format)).toBe('2021-06-10 00:00'); expect(note.author).toBe('Hao Liang'); const tags = await Tag.tagsByNoteId(note.id); expect(tags.length).toBe(2); const tagTitles = tags.map(tag => tag.title); expect(tagTitles).toContain('yaml'); expect(tagTitles).toContain('rmd'); }); it('should import r-markdown files with alternative author syntax', async () => { const note = await importTestFile('r-markdown_author.md'); expect(note.title).toBe('Distill for R Markdown'); expect(note.author).toBe('JJ Allaire'); }); it('should import Notesnook files with created and update timestamps', async () => { const note = await importTestFile('notesnook_updated_created.md'); expect(note.title).toBe('Frontmatter test'); expect(note.user_created_time).toBe(Date.parse('2024-01-01T01:23:00.000')); expect(note.user_updated_time).toBe(Date.parse('2024-01-02T04:56:00.000')); }); it('should handle date formats with timezone information', async () => { const note = await importTestFile('utc.md'); expect(note.user_updated_time).toBe(1556729640000); expect(note.user_created_time).toBe(1556754840000); }); it('should accept file with no newline after the block marker', async () => { const note = await importTestFile('no_newline_after_marker.md'); expect(note.body).toBe('note body\n'); }); it('should handle multiple newlines before the note body', async () => { const note = await importTestFile('multiple_newlines_after_marker.md'); expect(note.body).toBe('\n\nnote body'); }); it('should accept note with a title that starts with a dash', async () => { const note = await importTestFile('title_start_with_dash.md'); expect(note.title).toBe('-Start with dash'); }); it('should import a note that contains an image in DataUrl format', async () => { const note = await importTestFile('note_with_dataurl_image.md'); // cSpell:disable expect(note.body).toBe('Street View Pegman Control'); // cSpell:enable }); it('should recognize frontmatter in a file that starts with a UTF8 byte order mark', async () => { const note = await importTestFile('note_with_byte_order_mark.md'); expect(note.title).toBe('Frontmatter test'); expect(note.body).toBe('This note begins with an invisible byte order mark, just before its frontmatter.\n'); const tags = (await Tag.tagsByNoteId(note.id)).map(tag => tag.title).sort(); expect(tags).toMatchObject(['tag1', 'tag2']); }); it('should import completed tasks', async () => { const note = await importTestFile('task_completed.md'); expect(note.title).toBe('Task'); expect(note.body).toBe('This is a test. This task should import as completed.\n'); expect(note.is_todo).toBe(1); expect(note.todo_completed).toBeGreaterThan(0); }); it('should import notes that are not tasks', async () => { const note = await importTestFile('not_a_task.md'); expect(note).toMatchObject({ title: 'Not a task', body: 'This is a note.', is_todo: 0, todo_completed: 0, }); }); });