文件
mynode/packages/lib/services/interop/InteropService_Exporter_Html.test.ts
chen 301194963b
一些检查失败了
automerge / automerge (push) Has been cancelled
react-native-android-build-apk / AssembleRelease (push) Has been cancelled
Build macOS M1 / Main (push) Has been cancelled
Joplin Continuous Integration / Main (macos-15-intel) (push) Has been cancelled
Joplin Continuous Integration / Main (ubuntu-22.04) (push) Has been cancelled
Joplin Continuous Integration / Main (ubuntu-22.04-arm) (push) Has been cancelled
Joplin Continuous Integration / Main (windows-11-vs2026-arm) (push) Has been cancelled
Joplin Continuous Integration / Main (windows-2025) (push) Has been cancelled
Joplin Continuous Integration / ServerDockerImage (ubuntu-22.04) (push) Has been cancelled
Joplin Continuous Integration / ServerDockerImage (ubuntu-22.04-arm) (push) Has been cancelled
Joplin UI tests / Main (ubuntu-22.04) (push) Has been cancelled
Joplin UI tests / Main (windows-2025) (push) Has been cancelled
Joplin UI tests / Mobile (push) Has been cancelled
Close stale issues / ProcessStaleIssues (push) Has been cancelled
feat: Initial clean release of CHM
2026-11-04 11:29:39 +08:00

191 行
6.2 KiB
TypeScript

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('<strong>ma note</strong>');
}));
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 data-from-md="" title="/" href="" download="">a link starts with slash</a>');
}));
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');
}));
});