2020-11-07 17:59:37 +02:00
|
|
|
import MdToHtml from '@joplin/renderer/MdToHtml';
|
|
|
|
const { filename } = require('@joplin/lib/path-utils');
|
2021-08-12 17:54:10 +02:00
|
|
|
import { setupDatabaseAndSynchronizer, switchClient } from '@joplin/lib/testing/test-utils';
|
2021-01-22 19:41:11 +02:00
|
|
|
import shim from '@joplin/lib/shim';
|
2020-11-07 17:59:37 +02:00
|
|
|
const { themeStyle } = require('@joplin/lib/theme');
|
2020-02-14 01:59:23 +02:00
|
|
|
|
2020-11-12 21:13:28 +02:00
|
|
|
function newTestMdToHtml(options: any = null) {
|
2020-03-23 02:47:25 +02:00
|
|
|
options = {
|
|
|
|
ResourceModel: {
|
|
|
|
isResourceUrl: () => false,
|
|
|
|
},
|
|
|
|
fsDriver: shim.fsDriver(),
|
|
|
|
...options,
|
|
|
|
};
|
|
|
|
|
2021-01-23 17:51:19 +02:00
|
|
|
return new MdToHtml(options);
|
2020-03-23 02:47:25 +02:00
|
|
|
}
|
|
|
|
|
2023-02-20 17:02:29 +02:00
|
|
|
describe('MdToHtml', () => {
|
2020-02-14 01:59:23 +02:00
|
|
|
|
2022-11-15 12:23:50 +02:00
|
|
|
beforeEach(async () => {
|
2020-02-14 01:59:23 +02:00
|
|
|
await setupDatabaseAndSynchronizer(1);
|
|
|
|
await switchClient(1);
|
|
|
|
});
|
|
|
|
|
2020-12-01 20:05:24 +02:00
|
|
|
it('should convert from Markdown to Html', (async () => {
|
2020-02-14 01:59:23 +02:00
|
|
|
const basePath = `${__dirname}/md_to_html`;
|
|
|
|
const files = await shim.fsDriver().readDirStats(basePath);
|
2020-03-23 02:47:25 +02:00
|
|
|
const mdToHtml = newTestMdToHtml();
|
2020-02-14 01:59:23 +02:00
|
|
|
|
|
|
|
for (let i = 0; i < files.length; i++) {
|
|
|
|
const mdFilename = files[i].path;
|
|
|
|
if (mdFilename.indexOf('.md') < 0) continue;
|
|
|
|
|
|
|
|
const mdFilePath = `${basePath}/${mdFilename}`;
|
|
|
|
const htmlPath = `${basePath}/${filename(mdFilePath)}.html`;
|
|
|
|
|
2023-05-17 17:09:17 +02:00
|
|
|
// if (mdFilename !== 'sanitize_9.md') continue;
|
2020-02-14 01:59:23 +02:00
|
|
|
|
2020-11-12 21:13:28 +02:00
|
|
|
const mdToHtmlOptions: any = {
|
2020-02-14 01:59:23 +02:00
|
|
|
bodyOnly: true,
|
|
|
|
};
|
|
|
|
|
2020-03-23 02:47:25 +02:00
|
|
|
if (mdFilename === 'checkbox_alternative.md') {
|
|
|
|
mdToHtmlOptions.plugins = {
|
|
|
|
checkbox: {
|
2020-10-21 01:23:55 +02:00
|
|
|
checkboxRenderingType: 2,
|
2020-03-23 02:47:25 +02:00
|
|
|
},
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2020-02-14 01:59:23 +02:00
|
|
|
const markdown = await shim.fsDriver().readFile(mdFilePath);
|
|
|
|
let expectedHtml = await shim.fsDriver().readFile(htmlPath);
|
|
|
|
|
|
|
|
const result = await mdToHtml.render(markdown, null, mdToHtmlOptions);
|
|
|
|
let actualHtml = result.html;
|
|
|
|
|
2021-08-12 23:07:57 +02:00
|
|
|
expectedHtml = expectedHtml.replace(/\r?\n/g, '\n');
|
|
|
|
actualHtml = actualHtml.replace(/\r?\n/g, '\n');
|
2020-02-14 01:59:23 +02:00
|
|
|
|
|
|
|
if (actualHtml !== expectedHtml) {
|
2022-04-26 13:52:48 +02:00
|
|
|
const msg: string[] = [
|
|
|
|
'',
|
|
|
|
`Error converting file: ${mdFilename}`,
|
|
|
|
'--------------------------------- Got:',
|
|
|
|
actualHtml,
|
|
|
|
'--------------------------------- Raw:',
|
|
|
|
actualHtml.split('\n'),
|
|
|
|
'--------------------------------- Expected:',
|
|
|
|
expectedHtml.split('\n'),
|
|
|
|
'--------------------------------------------',
|
|
|
|
'',
|
|
|
|
];
|
|
|
|
|
2023-02-16 12:55:24 +02:00
|
|
|
// eslint-disable-next-line no-console
|
2022-04-26 13:52:48 +02:00
|
|
|
console.info(msg.join('\n'));
|
2020-02-14 01:59:23 +02:00
|
|
|
|
|
|
|
expect(false).toBe(true);
|
|
|
|
// return;
|
|
|
|
} else {
|
|
|
|
expect(true).toBe(true);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}));
|
|
|
|
|
2020-12-01 20:05:24 +02:00
|
|
|
it('should return enabled plugin assets', (async () => {
|
2020-11-12 21:13:28 +02:00
|
|
|
const pluginOptions: any = {};
|
2020-03-23 02:47:25 +02:00
|
|
|
const pluginNames = MdToHtml.pluginNames();
|
|
|
|
|
|
|
|
for (const n of pluginNames) pluginOptions[n] = { enabled: false };
|
|
|
|
|
|
|
|
{
|
|
|
|
const mdToHtml = newTestMdToHtml({ pluginOptions: pluginOptions });
|
|
|
|
const assets = await mdToHtml.allAssets(themeStyle(1));
|
|
|
|
expect(assets.length).toBe(1); // Base note style should always be returned
|
|
|
|
}
|
|
|
|
|
|
|
|
{
|
|
|
|
pluginOptions['checkbox'].enabled = true;
|
|
|
|
const mdToHtml = newTestMdToHtml({ pluginOptions: pluginOptions });
|
|
|
|
|
|
|
|
const assets = await mdToHtml.allAssets(themeStyle(1));
|
|
|
|
expect(assets.length).toBe(2);
|
|
|
|
expect(assets[1].mime).toBe('text/css');
|
|
|
|
|
|
|
|
const content = await shim.fsDriver().readFile(assets[1].path);
|
|
|
|
expect(content.indexOf('joplin-checklist') >= 0).toBe(true);
|
|
|
|
}
|
|
|
|
}));
|
|
|
|
|
2020-12-01 20:05:24 +02:00
|
|
|
it('should wrapped the rendered Markdown', (async () => {
|
2020-03-23 02:47:25 +02:00
|
|
|
const mdToHtml = newTestMdToHtml();
|
|
|
|
|
|
|
|
// In this case, the HTML contains both the style and
|
|
|
|
// the rendered markdown wrapped in a DIV.
|
|
|
|
const result = await mdToHtml.render('just **testing**');
|
2020-10-21 01:23:55 +02:00
|
|
|
expect(result.cssStrings.length).toBeGreaterThan(0);
|
2020-03-23 02:47:25 +02:00
|
|
|
expect(result.html.indexOf('rendered-md') >= 0).toBe(true);
|
|
|
|
}));
|
|
|
|
|
2020-12-01 20:05:24 +02:00
|
|
|
it('should return the rendered body only', (async () => {
|
2020-03-23 02:47:25 +02:00
|
|
|
const mdToHtml = newTestMdToHtml();
|
|
|
|
|
2020-11-05 18:58:23 +02:00
|
|
|
// In this case, the HTML contains only the rendered markdown, with
|
|
|
|
// no wrapper and no style. The style is instead in the cssStrings
|
|
|
|
// property.
|
|
|
|
{
|
|
|
|
const result = await mdToHtml.render('just **testing**', null, { bodyOnly: true });
|
|
|
|
expect(result.cssStrings.length).toBeGreaterThan(0);
|
|
|
|
expect(result.html.trim()).toBe('just <strong>testing</strong>');
|
|
|
|
}
|
|
|
|
|
|
|
|
// But it should not remove the wrapping <p> tags if there's more
|
|
|
|
// than one line
|
|
|
|
{
|
|
|
|
const result = await mdToHtml.render('one\n\ntwo', null, { bodyOnly: true });
|
|
|
|
expect(result.html.trim()).toBe('<p>one</p>\n<p>two</p>');
|
|
|
|
}
|
2020-03-23 02:47:25 +02:00
|
|
|
}));
|
|
|
|
|
2021-05-17 20:30:48 +02:00
|
|
|
it('should render an empty string', (async () => {
|
|
|
|
const mdToHtml = newTestMdToHtml();
|
|
|
|
const result = await mdToHtml.render('', null, { splitted: true });
|
|
|
|
// The TinyMCE component checks for this exact string to apply a hack,
|
|
|
|
// so make sure it doesn't change from version to version.
|
|
|
|
expect(result.html).toBe('<div id="rendered-md"></div>');
|
|
|
|
}));
|
|
|
|
|
2020-12-01 20:05:24 +02:00
|
|
|
it('should split HTML and CSS', (async () => {
|
2020-03-23 02:47:25 +02:00
|
|
|
const mdToHtml = newTestMdToHtml();
|
|
|
|
|
2020-11-05 18:58:23 +02:00
|
|
|
// It is similar to the bodyOnly option, excepts that the rendered
|
|
|
|
// Markdown is wrapped in a DIV
|
2020-03-23 02:47:25 +02:00
|
|
|
const result = await mdToHtml.render('just **testing**', null, { splitted: true });
|
2020-10-21 01:23:55 +02:00
|
|
|
expect(result.cssStrings.length).toBeGreaterThan(0);
|
2020-03-23 02:47:25 +02:00
|
|
|
expect(result.html.trim()).toBe('<div id="rendered-md"><p>just <strong>testing</strong></p>\n</div>');
|
|
|
|
}));
|
2020-03-10 01:24:57 +02:00
|
|
|
|
2021-01-02 18:53:59 +02:00
|
|
|
it('should render links correctly', (async () => {
|
|
|
|
const testCases = [
|
|
|
|
// 0: input
|
|
|
|
// 1: output with linkify = off
|
|
|
|
// 2: output with linkify = on
|
|
|
|
[
|
|
|
|
'https://example.com',
|
|
|
|
'https://example.com',
|
|
|
|
'<a data-from-md title=\'https://example.com\' href=\'https://example.com\'>https://example.com</a>',
|
|
|
|
],
|
|
|
|
[
|
|
|
|
'file://C:\\AUTOEXEC.BAT',
|
|
|
|
'file://C:\\AUTOEXEC.BAT',
|
|
|
|
'<a data-from-md title=\'file://C:%5CAUTOEXEC.BAT\' href=\'file://C:%5CAUTOEXEC.BAT\'>file://C:\\AUTOEXEC.BAT</a>',
|
|
|
|
],
|
|
|
|
[
|
|
|
|
'example.com',
|
|
|
|
'example.com',
|
|
|
|
'example.com',
|
|
|
|
],
|
|
|
|
[
|
|
|
|
'oo.ps',
|
|
|
|
'oo.ps',
|
|
|
|
'oo.ps',
|
|
|
|
],
|
|
|
|
[
|
|
|
|
'test@example.com',
|
|
|
|
'test@example.com',
|
|
|
|
'test@example.com',
|
|
|
|
],
|
|
|
|
[
|
|
|
|
'<https://example.com>',
|
|
|
|
'<a data-from-md title=\'https://example.com\' href=\'https://example.com\'>https://example.com</a>',
|
|
|
|
'<a data-from-md title=\'https://example.com\' href=\'https://example.com\'>https://example.com</a>',
|
|
|
|
],
|
|
|
|
[
|
|
|
|
'[ok](https://example.com)',
|
|
|
|
'<a data-from-md title=\'https://example.com\' href=\'https://example.com\'>ok</a>',
|
|
|
|
'<a data-from-md title=\'https://example.com\' href=\'https://example.com\'>ok</a>',
|
|
|
|
],
|
|
|
|
[
|
|
|
|
'[bla.pdf](file:///Users/tessus/Downloads/bla.pdf)',
|
|
|
|
'<a data-from-md title=\'file:///Users/tessus/Downloads/bla.pdf\' href=\'file:///Users/tessus/Downloads/bla.pdf\'>bla.pdf</a>',
|
|
|
|
'<a data-from-md title=\'file:///Users/tessus/Downloads/bla.pdf\' href=\'file:///Users/tessus/Downloads/bla.pdf\'>bla.pdf</a>',
|
|
|
|
],
|
|
|
|
];
|
|
|
|
|
|
|
|
const mdToHtmlLinkifyOn = newTestMdToHtml({
|
|
|
|
pluginOptions: {
|
|
|
|
linkify: { enabled: true },
|
|
|
|
},
|
|
|
|
});
|
|
|
|
|
|
|
|
const mdToHtmlLinkifyOff = newTestMdToHtml({
|
|
|
|
pluginOptions: {
|
|
|
|
linkify: { enabled: false },
|
|
|
|
},
|
|
|
|
});
|
|
|
|
|
|
|
|
for (const testCase of testCases) {
|
|
|
|
const [input, expectedLinkifyOff, expectedLinkifyOn] = testCase;
|
|
|
|
|
|
|
|
{
|
|
|
|
const actual = await mdToHtmlLinkifyOn.render(input, null, {
|
|
|
|
bodyOnly: true,
|
|
|
|
plainResourceRendering: true,
|
|
|
|
});
|
|
|
|
|
|
|
|
expect(actual.html).toBe(expectedLinkifyOn);
|
|
|
|
}
|
|
|
|
|
|
|
|
{
|
|
|
|
const actual = await mdToHtmlLinkifyOff.render(input, null, {
|
|
|
|
bodyOnly: true,
|
|
|
|
plainResourceRendering: true,
|
|
|
|
});
|
|
|
|
|
|
|
|
expect(actual.html).toBe(expectedLinkifyOff);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}));
|
2020-03-10 01:24:57 +02:00
|
|
|
|
2021-11-03 14:10:46 +02:00
|
|
|
it('should return attributes of line numbers', (async () => {
|
|
|
|
const mdToHtml = newTestMdToHtml();
|
|
|
|
|
|
|
|
// Mapping information between source lines and html elements is
|
|
|
|
// annotated.
|
|
|
|
{
|
|
|
|
const input = '# Head\nFruits\n- Apple\n';
|
|
|
|
const result = await mdToHtml.render(input, null, { bodyOnly: true, mapsToLine: true });
|
2022-04-10 12:31:17 +02:00
|
|
|
expect(result.html.trim()).toBe('<h1 id="head" class="maps-to-line" source-line="0" source-line-end="1">Head</h1>\n' +
|
|
|
|
'<p class="maps-to-line" source-line="1" source-line-end="2">Fruits</p>\n' +
|
|
|
|
'<ul>\n<li class="maps-to-line" source-line="2" source-line-end="3">Apple</li>\n</ul>'
|
2021-11-03 14:10:46 +02:00
|
|
|
);
|
|
|
|
}
|
|
|
|
}));
|
2020-02-14 01:59:23 +02:00
|
|
|
});
|