@matrixreq/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.
- package/LICENSE +22 -0
- package/README.md +147 -0
- package/htmldiff-cli.js +125 -0
- package/js/htmldiff.d.ts +17 -0
- package/js/htmldiff.js +1005 -0
- package/package.json +60 -0
- package/sample/after.html +18 -0
- package/sample/after.png +0 -0
- package/sample/before.html +18 -0
- package/sample/before.png +0 -0
- package/test/calculate_operations.spec.js +278 -0
- package/test/config.js +2 -0
- package/test/diff.spec.js +210 -0
- package/test/edge_cases.spec.js +31 -0
- package/test/find_matching_blocks.spec.js +168 -0
- package/test/from_port_source.spec.js +30 -0
- package/test/html_to_tokens.spec.js +128 -0
- package/test/mocha.opts +3 -0
- package/test/module.spec.js +11 -0
- package/test/pain_games.spec.js +18 -0
- package/test/render_operations.spec.js +177 -0
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
describe('findMatchingBlocks', function(){
|
|
2
|
+
var diff, cut, res, createToken, tokenize, createSegment, htmlToTokens;
|
|
3
|
+
|
|
4
|
+
beforeEach(function(){
|
|
5
|
+
diff = require('../js/htmldiff');
|
|
6
|
+
createSegment = diff.findMatchingBlocks.createSegment;
|
|
7
|
+
htmlToTokens = diff.htmlToTokens;
|
|
8
|
+
createToken = diff.findMatchingBlocks.createToken;
|
|
9
|
+
tokenize = function(tokens){
|
|
10
|
+
return tokens.map(function(token){
|
|
11
|
+
return createToken(token);
|
|
12
|
+
});
|
|
13
|
+
};
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
describe('createMap', function(){
|
|
17
|
+
beforeEach(function(){
|
|
18
|
+
cut = diff.findMatchingBlocks.createMap;
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it('should be a function', function(){
|
|
22
|
+
expect(cut).is.a('function');
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
describe('When the items exist in the search target', function(){
|
|
26
|
+
beforeEach(function(){
|
|
27
|
+
res = cut(tokenize(['a', 'apple', 'has', 'a', 'worm']));
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('should find "a" twice', function(){
|
|
31
|
+
expect(res['a'].length).to.equal(2);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('should find "a" at 0', function(){
|
|
35
|
+
expect(res['a'][0]).to.equal(0);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('should find "a" at 3', function(){
|
|
39
|
+
expect(res['a'][1]).to.equal(3);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('should find "has" at 2', function(){
|
|
43
|
+
expect(res['has'][0]).to.equal(2);
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
describe('findBestMatch', function(){
|
|
49
|
+
var invoke;
|
|
50
|
+
|
|
51
|
+
beforeEach(function(){
|
|
52
|
+
cut = diff.findMatchingBlocks.findBestMatch;
|
|
53
|
+
invoke = function(before, after){
|
|
54
|
+
var segment = createSegment(before, after, 0, 0);
|
|
55
|
+
|
|
56
|
+
res = cut(segment);
|
|
57
|
+
};
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
describe('When there is a match', function(){
|
|
61
|
+
beforeEach(function(){
|
|
62
|
+
var before = tokenize(['a', 'dog', 'bites']);
|
|
63
|
+
var after = tokenize(['a', 'dog', 'bites', 'a', 'man']);
|
|
64
|
+
invoke(before, after);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('should match the match', function(){
|
|
68
|
+
expect(res).to.exist;
|
|
69
|
+
expect(res.startInBefore).equal(0);
|
|
70
|
+
expect(res.startInAfter).equal(0);
|
|
71
|
+
expect(res.length).equal(3);
|
|
72
|
+
expect(res.endInBefore).equal(2);
|
|
73
|
+
expect(res.endInAfter).equal(2);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
describe('When the match is surrounded', function(){
|
|
77
|
+
beforeEach(function(){
|
|
78
|
+
before = tokenize(['dog', 'bites']);
|
|
79
|
+
after = tokenize(['the', 'dog', 'bites', 'a', 'man']);
|
|
80
|
+
invoke(before, after);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it('should match with appropriate indexing', function(){
|
|
84
|
+
expect(res).to.exist;
|
|
85
|
+
expect(res.startInBefore).to.equal(0);
|
|
86
|
+
expect(res.startInAfter).to.equal(1);
|
|
87
|
+
expect(res.endInBefore).to.equal(1);
|
|
88
|
+
expect(res.endInAfter).to.equal(2);
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
describe('When there is no match', function(){
|
|
94
|
+
beforeEach(function(){
|
|
95
|
+
var before = tokenize(['the', 'rat', 'sqeaks']);
|
|
96
|
+
var after = tokenize(['a', 'dog', 'bites', 'a', 'man']);
|
|
97
|
+
invoke(before, after);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it('should return nothing', function(){
|
|
101
|
+
expect(res).to.not.exist;
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
describe('findMatchingBlocks', function(){
|
|
107
|
+
var segment;
|
|
108
|
+
|
|
109
|
+
beforeEach(function(){
|
|
110
|
+
cut = diff.findMatchingBlocks;
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it('should be a function', function(){
|
|
114
|
+
expect(cut).is.a('function');
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
describe('When called with a single match', function(){
|
|
118
|
+
beforeEach(function(){
|
|
119
|
+
var before = htmlToTokens('a dog bites');
|
|
120
|
+
var after = htmlToTokens('when a dog bites it hurts');
|
|
121
|
+
segment = createSegment(before, after, 0, 0);
|
|
122
|
+
|
|
123
|
+
res = cut(segment);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it('should return a match', function(){
|
|
127
|
+
expect(res.length).to.equal(1);
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
describe('When called with multiple matches', function(){
|
|
132
|
+
beforeEach(function(){
|
|
133
|
+
var before = htmlToTokens('the dog bit a man');
|
|
134
|
+
var after = htmlToTokens('the large brown dog bit a tall man');
|
|
135
|
+
segment = createSegment(before, after, 0, 0);
|
|
136
|
+
res = cut(segment);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it('should return 3 matches', function(){
|
|
140
|
+
expect(res.length).to.equal(3);
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
it('should match "the"', function(){
|
|
144
|
+
expect(res[0].startInBefore).eql(0);
|
|
145
|
+
expect(res[0].startInAfter).eql(0);
|
|
146
|
+
expect(res[0].endInBefore).eql(0);
|
|
147
|
+
expect(res[0].endInAfter).eql(0);
|
|
148
|
+
expect(res[0].length).eql(1);
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it('should match "dog bit a"', function(){
|
|
152
|
+
expect(res[1].startInBefore).eql(1);
|
|
153
|
+
expect(res[1].startInAfter).eql(5);
|
|
154
|
+
expect(res[1].endInBefore).eql(7);
|
|
155
|
+
expect(res[1].endInAfter).eql(11);
|
|
156
|
+
expect(res[1].length).eql(7);
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
it('should match "man"', function(){
|
|
160
|
+
expect(res[2].startInBefore).eql(8);
|
|
161
|
+
expect(res[2].startInAfter).eql(14);
|
|
162
|
+
expect(res[2].endInBefore).eql(8);
|
|
163
|
+
expect(res[2].endInAfter).eql(14);
|
|
164
|
+
expect(res[2].length).eql(1);
|
|
165
|
+
});
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
});
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
describe('The specs from the ruby source project', function(){
|
|
2
|
+
var cut;
|
|
3
|
+
|
|
4
|
+
beforeEach(function(){
|
|
5
|
+
cut = require('../js/htmldiff');
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
it('should diff text', function(){
|
|
9
|
+
var diff = cut('a word is here', 'a nother word is there');
|
|
10
|
+
expect(diff).equal('a<ins data-operation-index="1"> nother</ins> word is ' +
|
|
11
|
+
'<del data-operation-index="3">here</del><ins data-operation-index="3">' +
|
|
12
|
+
'there</ins>');
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it('should insert a letter and a space', function(){
|
|
16
|
+
var diff = cut('a c', 'a b c');
|
|
17
|
+
expect(diff).equal('a <ins data-operation-index="1">b </ins>c');
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('should remove a letter and a space', function(){
|
|
21
|
+
var diff = cut('a b c', 'a c');
|
|
22
|
+
diff.should == 'a <del data-operation-index="1">b </del>c';
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('should change a letter', function(){
|
|
26
|
+
var diff = cut('a b c', 'a d c');
|
|
27
|
+
expect(diff).equal('a <del data-operation-index="1">b</del>' +
|
|
28
|
+
'<ins data-operation-index="1">d</ins> c');
|
|
29
|
+
});
|
|
30
|
+
});
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
describe('htmlToTokens', function(){
|
|
2
|
+
var cut, res, diff, createToken, tokenize;
|
|
3
|
+
|
|
4
|
+
beforeEach(function(){
|
|
5
|
+
diff = require('../js/htmldiff')
|
|
6
|
+
cut = diff.htmlToTokens;
|
|
7
|
+
|
|
8
|
+
createToken = diff.findMatchingBlocks.createToken;
|
|
9
|
+
tokenize = function(tokens){
|
|
10
|
+
return tokens.map(function(token){
|
|
11
|
+
return createToken(token);
|
|
12
|
+
});
|
|
13
|
+
};
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it('should be a function', function(){
|
|
17
|
+
expect(cut).is.a('function');
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
describe('when called with text', function(){
|
|
21
|
+
beforeEach(function(){
|
|
22
|
+
res = cut('this is a test');
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('should return 4', function(){
|
|
26
|
+
expect(res.length).to.equal(7);
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
describe('when called with html', function(){
|
|
31
|
+
beforeEach(function(){
|
|
32
|
+
res = cut('<p>this is a <strong>test</strong></p>');
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('should return 11', function(){
|
|
36
|
+
expect(res.length).to.equal(11);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('should remove any html comments', function(){
|
|
40
|
+
res = cut('<p> this is <!-- a comment! --> </p>');
|
|
41
|
+
expect(res.length).to.equal(8);
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('should identify contiguous whitespace as a single token', function(){
|
|
46
|
+
expect(cut('a b')).to.eql(tokenize(['a', ' ', 'b']));
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('should identify a single space as a single token', function(){
|
|
50
|
+
expect(cut(' a b ')).to.eql(tokenize([' ', 'a', ' ', 'b', ' ']));
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('should identify self closing tags as tokens', function(){
|
|
54
|
+
expect(cut('<p>hello</br>goodbye</p>')).eql(
|
|
55
|
+
tokenize(['<p>', 'hello', '</br>', 'goodbye', '</p>']));
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
describe('when encountering atomic tags', function(){
|
|
59
|
+
it('should identify an image tag as a single token', function(){
|
|
60
|
+
expect(cut('<p><img src="1.jpg"><img src="2.jpg"></p>')).eql(
|
|
61
|
+
tokenize(['<p>', '<img src="1.jpg">', '<img src="2.jpg">', '</p>']));
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('should identify an iframe tag as a single token', function(){
|
|
65
|
+
expect(cut('<p><iframe src="sample.html"></iframe></p>')).eql(
|
|
66
|
+
tokenize(['<p>', '<iframe src="sample.html"></iframe>', '</p>']));
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('should identify an object tag as a single token', function(){
|
|
70
|
+
var cutResult = cut('<p><object><param name="1" /><param name="2" /></object></p>');
|
|
71
|
+
var tokenizeResult = tokenize(
|
|
72
|
+
['<p>', '<object><param name="1" /><param name="2" /></object>','</p>']);
|
|
73
|
+
expect(cutResult).eql(tokenizeResult);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('should identify a math tag as a single token', function(){
|
|
77
|
+
var cutResult = cut('<p><math xmlns="http://www.w3.org/1998/Math/MathML">' +
|
|
78
|
+
'<mi>π<!-- π --></mi>' +
|
|
79
|
+
'<mo>⁢<!-- ⁢ --></mo>' +
|
|
80
|
+
'<msup><mi>r</mi><mn>2</mn></msup></math></p>');
|
|
81
|
+
var tokenizeResult = tokenize([
|
|
82
|
+
'<p>',
|
|
83
|
+
'<math xmlns="http://www.w3.org/1998/Math/MathML">' +
|
|
84
|
+
'<mi>π<!-- π --></mi>' +
|
|
85
|
+
'<mo>⁢<!-- ⁢ --></mo>' +
|
|
86
|
+
'<msup><mi>r</mi><mn>2</mn></msup></math>',
|
|
87
|
+
'</p>'
|
|
88
|
+
]);
|
|
89
|
+
expect(cutResult).eql(tokenizeResult);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it('should identify an svg tag as a single token', function(){
|
|
93
|
+
var cutResult = cut('<p><svg width="100" height="100">' +
|
|
94
|
+
'<circle cx="50" cy="50" r="40" stroke="green" stroke-width="4" ' +
|
|
95
|
+
'fill="yellow" /></svg></p>');
|
|
96
|
+
var tokenizeResult = tokenize([
|
|
97
|
+
'<p>',
|
|
98
|
+
'<svg width="100" height="100">' +
|
|
99
|
+
'<circle cx="50" cy="50" r="40" stroke="green" stroke-width="4" fill="yellow" />' +
|
|
100
|
+
'</svg>',
|
|
101
|
+
'</p>'
|
|
102
|
+
]);
|
|
103
|
+
expect(cutResult).eql(tokenizeResult);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it('should identify a script tag as a single token', function(){
|
|
107
|
+
expect(cut('<p><script>console.log("hi");</script></p>')).eql(
|
|
108
|
+
tokenize(['<p>', '<script>console.log("hi");</script>', '</p>']));
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
it('should identify tags with data-htmldiff-id attribute as single token', () => {
|
|
114
|
+
expect(
|
|
115
|
+
cut('<div><customtag data-htmldiff-id="1">hello</br>goodbye</customtag>' +
|
|
116
|
+
'<custom-tag data-htmldiff-id="2">some stuff</custom-tag>' +
|
|
117
|
+
'</div>')
|
|
118
|
+
).eql(tokenize(
|
|
119
|
+
[
|
|
120
|
+
'<div>',
|
|
121
|
+
'<customtag data-htmldiff-id="1">hello</br>goodbye</customtag>',
|
|
122
|
+
'<custom-tag data-htmldiff-id="2">some stuff</custom-tag>',
|
|
123
|
+
'</div>'
|
|
124
|
+
]
|
|
125
|
+
));
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
});
|
package/test/mocha.opts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
describe('Pain Games', function(){
|
|
2
|
+
var cut, res;
|
|
3
|
+
|
|
4
|
+
beforeEach(function(){
|
|
5
|
+
cut = require('../js/htmldiff');
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
describe('When an entire sentence is replaced', function(){
|
|
9
|
+
beforeEach(function(){
|
|
10
|
+
res = cut('this is what I had', 'and now we have a new one');
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it('should replace the whole chunk', function(){
|
|
14
|
+
expect(res).to.equal('<del data-operation-index="0">this is what I had</del>' +
|
|
15
|
+
'<ins data-operation-index="0">and now we have a new one</ins>');
|
|
16
|
+
});
|
|
17
|
+
});
|
|
18
|
+
});
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
describe('renderOperations', function(){
|
|
2
|
+
var cut, res, createToken, tokenize;
|
|
3
|
+
|
|
4
|
+
beforeEach(function(){
|
|
5
|
+
var diff = require('../js/htmldiff');
|
|
6
|
+
createToken = diff.findMatchingBlocks.createToken;
|
|
7
|
+
|
|
8
|
+
tokenize = function(tokens){
|
|
9
|
+
return tokens.map(function(token){
|
|
10
|
+
return createToken(token);
|
|
11
|
+
});
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
cut = function(before, after){
|
|
15
|
+
var ops = diff.calculateOperations(before, after);
|
|
16
|
+
return diff.renderOperations(before, after, ops);
|
|
17
|
+
};
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('should be a function', function(){
|
|
21
|
+
expect(cut).is.a('function');
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
describe('equal', function(){
|
|
25
|
+
beforeEach(function(){
|
|
26
|
+
var before = tokenize(['this', ' ', 'is', ' ', 'a', ' ', 'test']);
|
|
27
|
+
res = cut(before, before);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('should output the text', function(){
|
|
31
|
+
expect(res).equal('this is a test');
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
describe('insert', function(){
|
|
36
|
+
beforeEach(function(){
|
|
37
|
+
before = tokenize(['this', ' ', 'is']);
|
|
38
|
+
after = tokenize(['this', ' ', 'is', ' ', 'a', ' ', 'test']);
|
|
39
|
+
res = cut(before, after);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('should wrap in an <ins>', function(){
|
|
43
|
+
expect(res).equal('this is<ins data-operation-index="1"> a test</ins>');
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
describe('delete', function(){
|
|
48
|
+
beforeEach(function(){
|
|
49
|
+
var before = tokenize(['this', ' ', 'is', ' ', 'a', ' ', 'test',
|
|
50
|
+
' ', 'of', ' ', 'stuff']);
|
|
51
|
+
var after = tokenize(['this', ' ', 'is', ' ', 'a', ' ', 'test']);
|
|
52
|
+
res = cut(before, after);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('should wrap in a <del>', function(){
|
|
56
|
+
expect(res).to.equal('this is a test<del data-operation-index="1"> of stuff</del>');
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
describe('replace', function(){
|
|
61
|
+
beforeEach(function(){
|
|
62
|
+
var before = tokenize(['this', ' ', 'is', ' ', 'a', ' ', 'break']);
|
|
63
|
+
var after = tokenize(['this', ' ', 'is', ' ', 'a', ' ', 'test']);
|
|
64
|
+
res = cut(before, after);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('should wrap in both <ins> and <del>', function(){
|
|
68
|
+
expect(res).to.equal('this is a <del data-operation-index="1">break</del>' +
|
|
69
|
+
'<ins data-operation-index="1">test</ins>');
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
describe('Dealing with tags', function(){
|
|
74
|
+
var before, after;
|
|
75
|
+
|
|
76
|
+
beforeEach(function(){
|
|
77
|
+
before = tokenize(['<p>', 'a', '</p>']);
|
|
78
|
+
after = tokenize(['<p>', 'a', ' ', 'b', '</p>', '<p>', 'c', '</p>']);
|
|
79
|
+
res = cut(before, after);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it('should identify contained inserted tags', function(){
|
|
83
|
+
expect(res).to.equal('<p>a<ins data-operation-index="1"> b</ins></p>' +
|
|
84
|
+
'<p data-diff-node="ins" data-operation-index="3">' +
|
|
85
|
+
'<ins data-operation-index="3">c</ins></p>');
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it('should identify contained deleted tags', function(){
|
|
89
|
+
res = cut(after, before);
|
|
90
|
+
|
|
91
|
+
expect(res).to.equal('<p>a<del data-operation-index="1"> b</del></p>' +
|
|
92
|
+
'<p data-diff-node="del" data-operation-index="3">' +
|
|
93
|
+
'<del data-operation-index="3">c</del></p>');
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it('should not identify partial tags', function(){
|
|
97
|
+
var before = tokenize(['test', '</b>', 'non-bold']);
|
|
98
|
+
var after = tokenize(['test!', '</b>', 'non-bold', '<b>', 'bold']);
|
|
99
|
+
res = cut(before, after);
|
|
100
|
+
|
|
101
|
+
expect(res).to.equal('<del data-operation-index="0">test</del>' +
|
|
102
|
+
'<ins data-operation-index="0">test!</ins></b>non-bold<b>' +
|
|
103
|
+
'<ins data-operation-index="2">bold</ins>');
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
describe('When there is a change at the beginning, in a <p>', function(){
|
|
107
|
+
beforeEach(function(){
|
|
108
|
+
var before = tokenize(['<p>', 'this', ' ', 'is', ' ', 'awesome', '</p>']);
|
|
109
|
+
var after = tokenize(['<p>', 'I', ' ', 'is', ' ', 'awesome', '</p>']);
|
|
110
|
+
res = cut(before, after);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it('should keep the change inside the <p>', function(){
|
|
114
|
+
expect(res).to.equal('<p><del data-operation-index="1">this</del>' +
|
|
115
|
+
'<ins data-operation-index="1">I</ins> is awesome</p>');
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
describe('empty tokens', function(){
|
|
121
|
+
it('should not be wrapped', function(){
|
|
122
|
+
var before = tokenize(['text']);
|
|
123
|
+
var after = tokenize(['text', ' ']);
|
|
124
|
+
|
|
125
|
+
res = cut(before, after);
|
|
126
|
+
|
|
127
|
+
expect(res).to.equal('text');
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
describe('tags with attributes', function(){
|
|
132
|
+
it('should treat attribute changes as equal and output the after tag', function(){
|
|
133
|
+
var before = tokenize(['<p>', 'this', ' ', 'is', ' ', 'awesome', '</p>']);
|
|
134
|
+
var after = tokenize(['<p style="margin: 2px;" class="after">', 'this', ' ', 'is', ' ',
|
|
135
|
+
'awesome', '</p>']);
|
|
136
|
+
|
|
137
|
+
res = cut(before, after);
|
|
138
|
+
|
|
139
|
+
expect(res).to.equal('<p style="margin: 2px;" class="after">this is awesome</p>');
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
it('should show changes within tags with different attributes', function(){
|
|
143
|
+
var before = tokenize(['<p>', 'this', ' ', 'is', ' ', 'awesome', '</p>']);
|
|
144
|
+
var after = tokenize(['<p style="margin: 2px;" class="after">', 'that', ' ', 'is', ' ',
|
|
145
|
+
'awesome', '</p>']);
|
|
146
|
+
|
|
147
|
+
res = cut(before, after);
|
|
148
|
+
|
|
149
|
+
expect(res).to.equal('<p style="margin: 2px;" class="after">' +
|
|
150
|
+
'<del data-operation-index="1">this</del><ins data-operation-index="1">' +
|
|
151
|
+
'that</ins> is awesome</p>');
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
describe('wrappable tags', function(){
|
|
156
|
+
it('should wrap void tags', function(){
|
|
157
|
+
var before = tokenize(['old', ' ', 'text']);
|
|
158
|
+
var after = tokenize(['new', '<br/>', ' ', 'text']);
|
|
159
|
+
|
|
160
|
+
res = cut(before, after);
|
|
161
|
+
|
|
162
|
+
expect(res).to.equal('<del data-operation-index="0">old</del>' +
|
|
163
|
+
'<ins data-operation-index="0">new<br/></ins> text');
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
it('should wrap atomic tags', function(){
|
|
167
|
+
var before = tokenize(['old', '<iframe src="source.html"></iframe>', ' ', 'text']);
|
|
168
|
+
var after = tokenize(['new', ' ', 'text']);
|
|
169
|
+
|
|
170
|
+
res = cut(before, after);
|
|
171
|
+
|
|
172
|
+
expect(res).to.equal(
|
|
173
|
+
'<del data-operation-index="0">old<iframe src="source.html"></iframe></del>' +
|
|
174
|
+
'<ins data-operation-index="0">new</ins> text');
|
|
175
|
+
});
|
|
176
|
+
});
|
|
177
|
+
});
|