@postalsys/email-text-tools 2.4.26 → 2.6.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.
Files changed (88) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/README.md +12 -0
  3. package/dist/cjs/index.d.ts +20 -0
  4. package/dist/cjs/index.js +31 -0
  5. package/dist/cjs/lib/commonjs.cjs +17 -0
  6. package/dist/cjs/lib/commonjs.d.cts +4 -0
  7. package/dist/cjs/lib/format-date.d.ts +15 -0
  8. package/dist/cjs/lib/format-date.js +60 -0
  9. package/dist/cjs/lib/html-guard.d.ts +3 -0
  10. package/dist/cjs/lib/html-guard.js +119 -0
  11. package/dist/cjs/lib/html-to-text.d.ts +1 -0
  12. package/dist/cjs/lib/html-to-text.js +49 -0
  13. package/dist/cjs/lib/inline-html.d.ts +19 -0
  14. package/dist/cjs/lib/inline-html.js +150 -0
  15. package/dist/cjs/lib/inline-style.d.ts +2 -0
  16. package/dist/cjs/lib/inline-style.js +42 -0
  17. package/dist/cjs/lib/inline-text.d.ts +3 -0
  18. package/dist/cjs/lib/inline-text.js +111 -0
  19. package/dist/cjs/lib/juice-worker.d.ts +1 -0
  20. package/dist/cjs/lib/juice-worker.js +19 -0
  21. package/dist/cjs/lib/mime-html.d.ts +34 -0
  22. package/dist/cjs/lib/mime-html.js +496 -0
  23. package/dist/cjs/lib/text-to-html.d.ts +1 -0
  24. package/dist/cjs/lib/text-to-html.js +182 -0
  25. package/dist/cjs/lib/worker-pool.d.ts +61 -0
  26. package/dist/cjs/lib/worker-pool.js +229 -0
  27. package/dist/cjs/package.json +3 -0
  28. package/dist/esm/index.d.ts +20 -0
  29. package/dist/esm/index.js +13 -0
  30. package/dist/esm/lib/commonjs.cjs +17 -0
  31. package/dist/esm/lib/commonjs.d.cts +4 -0
  32. package/dist/esm/lib/format-date.d.ts +15 -0
  33. package/{lib → dist/esm/lib}/format-date.js +8 -16
  34. package/dist/esm/lib/html-guard.d.ts +3 -0
  35. package/{lib → dist/esm/lib}/html-guard.js +8 -24
  36. package/dist/esm/lib/html-to-text.d.ts +1 -0
  37. package/{lib → dist/esm/lib}/html-to-text.js +2 -14
  38. package/dist/esm/lib/inline-html.d.ts +19 -0
  39. package/{lib → dist/esm/lib}/inline-html.js +17 -55
  40. package/dist/esm/lib/inline-style.d.ts +2 -0
  41. package/dist/esm/lib/inline-style.js +39 -0
  42. package/dist/esm/lib/inline-text.d.ts +3 -0
  43. package/{lib → dist/esm/lib}/inline-text.js +8 -31
  44. package/dist/esm/lib/juice-worker.d.ts +1 -0
  45. package/{lib → dist/esm/lib}/juice-worker.js +7 -8
  46. package/dist/esm/lib/mime-html.d.ts +34 -0
  47. package/{lib → dist/esm/lib}/mime-html.js +80 -161
  48. package/dist/esm/lib/text-to-html.d.ts +1 -0
  49. package/{lib → dist/esm/lib}/text-to-html.js +35 -63
  50. package/dist/esm/lib/worker-pool.d.ts +61 -0
  51. package/{lib → dist/esm/lib}/worker-pool.js +18 -64
  52. package/dist/esm/package.json +3 -0
  53. package/package.json +67 -10
  54. package/.github/VULNERABILITY_REPORT.yml +0 -75
  55. package/.github/workflows/release.yaml +0 -38
  56. package/.github/workflows/test.yaml +0 -35
  57. package/.ncurc.js +0 -28
  58. package/.prettierignore +0 -14
  59. package/.prettierrc.js +0 -10
  60. package/.release-please-manifest.json +0 -3
  61. package/CLAUDE.md +0 -105
  62. package/benchmark/benchmark.js +0 -348
  63. package/benchmark/concurrency-benchmark.js +0 -176
  64. package/benchmark/quick-benchmark.js +0 -200
  65. package/benchmark/quick-concurrency.js +0 -64
  66. package/benchmark/real-world-benchmark.js +0 -257
  67. package/docs/PERFORMANCE.md +0 -262
  68. package/eslint.config.js +0 -71
  69. package/examples/failing.html +0 -617
  70. package/examples/fixtures/message-1.json +0 -74
  71. package/examples/inline-html.js +0 -24
  72. package/examples/inline-text.js +0 -24
  73. package/examples/mime-html.js +0 -284
  74. package/index.js +0 -22
  75. package/release-please-config.json +0 -10
  76. package/test/comprehensive.test.js +0 -358
  77. package/test/containment.test.js +0 -128
  78. package/test/edge-cases.test.js +0 -62
  79. package/test/hardening.test.js +0 -504
  80. package/test/html-to-text.test.js +0 -253
  81. package/test/inline-html.test.js +0 -230
  82. package/test/inline-text.test.js +0 -204
  83. package/test/mime-html-extra.test.js +0 -39
  84. package/test/mime-html.test.js +0 -302
  85. package/test/text-to-html-extra.test.js +0 -42
  86. package/test/worker-pool-extra.test.js +0 -132
  87. package/test/worker-pool-regression.test.js +0 -128
  88. package/test/worker-pool.test.js +0 -282
