文件
mynode/packages/renderer/htmlUtils.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

174 行
5.2 KiB
TypeScript

import htmlUtils, { extractHtmlBody, htmlDocIsImageOnly, ProcessAnchorTagsAction, ProcessAnchorTagsEvent, removeWrappingParagraphAndTrailingEmptyElements } from './htmlUtils';
describe('htmlUtils', () => {
test('should strip off HTML', () => {
const testCases = [
[
'',
'',
],
[
'<b>test</b>',
'test',
],
[
'Joplin&circledR;',
'Joplin®',
],
[
'&lt;b&gttest&lt;/b&gt',
'&lt;b>test&lt;/b>',
],
];
for (const t of testCases) {
const [input, expected] = t;
const actual = htmlUtils.stripHtml(input);
expect(actual).toBe(expected);
}
});
test('should extract the HTML body', () => {
const testCases: [string, string][] = [
[
'Just <b>testing</b>',
'Just <b>testing</b>',
],
[
'',
'',
],
[
'<html><head></head><meta bla><body>Here is the body<img src="test.png"/></body></html>',
'Here is the body<img src="test.png"/>',
],
];
for (const [input, expected] of testCases) {
const actual = extractHtmlBody(input);
expect(actual).toBe(expected);
}
});
test('should tell if an HTML document is an image only', () => {
const testCases: [string, boolean][] = [
[
// This is the kind of HTML that's pasted when copying an image from Chrome
'<meta charset=\'utf-8\'>\n<img src="https://example.com/img.png"/>',
true,
],
[
'',
false,
],
[
'<img src="https://example.com/img.png"/>',
true,
],
[
'<img src="https://example.com/img.png"/><img src="https://example.com/img.png"/>',
false,
],
[
'<img src="https://example.com/img.png"/><p>Some text</p>',
false,
],
[
'<img src="https://example.com/img.png"/> Some text',
false,
],
];
for (const [input, expected] of testCases) {
const actual = htmlDocIsImageOnly(input);
expect(actual).toBe(expected);
}
});
it.each([
['<p>Test</p><div></div>', 'Test'],
['<p>Testing</p><p>A test</p>', '<p>Testing</p><p>A test</p>'],
['<p>Testing</p><hr/>', '<p>Testing</p><hr/>'],
['<p>Testing</p><div style="border: 2px solid red;"></div>', '<p>Testing</p><div style="border: 2px solid red;"></div>'],
['<p>Testing</p><style onload=""></style>', 'Testing'],
['<p>is</p>\n<style onload="console.log(\'test\')"></style>', 'is\n'],
])('should remove empty elements (case %#)', (before, expected) => {
expect(removeWrappingParagraphAndTrailingEmptyElements(before)).toBe(expected);
});
it.each([
[':/0123456789abcdef0123456789abcdef', true],
[':/0123456789abcdef0123456789abcdef/anchor', true],
['javascript:/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/;alert(1)', false],
['data:/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/;alert(1)', false],
['vbscript:/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', false],
[':/short', false],
])('should only allow resource-style URLs anchored to the start (input: %s)', (href, shouldKeep) => {
const output = htmlUtils.sanitizeHtml(`<a href="${href}">Click</a>`);
if (shouldKeep) {
expect(output).toContain(`href="${href}"`);
} else {
expect(output).not.toContain(`href="${href}"`);
expect(output).toContain('href="#"');
}
});
it.each([
['href', 'https://example.com/', true],
['href', 'ms-msdt:/id/PCWDiagnostic', false],
['xlink:href', 'https://example.com/', true],
['xlink:href', 'ms-msdt:/id/PCWDiagnostic', false],
['xlink:href', 'javascript:alert(1)', false],
])('should scheme-filter %s links (input: %s)', (attrName, url, shouldKeep) => {
const output = htmlUtils.sanitizeHtml(`<a ${attrName}="${url}">Click</a>`);
if (shouldKeep) {
expect(output).toContain(`${attrName}="${url}"`);
} else {
expect(output).not.toContain(`${attrName}="${url}"`);
expect(output).toContain(`${attrName}="#"`);
}
});
it.each([
{
label: 'should replace anchor href attributes',
input: '<a>test <a href="href-1"></a></a><div><a href="href-1">another</a></div>',
mapper: (): ProcessAnchorTagsAction => (
{ type: 'replaceSource', href: 'updated' }
),
expected: '<a href="updated">test <a href="updated"></a></a><div><a href="updated">another</a></div>',
},
{
label: 'should replace full anchor opening tags',
input: '<a>test</a>',
mapper: (): ProcessAnchorTagsAction => (
{ type: 'replaceElement', html: '<a data-test>' }
),
expected: '<a data-test>test</a>',
},
{
label: 'should preserve comments',
input: '<a>test <a href="href-1"><!-- test --></a></a><!-- Test! -->',
mapper: (): null => null,
expected: '<a>test <a href="href-1"><!-- test --></a></a><!-- Test! -->',
},
{
label: 'should gracefully handle unbalanced tags',
input: '<div><a>test <a href="href-1"> test</a></span>',
mapper: (): null => null,
expected: '<div><a>test <a href="href-1"> test</a>',
},
{
label: 'should escape "s and >s in attribute names',
input: '<a data-test=">&gt;" href="http://example.com/?test=3&test2=4&amp;test3=5">Test</a>',
mapper: (event: ProcessAnchorTagsEvent): ProcessAnchorTagsAction => (
{ type: 'replaceSource', href: `${event.href}">` }
),
expected: '<a data-test="&gt;&gt;" href="http://example.com/?test=3&amp;test2=4&amp;test3=5&quot;&gt;">Test</a>',
},
])('should replace anchor tags: $label', ({ input, mapper, expected }) => {
expect(htmlUtils.processAnchorTags(input, mapper)).toBe(expected);
});
});