import InteropService from '../../services/interop/InteropService'; import { PluginStates } from '../../services/plugins/reducer'; import { setupDatabaseAndSynchronizer, switchClient, exportDir } from '../../testing/test-utils'; import Folder from '../../models/Folder'; import Note from '../../models/Note'; import * as fs from 'fs-extra'; import { tempFilePath } from '../../testing/test-utils'; import { ContentScriptType } from '../../services/plugins/api/types'; import { ExportModuleOutputFormat, FileSystemItem } from './types'; import { readFile } from 'fs/promises'; import shim from '../../shim'; async function recreateExportDir() { const dir = exportDir(); await fs.remove(dir); await fs.mkdirp(dir); } describe('interop/InteropService_Exporter_Html', () => { beforeEach(async () => { await setupDatabaseAndSynchronizer(1); await switchClient(1); await recreateExportDir(); }); test('should export HTML file', (async () => { const service = InteropService.instance(); const folder1 = await Folder.save({ title: 'folder1' }); await Note.save({ body: '**ma note**', parent_id: folder1.id }); const filePath = `${exportDir()}/test.html`; await service.export({ path: filePath, format: ExportModuleOutputFormat.Html, packIntoSingleFile: false, }); const content = await fs.readFile(filePath, 'utf8'); expect(content).toContain('ma note'); })); test('should export HTML directory', (async () => { const service = InteropService.instance(); const folder1 = await Folder.save({ title: 'folder1' }); await Folder.save({ title: 'folder2' }); await Note.save({ title: 'note1', parent_id: folder1.id }); await Note.save({ title: 'note2', parent_id: folder1.id }); const dir = exportDir(); await service.export({ path: dir, format: ExportModuleOutputFormat.Html, target: FileSystemItem.Directory, }); const rootDirs = await fs.readdir(dir); rootDirs.sort(); expect(rootDirs).toEqual(['folder1', 'folder2']); const files = await fs.readdir(`${dir}/${rootDirs[0]}`); expect(files).toContain('note1.html'); expect(files).toContain('note2.html'); })); test('should warn when a note title is truncated in the exported filename', async () => { const longTitle = 'a'.repeat(51); const longFolderTitle = 'b'.repeat(51); const folder = await Folder.save({ title: longFolderTitle }); const note = await Note.save({ title: longTitle, parent_id: folder.id }); const duplicateNote = await Note.save({ title: longTitle, parent_id: folder.id }); await Note.save({ title: 'short title', parent_id: folder.id }); const result = await InteropService.instance().export({ path: exportDir(), format: ExportModuleOutputFormat.Html, target: FileSystemItem.Directory, }); expect(result.warnings).toEqual([ `The notebook title "${longFolderTitle}" was truncated to "${'b'.repeat(50)}" in the exported folder name. Notebook ID: ${folder.id}`, `The note title "${longTitle}" was truncated to "${'a'.repeat(50)}.html" in the exported file name. Note ID: ${note.id}`, `The note title "${longTitle}" was truncated to "${'a'.repeat(50)} (1).html" in the exported file name. Note ID: ${duplicateNote.id}`, ]); expect(await fs.pathExists(`${exportDir()}/${'b'.repeat(50)}/${'a'.repeat(50)}.html`)).toBe(true); expect(await fs.pathExists(`${exportDir()}/${'b'.repeat(50)}/${'a'.repeat(50)} (1).html`)).toBe(true); }); test('should export plugin assets', (async () => { const service = InteropService.instance(); const folder1 = await Folder.save({ title: 'folder1' }); await Note.save({ body: '**ma note**', parent_id: folder1.id }); const filePath = `${exportDir()}/test.html`; const contentScriptPath = tempFilePath('js'); await fs.writeFile(contentScriptPath, `module.exports = { default: function(_context) { return { plugin: function (markdownIt, _options) { }, assets: function() { return [ { name: 'fence.css' } ]; }, } }, }`); const assetPath = `${require('path').dirname(contentScriptPath)}/fence.css`; const fenceContent = 'strong { color: red; }'; await fs.writeFile(assetPath, fenceContent); const plugins: PluginStates = { 'test': { id: 'test', contentScripts: { [ContentScriptType.MarkdownItPlugin]: [ { id: 'mdContentScript', path: contentScriptPath, }, ], }, views: {}, }, }; await service.export({ path: filePath, format: ExportModuleOutputFormat.Html, packIntoSingleFile: false, plugins, }); const fenceRelativePath = 'pluginAssets/mdContentScript/fence.css'; const content = await fs.readFile(filePath, 'utf8'); expect(content).toContain(fenceRelativePath); const readFenceContent = await fs.readFile(`${exportDir()}/${fenceRelativePath}`, 'utf8'); expect(readFenceContent).toBe(fenceContent); })); test('should not throw an error on invalid resource paths', (async () => { const service = InteropService.instance(); const folder1 = await Folder.save({ title: 'folder1' }); await Note.save({ title: 'note1', parent_id: folder1.id, body: '[a link starts with slash](/)' }); const filePath = `${exportDir()}/test.html`; await service.export({ path: filePath, format: ExportModuleOutputFormat.Html, packIntoSingleFile: true, target: FileSystemItem.File, }); const content = await readFile(filePath, 'utf-8'); expect(content).toContain('a link starts with slash'); })); test('should not embed zip file for pdf', (async () => { const service = InteropService.instance(); const folder1 = await Folder.save({ title: 'folder1' }); const zipPath = tempFilePath('zip'); await fs.writeFile(zipPath, 'fake zip content', 'utf8'); const resource = await shim.createResourceFromPath(zipPath); const note = await Note.save({ title: 'note1', parent_id: folder1.id, body: `[archive.zip](:/${resource.id})` }); await Note.save({ id: note.id }); const filePath = `${exportDir()}/test.html`; await service.export({ path: filePath, format: ExportModuleOutputFormat.Html, target: FileSystemItem.File, shouldEmbedOnlyImages: true, }); const content = await readFile(filePath, 'utf-8'); expect(content).not.toContain('data:application/zip'); })); });