@@ -1,253 +0,0 @@
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
18
- .trim()
19
- .split('\n')
20
- .filter(l => l.trim());
21
- assert.equal(lines.length, 3);
22
- });
23
-
24
- it('should preserve newlines from div tags', () => {
25
- const html = '<div>Line 1</div><div>Line 2</div><div>Line 3</div>';
26
- const text = htmlToText(html);
27
-
28
- assert.ok(text.includes('Line 1'));
29
- assert.ok(text.includes('Line 2'));
30
- assert.ok(text.includes('Line 3'));
31
-
32
- // Check that lines are separated
33
- const lines = text
34
- .trim()
35
- .split('\n')
36
- .filter(l => l.trim());
37
- assert.equal(lines.length, 3);
38
- });
39
-
40
- it('should preserve newlines from paragraph tags', () => {
41
- const html = '<p>Paragraph 1</p><p>Paragraph 2</p><p>Paragraph 3</p>';
42
- const text = htmlToText(html);
43
-
44
- assert.ok(text.includes('Paragraph 1'));
45
- assert.ok(text.includes('Paragraph 2'));
46
- assert.ok(text.includes('Paragraph 3'));
47
-
48
- // Check that paragraphs are separated
49
- const lines = text
50
- .trim()
51
- .split('\n')
52
- .filter(l => l.trim());
53
- assert.equal(lines.length, 3);
54
- });
55
-
56
- it('should handle MS Graph style HTML with mixed formatting', () => {
57
- // Typical MS Graph HTML structure
58
- const html = `
59
- <div>Hello there,</div>
60
- <div><br></div>
61
- <div>This is a test email with multiple lines.</div>
62
- <div>Each line should be preserved.</div>
63
- <div><br></div>
64
- <div>Thanks,</div>
65
- <div>User</div>
66
- `;
67
- const text = htmlToText(html);
68
-
69
- assert.ok(text.includes('Hello there'));
70
- assert.ok(text.includes('This is a test email'));
71
- assert.ok(text.includes('Each line should be preserved'));
72
- assert.ok(text.includes('Thanks'));
73
- assert.ok(text.includes('User'));
74
-
75
- // Verify structure is maintained
76
- const lines = text
77
- .split('\n')
78
- .map(l => l.trim())
79
- .filter(l => l);
80
- assert.ok(lines.length >= 5, `Expected at least 5 lines, got ${lines.length}`);
81
- });
82
-
83
- it('should handle lists with proper line breaks', () => {
84
- const html = '<ul><li>Item 1</li><li>Item 2</li><li>Item 3</li></ul>';
85
- const text = htmlToText(html);
86
-
87
- assert.ok(text.includes('Item 1'));
88
- assert.ok(text.includes('Item 2'));
89
- assert.ok(text.includes('Item 3'));
90
-
91
- // Each list item should be on a new line
92
- const lines = text
93
- .trim()
94
- .split('\n')
95
- .filter(l => l.trim());
96
- assert.ok(lines.length >= 3);
97
- });
98
-
99
- it('should handle headers with appropriate spacing', () => {
100
- const html = '<h1>Main Title</h1><p>Some content</p><h2>Subtitle</h2><p>More content</p>';
101
- const text = htmlToText(html);
102
-
103
- assert.ok(text.includes('Main Title'));
104
- assert.ok(text.includes('Some content'));
105
- assert.ok(text.includes('Subtitle'));
106
- assert.ok(text.includes('More content'));
107
-
108
- // Headers should create separation
109
- const lines = text
110
- .trim()
111
- .split('\n')
112
- .filter(l => l.trim());
113
- assert.ok(lines.length >= 4);
114
- });
115
-
116
- it('should handle blockquotes with line breaks', () => {
117
- const html = '<p>Normal text</p><blockquote>Quoted text line 1<br>Quoted text line 2</blockquote><p>More normal text</p>';
118
- const text = htmlToText(html);
119
-
120
- assert.ok(text.includes('Normal text'));
121
- assert.ok(text.includes('Quoted text line 1'));
122
- assert.ok(text.includes('Quoted text line 2'));
123
- assert.ok(text.includes('More normal text'));
124
- });
125
-
126
- it('should not create excessive blank lines', () => {
127
- const html = '<p>Text 1</p><p>Text 2</p><p>Text 3</p>';
128
- const text = htmlToText(html);
129
-
130
- // Should not have more than 3 consecutive newlines
131
- assert.ok(!text.includes('\n\n\n\n'), 'Should not have 4 or more consecutive newlines');
132
- });
133
-
134
- it('should handle tables with line breaks between rows', () => {
135
- const html = '<table><tr><td>Cell 1</td><td>Cell 2</td></tr><tr><td>Cell 3</td><td>Cell 4</td></tr></table>';
136
- const text = htmlToText(html);
137
-
138
- assert.ok(text.includes('Cell 1'));
139
- assert.ok(text.includes('Cell 2'));
140
- assert.ok(text.includes('Cell 3'));
141
- assert.ok(text.includes('Cell 4'));
142
-
143
- // Rows should be separated
144
- const lines = text
145
- .trim()
146
- .split('\n')
147
- .filter(l => l.trim());
148
- assert.ok(lines.length >= 2);
149
- });
150
-
151
- it('should handle empty elements gracefully', () => {
152
- const html = '<div>Text</div><div></div><div>More text</div>';
153
- const text = htmlToText(html);
154
-
155
- assert.ok(text.includes('Text'));
156
- assert.ok(text.includes('More text'));
157
- });
158
-
159
- it('should preserve newlines in pre tags', () => {
160
- const html = '<pre>Line 1\nLine 2\nLine 3</pre>';
161
- const text = htmlToText(html);
162
-
163
- assert.ok(text.includes('Line 1'));
164
- assert.ok(text.includes('Line 2'));
165
- assert.ok(text.includes('Line 3'));
166
- });
167
-
168
- it('should handle complex nested structure', () => {
169
- const html = `
170
- <div>
171
- <p>First paragraph</p>
172
- <div>
173
- <div>Nested content</div>
174
- </div>
175
- <p>Last paragraph</p>
176
- </div>
177
- `;
178
- const text = htmlToText(html);
179
-
180
- assert.ok(text.includes('First paragraph'));
181
- assert.ok(text.includes('Nested content'));
182
- assert.ok(text.includes('Last paragraph'));
183
-
184
- const lines = text
185
- .trim()
186
- .split('\n')
187
- .filter(l => l.trim());
188
- assert.equal(lines.length, 3);
189
- });
190
-
191
- it('should handle real-world MS Graph HTML example', () => {
192
- // Real example of how MS Graph might return HTML
193
- const html = `
194
- <html>
195
- <head></head>
196
- <body>
197
- <div style="font-family:Calibri,Arial,Helvetica,sans-serif; font-size:12pt; color:rgb(0,0,0)">
198
- Hi Team,</div>
199
- <div style="font-family:Calibri,Arial,Helvetica,sans-serif; font-size:12pt; color:rgb(0,0,0)">
200
- <br>
201
- </div>
202
- <div style="font-family:Calibri,Arial,Helvetica,sans-serif; font-size:12pt; color:rgb(0,0,0)">
203
- I wanted to update you on the following:</div>
204
- <div style="font-family:Calibri,Arial,Helvetica,sans-serif; font-size:12pt; color:rgb(0,0,0)">
205
- <br>
206
- </div>
207
- <div style="font-family:Calibri,Arial,Helvetica,sans-serif; font-size:12pt; color:rgb(0,0,0)">
208
- - Item 1</div>
209
- <div style="font-family:Calibri,Arial,Helvetica,sans-serif; font-size:12pt; color:rgb(0,0,0)">
210
- - Item 2</div>
211
- <div style="font-family:Calibri,Arial,Helvetica,sans-serif; font-size:12pt; color:rgb(0,0,0)">
212
- - Item 3</div>
213
- <div style="font-family:Calibri,Arial,Helvetica,sans-serif; font-size:12pt; color:rgb(0,0,0)">
214
- <br>
215
- </div>
216
- <div style="font-family:Calibri,Arial,Helvetica,sans-serif; font-size:12pt; color:rgb(0,0,0)">
217
- Best regards,</div>
218
- <div style="font-family:Calibri,Arial,Helvetica,sans-serif; font-size:12pt; color:rgb(0,0,0)">
219
- John</div>
220
- </body>
221
- </html>
222
- `;
223
- const text = htmlToText(html);
224
-
225
- // Check all content is present
226
- assert.ok(text.includes('Hi Team'));
227
- assert.ok(text.includes('I wanted to update you'));
228
- assert.ok(text.includes('- Item 1'));
229
- assert.ok(text.includes('- Item 2'));
230
- assert.ok(text.includes('- Item 3'));
231
- assert.ok(text.includes('Best regards'));
232
- assert.ok(text.includes('John'));
233
-
234
- // Check structure is preserved
235
- const lines = text
236
- .trim()
237
- .split('\n')
238
- .filter(l => l.trim());
239
- assert.ok(lines.length >= 7, `Expected at least 7 content lines, got ${lines.length}`);
240
-
241
- // Items should be on separate lines
242
- const itemIndex1 = text.indexOf('- Item 1');
243
- const itemIndex2 = text.indexOf('- Item 2');
244
- const itemIndex3 = text.indexOf('- Item 3');
245
-
246
- assert.ok(itemIndex2 > itemIndex1);
247
- assert.ok(itemIndex3 > itemIndex2);
248
-
249
- // There should be newlines between items
250
- assert.ok(text.substring(itemIndex1, itemIndex2).includes('\n'));
251
- assert.ok(text.substring(itemIndex2, itemIndex3).includes('\n'));
252
- });
253
- });
@@ -1,230 +0,0 @@
1
- 'use strict';
2
-
3
- const { describe, it } = require('node:test');
4
- const assert = require('node:assert/strict');
5
- const inlineHtml = require('../lib/inline-html');
6
-
7
- describe('inline-html', () => {
8
- const baseDate = new Date('2024-06-15T14:30:00Z');
9
-
10
- const fullMessageData = {
11
- from: [{ name: 'Alice Sender', address: 'alice@example.com' }],
12
- subject: 'Test Subject',
13
- date: baseDate,
14
- to: [{ name: 'Bob Receiver', address: 'bob@example.com' }],
15
- cc: [{ name: 'Charlie', address: 'charlie@example.com' }],
16
- bcc: [{ address: 'secret@example.com' }],
17
- html: '<html><body><p>Original message</p></body></html>'
18
- };
19
-
20
- describe('reply action', () => {
21
- it('should create reply with date and from header', () => {
22
- const result = inlineHtml('reply', '<p>My reply</p>', fullMessageData);
23
- assert.ok(result.includes('wrote:'));
24
- assert.ok(result.includes('alice@example.com'));
25
- assert.ok(result.includes('My reply'));
26
- assert.ok(result.includes('<blockquote'));
27
- assert.ok(result.includes('Original message'));
28
- });
29
-
30
- it('should create reply with no from address using <> fallback', () => {
31
- const data = { date: baseDate, html: '<p>Body</p>' };
32
- const result = inlineHtml('reply', '', data);
33
- assert.ok(result.includes('&lt;&gt;'));
34
- assert.ok(result.includes('wrote:'));
35
- });
36
-
37
- it('should create reply with no date', () => {
38
- const data = {
39
- from: [{ name: 'Sender', address: 's@test.com' }],
40
- html: '<p>Body</p>'
41
- };
42
- const result = inlineHtml('reply', '', data);
43
- assert.ok(result.includes('wrote:'));
44
- assert.ok(!result.includes('undefined'));
45
- });
46
- });
47
-
48
- describe('forward action', () => {
49
- it('should create forward with all headers', () => {
50
- const result = inlineHtml('forward', '<p>FYI</p>', fullMessageData);
51
- assert.ok(result.includes('Begin forwarded message:'));
52
- assert.ok(result.includes('<b>From: </b>'));
53
- assert.ok(result.includes('<b>Subject: </b>'));
54
- assert.ok(result.includes('<b>Date: </b>'));
55
- assert.ok(result.includes('<b>To: </b>'));
56
- assert.ok(result.includes('<b>Cc: </b>'));
57
- assert.ok(result.includes('<b>Bcc: </b>'));
58
- assert.ok(result.includes('FYI'));
59
- assert.ok(result.includes('Original message'));
60
- });
61
-
62
- it('should include from fallback in forward with no from', () => {
63
- const data = { subject: 'Fwd', html: '<p>Body</p>' };
64
- const result = inlineHtml('forward', '', data);
65
- assert.ok(result.includes('Begin forwarded message:'));
66
- assert.ok(!result.includes('<b>From: </b>'));
67
- });
68
- });
69
-
70
- describe('unknown action', () => {
71
- it('should include original body in blockquote without header lines', () => {
72
- const data = { html: '<p>Body</p>' };
73
- const result = inlineHtml('draft', '', data);
74
- assert.ok(result.includes('<blockquote'));
75
- assert.ok(result.includes('Body'));
76
- assert.ok(!result.includes('wrote:'));
77
- assert.ok(!result.includes('Begin forwarded message:'));
78
- });
79
- });
80
-
81
- describe('original body extraction', () => {
82
- it('should use textToHtml when messageData has text but no html', () => {
83
- const data = { text: 'Plain text original' };
84
- const result = inlineHtml('reply', '', data);
85
- assert.ok(result.includes('Plain text original'));
86
- assert.ok(result.includes('<blockquote'));
87
- });
88
-
89
- it('should extract body innerHTML when messageData has html', () => {
90
- const data = { html: '<html><body><p>Extracted</p></body></html>' };
91
- const result = inlineHtml('reply', '', data);
92
- assert.ok(result.includes('<p>Extracted</p>'));
93
- assert.ok(!result.includes('<body>'));
94
- });
95
-
96
- it('should extract content when html has no body tag', () => {
97
- const data = { html: '<p>No body tag</p>' };
98
- const result = inlineHtml('reply', '', data);
99
- assert.ok(result.includes('No body tag'));
100
- });
101
-
102
- it('should return messageContent unchanged when no originalBody', () => {
103
- const data = { text: '', html: '' };
104
- const result = inlineHtml('reply', 'Untouched', data);
105
- assert.equal(result, 'Untouched');
106
- });
107
-
108
- it('should return messageContent when messageData has no text or html', () => {
109
- const result = inlineHtml('reply', 'Keep this', {});
110
- assert.equal(result, 'Keep this');
111
- });
112
- });
113
-
114
- describe('address formatting', () => {
115
- it('should format address with both name and address', () => {
116
- const data = {
117
- from: [{ name: 'Full Name', address: 'full@test.com' }],
118
- html: '<p>Body</p>'
119
- };
120
- const result = inlineHtml('forward', '', data);
121
- assert.ok(result.includes('Full Name'));
122
- assert.ok(result.includes('mailto:full@test.com'));
123
- });
124
-
125
- it('should format address with name only', () => {
126
- const data = {
127
- from: [{ name: 'Name Only' }],
128
- html: '<p>Body</p>'
129
- };
130
- const result = inlineHtml('forward', '', data);
131
- assert.ok(result.includes('Name Only'));
132
- assert.ok(!result.includes('mailto:'));
133
- });
134
-
135
- it('should format address with address only', () => {
136
- const data = {
137
- from: [{ address: 'addr@test.com' }],
138
- html: '<p>Body</p>'
139
- };
140
- const result = inlineHtml('forward', '', data);
141
- assert.ok(result.includes('mailto:addr@test.com'));
142
- });
143
-
144
- it('should use <> fallback for empty address object', () => {
145
- const data = {
146
- from: [{}],
147
- html: '<p>Body</p>'
148
- };
149
- const result = inlineHtml('reply', '', data);
150
- assert.ok(result.includes('&lt;&gt;'));
151
- });
152
-
153
- it('should format multiple addresses separated by commas', () => {
154
- const data = {
155
- to: [
156
- { name: 'A', address: 'a@t.com' },
157
- { name: 'B', address: 'b@t.com' }
158
- ],
159
- html: '<p>Body</p>'
160
- };
161
- const result = inlineHtml('forward', '', data);
162
- assert.ok(result.includes('a@t.com'));
163
- assert.ok(result.includes('b@t.com'));
164
- });
165
-
166
- it('should skip invalid entries in address list', () => {
167
- const data = {
168
- to: [null, {}, { name: 'Valid', address: 'v@t.com' }],
169
- html: '<p>Body</p>'
170
- };
171
- const result = inlineHtml('forward', '', data);
172
- assert.ok(result.includes('v@t.com'));
173
- });
174
-
175
- it('should omit header for empty address list result', () => {
176
- const data = {
177
- to: [{}],
178
- html: '<p>Body</p>'
179
- };
180
- const result = inlineHtml('forward', '', data);
181
- assert.ok(!result.includes('<b>To: </b>'));
182
- });
183
- });
184
-
185
- describe('date formatting', () => {
186
- it('should format date with locale option', () => {
187
- const data = { date: baseDate, html: '<p>Body</p>' };
188
- const result = inlineHtml('forward', '', data, { locale: 'de' });
189
- assert.ok(result.includes('<b>Date: </b>'));
190
- });
191
-
192
- it('should format date with timezone option', () => {
193
- const data = { date: baseDate, html: '<p>Body</p>' };
194
- const result = inlineHtml('forward', '', data, { tz: 'America/New_York' });
195
- assert.ok(result.includes('<b>Date: </b>'));
196
- });
197
-
198
- it('should format date with both locale and timezone', () => {
199
- const data = { date: baseDate, html: '<p>Body</p>' };
200
- const result = inlineHtml('forward', '', data, { locale: 'en', tz: 'UTC' });
201
- assert.ok(result.includes('<b>Date: </b>'));
202
- });
203
- });
204
-
205
- describe('messageContent handling', () => {
206
- it('should handle null messageContent', () => {
207
- const data = { html: '<p>Body</p>' };
208
- const result = inlineHtml('reply', null, data);
209
- assert.ok(result.includes('<html>'));
210
- assert.ok(result.includes('Body'));
211
- });
212
-
213
- it('should extract body from full HTML messageContent', () => {
214
- const data = { html: '<p>Original</p>' };
215
- const result = inlineHtml('reply', '<html><body><p>Reply content</p></body></html>', data);
216
- assert.ok(result.includes('Reply content'));
217
- });
218
- });
219
-
220
- describe('output structure', () => {
221
- it('should produce valid HTML document structure', () => {
222
- const result = inlineHtml('reply', 'Reply', fullMessageData);
223
- assert.ok(result.includes('<html>'));
224
- assert.ok(result.includes('<head>'));
225
- assert.ok(result.includes('charset=utf-8'));
226
- assert.ok(result.includes('<body'));
227
- assert.ok(result.includes('</html>'));
228
- });
229
- });
230
- });
@@ -1,204 +0,0 @@
1
- 'use strict';
2
-
3
- const { describe, it } = require('node:test');
4
- const assert = require('node:assert/strict');
5
- const inlineText = require('../lib/inline-text');
6
-
7
- describe('inline-text', () => {
8
- const baseDate = new Date('2024-06-15T14:30:00Z');
9
-
10
- const fullMessageData = {
11
- from: [{ name: 'Alice Sender', address: 'alice@example.com' }],
12
- subject: 'Test Subject',
13
- date: baseDate,
14
- to: [{ name: 'Bob', address: 'bob@example.com' }],
15
- cc: [{ name: 'Charlie', address: 'charlie@example.com' }],
16
- bcc: [{ address: 'secret@example.com' }],
17
- text: 'Original message text'
18
- };
19
-
20
- describe('reply action', () => {
21
- it('should create reply with "On date, from wrote:" header', () => {
22
- const result = inlineText('reply', 'My reply', fullMessageData);
23
- assert.ok(result.includes('My reply'));
24
- assert.ok(result.includes('> On '));
25
- assert.ok(result.includes('wrote:'));
26
- assert.ok(result.includes('alice@example.com'));
27
- assert.ok(result.includes('> Original message text'));
28
- });
29
-
30
- it('should create reply with no from using <> fallback', () => {
31
- const data = { date: baseDate, text: 'Body text' };
32
- const result = inlineText('reply', '', data);
33
- assert.ok(result.includes('<>'));
34
- assert.ok(result.includes('wrote:'));
35
- });
36
-
37
- it('should include original body with > quoting', () => {
38
- const data = { text: 'Line 1\nLine 2\nLine 3' };
39
- const result = inlineText('reply', 'Reply', data);
40
- assert.ok(result.includes('> Line 1'));
41
- assert.ok(result.includes('> Line 2'));
42
- assert.ok(result.includes('> Line 3'));
43
- });
44
- });
45
-
46
- describe('forward action', () => {
47
- it('should create forward with all headers', () => {
48
- const result = inlineText('forward', 'FYI', fullMessageData);
49
- assert.ok(result.includes('> Begin forwarded message:'));
50
- assert.ok(result.includes('> From:'));
51
- assert.ok(result.includes('> Subject: Test Subject'));
52
- assert.ok(result.includes('> Date:'));
53
- assert.ok(result.includes('> To:'));
54
- assert.ok(result.includes('> Cc:'));
55
- assert.ok(result.includes('> Bcc:'));
56
- assert.ok(result.includes('> Original message text'));
57
- });
58
-
59
- it('should omit from header when no from provided', () => {
60
- const data = { subject: 'Fwd', text: 'Body' };
61
- const result = inlineText('forward', '', data);
62
- assert.ok(result.includes('> Begin forwarded message:'));
63
- assert.ok(result.includes('> Subject: Fwd'));
64
- assert.ok(!result.includes('> From:'));
65
- });
66
- });
67
-
68
- describe('unknown action', () => {
69
- it('should include original body without typed headers', () => {
70
- const data = { text: 'Body text' };
71
- const result = inlineText('other', '', data);
72
- assert.ok(result.includes('> Body text'));
73
- assert.ok(!result.includes('wrote:'));
74
- assert.ok(!result.includes('Begin forwarded message:'));
75
- });
76
- });
77
-
78
- describe('original body source', () => {
79
- it('should use htmlToText when messageData has html but no text', () => {
80
- const data = { html: '<p>HTML content</p>' };
81
- const result = inlineText('reply', '', data);
82
- assert.ok(result.includes('HTML content'));
83
- });
84
-
85
- it('should use text directly when available', () => {
86
- const data = { text: 'Direct text', html: '<p>HTML</p>' };
87
- const result = inlineText('reply', '', data);
88
- assert.ok(result.includes('> Direct text'));
89
- });
90
-
91
- it('should return messageContent unchanged when no originalBody', () => {
92
- const result = inlineText('reply', 'Keep this', { text: '' });
93
- assert.equal(result, 'Keep this');
94
- });
95
-
96
- it('should return messageContent when messageData is empty', () => {
97
- const result = inlineText('reply', 'Keep', {});
98
- assert.equal(result, 'Keep');
99
- });
100
- });
101
-
102
- describe('address formatting', () => {
103
- it('should format address with name and address', () => {
104
- const data = {
105
- from: [{ name: 'Full Name', address: 'full@test.com' }],
106
- text: 'Body'
107
- };
108
- const result = inlineText('forward', '', data);
109
- assert.ok(result.includes('Full Name <full@test.com>'));
110
- });
111
-
112
- it('should format address with name only', () => {
113
- const data = {
114
- from: [{ name: 'Name Only' }],
115
- text: 'Body'
116
- };
117
- const result = inlineText('forward', '', data);
118
- assert.ok(result.includes('Name Only'));
119
- assert.ok(!result.includes('<>'));
120
- });
121
-
122
- it('should format address with address only', () => {
123
- const data = {
124
- from: [{ address: 'addr@test.com' }],
125
- text: 'Body'
126
- };
127
- const result = inlineText('forward', '', data);
128
- assert.ok(result.includes('<addr@test.com>'));
129
- });
130
-
131
- it('should use <> fallback for empty address object', () => {
132
- const data = {
133
- from: [{}],
134
- text: 'Body'
135
- };
136
- const result = inlineText('reply', '', data);
137
- assert.ok(result.includes('<>'));
138
- });
139
-
140
- it('should format multiple addresses separated by commas', () => {
141
- const data = {
142
- to: [
143
- { name: 'A', address: 'a@t.com' },
144
- { name: 'B', address: 'b@t.com' }
145
- ],
146
- text: 'Body'
147
- };
148
- const result = inlineText('forward', '', data);
149
- assert.ok(result.includes('A <a@t.com>'));
150
- assert.ok(result.includes('B <b@t.com>'));
151
- });
152
-
153
- it('should skip invalid entries in address list', () => {
154
- const data = {
155
- to: [null, {}, { name: 'Valid', address: 'v@t.com' }],
156
- text: 'Body'
157
- };
158
- const result = inlineText('forward', '', data);
159
- assert.ok(result.includes('Valid <v@t.com>'));
160
- });
161
-
162
- it('should omit header for empty address list result', () => {
163
- const data = {
164
- to: [{}],
165
- text: 'Body'
166
- };
167
- const result = inlineText('forward', '', data);
168
- assert.ok(!result.includes('> To:'));
169
- });
170
- });
171
-
172
- describe('escapeText', () => {
173
- it('should handle \\r\\n line endings', () => {
174
- const data = { text: 'Line1\r\nLine2\r\nLine3' };
175
- const result = inlineText('reply', '', data);
176
- assert.ok(result.includes('> Line1'));
177
- assert.ok(result.includes('> Line2'));
178
- assert.ok(result.includes('> Line3'));
179
- });
180
- });
181
-
182
- describe('date formatting', () => {
183
- it('should format date with locale', () => {
184
- const data = { date: baseDate, text: 'Body' };
185
- const result = inlineText('forward', '', data, { locale: 'de' });
186
- assert.ok(result.includes('> Date:'));
187
- });
188
-
189
- it('should format date with timezone', () => {
190
- const data = { date: baseDate, text: 'Body' };
191
- const result = inlineText('forward', '', data, { tz: 'America/New_York' });
192
- assert.ok(result.includes('> Date:'));
193
- });
194
- });
195
-
196
- describe('messageContent handling', () => {
197
- it('should handle null messageContent', () => {
198
- const data = { text: 'Body' };
199
- const result = inlineText('reply', null, data);
200
- assert.ok(typeof result === 'string');
201
- assert.ok(result.includes('> Body'));
202
- });
203
- });
204
- });