@plannotator/web-highlighter 0.8.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/.cursor/environment.json +6 -0
- package/.eslintrc.js +250 -0
- package/.prettierrc +9 -0
- package/.travis.yml +17 -0
- package/CHANGELOG.md +220 -0
- package/LICENSE +21 -0
- package/README.md +371 -0
- package/README.zh_CN.md +367 -0
- package/config/base.config.js +25 -0
- package/config/base.example.config.js +38 -0
- package/config/paths.js +22 -0
- package/config/server.config.js +17 -0
- package/config/webpack.config.dev.js +18 -0
- package/config/webpack.config.example.js +20 -0
- package/config/webpack.config.prod.js +28 -0
- package/dist/data/cache.d.ts +13 -0
- package/dist/index.d.ts +58 -0
- package/dist/model/range/dom.d.ts +6 -0
- package/dist/model/range/index.d.ts +20 -0
- package/dist/model/range/selection.d.ts +14 -0
- package/dist/model/source/dom.d.ts +23 -0
- package/dist/model/source/index.d.ts +18 -0
- package/dist/painter/dom.d.ts +22 -0
- package/dist/painter/index.d.ts +19 -0
- package/dist/painter/style.d.ts +1 -0
- package/dist/types/index.d.ts +102 -0
- package/dist/util/camel.d.ts +5 -0
- package/dist/util/const.d.ts +41 -0
- package/dist/util/deferred.d.ts +9 -0
- package/dist/util/dom.d.ts +32 -0
- package/dist/util/event.emitter.d.ts +13 -0
- package/dist/util/hook.d.ts +15 -0
- package/dist/util/interaction.d.ts +6 -0
- package/dist/util/is.mobile.d.ts +5 -0
- package/dist/util/tool.d.ts +4 -0
- package/dist/util/uuid.d.ts +4 -0
- package/dist/web-highlighter.min.js +3 -0
- package/dist/web-highlighter.min.js.map +1 -0
- package/docs/ADVANCE.md +113 -0
- package/docs/ADVANCE.zh_CN.md +111 -0
- package/docs/img/create-flow.jpg +0 -0
- package/docs/img/create-flow.zh_CN.jpg +0 -0
- package/docs/img/logo.png +0 -0
- package/docs/img/remove-flow.jpg +0 -0
- package/docs/img/remove-flow.zh_CN.jpg +0 -0
- package/docs/img/sample.gif +0 -0
- package/example/index.css +2 -0
- package/example/index.js +214 -0
- package/example/local.store.js +72 -0
- package/example/my.css +119 -0
- package/example/tpl.html +59 -0
- package/package.json +103 -0
- package/script/build.js +17 -0
- package/script/convet-md.js +25 -0
- package/script/dev.js +22 -0
- package/src/data/cache.ts +57 -0
- package/src/index.ts +285 -0
- package/src/model/range/dom.ts +94 -0
- package/src/model/range/index.ts +88 -0
- package/src/model/range/selection.ts +55 -0
- package/src/model/source/dom.ts +66 -0
- package/src/model/source/index.ts +54 -0
- package/src/painter/dom.ts +345 -0
- package/src/painter/index.ts +199 -0
- package/src/painter/style.ts +21 -0
- package/src/types/index.ts +118 -0
- package/src/util/camel.ts +6 -0
- package/src/util/const.ts +54 -0
- package/src/util/deferred.ts +37 -0
- package/src/util/dom.ts +155 -0
- package/src/util/event.emitter.ts +45 -0
- package/src/util/hook.ts +52 -0
- package/src/util/interaction.ts +20 -0
- package/src/util/is.mobile.ts +7 -0
- package/src/util/tool.ts +14 -0
- package/src/util/uuid.ts +10 -0
- package/test/api.spec.ts +555 -0
- package/test/event.spec.ts +284 -0
- package/test/fixtures/broken.json +32 -0
- package/test/fixtures/index.html +11 -0
- package/test/fixtures/source.json +47 -0
- package/test/hook.spec.ts +244 -0
- package/test/integrate.spec.ts +48 -0
- package/test/mobile.spec.ts +87 -0
- package/test/option.spec.ts +212 -0
- package/test/util.spec.ts +244 -0
- package/test-newlines.html +226 -0
- package/tsconfig.json +23 -0
package/test/api.spec.ts
ADDED
|
@@ -0,0 +1,555 @@
|
|
|
1
|
+
import { expect } from 'chai';
|
|
2
|
+
import { readFileSync } from 'fs';
|
|
3
|
+
import { resolve } from 'path';
|
|
4
|
+
import jsdomGlobal from 'jsdom-global';
|
|
5
|
+
import Highlighter from '../src/index';
|
|
6
|
+
import { getDefaultOptions, DATASET_SPLIT_TYPE, DATASET_IDENTIFIER } from '../src/util/const';
|
|
7
|
+
import { SplitType } from '../src/types/index';
|
|
8
|
+
import sources from './fixtures/source.json';
|
|
9
|
+
import brokenSources from './fixtures/broken.json';
|
|
10
|
+
import getInteraction from '../src/util/interaction';
|
|
11
|
+
|
|
12
|
+
describe('Highlighter API', function () {
|
|
13
|
+
this.timeout(50000);
|
|
14
|
+
|
|
15
|
+
let highlighter: Highlighter;
|
|
16
|
+
let cleanup;
|
|
17
|
+
let wrapSelector: string;
|
|
18
|
+
|
|
19
|
+
beforeEach(() => {
|
|
20
|
+
const html = readFileSync(resolve(__dirname, 'fixtures', 'index.html'), 'utf-8');
|
|
21
|
+
cleanup = jsdomGlobal();
|
|
22
|
+
document.body.innerHTML = html;
|
|
23
|
+
highlighter = new Highlighter();
|
|
24
|
+
wrapSelector = `${getDefaultOptions().wrapTag}[data-${DATASET_IDENTIFIER}]`;
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
describe('#fromRange', () => {
|
|
28
|
+
it('should wrap correctly in p', () => {
|
|
29
|
+
const range = document.createRange();
|
|
30
|
+
const $p = document.querySelectorAll('p')[0];
|
|
31
|
+
range.setStart($p.childNodes[0], 0);
|
|
32
|
+
range.setEnd($p.childNodes[0], 17);
|
|
33
|
+
const content = range.toString();
|
|
34
|
+
highlighter.fromRange(range);
|
|
35
|
+
const wrapper = $p.querySelector(wrapSelector);
|
|
36
|
+
|
|
37
|
+
expect(wrapper.textContent).to.be.equal(content, 'wrapped text should be the same as the range')
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('should wrap nothing when range is empty', () => {
|
|
41
|
+
const range = document.createRange();
|
|
42
|
+
const $p = document.querySelectorAll('p')[0];
|
|
43
|
+
range.setStart($p.childNodes[0], 0);
|
|
44
|
+
range.setEnd($p.childNodes[0], 0);
|
|
45
|
+
highlighter.fromRange(range);
|
|
46
|
+
|
|
47
|
+
expect($p.querySelector(wrapSelector).textContent.length).to.be.equal(0);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('should wrap correctly when cross multi dom', () => {
|
|
51
|
+
const range = document.createRange();
|
|
52
|
+
const $p1 = document.querySelectorAll('p')[0];
|
|
53
|
+
const $p2 = document.querySelectorAll('p')[1];
|
|
54
|
+
range.setStart($p1.childNodes[0], 54);
|
|
55
|
+
range.setEnd($p2.childNodes[0], 11);
|
|
56
|
+
highlighter.fromRange(range);
|
|
57
|
+
|
|
58
|
+
const segContent1 = 'save the highlighted areas just like what you do in PDF.';
|
|
59
|
+
const segContent2 = 'If you have';
|
|
60
|
+
|
|
61
|
+
expect($p1.querySelector(wrapSelector).textContent).to.be.equal(segContent1, 'first segment correct');
|
|
62
|
+
expect($p2.querySelector(wrapSelector).textContent).to.be.equal(segContent2, 'second segment correct');
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('should split correctly when the new selection is inside an exist selection', () => {
|
|
66
|
+
const range = document.createRange();
|
|
67
|
+
const $p = document.querySelectorAll('p')[3];
|
|
68
|
+
const $highlight = $p.querySelector('span');
|
|
69
|
+
range.setStart($highlight.childNodes[0], 12);
|
|
70
|
+
range.setEnd($highlight.childNodes[0], 21);
|
|
71
|
+
highlighter.fromRange(range);
|
|
72
|
+
|
|
73
|
+
const wraps = $p.querySelectorAll(wrapSelector);
|
|
74
|
+
const attr = `data-${DATASET_SPLIT_TYPE}`;
|
|
75
|
+
expect(wraps.length).to.be.equal(3, 'split into three pieces');
|
|
76
|
+
expect(wraps[1].textContent).to.be.equal('developer', 'highlighted the correct content');
|
|
77
|
+
expect(wraps[0].getAttribute(attr)).to.be.equal(SplitType.both);
|
|
78
|
+
expect(wraps[1].getAttribute(attr)).to.be.equal(SplitType.both);
|
|
79
|
+
expect(wraps[2].getAttribute(attr)).to.be.equal(SplitType.both);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it('should split correctly when the new selection is across an exist selection', () => {
|
|
83
|
+
const range = document.createRange();
|
|
84
|
+
const $p = document.querySelectorAll('p')[3];
|
|
85
|
+
range.setStart($p.querySelector('span').childNodes[0], 64);
|
|
86
|
+
range.setEnd($p.querySelector('span').nextSibling, 9);
|
|
87
|
+
highlighter.fromRange(range);
|
|
88
|
+
|
|
89
|
+
const wraps = $p.querySelectorAll(wrapSelector);
|
|
90
|
+
const attr = `data-${DATASET_SPLIT_TYPE}`;
|
|
91
|
+
expect(wraps.length).to.be.equal(3, 'split into three pieces');
|
|
92
|
+
expect(wraps[1].textContent).to.be.equal('attract more visits.', 'highlighted the correct content');
|
|
93
|
+
expect(wraps[2].textContent).to.be.equal('If you\'re', 'highlighted the correct content');
|
|
94
|
+
expect(wraps[0].getAttribute(attr)).to.be.equal(SplitType.both);
|
|
95
|
+
expect(wraps[1].getAttribute(attr)).to.be.equal(SplitType.head);
|
|
96
|
+
expect(wraps[2].getAttribute(attr)).to.be.equal(SplitType.tail);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it('should not split when the new selection matches an exist wrapper', () => {
|
|
100
|
+
const startOffset = 0;
|
|
101
|
+
const endOffset = 17;
|
|
102
|
+
|
|
103
|
+
let range = document.createRange();
|
|
104
|
+
const $p = document.querySelectorAll('p')[0];
|
|
105
|
+
range.setStart($p.childNodes[0], startOffset);
|
|
106
|
+
range.setEnd($p.childNodes[0], endOffset);
|
|
107
|
+
highlighter.fromRange(range);
|
|
108
|
+
|
|
109
|
+
const $pre = [...$p.querySelectorAll(wrapSelector)];
|
|
110
|
+
|
|
111
|
+
// select a exist wrapper
|
|
112
|
+
range = document.createRange();
|
|
113
|
+
const $wrapper = $p.querySelector(wrapSelector);
|
|
114
|
+
range.setStart($wrapper.childNodes[0], startOffset);
|
|
115
|
+
range.setEnd($wrapper.childNodes[0], endOffset);
|
|
116
|
+
highlighter.fromRange(range);
|
|
117
|
+
|
|
118
|
+
const $after = [...$p.querySelectorAll(wrapSelector)];
|
|
119
|
+
|
|
120
|
+
expect($after).lengthOf($pre.length, 'its length should be the same as before');
|
|
121
|
+
expect($after.every($n => $pre.indexOf($n) > -1), 'wrappers should be the same').to.be.true;
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it('should work correctly when a container is not a Text/Comment/CDATASection', () => {
|
|
125
|
+
let range = document.createRange();
|
|
126
|
+
const $p = document.querySelectorAll('p')[5];
|
|
127
|
+
range.setStart($p, 1);
|
|
128
|
+
range.setEnd($p.childNodes[2], 8);
|
|
129
|
+
highlighter.fromRange(range);
|
|
130
|
+
|
|
131
|
+
const $pre = [...$p.querySelectorAll(wrapSelector)];
|
|
132
|
+
|
|
133
|
+
const text = $pre.reduce((t, $n) => t + $n.textContent, '');
|
|
134
|
+
expect(text).to.be.equal('have fun');
|
|
135
|
+
});
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
describe('#fromStore', () => {
|
|
139
|
+
it('should re-create(highlighting) correctly', () => {
|
|
140
|
+
const s = sources[0];
|
|
141
|
+
const $p = document.querySelectorAll('p')[0];
|
|
142
|
+
let $wrappers = $p.querySelectorAll(wrapSelector);
|
|
143
|
+
expect($wrappers.length, 'has no wrapper before highlighting').to.be.equal(0);
|
|
144
|
+
|
|
145
|
+
highlighter.fromStore(s.startMeta, s.endMeta, s.text, s.id);
|
|
146
|
+
$wrappers = $p.querySelectorAll(wrapSelector);
|
|
147
|
+
expect($wrappers.length, 'only has one wrapper').to.be.equal(1);
|
|
148
|
+
expect($wrappers[0].textContent, 'highlight correct text').to.be.equal(s.text);
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it('should highlight correctly when structure is complex', () => {
|
|
152
|
+
sources.forEach(s => highlighter.fromStore(s.startMeta, s.endMeta, s.text, s.id));
|
|
153
|
+
const $p = document.querySelectorAll('p')[0];
|
|
154
|
+
const $w = $p.querySelectorAll(wrapSelector);
|
|
155
|
+
expect($w.length, 'has three wrapper').to.be.equal(5);
|
|
156
|
+
expect($w[0].textContent + $w[1].textContent + $w[2].textContent, 'correct text 1').to.be.equal(sources[0].text);
|
|
157
|
+
expect($w[2].textContent + $w[3].textContent + $w[4].textContent, 'correct text 2').to.be.equal(sources[1].text);
|
|
158
|
+
expect($w[1].textContent + $w[2].textContent + $w[3].textContent, 'correct text 3').to.be.equal(sources[2].text);
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
it('should highlight correctly by different re-creating sequence', () => {
|
|
162
|
+
const typeReg = new RegExp(`data-${DATASET_SPLIT_TYPE}=".+"`, 'g');
|
|
163
|
+
sources.forEach(s => highlighter.fromStore(s.startMeta, s.endMeta, s.text, s.id));
|
|
164
|
+
const html1 = document.body.innerHTML.replace(typeReg, '');
|
|
165
|
+
|
|
166
|
+
document.body.innerHTML = readFileSync(resolve(__dirname, 'fixtures', 'index.html'), 'utf-8');
|
|
167
|
+
sources.slice(0).reverse().forEach(s => highlighter.fromStore(s.startMeta, s.endMeta, s.text, s.id));
|
|
168
|
+
const html2 = document.body.innerHTML.replace(typeReg, '');
|
|
169
|
+
|
|
170
|
+
expect(html1).to.be.equal(html2);
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
it('should not crash when highlight source is invalid', () => {
|
|
174
|
+
const s = brokenSources[0];
|
|
175
|
+
expect(() => highlighter.fromStore(s.startMeta, s.endMeta, s.text, s.id)).not.to.throw();
|
|
176
|
+
});
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
describe('#remove', () => {
|
|
180
|
+
beforeEach(() => {
|
|
181
|
+
const s = sources[0];
|
|
182
|
+
highlighter.fromStore(s.startMeta, s.endMeta, s.text, s.id);
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
it('should remove all highlighted areas', () => {
|
|
186
|
+
const id = sources[0].id;
|
|
187
|
+
highlighter.remove(id);
|
|
188
|
+
|
|
189
|
+
const hasItem = [...document.querySelectorAll(wrapSelector)].some(n => n.getAttribute(`data-${DATASET_IDENTIFIER}`) === id);
|
|
190
|
+
|
|
191
|
+
expect(hasItem).to.be.false;
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
it('should not occur errors when the id does not exist', () => {
|
|
195
|
+
expect(() => highlighter.remove('fake id')).not.to.throw();
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
it('should not affect document when the id is empty', () => {
|
|
199
|
+
const html = document.body.innerHTML;
|
|
200
|
+
highlighter.remove('');
|
|
201
|
+
expect(html).to.be.equal(document.body.innerHTML);
|
|
202
|
+
});
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
describe('#removeAll', () => {
|
|
206
|
+
beforeEach(() => {
|
|
207
|
+
const s = sources[0];
|
|
208
|
+
highlighter.fromStore(s.startMeta, s.endMeta, s.text, s.id);
|
|
209
|
+
highlighter.removeAll();
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
it('should remove all highlighted areas', () => {
|
|
213
|
+
expect(document.querySelectorAll(wrapSelector).length).to.be.equal(0);
|
|
214
|
+
});
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
describe('#run', () => {
|
|
218
|
+
it('should highlight automatically after the user\'s interaction', () => {
|
|
219
|
+
expect(getInteraction().PointerEnd).to.be.equal('mouseup');
|
|
220
|
+
|
|
221
|
+
highlighter.run();
|
|
222
|
+
|
|
223
|
+
const range = document.createRange();
|
|
224
|
+
const $p = document.querySelectorAll('p')[0];
|
|
225
|
+
range.setStart($p.childNodes[0], 0);
|
|
226
|
+
range.setEnd($p.childNodes[0], 17);
|
|
227
|
+
window.getSelection().addRange(range);
|
|
228
|
+
|
|
229
|
+
const content = range.toString();
|
|
230
|
+
const e = new MouseEvent('mouseup', {
|
|
231
|
+
view: window,
|
|
232
|
+
bubbles: true,
|
|
233
|
+
cancelable: true
|
|
234
|
+
});
|
|
235
|
+
document.body.dispatchEvent(e);
|
|
236
|
+
const $w = document.querySelectorAll('p')[0].querySelector(wrapSelector);
|
|
237
|
+
expect($w.textContent).to.be.equal(content);
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
it('should not affect the document when selection is collapsed', () => {
|
|
241
|
+
highlighter.run();
|
|
242
|
+
|
|
243
|
+
const html = document.body.innerHTML;
|
|
244
|
+
const range = document.createRange();
|
|
245
|
+
const $p = document.querySelectorAll('p')[0];
|
|
246
|
+
range.setStart($p.childNodes[0], 5);
|
|
247
|
+
range.setEnd($p.childNodes[0], 5);
|
|
248
|
+
window.getSelection().addRange(range);
|
|
249
|
+
|
|
250
|
+
const e = new MouseEvent('mouseup', {
|
|
251
|
+
view: window,
|
|
252
|
+
bubbles: true,
|
|
253
|
+
cancelable: true
|
|
254
|
+
});
|
|
255
|
+
document.body.dispatchEvent(e);
|
|
256
|
+
|
|
257
|
+
expect(document.body.innerHTML).to.be.equal(html);
|
|
258
|
+
});
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
describe('#dispose', () => {
|
|
262
|
+
it('should not highlight automatically after calling .dispose', () => {
|
|
263
|
+
highlighter.run();
|
|
264
|
+
highlighter.dispose();
|
|
265
|
+
|
|
266
|
+
const range = document.createRange();
|
|
267
|
+
const $p = document.querySelectorAll('p')[0];
|
|
268
|
+
range.setStart($p.childNodes[0], 0);
|
|
269
|
+
range.setEnd($p.childNodes[0], 17);
|
|
270
|
+
window.getSelection().addRange(range);
|
|
271
|
+
|
|
272
|
+
const e = new MouseEvent('mouseup', {
|
|
273
|
+
view: window,
|
|
274
|
+
bubbles: true,
|
|
275
|
+
cancelable: true
|
|
276
|
+
});
|
|
277
|
+
document.body.dispatchEvent(e);
|
|
278
|
+
|
|
279
|
+
const $w = document.querySelectorAll('p')[0].querySelectorAll(wrapSelector);
|
|
280
|
+
expect($w.length).to.be.equal(0);
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
it('should remove all highlights after calling .dispose', () => {
|
|
284
|
+
highlighter.dispose();
|
|
285
|
+
|
|
286
|
+
const $w = document.querySelectorAll(wrapSelector);
|
|
287
|
+
expect($w.length).to.be.equal(0);
|
|
288
|
+
});
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
describe('#stop', () => {
|
|
292
|
+
it('should not highlight automatically after calling .stop', () => {
|
|
293
|
+
highlighter.run();
|
|
294
|
+
highlighter.stop();
|
|
295
|
+
|
|
296
|
+
const range = document.createRange();
|
|
297
|
+
const $p = document.querySelectorAll('p')[0];
|
|
298
|
+
range.setStart($p.childNodes[0], 0);
|
|
299
|
+
range.setEnd($p.childNodes[0], 17);
|
|
300
|
+
window.getSelection().addRange(range);
|
|
301
|
+
|
|
302
|
+
const e = new MouseEvent('mouseup', {
|
|
303
|
+
view: window,
|
|
304
|
+
bubbles: true,
|
|
305
|
+
cancelable: true
|
|
306
|
+
});
|
|
307
|
+
document.body.dispatchEvent(e);
|
|
308
|
+
|
|
309
|
+
const $w = document.querySelectorAll('p')[0].querySelectorAll(wrapSelector);
|
|
310
|
+
expect($w.length).to.be.equal(0);
|
|
311
|
+
});
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
describe('#getDoms', () => {
|
|
315
|
+
beforeEach(() => {
|
|
316
|
+
highlighter.removeAll();
|
|
317
|
+
sources.forEach(s => highlighter.fromStore(s.startMeta, s.endMeta, s.text, s.id));
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
it('get specific highlight\'s doms by passing the id', () => {
|
|
321
|
+
const s = sources[0];
|
|
322
|
+
const doms = highlighter.getDoms(s.id);
|
|
323
|
+
expect(doms.length).gt(0);
|
|
324
|
+
expect(doms.map(n => n.textContent).join('')).to.be.equal(s.text);
|
|
325
|
+
expect(doms.every(Highlighter.isHighlightWrapNode), 'dom is wrapper').to.be.true;
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
it('get no doms when id does not exist', () => {
|
|
329
|
+
const doms = highlighter.getDoms(sources[0].id + 'fake');
|
|
330
|
+
expect(doms.length).to.be.equal(0);
|
|
331
|
+
expect(doms.every(Highlighter.isHighlightWrapNode), 'dom is wrapper').to.be.true;
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
it('get all doms without an argument', () => {
|
|
335
|
+
const doms = highlighter.getDoms();
|
|
336
|
+
document.querySelectorAll(wrapSelector);
|
|
337
|
+
expect(doms.length).to.be.equal(document.querySelectorAll(wrapSelector).length);
|
|
338
|
+
expect(doms.every(Highlighter.isHighlightWrapNode), 'dom is wrapper').to.be.true;
|
|
339
|
+
});
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
describe('#addClass', () => {
|
|
343
|
+
const className = 'test-class';
|
|
344
|
+
|
|
345
|
+
beforeEach(() => {
|
|
346
|
+
highlighter.removeAll();
|
|
347
|
+
sources.forEach(s => highlighter.fromStore(s.startMeta, s.endMeta, s.text, s.id));
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
it('should add class name to the exact doms by id', () => {
|
|
351
|
+
const id = sources[0].id;
|
|
352
|
+
highlighter.addClass(className, id);
|
|
353
|
+
const containClassName = highlighter
|
|
354
|
+
.getDoms(id)
|
|
355
|
+
.every(n => n.getAttribute('class').indexOf(className) > -1);
|
|
356
|
+
expect(containClassName).to.be.true;
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
it('should not add class name to the doms without the id', () => {
|
|
360
|
+
const id = sources[0].id;
|
|
361
|
+
highlighter.addClass(className, id);
|
|
362
|
+
expect(document.querySelectorAll(`.${className}`).length).not.gt(highlighter.getDoms(id).length);
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
it('should not affect the document when id dose not exist', () => {
|
|
366
|
+
expect(document.querySelectorAll(`.${className}`)).lengthOf(0);
|
|
367
|
+
highlighter.addClass(className, sources[0].id + 'fake');
|
|
368
|
+
expect(document.querySelectorAll(`.${className}`)).lengthOf(0);
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
it('should affect all wrapper nodes when not passing id', () => {
|
|
372
|
+
highlighter.addClass(className);
|
|
373
|
+
const $set = [...document.querySelectorAll(`.${className}`)];
|
|
374
|
+
const $doms = highlighter.getDoms();
|
|
375
|
+
expect($set).lengthOf($doms.length);
|
|
376
|
+
expect($set.every(n => $doms.indexOf((n as HTMLElement)) > -1)).to.be.true;
|
|
377
|
+
});
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
describe('#removeClass', () => {
|
|
381
|
+
const className = 'test-class';
|
|
382
|
+
|
|
383
|
+
beforeEach(() => {
|
|
384
|
+
highlighter.removeAll();
|
|
385
|
+
sources.forEach(s => highlighter.fromStore(s.startMeta, s.endMeta, s.text, s.id));
|
|
386
|
+
highlighter.addClass(className);
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
it('should remove the class name from all wrappers without the id argument', () => {
|
|
390
|
+
highlighter.removeClass(className);
|
|
391
|
+
expect(document.querySelectorAll(`.${className}`)).lengthOf(0);
|
|
392
|
+
});
|
|
393
|
+
|
|
394
|
+
it('should remove the class name from the specific highlight', () => {
|
|
395
|
+
const id = sources[0].id;
|
|
396
|
+
highlighter.removeClass(className, id);
|
|
397
|
+
const notContain = highlighter
|
|
398
|
+
.getDoms(id)
|
|
399
|
+
.every(n => n.getAttribute('class').indexOf(className) === -1);
|
|
400
|
+
|
|
401
|
+
expect(notContain).to.be.true;
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
it('should not affect the document when id dose not exist', () => {
|
|
405
|
+
expect(document.querySelectorAll(`.${className}`)).lengthOf(highlighter.getDoms().length);
|
|
406
|
+
highlighter.removeClass(className, sources[0].id + 'fake');
|
|
407
|
+
expect(document.querySelectorAll(`.${className}`)).lengthOf(highlighter.getDoms().length);
|
|
408
|
+
});
|
|
409
|
+
});
|
|
410
|
+
|
|
411
|
+
describe('#getIdByDom', () => {
|
|
412
|
+
beforeEach(() => {
|
|
413
|
+
highlighter.removeAll();
|
|
414
|
+
sources.forEach(s => highlighter.fromStore(s.startMeta, s.endMeta, s.text, s.id));
|
|
415
|
+
});
|
|
416
|
+
|
|
417
|
+
it('should return the correct id when it\'s a wrapper', () => {
|
|
418
|
+
const id = sources[0].id;
|
|
419
|
+
const dom = highlighter.getDoms(id)[0];
|
|
420
|
+
expect(highlighter.getIdByDom(dom)).to.be.equal(id);
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
it('should return the correct id when it\'s inside a wrapper', () => {
|
|
424
|
+
const id = sources[0].id;
|
|
425
|
+
const dom = highlighter.getDoms(id)[0];
|
|
426
|
+
expect(highlighter.getIdByDom(dom.childNodes[0] as HTMLElement)).to.be.equal(id);
|
|
427
|
+
});
|
|
428
|
+
|
|
429
|
+
it('should return \'\' when it\'s outside a wrapper', () => {
|
|
430
|
+
const id = sources[0].id;
|
|
431
|
+
const dom = highlighter.getDoms(id)[0];
|
|
432
|
+
expect(highlighter.getIdByDom(dom.parentElement)).to.be.empty;
|
|
433
|
+
});
|
|
434
|
+
|
|
435
|
+
it('should return \'\' when a valid wrapper is outside the root', () => {
|
|
436
|
+
const footerHighlighter = new Highlighter({
|
|
437
|
+
$root: document.querySelector('footer')
|
|
438
|
+
});
|
|
439
|
+
const dom = highlighter.getDoms(sources[0].id)[0];
|
|
440
|
+
expect(footerHighlighter.getIdByDom(dom)).to.be.empty;
|
|
441
|
+
});
|
|
442
|
+
|
|
443
|
+
it('should return \'\' when the dom is not be wrapped', () => {
|
|
444
|
+
expect(highlighter.getIdByDom(document.querySelector('img'))).to.be.empty;
|
|
445
|
+
});
|
|
446
|
+
});
|
|
447
|
+
|
|
448
|
+
describe('#getExtraIdByDom', () => {
|
|
449
|
+
beforeEach(() => {
|
|
450
|
+
highlighter.removeAll();
|
|
451
|
+
sources.forEach(s => highlighter.fromStore(s.startMeta, s.endMeta, s.text, s.id));
|
|
452
|
+
});
|
|
453
|
+
|
|
454
|
+
it('should return the correct ids when it\'s a wrapper', () => {
|
|
455
|
+
const id = sources[0].id;
|
|
456
|
+
const dom = highlighter.getDoms(id)[2];
|
|
457
|
+
const ids = highlighter.getExtraIdByDom(dom);
|
|
458
|
+
expect(ids.sort()).to.deep.equal([sources[0].id, sources[1].id].sort());
|
|
459
|
+
});
|
|
460
|
+
|
|
461
|
+
it('should return the correct ids when it\'s inside a wrapper', () => {
|
|
462
|
+
const id = sources[0].id;
|
|
463
|
+
const dom = highlighter.getDoms(id)[2].childNodes[0];
|
|
464
|
+
const ids = highlighter.getExtraIdByDom(dom as HTMLElement);
|
|
465
|
+
expect(ids.sort()).to.deep.equal([sources[0].id, sources[1].id].sort());
|
|
466
|
+
});
|
|
467
|
+
|
|
468
|
+
it('should return [] when it\'s outside a wrapper', () => {
|
|
469
|
+
const id = sources[0].id;
|
|
470
|
+
const dom = highlighter.getDoms(id)[2].parentElement;
|
|
471
|
+
const ids = highlighter.getExtraIdByDom(dom);
|
|
472
|
+
expect(ids).to.deep.equal([]);
|
|
473
|
+
});
|
|
474
|
+
|
|
475
|
+
it('should return [] when a valid wrapper is outside the root', () => {
|
|
476
|
+
const footerHighlighter = new Highlighter({
|
|
477
|
+
$root: document.querySelector('footer')
|
|
478
|
+
});
|
|
479
|
+
const dom = highlighter.getDoms(sources[0].id)[0];
|
|
480
|
+
expect(footerHighlighter.getExtraIdByDom(dom)).to.deep.equal([]);
|
|
481
|
+
});
|
|
482
|
+
|
|
483
|
+
it('should return [] when there is no extra id', () => {
|
|
484
|
+
const dom = highlighter.getDoms(sources[0].id)[0];
|
|
485
|
+
expect(highlighter.getExtraIdByDom(dom)).to.deep.equal([]);
|
|
486
|
+
});
|
|
487
|
+
|
|
488
|
+
it('should return [] when the dom is not be wrapped', () => {
|
|
489
|
+
expect(highlighter.getExtraIdByDom(document.querySelector('img'))).to.deep.equal([]);
|
|
490
|
+
});
|
|
491
|
+
});
|
|
492
|
+
|
|
493
|
+
describe('changing className by .setOption', () => {
|
|
494
|
+
it('should get updated after deleting when the highlight wrapper is inside another wrapper', () => {
|
|
495
|
+
// https://github.com/alienzhou/web-highlighter/pull/80
|
|
496
|
+
const defaultClassName = getDefaultOptions().style.className;
|
|
497
|
+
const range = document.createRange();
|
|
498
|
+
const $p = document.querySelectorAll('p')[3];
|
|
499
|
+
const $highlight = $p.querySelector('span');
|
|
500
|
+
range.setStart($highlight.childNodes[0], 12);
|
|
501
|
+
range.setEnd($highlight.childNodes[0], 21);
|
|
502
|
+
|
|
503
|
+
// change className and highlight it
|
|
504
|
+
highlighter.setOption({ style: { className: 'highlight-test' } });
|
|
505
|
+
const { id } = highlighter.fromRange(range);
|
|
506
|
+
|
|
507
|
+
// remove the highlight
|
|
508
|
+
highlighter.remove(id);
|
|
509
|
+
|
|
510
|
+
const classnames: string[] = [];
|
|
511
|
+
const $wraps = $p.querySelectorAll(wrapSelector);
|
|
512
|
+
$wraps.forEach($n => classnames.push($n.className));
|
|
513
|
+
|
|
514
|
+
expect(classnames).to.be.deep.equal(new Array($wraps.length).fill(defaultClassName));
|
|
515
|
+
});
|
|
516
|
+
|
|
517
|
+
it('should set the only new className on an already existed wrapper after highlighting', () => {
|
|
518
|
+
const range = document.createRange();
|
|
519
|
+
const $p = document.querySelectorAll('p')[3];
|
|
520
|
+
range.setStart($p.querySelector('span').childNodes[0], 64);
|
|
521
|
+
range.setEnd($p.querySelector('span').nextSibling, 9);
|
|
522
|
+
highlighter.fromRange(range);
|
|
523
|
+
|
|
524
|
+
const $span = $p.querySelectorAll(wrapSelector)[1];
|
|
525
|
+
const range2 = document.createRange();
|
|
526
|
+
range2.setStart($span.childNodes[0], 0);
|
|
527
|
+
range2.setEnd($span.childNodes[0], 20);
|
|
528
|
+
highlighter.setOption({ style: { className: 'highlight-test' } });
|
|
529
|
+
highlighter.fromRange(range2);
|
|
530
|
+
|
|
531
|
+
const $wraps = $p.querySelectorAll(wrapSelector);
|
|
532
|
+
expect($wraps[1].className).to.be.equal('highlight-test');
|
|
533
|
+
});
|
|
534
|
+
|
|
535
|
+
it('should set the only new className on a split wrapper after highlighting', () => {
|
|
536
|
+
const range = document.createRange();
|
|
537
|
+
const $p = document.querySelectorAll('p')[3];
|
|
538
|
+
const $highlight = $p.querySelector('span');
|
|
539
|
+
range.setStart($highlight.childNodes[0], 12);
|
|
540
|
+
range.setEnd($highlight.childNodes[0], 21);
|
|
541
|
+
|
|
542
|
+
// change className and highlight it
|
|
543
|
+
highlighter.setOption({ style: { className: 'highlight-test' } });
|
|
544
|
+
highlighter.fromRange(range);
|
|
545
|
+
|
|
546
|
+
const $wraps = $p.querySelectorAll(wrapSelector);
|
|
547
|
+
|
|
548
|
+
expect($wraps[1].className).to.be.equal('highlight-test');
|
|
549
|
+
});
|
|
550
|
+
});
|
|
551
|
+
|
|
552
|
+
afterEach(() => {
|
|
553
|
+
cleanup();
|
|
554
|
+
});
|
|
555
|
+
});
|