@createiq/htmldiff 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,251 @@
1
+ import { describe, expect, it } from 'vitest'
2
+
3
+ import HtmlDiff from '../src/HtmlDiff'
4
+
5
+ describe('HtmlDiff', () => {
6
+ // Shamelessly copied specs from here with a few modifications: https://github.com/myobie/htmldiff/blob/master/spec/htmldiff_spec.rb
7
+ it.for([
8
+ [
9
+ 'a word is here',
10
+ 'a nother word is there',
11
+ "a<ins class='diffins'>&nbsp;nother</ins> word is <del class='diffmod'>here</del><ins class='diffmod'>there</ins>",
12
+ ],
13
+ ['a c', 'a b c', "a <ins class='diffins'>b </ins>c"],
14
+ ['a b c', 'a c', "a <del class='diffdel'>b </del>c"],
15
+ ['a b c', 'a <strong>b</strong> c', "a <strong><ins class='mod'>b</ins></strong> c"],
16
+ ['a b c', 'a d c', "a <del class='diffmod'>b</del><ins class='diffmod'>d</ins> c"],
17
+ ["<a title='xx'>test</a>", "<a title='yy'>test</a>", "<a title='yy'>test</a>"],
18
+ ["<img src='logo.jpg'/>", '', "<del class='diffdel'><img src='logo.jpg'/></del>"],
19
+ ['', "<img src='logo.jpg'/>", "<ins class='diffins'><img src='logo.jpg'/></ins>"],
20
+ [
21
+ "symbols 'should not' belong <b>to</b> words",
22
+ 'symbols should not belong <b>"to"</b> words',
23
+ "symbols <del class='diffdel'>'</del>should not<del class='diffdel'>'</del> belong <b><ins class='diffins'>\"</ins>to<ins class='diffins'>\"</ins></b> words",
24
+ ],
25
+ [
26
+ 'entities are separate amp;words',
27
+ 'entities are&nbsp;separate &amp;words',
28
+ "entities are<del class='diffmod'>&nbsp;</del><ins class='diffmod'>&nbsp;</ins>separate <del class='diffmod'>amp;</del><ins class='diffmod'>&amp;</ins>words",
29
+ ],
30
+ [
31
+ 'This is a longer piece of text to ensure the new blocksize algorithm works',
32
+ 'This is a longer piece of text to <strong>ensure</strong> the new blocksize algorithm works decently',
33
+ "This is a longer piece of text to <strong><ins class='mod'>ensure</ins></strong> the new blocksize algorithm works<ins class='diffins'>&nbsp;decently</ins>",
34
+ ],
35
+ [
36
+ 'By virtue of an agreement between xxx and the <b>yyy schools</b>, ...',
37
+ 'By virtue of an agreement between xxx and the <b>yyy</b> schools, ...',
38
+ 'By virtue of an agreement between xxx and the <b>yyy</b> schools, ...',
39
+ ],
40
+ [
41
+ 'Some plain text',
42
+ 'Some <strong><i>plain</i></strong> text',
43
+ "Some <strong><i><ins class='mod'>plain</ins></i></strong> text",
44
+ ],
45
+ [
46
+ 'Some <strong><i>formatted</i></strong> text',
47
+ 'Some formatted text',
48
+ "Some <ins class='mod'>formatted</ins> text",
49
+ ],
50
+ [
51
+ '<table><tr><td>col1</td><td>col2</td></tr><tr><td>Data 1</td><td>Data 2</td></tr></table>',
52
+ '<table><tr><td>col1</td><td>col2</td></tr></table>',
53
+ "<table><tr><td>col1</td><td>col2</td></tr><tr><td><del class='diffdel'>Data 1</del></td><td><del class='diffdel'>Data 2</del></td></tr></table>",
54
+ ],
55
+ [
56
+ 'text',
57
+ '<span style="text-decoration: line-through;">text</span>',
58
+ '<span style="text-decoration: line-through;"><ins class=\'mod\'>text</ins></span>',
59
+ ],
60
+
61
+ // TODO: Don't speak Chinese, this needs to be validated
62
+ [
63
+ '这个是中文内容, CSharp is the bast',
64
+ '这是中国语内容,CSharp is the best language.',
65
+ "这<del class='diffdel'>个</del>是中<del class='diffmod'>文</del><ins class='diffmod'>国语</ins>内容<del class='diffmod'>, </del><ins class='diffmod'>,</ins>CSharp is the <del class='diffmod'>bast</del><ins class='diffmod'>best language.</ins>",
66
+ ],
67
+ ] as const)('should diff (%s, %s) -> %s', ([oldText, newText, expected]) => {
68
+ expect(HtmlDiff.execute(oldText, newText)).toEqual(expected)
69
+ })
70
+
71
+ it.for([
72
+ [
73
+ 'one a word is somewhere',
74
+ 'two a nother word is somewhere',
75
+ "<del class='diffmod'>one a</del><ins class='diffmod'>two a nother</ins> word is somewhere",
76
+ 0.2,
77
+ ],
78
+ [
79
+ 'one a word is somewhere',
80
+ 'two a nother word is somewhere',
81
+ "<del class='diffmod'>one</del><ins class='diffmod'>two</ins> a<ins class='diffins'>&nbsp;nother</ins> word is somewhere",
82
+ 0.1,
83
+ ],
84
+ ] as const)(
85
+ 'should diff (%s, %s) -> %s with orphanMatchThreshold %d',
86
+ ([oldText, newText, expected, orphanMatchThreshold]) => {
87
+ const diff = new HtmlDiff(oldText, newText)
88
+ diff.orphanMatchThreshold = orphanMatchThreshold
89
+ expect(diff.build()).toEqual(expected)
90
+ }
91
+ )
92
+
93
+ it.for([
94
+ [
95
+ 'This is a date 1 Jan 2016 that will change',
96
+ 'This is a date 22 Feb 2017 that did change',
97
+ "This is a date<del class='diffmod'> 1 Jan 2016</del><ins class='diffmod'> 22 Feb 2017</ins> that <del class='diffmod'>will</del><ins class='diffmod'>did</ins> change",
98
+ /\d{1,2}\s*(Jan|Feb)\s*\d{4}/g,
99
+ ],
100
+ [
101
+ 'This is a date 1 Jan 2016 that will change',
102
+ "This is a date 22 Feb 2017 that won't change",
103
+ "This is a date <del class='diffmod'>1</del><ins class='diffmod'>22</ins> <del class='diffmod'>Jan</del><ins class='diffmod'>Feb</ins> <del class='diffmod'>2016</del><ins class='diffmod'>2017</ins> that <del class='diffmod'>will</del><ins class='diffmod'>won't</ins> change",
104
+ null,
105
+ ],
106
+ ] as const)(
107
+ 'should diff (%s, %s) %s with grouping expression %s',
108
+ ([oldText, newText, expected, groupExpression]) => {
109
+ const diff = new HtmlDiff(oldText, newText)
110
+ if (groupExpression) {
111
+ diff.addBlockExpression(groupExpression)
112
+ }
113
+ expect(diff.build()).toEqual(expected)
114
+ }
115
+ )
116
+
117
+ it('should throw ArgumentException with invalid overlapping groups', () => {
118
+ const oldText = 'This is a date 1 Jan 2016 that will change'
119
+ const newText = "This is a date 22 Feb 2017 that won't change"
120
+ const diff = new HtmlDiff(oldText, newText)
121
+
122
+ // purposefully cause an overlapping expression
123
+ const pattern = /\d{1,2}\s*(Jan|Feb)\s*\d{4}/g
124
+ diff.addBlockExpression(pattern)
125
+ diff.addBlockExpression(pattern)
126
+
127
+ expect(() => diff.build()).toThrowError(
128
+ 'One or more block expressions result in a text sequence that overlaps. Current expression: /\\d{1,2}\\s*(Jan|Feb)\\s*\\d{4}/'
129
+ )
130
+ })
131
+
132
+ // https://github.com/Rohland/htmldiff.net/issues/3
133
+ it('should not include prefix in diff when non-alphanumeric characters are adjoined to the word diff', () => {
134
+ const oldText = 'The Dealer.'
135
+ const newText = 'The Dealer info,'
136
+
137
+ expect(HtmlDiff.execute(oldText, newText)).toEqual(
138
+ "The Dealer<del class='diffmod'>.</del><ins class='diffmod'>&nbsp;info,</ins>"
139
+ )
140
+ })
141
+
142
+ // https://github.com/Rohland/htmldiff.net/issues/20
143
+ it.for([
144
+ // Original Testcase from Issue 20, but with the correct expected result (the original expected result was missing the <ins class='mod'>[...]</ins>)
145
+ [
146
+ '<div class="dumb">Thiis a text without any sup-tags and other special things</div>',
147
+ '<div class="dumb">Thiis a text <sup>1</sup>without any sup-tags and other special things</div>',
148
+ "<div class=\"dumb\">Thiis a text <sup><ins class='mod'><ins class='diffins'>1</ins></ins></sup>without any sup-tags and other special things</div>",
149
+ ],
150
+
151
+ // Inserting a new word at the end of the reformatted text
152
+ [
153
+ '<span>text remains</span>',
154
+ '<span><strong>text remains Test</strong></span>',
155
+ "<span><strong><ins class='mod'>text remains<ins class='diffins'>&nbsp;Test</ins></ins></strong></span>",
156
+ ],
157
+
158
+ // Inserting a new word at the end of a reformatted text and another word at the end outside the reformatted text
159
+ [
160
+ '<span>text remains</span>',
161
+ '<span><strong>text remains Test</strong> Test</span>',
162
+ "<span><strong><ins class='mod'>text remains<ins class='diffins'>&nbsp;Test</ins></ins></strong><ins class='diffins'>&nbsp;Test</ins></span>",
163
+ ],
164
+
165
+ // Twice reformatted text with an offset at the end
166
+ [
167
+ '<span>text remains</span>',
168
+ '<span><strong><big>text </big>remains</strong></span>',
169
+ "<span><strong><big><ins class='mod'>text </big>remains</ins></strong></span>",
170
+ ],
171
+
172
+ // Inserting a new word at the beginning of a reformatted text.
173
+ [
174
+ '<span>text remains</span>',
175
+ '<span><strong>Test text remains</strong></span>',
176
+ "<span><strong><ins class='mod'><ins class='diffins'>Test </ins>text remains</ins></strong></span>",
177
+ ],
178
+ ] as const)('should handle missing closing tag in diff (%s, %s) -> %s', ([oldText, newText, expected]) => {
179
+ expect(HtmlDiff.execute(oldText, newText)).toEqual(expected)
180
+ })
181
+
182
+ it.for([
183
+ [
184
+ '<p>The additional term only applies where 5 years have elapsed since the execution of this language.</p>',
185
+ '<p>The additional term only applies where <strong><em>5 years</em></strong> have elapsed since the execution of this language.</p>',
186
+ `<p>The additional term only applies where <strong><em><ins class='mod'>5 years</ins></em></strong> have elapsed since the execution of this language.</p>`,
187
+ ],
188
+ [
189
+ '<p>The additional term only applies where 5 years have elapsed since the execution of this language.</p>',
190
+ '<p>The additional term only applies where <em><strong>5 years</strong></em> have elapsed since the execution of this language.</p>',
191
+ `<p>The additional term only applies where <em><strong><ins class='mod'>5 years</ins></strong></em> have elapsed since the execution of this language.</p>`,
192
+ ],
193
+ [
194
+ '<p>The additional term only applies where 5 years have elapsed since the execution of this language.</p>',
195
+ '<p>The additional term only applies where [5 years] have elapsed since the execution of this language.</p>',
196
+ `<p>The additional term only applies where <ins class='diffins'>[</ins>5 years<ins class='diffins'>]</ins> have elapsed since the execution of this language.</p>`,
197
+ ],
198
+ [
199
+ '<p>The additional term only applies where 5 years have elapsed since the execution of this language.</p>',
200
+ '<p>The additional term only applies where [<strong><em>5 years</em></strong>] have elapsed since the execution of this language.</p>',
201
+ `<p>The additional term only applies where <ins class='diffins'>[</ins><strong><em><ins class='mod'>5 years</ins></em></strong><ins class='diffins'>]</ins> have elapsed since the execution of this language.</p>`,
202
+ ],
203
+ [
204
+ '<p>The additional term only applies where 5 years have elapsed since the execution of this language.</p>',
205
+ '<p>The additional term only applies where [<em><strong>5 years</strong></em>] have elapsed since the execution of this language.</p>',
206
+ `<p>The additional term only applies where <ins class='diffins'>[</ins><em><strong><ins class='mod'>5 years</ins></strong></em><ins class='diffins'>]</ins> have elapsed since the execution of this language.</p>`,
207
+ ],
208
+ ] as const)('should work when the comparison adds styling', ([oldText, newText, expected]) => {
209
+ expect(HtmlDiff.execute(oldText, newText)).toEqual(expected)
210
+ })
211
+
212
+ it.for([
213
+ [
214
+ '<p>The additional term only applies where <strong><em>5 years</em></strong> have elapsed since the execution of this language.</p>',
215
+ '<p>The additional term only applies where 5 years have elapsed since the execution of this language.</p>',
216
+ `<p>The additional term only applies where <ins class='mod'>5 years</ins> have elapsed since the execution of this language.</p>`,
217
+ ],
218
+ [
219
+ '<p>The additional term only applies where <em><strong>5 years</strong></em> have elapsed since the execution of this language.</p>',
220
+ '<p>The additional term only applies where 5 years have elapsed since the execution of this language.</p>',
221
+ `<p>The additional term only applies where <ins class='mod'>5 years</ins> have elapsed since the execution of this language.</p>`,
222
+ ],
223
+ [
224
+ '<p>The additional term only applies where [5 years] have elapsed since the execution of this language.</p>',
225
+ '<p>The additional term only applies where 5 years have elapsed since the execution of this language.</p>',
226
+ `<p>The additional term only applies where <del class='diffdel'>[</del>5 years<del class='diffdel'>]</del> have elapsed since the execution of this language.</p>`,
227
+ ],
228
+ [
229
+ '<p>The additional term only applies where [<strong><em>5 years</em></strong>] have elapsed since the execution of this language.</p>',
230
+ '<p>The additional term only applies where 5 years have elapsed since the execution of this language.</p>',
231
+ `<p>The additional term only applies where <del class='diffdel'>[</del><ins class='mod'>5 years</ins><del class='diffdel'>]</del> have elapsed since the execution of this language.</p>`,
232
+ ],
233
+ [
234
+ '<p>The additional term only applies where [<em><strong>5 years</strong></em>] have elapsed since the execution of this language.</p>',
235
+ '<p>The additional term only applies where 5 years have elapsed since the execution of this language.</p>',
236
+ `<p>The additional term only applies where <del class='diffdel'>[</del><ins class='mod'>5 years</ins><del class='diffdel'>]</del> have elapsed since the execution of this language.</p>`,
237
+ ],
238
+ [
239
+ '<p>The additional term only applies where [<strong><em>5 years</em></strong>] have elapsed since the execution of this language.</p>',
240
+ '<p>The additional term only applies where [<strong>5 years</strong>] have elapsed since the execution of this language.</p>',
241
+ `<p>The additional term only applies where [<strong><ins class='mod'>5 years</ins></strong>] have elapsed since the execution of this language.</p>`,
242
+ ],
243
+ [
244
+ '<p>The additional term only applies where <strong><em>5 years</em></strong> have elapsed since the execution of this language.</p>',
245
+ '<p>The additional term only applies where [5 years] have elapsed since the execution of this language.</p>',
246
+ `<p>The additional term only applies where <ins class='mod'><ins class='diffmod'>[</ins>5 years</ins><ins class='diffmod'>]</ins> have elapsed since the execution of this language.</p>`,
247
+ ],
248
+ ] as const)('should work when the comparison removes styling', ([oldText, newText, expected]) => {
249
+ expect(HtmlDiff.execute(oldText, newText)).toEqual(expected)
250
+ })
251
+ })
@@ -0,0 +1,77 @@
1
+ import { describe, expect, it } from 'vitest'
2
+
3
+ import Match from '../src/Match'
4
+ import MatchFinder from '../src/MatchFinder'
5
+
6
+ describe('MatchFinder', () => {
7
+ describe('findMatch()', () => {
8
+ it("if matches wasn't found - return null", () => {
9
+ const oldWords = ['this', ' ', 'is', ' ', 'old', ' ', 'text']
10
+ const newWords = ['different', 'words', 'here']
11
+ const finder = new MatchFinder(oldWords, newWords, 0, oldWords.length, 0, newWords.length, {
12
+ blockSize: 3,
13
+ ignoreWhitespaceDifferences: false,
14
+ repeatingWordsAccuracy: 1.0,
15
+ })
16
+
17
+ expect(finder.findMatch()).toEqual(null)
18
+ })
19
+
20
+ it('find matches in two sequenses of words and return Match (its position in old sequence and new one, and size)', () => {
21
+ const oldWords = ['this', ' ', 'is', ' ', 'old', ' ', 'text']
22
+ const newWords = ['this', ' ', 'is', ' ', 'new', ' ', 'text']
23
+ const finder = new MatchFinder(
24
+ oldWords,
25
+ newWords,
26
+ 0,
27
+ oldWords.length, // will check all text from start to finish
28
+ 0,
29
+ newWords.length, // will check all text from start to finish
30
+ {
31
+ blockSize: 4,
32
+ ignoreWhitespaceDifferences: false,
33
+ repeatingWordsAccuracy: 1.0,
34
+ }
35
+ )
36
+
37
+ const expectedMatch = new Match(0, 0, 4)
38
+
39
+ expect(finder.findMatch()).toEqual(expectedMatch) // will return that sequence - "this is " is the same in old and new
40
+ })
41
+
42
+ it(`if you reduce block size and change pointers (starts and ends in old and new)
43
+ - it will return more precise positions which could be skiped in first runthrough`, () => {
44
+ const finder = new MatchFinder(
45
+ ['this', ' ', 'is', ' ', 'old', ' ', 'text'],
46
+ ['this', ' ', 'is', ' ', 'new', ' ', 'text'],
47
+ 4, // we change pointers for it to look only part of the text
48
+ 7,
49
+ 4, // we change pointers for it to look only part of the text
50
+ 7,
51
+ {
52
+ blockSize: 2, // we change block size in order to notice smaller changes
53
+ ignoreWhitespaceDifferences: false,
54
+ repeatingWordsAccuracy: 1.0,
55
+ }
56
+ )
57
+
58
+ const expectedMatch = new Match(5, 5, 2)
59
+
60
+ expect(finder.findMatch()).toEqual(expectedMatch) // will return that sequence - " text" is the same in old and new
61
+ })
62
+
63
+ it('when have repeating sentences will return all sentence', () => {
64
+ const oldWords = ['this', ' ', 'is', ' ', 'this', ' ', 'is']
65
+ const newWords = ['this', ' ', 'is', ' ', 'this', ' ', 'is']
66
+ const finder = new MatchFinder(oldWords, newWords, 0, oldWords.length, 0, newWords.length, {
67
+ blockSize: 2,
68
+ ignoreWhitespaceDifferences: false,
69
+ repeatingWordsAccuracy: 1.0,
70
+ })
71
+
72
+ const expectedMatch = new Match(0, 0, 7)
73
+
74
+ expect(finder.findMatch()).toEqual(expectedMatch) // will return that all sequence - "this is this is" is the same in old and new
75
+ })
76
+ })
77
+ })
@@ -0,0 +1,120 @@
1
+ import { describe, expect, it } from 'vitest'
2
+
3
+ import Utils from '../src/Utils'
4
+
5
+ describe('Utils', () => {
6
+ describe('getTagName()', () => {
7
+ it.for([
8
+ ['', ''],
9
+ [null, ''],
10
+ ['test', ''],
11
+ ['<test', ''],
12
+ ['< div >', ''],
13
+ ['</ div>', ''],
14
+ ['<div>', 'div'],
15
+ [' \t<div> \t', 'div'],
16
+ ['<DIV>', 'div'],
17
+ ['</div>', 'div'],
18
+ ["<div attr='test'>", 'div'],
19
+ ['<div attr=test>', 'div'],
20
+ ] as const)('should map %s to tag name %s', ([input, expected]) => {
21
+ expect(Utils.getTagName(input)).toEqual(expected)
22
+ })
23
+ })
24
+
25
+ describe('isTag()', () => {
26
+ it('accept string and return true if it is tag', () => {
27
+ expect(Utils.isTag('')).toBe(false)
28
+
29
+ expect(Utils.isTag('<span>')).toBe(true)
30
+ expect(Utils.isTag('<video />')).toBe(true)
31
+ })
32
+
33
+ it('if it is "<img" tag - return false', () => {
34
+ expect(Utils.isTag('<img />')).toBe(false)
35
+ })
36
+ })
37
+
38
+ describe('stripTagAttributes()', () => {
39
+ it('accept string and return stripped tag - without any attributes', () => {
40
+ expect(Utils.stripTagAttributes('')).toBe('')
41
+
42
+ expect(Utils.stripTagAttributes('<span class="bold" style="font-size: 8px;" id="id-1" >')).toBe('<span>')
43
+ expect(Utils.stripTagAttributes('<img src="./image-1.png" />')).toBe('<img/>')
44
+ })
45
+ })
46
+
47
+ describe('wrapText()', () => {
48
+ it('create tag with passed text inside', () => {
49
+ expect(Utils.wrapText('text', 'span', '')).toBe("<span class=''>text</span>")
50
+ })
51
+
52
+ it('set passed css class to tag ', () => {
53
+ expect(Utils.wrapText('text', 'span', 'bold')).toBe("<span class='bold'>text</span>")
54
+ })
55
+ })
56
+
57
+ describe('isStartOfTag()', () => {
58
+ it('return true if it is start of the tag', () => {
59
+ expect(Utils.isStartOfTag('')).toBe(false)
60
+ expect(Utils.isStartOfTag('>')).toBe(false)
61
+
62
+ expect(Utils.isStartOfTag('<')).toBe(true)
63
+ })
64
+ })
65
+
66
+ describe('isEndOfTag()', () => {
67
+ it('return true if it is end of the tag', () => {
68
+ expect(Utils.isEndOfTag('')).toBe(false)
69
+ expect(Utils.isEndOfTag('<')).toBe(false)
70
+
71
+ expect(Utils.isEndOfTag('>')).toBe(true)
72
+ })
73
+ })
74
+
75
+ describe('isStartOfEntity()', () => {
76
+ it('return true if it is start of the enitity', () => {
77
+ expect(Utils.isStartOfEntity('')).toBe(false)
78
+ expect(Utils.isStartOfEntity('<')).toBe(false)
79
+
80
+ expect(Utils.isStartOfEntity('&')).toBe(true)
81
+ })
82
+ })
83
+
84
+ describe('isEndOfEntity()', () => {
85
+ it('return true if it is end of the enitity', () => {
86
+ expect(Utils.isEndOfEntity('')).toBe(false)
87
+ expect(Utils.isEndOfEntity('<')).toBe(false)
88
+
89
+ expect(Utils.isEndOfEntity(';')).toBe(true)
90
+ })
91
+ })
92
+
93
+ describe('isWhiteSpace()', () => {
94
+ it('return true if it is whitespace', () => {
95
+ expect(Utils.isWhiteSpace('')).toBe(false)
96
+ expect(Utils.isWhiteSpace('<')).toBe(false)
97
+
98
+ expect(Utils.isWhiteSpace(' ')).toBe(true)
99
+ expect(Utils.isWhiteSpace('&nbsp;')).toBe(true)
100
+ })
101
+ })
102
+
103
+ describe('stripAnyAttributes()', () => {
104
+ it('if passed tag - strip attirbutes otherwise return same string', () => {
105
+ expect(Utils.stripAnyAttributes('<tag atribute="value" />')).toBe('<tag/>')
106
+ expect(Utils.stripAnyAttributes('word')).toBe('word')
107
+ })
108
+ })
109
+
110
+ describe('isWord()', () => {
111
+ it('return true if it is number', () => {
112
+ expect(Utils.isWord('+')).toBe(false)
113
+ expect(Utils.isWord('[')).toBe(false)
114
+ expect(Utils.isWord(';')).toBe(false)
115
+
116
+ expect(Utils.isWord('a')).toBe(true)
117
+ expect(Utils.isWord('b')).toBe(true)
118
+ })
119
+ })
120
+ })
@@ -0,0 +1,27 @@
1
+ import { describe, expect, it } from 'vitest'
2
+
3
+ import WordSplitter from '../src/WordSplitter'
4
+
5
+ describe('WordSplitter', () => {
6
+ it.for([
7
+ ['<td>a b c</td><td>d e f</td>', ['<td>a b c</td>', '<td>d e f</td>'], /<td>(.*?)<\/td>/gi],
8
+ ['<td>a b</td><br/><td>c d</td>', ['<td>a b</td>', '<br/>', '<td>c d</td>'], /<td>(.*?)<\/td>/gi],
9
+ ] as const)('should map %s to %s with custom blocks %s', ([text, expected, regex]) => {
10
+ expect(WordSplitter.convertHtmlToListOfWords(text, [regex])).toEqual(expected)
11
+ })
12
+
13
+ it.for([
14
+ ['old words', ['old', ' ', 'words']],
15
+ ['new text', ['new', ' ', 'text']],
16
+ // ['12345', ['12345']],
17
+ ['12345<img src="123" />', ['12345', '<img src="123" />']],
18
+ ['123&entity', ['123', '&entity']],
19
+ ['123[] 123 ', ['123', '[', ']', ' ', '123', ' ']],
20
+ [' &entity ', [' ', '&entity', ' ']],
21
+ [' [&entity]', [' ', '[', '&entity', ']']],
22
+ ['[text]', ['[', 'text', ']']],
23
+ ['<tag>some text </tag>', ['<tag>', 'some', ' ', 'text', ' ', '</tag>']],
24
+ ] as const satisfies [string, string[]][])('should split inputs into words, %s -> %s', ([input, expected]) => {
25
+ expect(WordSplitter.convertHtmlToListOfWords(input, [])).toEqual(expected)
26
+ })
27
+ })
package/tsconfig.json ADDED
@@ -0,0 +1,16 @@
1
+ {
2
+ "extends": "@tsconfig/recommended/tsconfig.json",
3
+ "compilerOptions": {
4
+ "target": "esnext",
5
+ "module": "esnext",
6
+ "declaration": true,
7
+ "declarationDir": "dist",
8
+ "outDir": "dist",
9
+ "strict": true,
10
+ "moduleResolution": "bundler",
11
+ "esModuleInterop": true,
12
+ "resolveJsonModule": true,
13
+ "skipLibCheck": true,
14
+ "forceConsistentCasingInFileNames": true
15
+ }
16
+ }
package/tsup.config.ts ADDED
@@ -0,0 +1,14 @@
1
+ import * as path from 'node:path'
2
+ import { defineConfig } from 'tsup'
3
+
4
+ export default defineConfig({
5
+ name: '@createiq/htmldiff',
6
+ entry: ['src/HtmlDiff.ts'],
7
+ format: ['esm', 'cjs'],
8
+ target: 'esnext',
9
+ outDir: 'dist',
10
+ clean: true,
11
+ dts: true,
12
+ sourcemap: true,
13
+ tsconfig: path.resolve(__dirname, 'tsconfig.json'),
14
+ })
@@ -0,0 +1,24 @@
1
+ import path from 'node:path'
2
+ import { defineConfig } from 'vitest/config'
3
+
4
+ export default defineConfig({
5
+ test: {
6
+ reporters: [
7
+ 'default',
8
+ [
9
+ 'junit',
10
+ { outputFile: path.resolve(process.env.CI_PROJECT_DIR || '.', 'junit.xml'), includeConsoleOutput: true },
11
+ ],
12
+ ],
13
+ coverage: {
14
+ provider: 'istanbul',
15
+ reporter: ['html', 'text', 'cobertura', 'lcov'],
16
+ reportsDirectory: path.resolve(process.env.CI_PROJECT_DIR || '.', 'coverage'),
17
+ include: ['src/**'],
18
+ },
19
+ clearMocks: true,
20
+ expect: {
21
+ requireAssertions: true,
22
+ },
23
+ },
24
+ })