@adobe/helix-importer 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,86 @@
1
+ /*
2
+ * Copyright 2022 Adobe. All rights reserved.
3
+ * This file is licensed to you under the Apache License, Version 2.0 (the "License");
4
+ * you may not use this file except in compliance with the License. You may obtain a copy
5
+ * of the License at http://www.apache.org/licenses/LICENSE-2.0
6
+ *
7
+ * Unless required by applicable law or agreed to in writing, software distributed under
8
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9
+ * OF ANY KIND, either express or implied. See the License for the specific language
10
+ * governing permissions and limitations under the License.
11
+ */
12
+
13
+ /* eslint-disable max-classes-per-file, class-methods-use-this */
14
+
15
+ import { strictEqual, ok } from 'assert';
16
+ import { describe, it } from 'mocha';
17
+ import { Response } from 'node-fetch';
18
+
19
+ import { docx2md } from '@adobe/helix-docx2md';
20
+
21
+ import PageImporter from '../../src/importer/PageImporter.js';
22
+ import PageImporterResource from '../../src/importer/PageImporterResource.js';
23
+ import MemoryHandler from '../../src/storage/MemoryHandler.js';
24
+
25
+ import MockMediaHandler from '../mocks/MockMediaHandler.js';
26
+ import NoopLogger from '../mocks/NoopLogger.js';
27
+
28
+ const logger = new NoopLogger();
29
+
30
+ describe('PageImporter tests', () => {
31
+ const storageHandler = new MemoryHandler(logger);
32
+ const config = {
33
+ storageHandler,
34
+ logger,
35
+ };
36
+
37
+ it('import - do nothing', async () => {
38
+ class TestImporter extends PageImporter {
39
+ async fetch() {
40
+ return new Response('test');
41
+ }
42
+ }
43
+
44
+ const se = new TestImporter(config);
45
+ const results = await se.import('someurl');
46
+
47
+ strictEqual(results.length, 0, 'expect no result');
48
+ });
49
+ });
50
+
51
+ describe('PageImporter tests - various options', () => {
52
+ class Test extends PageImporter {
53
+ async fetch() {
54
+ return new Response('<html><body><h1>heading1</h1><p>paragraph</p><div class="useless"></div></body></html>');
55
+ }
56
+
57
+ async process(document, url) {
58
+ const pir = new PageImporterResource('resource1', `${url}/somecomputedpath`, document.body, null, null);
59
+ return [pir];
60
+ }
61
+ }
62
+
63
+ it('import - basic', async () => {
64
+ const storageHandler = new MemoryHandler(logger);
65
+ const config = {
66
+ storageHandler,
67
+ logger,
68
+ };
69
+ const se = new Test(config);
70
+ const results = await se.import('/someurl');
71
+
72
+ strictEqual(results.length, 1, 'expect no result');
73
+
74
+ ok(await storageHandler.exists('/someurl/somecomputedpath/resource1.md'), 'md has been stored');
75
+ ok(await storageHandler.exists('/someurl/somecomputedpath/resource1.docx'), 'docx has been stored');
76
+
77
+ const md = await storageHandler.get('/someurl/somecomputedpath/resource1.md');
78
+ strictEqual(md, '# heading1\n\nparagraph\n', 'valid markdown created');
79
+
80
+ const docx = await storageHandler.get('/someurl/somecomputedpath/resource1.docx');
81
+ const test = await docx2md(docx, {
82
+ mediaHandler: new MockMediaHandler(),
83
+ });
84
+ strictEqual(md, test, 'valid backward conversion');
85
+ });
86
+ });
@@ -0,0 +1,41 @@
1
+ /*
2
+ * Copyright 2022 Adobe. All rights reserved.
3
+ * This file is licensed to you under the Apache License, Version 2.0 (the "License");
4
+ * you may not use this file except in compliance with the License. You may obtain a copy
5
+ * of the License at http://www.apache.org/licenses/LICENSE-2.0
6
+ *
7
+ * Unless required by applicable law or agreed to in writing, software distributed under
8
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9
+ * OF ANY KIND, either express or implied. See the License for the specific language
10
+ * governing permissions and limitations under the License.
11
+ */
12
+ import { MediaHandler } from '@adobe/helix-mediahandler';
13
+
14
+ export default class MockMediaHandler extends MediaHandler {
15
+ constructor(params) {
16
+ super({
17
+ owner: 'owner',
18
+ repo: 'repo',
19
+ ref: 'ref',
20
+ contentBusId: 'dummyId',
21
+ ...params,
22
+ });
23
+ }
24
+
25
+ // eslint-disable-next-line class-methods-use-this
26
+ async checkBlobExists() {
27
+ return true;
28
+ }
29
+
30
+ // eslint-disable-next-line class-methods-use-this
31
+ async getBlob(uri) {
32
+ return {
33
+ contentType: 'image/png',
34
+ uri,
35
+ meta: {
36
+ width: '32',
37
+ height: '32',
38
+ },
39
+ };
40
+ }
41
+ }
@@ -0,0 +1,26 @@
1
+ /*
2
+ * Copyright 2022 Adobe. All rights reserved.
3
+ * This file is licensed to you under the Apache License, Version 2.0 (the "License");
4
+ * you may not use this file except in compliance with the License. You may obtain a copy
5
+ * of the License at http://www.apache.org/licenses/LICENSE-2.0
6
+ *
7
+ * Unless required by applicable law or agreed to in writing, software distributed under
8
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9
+ * OF ANY KIND, either express or implied. See the License for the specific language
10
+ * governing permissions and limitations under the License.
11
+ */
12
+ /* eslint-disable no-console, class-methods-use-this */
13
+ // eslint-disable-next-line: no-empty
14
+ const noop = () => {};
15
+
16
+ export default class NoopLogger {
17
+ debug = noop;
18
+
19
+ info = noop;
20
+
21
+ log = noop;
22
+
23
+ warn = (...args) => console.error(args);
24
+
25
+ error = (...args) => console.error(args);
26
+ }
@@ -0,0 +1,52 @@
1
+ /*
2
+ * Copyright 2020 Adobe. All rights reserved.
3
+ * This file is licensed to you under the Apache License, Version 2.0 (the "License");
4
+ * you may not use this file except in compliance with the License. You may obtain a copy
5
+ * of the License at http://www.apache.org/licenses/LICENSE-2.0
6
+ *
7
+ * Unless required by applicable law or agreed to in writing, software distributed under
8
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9
+ * OF ANY KIND, either express or implied. See the License for the specific language
10
+ * governing permissions and limitations under the License.
11
+ */
12
+
13
+ /* global beforeEach, afterEach */
14
+
15
+ import mockfs from 'mock-fs';
16
+
17
+ import { strictEqual, ok } from 'assert';
18
+ import { describe, it } from 'mocha';
19
+
20
+ import FSHandler from '../../src/storage/FSHandler.js';
21
+
22
+ describe('FSHandler tests', () => {
23
+ beforeEach(() => {
24
+ mockfs({
25
+ 'tmp/path/to/fake/dir': {
26
+ 'some-file.txt': 'file content here',
27
+ },
28
+ });
29
+ });
30
+
31
+ afterEach(() => {
32
+ mockfs.restore();
33
+ });
34
+
35
+ it('get a file', async () => {
36
+ const handler = new FSHandler('tmp', console);
37
+ const content = await handler.get('path/to/fake/dir/some-file.txt');
38
+ strictEqual(content.toString(), 'file content here');
39
+ });
40
+
41
+ it('a file exsits', async () => {
42
+ const handler = new FSHandler('tmp', console);
43
+ ok(await handler.exists('path/to/fake/dir/some-file.txt'));
44
+ });
45
+
46
+ it('put a file', async () => {
47
+ const handler = new FSHandler('tmp', console);
48
+ await handler.put('path/to/fake/dir/some-other-file.txt', 'this is a new file');
49
+ const content = await handler.get('path/to/fake/dir/some-other-file.txt');
50
+ strictEqual(content.toString(), 'this is a new file');
51
+ });
52
+ });
@@ -0,0 +1,33 @@
1
+ /*
2
+ * Copyright 2020 Adobe. All rights reserved.
3
+ * This file is licensed to you under the Apache License, Version 2.0 (the "License");
4
+ * you may not use this file except in compliance with the License. You may obtain a copy
5
+ * of the License at http://www.apache.org/licenses/LICENSE-2.0
6
+ *
7
+ * Unless required by applicable law or agreed to in writing, software distributed under
8
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9
+ * OF ANY KIND, either express or implied. See the License for the specific language
10
+ * governing permissions and limitations under the License.
11
+ */
12
+
13
+ import { strictEqual, ok } from 'assert';
14
+ import { describe, it } from 'mocha';
15
+
16
+ import MemoryHandler from '../../src/storage/MemoryHandler.js';
17
+
18
+ describe('MemoryHandler tests', () => {
19
+ it('get/put content', async () => {
20
+ const handler = new MemoryHandler();
21
+ handler.put('somepath', 'somecontent');
22
+
23
+ const content = await handler.get('somepath');
24
+ strictEqual(content, 'somecontent');
25
+ });
26
+
27
+ it('content exist / does not exist', async () => {
28
+ const handler = new MemoryHandler();
29
+ handler.put('somepath', 'somecontent');
30
+ ok((await handler.exists('somepath')));
31
+ ok(!(await handler.exists('somepath_doesnotexist')));
32
+ });
33
+ });
@@ -0,0 +1,60 @@
1
+ /*
2
+ * Copyright 2020 Adobe. All rights reserved.
3
+ * This file is licensed to you under the Apache License, Version 2.0 (the "License");
4
+ * you may not use this file except in compliance with the License. You may obtain a copy
5
+ * of the License at http://www.apache.org/licenses/LICENSE-2.0
6
+ *
7
+ * Unless required by applicable law or agreed to in writing, software distributed under
8
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9
+ * OF ANY KIND, either express or implied. See the License for the specific language
10
+ * governing permissions and limitations under the License.
11
+ */
12
+
13
+ import { strictEqual, deepStrictEqual } from 'assert';
14
+ import { describe, it } from 'mocha';
15
+
16
+ import os from 'os';
17
+
18
+ import CSV from '../../src/utils/CSV.js';
19
+
20
+ describe('CSV tests', () => {
21
+ it('CSV#toCSV empty', () => {
22
+ strictEqual(CSV.toCSV([]), '');
23
+ });
24
+
25
+ it('CSV#toCSV conversion', () => {
26
+ strictEqual(CSV.toCSV([{ a: 'a1', b: 'b1' }, { a: 'a2', b: 'b2' }]), `a;b;${os.EOL}a1;b1;${os.EOL}a2;b2;${os.EOL}`);
27
+ });
28
+
29
+ it('CSV#toCSV conversion - skip headers', () => {
30
+ strictEqual(CSV.toCSV([{ a: 'a1', b: 'b1' }, { a: 'a2', b: 'b2' }], ';', true), `a1;b1;${os.EOL}a2;b2;${os.EOL}`);
31
+ });
32
+
33
+ it('CSV#toCSV missing properties', () => {
34
+ strictEqual(CSV.toCSV([{ a: 'a1', b: 'b1' }, { b: 'b2' }]), `a;b;${os.EOL}a1;b1;${os.EOL};b2;${os.EOL}`);
35
+ });
36
+
37
+ it('CSV#toArray empty', () => {
38
+ deepStrictEqual(CSV.toArray(''), []);
39
+ });
40
+
41
+ it('CSV#toArray missing properties', () => {
42
+ deepStrictEqual(CSV.toArray(`a;b;${os.EOL}a1;b1;${os.EOL}a2;;${os.EOL}`), [{ a: 'a1', b: 'b1' }, { a: 'a2', b: '' }]);
43
+ });
44
+
45
+ it('CSV#toArray no trailing semi-column in header', () => {
46
+ deepStrictEqual(CSV.toArray(`a;b${os.EOL}a1;b1;${os.EOL}a2;b2;${os.EOL}`), [{ a: 'a1', b: 'b1' }, { a: 'a2', b: 'b2' }]);
47
+ });
48
+
49
+ it('CSV#toArray no trailing semi-column in one row', () => {
50
+ deepStrictEqual(CSV.toArray(`a;b;${os.EOL}a1;b1;${os.EOL}a2;b2${os.EOL}`), [{ a: 'a1', b: 'b1' }, { a: 'a2', b: 'b2' }]);
51
+ });
52
+
53
+ it('CSV#toArray no trailing semi-column at all', () => {
54
+ deepStrictEqual(CSV.toArray(`a;b${os.EOL}a1;b1${os.EOL}a2;b2${os.EOL}`), [{ a: 'a1', b: 'b1' }, { a: 'a2', b: 'b2' }]);
55
+ });
56
+
57
+ it('CSV#toArray no trailing EOL', () => {
58
+ deepStrictEqual(CSV.toArray(`a;b${os.EOL}a1;b1;${os.EOL}a2;b2;`), [{ a: 'a1', b: 'b1' }, { a: 'a2', b: 'b2' }]);
59
+ });
60
+ });
@@ -0,0 +1,270 @@
1
+ /*
2
+ * Copyright 2020 Adobe. All rights reserved.
3
+ * This file is licensed to you under the Apache License, Version 2.0 (the "License");
4
+ * you may not use this file except in compliance with the License. You may obtain a copy
5
+ * of the License at http://www.apache.org/licenses/LICENSE-2.0
6
+ *
7
+ * Unless required by applicable law or agreed to in writing, software distributed under
8
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9
+ * OF ANY KIND, either express or implied. See the License for the specific language
10
+ * governing permissions and limitations under the License.
11
+ */
12
+
13
+ /* eslint-disable no-shadow */
14
+
15
+ import { strictEqual } from 'assert';
16
+ import { describe, it } from 'mocha';
17
+
18
+ import { JSDOM } from 'jsdom';
19
+
20
+ import DOMUtils from '../../src/utils/DOMUtils.js';
21
+
22
+ describe('DOMUtils#reviewInlineElement tests', () => {
23
+ const test = (input, tag, expected) => {
24
+ const { document } = (new JSDOM(input)).window;
25
+ DOMUtils.reviewInlineElement(document, tag);
26
+ strictEqual(document.body.innerHTML, expected);
27
+ };
28
+
29
+ it('reviewInlineElement does not change the DOM', () => {
30
+ test('<a href="linkhref">linkcontent</a>', 'a', '<a href="linkhref">linkcontent</a>');
31
+ test('<a href="linkhref">linkcontent</a><a href="linkhref2">linkcontent2</a>', 'a', '<a href="linkhref">linkcontent</a><a href="linkhref2">linkcontent2</a>');
32
+ });
33
+
34
+ it('reviewInlineElement merges nodes', () => {
35
+ test('<a href="linkhref">linkcontent</a><a href="linkhref">linkcontent2</a>', 'a', '<a href="linkhref">linkcontentlinkcontent2</a>');
36
+ test('<span>text</span><span> and more text</span>', 'span', '<span>text and more text</span>');
37
+ test('<em>text</em><em> and more text</em>', 'em', '<em>text and more text</em>');
38
+ test('<em> text</em><em> and more text</em>', 'em', '<span> </span><em>text and more text</em>');
39
+ test('<em> text </em><em>and more text </em>', 'em', '<span> </span><em>text and more text</em><span> </span>');
40
+ });
41
+
42
+ it('reviewInlineElement filters out useless punctuation tags', () => {
43
+ test('<a href="linkhref">linkcontent<strong>:</strong></a>', 'strong', '<a href="linkhref">linkcontent:</a>');
44
+ test('<a href="linkhref">linkcontent<span>: </span></a>', 'span', '<a href="linkhref">linkcontent: </a>');
45
+ test('<a href="linkhref">linkcontent</a><em>.</em>', 'em', '<a href="linkhref">linkcontent</a>.');
46
+ test('<a href="linkhref">linkcontent</a><i>. </i>', 'i', '<a href="linkhref">linkcontent</a>. ');
47
+ });
48
+
49
+ it('reviewInlineElement cleans up &nbsp;', () => {
50
+ test('<p><strong>So</strong><strong>me</strong><strong> - </strong><strong>complicated&nbsp;</strong><strong>setup&nbsp;</strong><strong>found&nbsp;</strong><strong>on&nbsp;</strong><strong>real site.</strong></p>', 'strong', '<p><strong>Some - complicated setup found on real site.</strong></p>');
51
+ });
52
+
53
+ it('reviewInlineElement nested tags', () => {
54
+ const intermediate = '<p><strong><em>emphasis</em><em> space </em><em>another emphasis</em> <em>last emphasis</em></strong></p>';
55
+ test('<p><strong><em>emphasis</em></strong><strong><em> space </em></strong><strong><em>another emphasis</em></strong> <strong><em>last emphasis</em></strong></p>', 'strong', intermediate);
56
+ test(intermediate, 'em', '<p><strong><em>emphasis space another emphasis last emphasis</em></strong></p>');
57
+ });
58
+
59
+ it('reviewInlineElement removes empty tags', () => {
60
+ test('<p><strong></strong><strong>only strong</strong></p>', 'strong', '<p><strong>only strong</strong></p>');
61
+ test('<p><strong><em></em></strong><strong><em>only strong</em></strong></p>', 'strong', '<p><strong><em>only strong</em></strong></p>');
62
+ });
63
+
64
+ it('reviewInlineElement does not remove useful tags', () => {
65
+ test('<p><a href="animage.jpg"><img src="animage.jpg"></a></p>', 'a', '<p><a href="animage.jpg"><img src="animage.jpg"></a></p>');
66
+ });
67
+
68
+ it('reviewInlineElement digests spaces', () => {
69
+ test('<p><strong>Sentence</strong> <strong>must</strong> <strong>be</strong> <strong>strong!</strong></p>', 'strong', '<p><strong>Sentence must be strong!</strong></p>');
70
+ });
71
+
72
+ it('reviewInlineElement extract trailing spaces', () => {
73
+ test('<div><ul><li>The <strong>ideal image size </strong>for your Facebook cover photo is <strong>851px by 315px.</strong></li><li>For <u><a href="https://www.facebook.com/help/266520536764594?helpref=uf_permalink">best results</a></u>, make sure your image is <strong>JPG format</strong>, with RGB color, and <strong>less than 100 KB.</strong></li><li>Facebook will automatically format your photo to fit the cover photo slot, so if it’s not sized correctly, you might experience some distortion. If you can’t meet the recommended sizing, <strong>make sure your image is at least 400px by 150px.</strong></li><li>Cover photos are displayed at <strong>820px by 312px </strong>on desktop and at <strong>640px by 360px</strong> on a smartphone so stick to a design that works at both sizes.</li></ul><p></p></div>', 'strong', '<div><ul><li>The <strong>ideal image size</strong><span> </span>for your Facebook cover photo is <strong>851px by 315px.</strong></li><li>For <u><a href="https://www.facebook.com/help/266520536764594?helpref=uf_permalink">best results</a></u>, make sure your image is <strong>JPG format</strong>, with RGB color, and <strong>less than 100 KB.</strong></li><li>Facebook will automatically format your photo to fit the cover photo slot, so if it’s not sized correctly, you might experience some distortion. If you can’t meet the recommended sizing, <strong>make sure your image is at least 400px by 150px.</strong></li><li>Cover photos are displayed at <strong>820px by 312px</strong><span> </span>on desktop and at <strong>640px by 360px</strong> on a smartphone so stick to a design that works at both sizes.</li></ul><p></p></div>');
74
+ });
75
+ });
76
+
77
+ describe('DOMUtils#reviewParagraphs tests', () => {
78
+ const test = (input, expected) => {
79
+ const { document } = (new JSDOM(input)).window;
80
+ DOMUtils.reviewParagraphs(document);
81
+ strictEqual(document.body.innerHTML, expected);
82
+ };
83
+
84
+ it('reviewParagraphs remove useless paragraphs', () => {
85
+ test('<p><strong><em>&nbsp;</em></strong></p><p>usefull</p>', '<p>usefull</p>');
86
+ test('<p><em>&nbsp;</em></p><p>usefull</p>', '<p>usefull</p>');
87
+ test('<p>usefull</p><p>&nbsp;</p>', '<p>usefull</p>');
88
+ test('<p>usefull</p><p>&nbsp;</p><p>usefull too</p>', '<p>usefull</p><p>usefull too</p>');
89
+ });
90
+
91
+ it('reviewParagraphs does not remove usefull paragraphs', () => {
92
+ test('<p><img src="animage.jpg"></p>', '<p><img src="animage.jpg"></p>');
93
+ test('<p><a href="animage.jpg"><img src="animage.jpg"></a></p>', '<p><a href="animage.jpg"><img src="animage.jpg"></a></p>');
94
+ test('<p><div></div></p>', '<div></div>');
95
+ test('<p><video width="320" height="240" controls=""><source src="movie.mp4" type="video/mp4"></video></p>', '<p><video width="320" height="240" controls=""><source src="movie.mp4" type="video/mp4"></video></p>');
96
+ test('<p><iframe src="www.iframe.com"></iframe></p>', '<p><iframe src="www.iframe.com"></iframe></p>');
97
+ });
98
+ });
99
+
100
+ describe('DOMUtils#reviewHeadings tests', () => {
101
+ const test = (input, expected) => {
102
+ const { document } = (new JSDOM(input)).window;
103
+ DOMUtils.reviewHeadings(document);
104
+ strictEqual(document.body.innerHTML, expected);
105
+ };
106
+
107
+ it('reviewHeadings filters headings', () => {
108
+ test('<h1><strong>Super title</strong></h1>', '<h1>Super title</h1>');
109
+ });
110
+
111
+ it('reviewHeadings filters headings but keep the ones clean', () => {
112
+ test('<h1><strong>Super title</strong></h1><h2><strong>Another title</strong></h2><h3>Do not touch this title</h3>', '<h1>Super title</h1><h2>Another title</h2><h3>Do not touch this title</h3>');
113
+ });
114
+
115
+ it('reviewHeadings filters headings but do not change other elements', () => {
116
+ test('<h1><strong>Super title</strong></h1><p><strong>String text</strong></p>', '<h1>Super title</h1><p><strong>String text</strong></p>');
117
+ });
118
+
119
+ it('reviewHeadings filters all headings', () => {
120
+ test('<h1><strong>H1</strong></h1><h2><strong>H2</strong></h2><h3><strong>H3</strong></h3><h4><strong>H4</strong></h4><h5><strong>H5</strong></h5><h6><strong>H6</strong></h6>', '<h1>H1</h1><h2>H2</h2><h3>H3</h3><h4>H4</h4><h5>H5</h5><h6>H6</h6>');
121
+ });
122
+
123
+ it('reviewHeadings removes empty headings', () => {
124
+ test('<h1><strong>H1</strong></h1><h2><strong></strong></h2><h3><strong>H3</strong></h3><h4></h4><h5><strong>H5</strong></h5><h6></h6>', '<h1>H1</h1><h3>H3</h3><h5>H5</h5>');
125
+ });
126
+ });
127
+
128
+ describe('DOMUtils#escapeSpecialCharacters tests', () => {
129
+ const test = (input, expected) => {
130
+ const { document } = (new JSDOM(input)).window;
131
+ DOMUtils.escapeSpecialCharacters(document);
132
+ strictEqual(document.body.innerHTML, expected);
133
+ };
134
+
135
+ it('escapeSpecialCharacters escape tidles', () => {
136
+ test('<p>Paragraph with 2 tildes: 20~30 and 40~50</p>', '<p>Paragraph with 2 tildes: 20\\~30 and 40\\~50</p>');
137
+ });
138
+ });
139
+
140
+ describe('DOMUtils#remove tests', () => {
141
+ const test = (input, selectors, expected) => {
142
+ const { document } = (new JSDOM(input)).window;
143
+ DOMUtils.remove(document, selectors);
144
+ strictEqual(document.body.innerHTML, expected);
145
+ };
146
+
147
+ it('remove elements', () => {
148
+ test('<a>link</a>', ['a'], '');
149
+ test('<a>link</a><a>link2</a>', ['a'], '');
150
+ test('<a>link</a><p>paragraph</p><a>link2</a>', ['a'], '<p>paragraph</p>');
151
+ test('<a>link</a><p>paragraph</p><a>link2</a>', ['p'], '<a>link</a><a>link2</a>');
152
+ test('<a class="badlink">link</a><p>paragraph</p><a>link2</a>', ['.badlink'], '<p>paragraph</p><a>link2</a>');
153
+ });
154
+ });
155
+
156
+ describe('DOMUtils#removeCommments tests', () => {
157
+ const test = (input, expected) => {
158
+ const { document } = (new JSDOM(input)).window;
159
+ DOMUtils.removeComments(document);
160
+ strictEqual(document.body.innerHTML, expected);
161
+ };
162
+
163
+ it('remove comments', () => {
164
+ // do nothing
165
+ test('<p></p>', '<p></p>');
166
+
167
+ // remove comments
168
+ test('<p><!-- useless comment --></p>', '<p></p>');
169
+ test('<p><!-- useless comment \n multiline --></p>', '<p></p>');
170
+ test('<p><!-- useless comment \n multiline \n multiline --></p>', '<p></p>');
171
+ test('<!-- useless comment --><p>The content stays</p><!-- another useless comment with \n line break -->', '<p>The content stays</p>');
172
+ });
173
+
174
+ describe('DOMUtils#removeSpans tests', () => {
175
+ const test = (input, expected) => {
176
+ const { document } = (new JSDOM(input)).window;
177
+ DOMUtils.removeSpans(document);
178
+ strictEqual(document.body.innerHTML, expected);
179
+ };
180
+
181
+ it('remove spans', () => {
182
+ // do nothing
183
+ test('<p></p>', '<p></p>');
184
+
185
+ // remove spans
186
+ test('<p><span></span></p>', '<p></p>');
187
+ test('<p><span>Content should remain</span> the same</p>', '<p>Content should remain the same</p>');
188
+ test('<p>Spacing<span> should</span> remain the same</p>', '<p>Spacing should remain the same</p>');
189
+ test('<p>Spacing<span> should</span> remain the <span>same even</span> with<span> multiple spans</span></p>', '<p>Spacing should remain the same even with multiple spans</p>');
190
+ });
191
+ });
192
+
193
+ describe('DOMUtils#removeNoscripts tests', () => {
194
+ const test = (input, expected) => {
195
+ strictEqual(DOMUtils.removeNoscripts(input), expected);
196
+ };
197
+
198
+ it('remove no scripts', () => {
199
+ // do nothing
200
+ test('<p>Some content</p>', '<p>Some content</p>');
201
+
202
+ // remove noscript
203
+ test('<body>Do A<noscript>Do Z</noscript></body>', '<body>Do A</body>');
204
+ test('<body>Do A<noscript>Do Z</noscript> but also do B<noscript>and X</noscript></body>', '<body>Do A but also do B</body>');
205
+ test('<body>Do A<noscript>Do Z\n Do X</noscript> but also do B<noscript>and W \n and Y</noscript></body>', '<body>Do A but also do B</body>');
206
+ });
207
+ });
208
+
209
+ describe('DOMUtils#replaceByCaptions tests', () => {
210
+ const test = (input, selectors, expected) => {
211
+ const { document } = (new JSDOM(input)).window;
212
+ DOMUtils.replaceByCaptions(document, selectors);
213
+ strictEqual(document.body.innerHTML, expected);
214
+ };
215
+
216
+ it('replace by captions', () => {
217
+ // do nothing
218
+ test('<p>Some content</p>', ['i'], '<p>Some content</p>');
219
+
220
+ test('<p>Some content</p><img src="image.png"><figcaption>Copyright to author.</figcaption><p>Some more content</p>', ['figcaption'], '<p>Some content</p><img src="image.png"><p><em>Copyright to author.</em></p><p>Some more content</p>');
221
+ test('<p>Some content</p><img src="image.png"><figcaption>Copyright to author.</figcaption><div class="custom-caption">Another copyright to author.</div><p>Some more content</p>', ['figcaption', '.custom-caption'], '<p>Some content</p><img src="image.png"><p><em>Copyright to author.</em></p><p><em>Another copyright to author.</em></p><p>Some more content</p>');
222
+ });
223
+ });
224
+
225
+ describe('DOMUtils#replaceEmbeds', () => {
226
+ const test = (input, expected) => {
227
+ const { document } = (new JSDOM(input)).window;
228
+ DOMUtils.replaceEmbeds(document);
229
+ strictEqual(document.body.innerHTML, expected);
230
+ };
231
+
232
+ it('replace embeds', () => {
233
+ // do nothing
234
+ test('<p>Some content</p>', '<p>Some content</p>');
235
+ });
236
+
237
+ it('replace embeds deals with iframes', () => {
238
+ test('<p>Some content</p><iframe src="https://www.youtube.com/xyz"></iframe>', '<p>Some content</p><table><tbody><tr><th>Embed</th></tr><tr><td><a href="https://www.youtube.com/xyz">https://www.youtube.com/xyz</a></td></tr></tbody></table>');
239
+ test('<p>Some content</p><iframe data-src="https://www.youtube.com/xyz"></iframe>', '<p>Some content</p><table><tbody><tr><th>Embed</th></tr><tr><td><a href="https://www.youtube.com/xyz">https://www.youtube.com/xyz</a></td></tr></tbody></table>');
240
+ test('<p>Some content</p><iframe data-src="https://www.youtube.com/data-src" src="https://www.youtube.com/src"></iframe>', '<p>Some content</p><table><tbody><tr><th>Embed</th></tr><tr><td><a href="https://www.youtube.com/data-src">https://www.youtube.com/data-src</a></td></tr></tbody></table>');
241
+ });
242
+
243
+ it('replace embeds deals video tag / content blocks', () => {
244
+ // Video block
245
+ test('<p>Some content</p><video src="https://www.server.com/video.mp4"></video>', '<p>Some content</p><table><tbody><tr><th>Video</th></tr><tr><td><video src="https://www.server.com/video.mp4"></video></td></tr></tbody></table>');
246
+
247
+ // Animation block
248
+ test('<p>Some content</p><video src="https://www.server.com/video.mp4" autoplay="true"></video>', '<p>Some content</p><table><tbody><tr><th>Animation</th></tr><tr><td><video src="https://www.server.com/video.mp4" autoplay="true"></video></td></tr></tbody></table>');
249
+ });
250
+ });
251
+
252
+ describe('DOMUtils#encodeImagesForTable', () => {
253
+ const test = (input, expected) => {
254
+ const { document } = (new JSDOM(input)).window;
255
+ DOMUtils.encodeImagesForTable(document);
256
+ strictEqual(document.body.innerHTML, expected);
257
+ };
258
+
259
+ it('encode images for table', () => {
260
+ // do nothing
261
+ test('<p>Some content</p>', '<p>Some content</p>');
262
+
263
+ // encode pipe if image is in table
264
+ test('<p>Some content</p><table><tr><td><img src="https://www.server.com/image.jpg" title="Some title | which contains a pipe"></td></tr></table>', '<p>Some content</p><table><tbody><tr><td><img src="https://www.server.com/image.jpg" title="Some title \\| which contains a pipe"></td></tr></tbody></table>');
265
+
266
+ // don't encode pipe if image is not in a table
267
+ test('<p>Some content</p><img src="https://www.server.com/image.jpg" title="Some title | which contains a pipe">', '<p>Some content</p><img src="https://www.server.com/image.jpg" title="Some title | which contains a pipe">');
268
+ });
269
+ });
270
+ });