@postalsys/email-text-tools 2.3.4 → 2.4.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.
- package/.release-please-manifest.json +1 -1
- package/CHANGELOG.md +45 -0
- package/lib/html-to-text.js +42 -4
- package/package.json +5 -5
- package/release-please-config.json +2 -1
- package/test/html-to-text.test.js +226 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,50 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [2.4.0](https://github.com/postalsys/email-text-tools/compare/v2.3.5...v2.4.0) (2025-12-11)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
* **mime-html:** add timeout protection for CSS processing with juice ([9bd3542](https://github.com/postalsys/email-text-tools/commit/9bd35426642c00c20fa1a8ce9c898f7ab7fc7e43))
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Bug Fixes
|
|
12
|
+
|
|
13
|
+
* bumped deps ([45f4533](https://github.com/postalsys/email-text-tools/commit/45f453390e00a8b5dea00c0ab3a7b4a742e9b3ce))
|
|
14
|
+
* Bumped deps ([519eb91](https://github.com/postalsys/email-text-tools/commit/519eb913a2aa91afe2eb1c782186f4f043ce1c55))
|
|
15
|
+
* Bumped deps ([00af333](https://github.com/postalsys/email-text-tools/commit/00af333ece8b1e6d14091fda1aea7662f314c721))
|
|
16
|
+
* Bumped deps ([da979c9](https://github.com/postalsys/email-text-tools/commit/da979c9feef5c8fd4e08b9967cbffcc058a70677))
|
|
17
|
+
* **deploy:** Set up automatic deployment ([21e55be](https://github.com/postalsys/email-text-tools/commit/21e55be18bb2ac1b991ee6000928c429a5b09ddd))
|
|
18
|
+
* **deploy:** Set up automatic deployment ([c816aaf](https://github.com/postalsys/email-text-tools/commit/c816aafa0457318cfd84497dfcd4e5628503c5b4))
|
|
19
|
+
* **deps:** Bumped deps ([3b71891](https://github.com/postalsys/email-text-tools/commit/3b71891b2c6bcb5d87fb9f583a2aec221bcb142f))
|
|
20
|
+
* **deps:** Bumped deps ([498e56a](https://github.com/postalsys/email-text-tools/commit/498e56a011321f2f437c461008724ca07233eb41))
|
|
21
|
+
* **deps:** Bumped deps ([59e44d8](https://github.com/postalsys/email-text-tools/commit/59e44d8cb0877e407becac7ef6744a4dfcdba083))
|
|
22
|
+
* **deps:** Bumped deps ([4f8a120](https://github.com/postalsys/email-text-tools/commit/4f8a1208b2841d343baf610c1391788b60b4297c))
|
|
23
|
+
* **deps:** Bumped deps ([a6711a1](https://github.com/postalsys/email-text-tools/commit/a6711a1938d8e2e75f5afd83b5a8c443be0adb9c))
|
|
24
|
+
* **deps:** Bumped deps ([15233fa](https://github.com/postalsys/email-text-tools/commit/15233fa7850f3227bfbc4f06500f0f267e651913))
|
|
25
|
+
* **deps:** Bumped deps ([4815314](https://github.com/postalsys/email-text-tools/commit/4815314f9d36da2b2163d6990acc8f68fc1bc356))
|
|
26
|
+
* **deps:** Bumped deps (juice) ([69eaee3](https://github.com/postalsys/email-text-tools/commit/69eaee36cceadbbf5ebf23caaf24385c38859a03))
|
|
27
|
+
* **deps:** fixed deps ([ccf26e2](https://github.com/postalsys/email-text-tools/commit/ccf26e2f0f8047482b9cc7e71cd0a3e61b286ddc))
|
|
28
|
+
* **deps:** removed js-beautify ([a73ccc9](https://github.com/postalsys/email-text-tools/commit/a73ccc97a4acf9bfa7e439939c41869c2554b073))
|
|
29
|
+
* Downgraded jsdom to 27.0.0 ([a2969e9](https://github.com/postalsys/email-text-tools/commit/a2969e9399ed6c9d15a85d7d8c11342bc5bf00ce))
|
|
30
|
+
* **htmlToText:** Improve HTML-to-text conversion for better newline preservation ([6606d5c](https://github.com/postalsys/email-text-tools/commit/6606d5c5b9d3b05f41d54c434f2b68760141dacb))
|
|
31
|
+
* **mime-html:** ensure async version pre-filters problematic CSS selectors ([5166f71](https://github.com/postalsys/email-text-tools/commit/5166f714b8532c62aa33c76b2b4e2ac3c8642ca6))
|
|
32
|
+
* remove registry-url to enable OIDC authentication for trusted publishing ([319e61e](https://github.com/postalsys/email-text-tools/commit/319e61ed7c83af3db4f0a58f4b5b85c296f6f6a6))
|
|
33
|
+
* removed unneeded console log lines ([3a10214](https://github.com/postalsys/email-text-tools/commit/3a1021483029a1c99df122241c4e0f6062b91059))
|
|
34
|
+
* rename release-please config file to remove dot prefix ([0bb0343](https://github.com/postalsys/email-text-tools/commit/0bb0343f7c4d35f62b66b159604ba0f19665a177))
|
|
35
|
+
* separate publish job with npm-publish environment for trusted publishing ([1ea55bd](https://github.com/postalsys/email-text-tools/commit/1ea55bdcfa0726f48d927fb970cf900a4cd4943f))
|
|
36
|
+
* trigger release to test npm publishing configuration ([88e7110](https://github.com/postalsys/email-text-tools/commit/88e711007a73a88e0f40b5bfb278826821c1fc6a))
|
|
37
|
+
* upgrade Node.js to version 24 for OIDC trusted publishing support ([fd41ca2](https://github.com/postalsys/email-text-tools/commit/fd41ca22a959c06eb2df4e8b957a0da86f42d9da))
|
|
38
|
+
* upgrade tooling and configure release-please v4 ([4620dd7](https://github.com/postalsys/email-text-tools/commit/4620dd7d67c70486789af35abb1732ee151545c0))
|
|
39
|
+
* **worker-pool:** properly return workers to free list after task completion ([2d6cd2c](https://github.com/postalsys/email-text-tools/commit/2d6cd2c451941932d44d2ce025a5ba867ec66fec))
|
|
40
|
+
|
|
41
|
+
## [2.3.5](https://github.com/postalsys/email-text-tools/compare/email-text-tools-v2.3.4...email-text-tools-v2.3.5) (2025-10-29)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
### Bug Fixes
|
|
45
|
+
|
|
46
|
+
* **htmlToText:** Improve HTML-to-text conversion for better newline preservation ([6606d5c](https://github.com/postalsys/email-text-tools/commit/6606d5c5b9d3b05f41d54c434f2b68760141dacb))
|
|
47
|
+
|
|
3
48
|
## [2.3.4](https://github.com/postalsys/email-text-tools/compare/email-text-tools-v2.3.3...email-text-tools-v2.3.4) (2025-10-23)
|
|
4
49
|
|
|
5
50
|
|
package/lib/html-to-text.js
CHANGED
|
@@ -1,16 +1,54 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
const { compile } = require('html-to-text');
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
// Post-process function to clean up excessive newlines
|
|
5
|
+
function cleanupNewlines(text) {
|
|
6
|
+
// Replace 3 or more consecutive newlines with just 2
|
|
7
|
+
// This handles cases where HTML has many nested empty divs
|
|
8
|
+
return text.replace(/\n{3,}/g, '\n\n');
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const htmlToTextCompiled = compile({
|
|
5
12
|
wordwrap: 160,
|
|
6
13
|
selectors: [
|
|
14
|
+
// Block elements that should preserve line breaks
|
|
7
15
|
{ selector: 'p', options: { leadingLineBreaks: 1, trailingLineBreaks: 1 } },
|
|
16
|
+
// Divs get minimal breaks - many HTML emails wrap each line in a div
|
|
17
|
+
{ selector: 'div', options: { leadingLineBreaks: 1, trailingLineBreaks: 0 } },
|
|
8
18
|
{ selector: 'pre', options: { leadingLineBreaks: 1, trailingLineBreaks: 1 } },
|
|
9
|
-
{ selector: '
|
|
10
|
-
|
|
19
|
+
{ selector: 'blockquote', options: { leadingLineBreaks: 1, trailingLineBreaks: 1, trimEmptyLines: true } },
|
|
20
|
+
|
|
21
|
+
// Headers should have spacing
|
|
22
|
+
{ selector: 'h1', options: { leadingLineBreaks: 2, trailingLineBreaks: 1, uppercase: false } },
|
|
23
|
+
{ selector: 'h2', options: { leadingLineBreaks: 2, trailingLineBreaks: 1, uppercase: false } },
|
|
24
|
+
{ selector: 'h3', options: { leadingLineBreaks: 2, trailingLineBreaks: 1, uppercase: false } },
|
|
25
|
+
{ selector: 'h4', options: { leadingLineBreaks: 2, trailingLineBreaks: 1, uppercase: false } },
|
|
26
|
+
{ selector: 'h5', options: { leadingLineBreaks: 2, trailingLineBreaks: 1, uppercase: false } },
|
|
27
|
+
{ selector: 'h6', options: { leadingLineBreaks: 2, trailingLineBreaks: 1, uppercase: false } },
|
|
28
|
+
|
|
29
|
+
// Line breaks
|
|
30
|
+
{ selector: 'br', options: { leadingLineBreaks: 1, trailingLineBreaks: 0 } },
|
|
31
|
+
{ selector: 'hr', options: { leadingLineBreaks: 1, trailingLineBreaks: 1 } },
|
|
32
|
+
|
|
33
|
+
// Lists should preserve structure
|
|
34
|
+
{ selector: 'ul', options: { leadingLineBreaks: 1, trailingLineBreaks: 1 } },
|
|
35
|
+
{ selector: 'ol', options: { leadingLineBreaks: 1, trailingLineBreaks: 1 } },
|
|
36
|
+
|
|
37
|
+
// Tables
|
|
38
|
+
{ selector: 'table', format: 'dataTable' },
|
|
39
|
+
|
|
40
|
+
// Ignore images
|
|
11
41
|
{ selector: 'img', format: 'skip' }
|
|
12
42
|
],
|
|
13
|
-
hideLinkHrefIfSameAsText: true
|
|
43
|
+
hideLinkHrefIfSameAsText: true,
|
|
44
|
+
// Preserve whitespace better for content
|
|
45
|
+
preserveNewlines: true
|
|
14
46
|
});
|
|
15
47
|
|
|
48
|
+
// Wrapper function that applies post-processing
|
|
49
|
+
function htmlToText(html) {
|
|
50
|
+
const text = htmlToTextCompiled(html);
|
|
51
|
+
return cleanupNewlines(text);
|
|
52
|
+
}
|
|
53
|
+
|
|
16
54
|
module.exports = htmlToText;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@postalsys/email-text-tools",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.4.0",
|
|
4
4
|
"description": "Email text tools",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -25,13 +25,13 @@
|
|
|
25
25
|
},
|
|
26
26
|
"homepage": "https://github.com/postalsys/email-text-tools#readme",
|
|
27
27
|
"devDependencies": {
|
|
28
|
-
"@eslint/js": "9.
|
|
29
|
-
"eslint": "9.
|
|
28
|
+
"@eslint/js": "9.39.1",
|
|
29
|
+
"eslint": "9.39.1",
|
|
30
30
|
"eslint-config-prettier": "10.1.8",
|
|
31
|
-
"prettier": "3.
|
|
31
|
+
"prettier": "3.7.4"
|
|
32
32
|
},
|
|
33
33
|
"dependencies": {
|
|
34
|
-
"dompurify": "3.3.
|
|
34
|
+
"dompurify": "3.3.1",
|
|
35
35
|
"he": "1.2.0",
|
|
36
36
|
"html-to-text": "9.0.5",
|
|
37
37
|
"jsdom": "27.0.0",
|
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
".": {
|
|
4
4
|
"release-type": "node",
|
|
5
5
|
"package-name": "@postalsys/email-text-tools",
|
|
6
|
-
"pull-request-title-pattern": "chore${scope}: release ${version} [skip-ci]"
|
|
6
|
+
"pull-request-title-pattern": "chore${scope}: release ${version} [skip-ci]",
|
|
7
|
+
"include-component-in-tag": false
|
|
7
8
|
}
|
|
8
9
|
}
|
|
9
10
|
}
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { describe, it } = require('node:test');
|
|
4
|
+
const assert = require('node:assert/strict');
|
|
5
|
+
const htmlToText = require('../lib/html-to-text');
|
|
6
|
+
|
|
7
|
+
describe('html-to-text conversion', () => {
|
|
8
|
+
it('should preserve newlines from br tags', () => {
|
|
9
|
+
const html = 'Line 1<br>Line 2<br>Line 3';
|
|
10
|
+
const text = htmlToText(html);
|
|
11
|
+
|
|
12
|
+
assert.ok(text.includes('Line 1'));
|
|
13
|
+
assert.ok(text.includes('Line 2'));
|
|
14
|
+
assert.ok(text.includes('Line 3'));
|
|
15
|
+
|
|
16
|
+
// Check that lines are separated
|
|
17
|
+
const lines = text.trim().split('\n').filter(l => l.trim());
|
|
18
|
+
assert.equal(lines.length, 3);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it('should preserve newlines from div tags', () => {
|
|
22
|
+
const html = '<div>Line 1</div><div>Line 2</div><div>Line 3</div>';
|
|
23
|
+
const text = htmlToText(html);
|
|
24
|
+
|
|
25
|
+
assert.ok(text.includes('Line 1'));
|
|
26
|
+
assert.ok(text.includes('Line 2'));
|
|
27
|
+
assert.ok(text.includes('Line 3'));
|
|
28
|
+
|
|
29
|
+
// Check that lines are separated
|
|
30
|
+
const lines = text.trim().split('\n').filter(l => l.trim());
|
|
31
|
+
assert.equal(lines.length, 3);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('should preserve newlines from paragraph tags', () => {
|
|
35
|
+
const html = '<p>Paragraph 1</p><p>Paragraph 2</p><p>Paragraph 3</p>';
|
|
36
|
+
const text = htmlToText(html);
|
|
37
|
+
|
|
38
|
+
assert.ok(text.includes('Paragraph 1'));
|
|
39
|
+
assert.ok(text.includes('Paragraph 2'));
|
|
40
|
+
assert.ok(text.includes('Paragraph 3'));
|
|
41
|
+
|
|
42
|
+
// Check that paragraphs are separated
|
|
43
|
+
const lines = text.trim().split('\n').filter(l => l.trim());
|
|
44
|
+
assert.equal(lines.length, 3);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('should handle MS Graph style HTML with mixed formatting', () => {
|
|
48
|
+
// Typical MS Graph HTML structure
|
|
49
|
+
const html = `
|
|
50
|
+
<div>Hello there,</div>
|
|
51
|
+
<div><br></div>
|
|
52
|
+
<div>This is a test email with multiple lines.</div>
|
|
53
|
+
<div>Each line should be preserved.</div>
|
|
54
|
+
<div><br></div>
|
|
55
|
+
<div>Thanks,</div>
|
|
56
|
+
<div>User</div>
|
|
57
|
+
`;
|
|
58
|
+
const text = htmlToText(html);
|
|
59
|
+
|
|
60
|
+
assert.ok(text.includes('Hello there'));
|
|
61
|
+
assert.ok(text.includes('This is a test email'));
|
|
62
|
+
assert.ok(text.includes('Each line should be preserved'));
|
|
63
|
+
assert.ok(text.includes('Thanks'));
|
|
64
|
+
assert.ok(text.includes('User'));
|
|
65
|
+
|
|
66
|
+
// Verify structure is maintained
|
|
67
|
+
const lines = text.split('\n').map(l => l.trim()).filter(l => l);
|
|
68
|
+
assert.ok(lines.length >= 5, `Expected at least 5 lines, got ${lines.length}`);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('should handle lists with proper line breaks', () => {
|
|
72
|
+
const html = '<ul><li>Item 1</li><li>Item 2</li><li>Item 3</li></ul>';
|
|
73
|
+
const text = htmlToText(html);
|
|
74
|
+
|
|
75
|
+
assert.ok(text.includes('Item 1'));
|
|
76
|
+
assert.ok(text.includes('Item 2'));
|
|
77
|
+
assert.ok(text.includes('Item 3'));
|
|
78
|
+
|
|
79
|
+
// Each list item should be on a new line
|
|
80
|
+
const lines = text.trim().split('\n').filter(l => l.trim());
|
|
81
|
+
assert.ok(lines.length >= 3);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it('should handle headers with appropriate spacing', () => {
|
|
85
|
+
const html = '<h1>Main Title</h1><p>Some content</p><h2>Subtitle</h2><p>More content</p>';
|
|
86
|
+
const text = htmlToText(html);
|
|
87
|
+
|
|
88
|
+
assert.ok(text.includes('Main Title'));
|
|
89
|
+
assert.ok(text.includes('Some content'));
|
|
90
|
+
assert.ok(text.includes('Subtitle'));
|
|
91
|
+
assert.ok(text.includes('More content'));
|
|
92
|
+
|
|
93
|
+
// Headers should create separation
|
|
94
|
+
const lines = text.trim().split('\n').filter(l => l.trim());
|
|
95
|
+
assert.ok(lines.length >= 4);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it('should handle blockquotes with line breaks', () => {
|
|
99
|
+
const html = '<p>Normal text</p><blockquote>Quoted text line 1<br>Quoted text line 2</blockquote><p>More normal text</p>';
|
|
100
|
+
const text = htmlToText(html);
|
|
101
|
+
|
|
102
|
+
assert.ok(text.includes('Normal text'));
|
|
103
|
+
assert.ok(text.includes('Quoted text line 1'));
|
|
104
|
+
assert.ok(text.includes('Quoted text line 2'));
|
|
105
|
+
assert.ok(text.includes('More normal text'));
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it('should not create excessive blank lines', () => {
|
|
109
|
+
const html = '<p>Text 1</p><p>Text 2</p><p>Text 3</p>';
|
|
110
|
+
const text = htmlToText(html);
|
|
111
|
+
|
|
112
|
+
// Should not have more than 3 consecutive newlines
|
|
113
|
+
assert.ok(!text.includes('\n\n\n\n'), 'Should not have 4 or more consecutive newlines');
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it('should handle tables with line breaks between rows', () => {
|
|
117
|
+
const html = '<table><tr><td>Cell 1</td><td>Cell 2</td></tr><tr><td>Cell 3</td><td>Cell 4</td></tr></table>';
|
|
118
|
+
const text = htmlToText(html);
|
|
119
|
+
|
|
120
|
+
assert.ok(text.includes('Cell 1'));
|
|
121
|
+
assert.ok(text.includes('Cell 2'));
|
|
122
|
+
assert.ok(text.includes('Cell 3'));
|
|
123
|
+
assert.ok(text.includes('Cell 4'));
|
|
124
|
+
|
|
125
|
+
// Rows should be separated
|
|
126
|
+
const lines = text.trim().split('\n').filter(l => l.trim());
|
|
127
|
+
assert.ok(lines.length >= 2);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it('should handle empty elements gracefully', () => {
|
|
131
|
+
const html = '<div>Text</div><div></div><div>More text</div>';
|
|
132
|
+
const text = htmlToText(html);
|
|
133
|
+
|
|
134
|
+
assert.ok(text.includes('Text'));
|
|
135
|
+
assert.ok(text.includes('More text'));
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it('should preserve newlines in pre tags', () => {
|
|
139
|
+
const html = '<pre>Line 1\nLine 2\nLine 3</pre>';
|
|
140
|
+
const text = htmlToText(html);
|
|
141
|
+
|
|
142
|
+
assert.ok(text.includes('Line 1'));
|
|
143
|
+
assert.ok(text.includes('Line 2'));
|
|
144
|
+
assert.ok(text.includes('Line 3'));
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it('should handle complex nested structure', () => {
|
|
148
|
+
const html = `
|
|
149
|
+
<div>
|
|
150
|
+
<p>First paragraph</p>
|
|
151
|
+
<div>
|
|
152
|
+
<div>Nested content</div>
|
|
153
|
+
</div>
|
|
154
|
+
<p>Last paragraph</p>
|
|
155
|
+
</div>
|
|
156
|
+
`;
|
|
157
|
+
const text = htmlToText(html);
|
|
158
|
+
|
|
159
|
+
assert.ok(text.includes('First paragraph'));
|
|
160
|
+
assert.ok(text.includes('Nested content'));
|
|
161
|
+
assert.ok(text.includes('Last paragraph'));
|
|
162
|
+
|
|
163
|
+
const lines = text.trim().split('\n').filter(l => l.trim());
|
|
164
|
+
assert.equal(lines.length, 3);
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
it('should handle real-world MS Graph HTML example', () => {
|
|
168
|
+
// Real example of how MS Graph might return HTML
|
|
169
|
+
const html = `
|
|
170
|
+
<html>
|
|
171
|
+
<head></head>
|
|
172
|
+
<body>
|
|
173
|
+
<div style="font-family:Calibri,Arial,Helvetica,sans-serif; font-size:12pt; color:rgb(0,0,0)">
|
|
174
|
+
Hi Team,</div>
|
|
175
|
+
<div style="font-family:Calibri,Arial,Helvetica,sans-serif; font-size:12pt; color:rgb(0,0,0)">
|
|
176
|
+
<br>
|
|
177
|
+
</div>
|
|
178
|
+
<div style="font-family:Calibri,Arial,Helvetica,sans-serif; font-size:12pt; color:rgb(0,0,0)">
|
|
179
|
+
I wanted to update you on the following:</div>
|
|
180
|
+
<div style="font-family:Calibri,Arial,Helvetica,sans-serif; font-size:12pt; color:rgb(0,0,0)">
|
|
181
|
+
<br>
|
|
182
|
+
</div>
|
|
183
|
+
<div style="font-family:Calibri,Arial,Helvetica,sans-serif; font-size:12pt; color:rgb(0,0,0)">
|
|
184
|
+
- Item 1</div>
|
|
185
|
+
<div style="font-family:Calibri,Arial,Helvetica,sans-serif; font-size:12pt; color:rgb(0,0,0)">
|
|
186
|
+
- Item 2</div>
|
|
187
|
+
<div style="font-family:Calibri,Arial,Helvetica,sans-serif; font-size:12pt; color:rgb(0,0,0)">
|
|
188
|
+
- Item 3</div>
|
|
189
|
+
<div style="font-family:Calibri,Arial,Helvetica,sans-serif; font-size:12pt; color:rgb(0,0,0)">
|
|
190
|
+
<br>
|
|
191
|
+
</div>
|
|
192
|
+
<div style="font-family:Calibri,Arial,Helvetica,sans-serif; font-size:12pt; color:rgb(0,0,0)">
|
|
193
|
+
Best regards,</div>
|
|
194
|
+
<div style="font-family:Calibri,Arial,Helvetica,sans-serif; font-size:12pt; color:rgb(0,0,0)">
|
|
195
|
+
John</div>
|
|
196
|
+
</body>
|
|
197
|
+
</html>
|
|
198
|
+
`;
|
|
199
|
+
const text = htmlToText(html);
|
|
200
|
+
|
|
201
|
+
// Check all content is present
|
|
202
|
+
assert.ok(text.includes('Hi Team'));
|
|
203
|
+
assert.ok(text.includes('I wanted to update you'));
|
|
204
|
+
assert.ok(text.includes('- Item 1'));
|
|
205
|
+
assert.ok(text.includes('- Item 2'));
|
|
206
|
+
assert.ok(text.includes('- Item 3'));
|
|
207
|
+
assert.ok(text.includes('Best regards'));
|
|
208
|
+
assert.ok(text.includes('John'));
|
|
209
|
+
|
|
210
|
+
// Check structure is preserved
|
|
211
|
+
const lines = text.trim().split('\n').filter(l => l.trim());
|
|
212
|
+
assert.ok(lines.length >= 7, `Expected at least 7 content lines, got ${lines.length}`);
|
|
213
|
+
|
|
214
|
+
// Items should be on separate lines
|
|
215
|
+
const itemIndex1 = text.indexOf('- Item 1');
|
|
216
|
+
const itemIndex2 = text.indexOf('- Item 2');
|
|
217
|
+
const itemIndex3 = text.indexOf('- Item 3');
|
|
218
|
+
|
|
219
|
+
assert.ok(itemIndex2 > itemIndex1);
|
|
220
|
+
assert.ok(itemIndex3 > itemIndex2);
|
|
221
|
+
|
|
222
|
+
// There should be newlines between items
|
|
223
|
+
assert.ok(text.substring(itemIndex1, itemIndex2).includes('\n'));
|
|
224
|
+
assert.ok(text.substring(itemIndex2, itemIndex3).includes('\n'));
|
|
225
|
+
});
|
|
226
|
+
});
|