Skip to content

Commit bc8decd

Browse files
committed
feat: enhance DOM manipulation and selection features with new methods and improved tests
1 parent 0a531ff commit bc8decd

13 files changed

Lines changed: 441 additions & 61 deletions

src/core/dom/README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,9 @@ For example, your plugin can search for some nodes, or text. If each time you ru
3030
then the interface will noticeably slow down on large documents. To avoid this, this class is made.
3131

3232
```js
33-
const walker = new Jodit.modules.LazyWalker(new Jodit.modules.Async(), 100);
33+
const walker = new Jodit.modules.LazyWalker(new Jodit.modules.Async(), {
34+
timeoutChunkSize: 100
35+
});
3436
const names = [];
3537

3638
walker

src/core/dom/dom.test.js

Lines changed: 215 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,91 @@ describe('Test Dom module', function () {
149149
expect(names.toString()).equals('LI,2,LI,1');
150150
});
151151
});
152+
153+
describe('Many siblings', function () {
154+
it('Should iterate wide sibling lists in proper order', () => {
155+
const node = document.createElement('div');
156+
157+
node.innerHTML = new Array(10)
158+
.fill(0)
159+
.map((_, i) => `<span>${i}</span>`)
160+
.join('');
161+
162+
const values = [];
163+
164+
Dom.find(
165+
node.firstChild,
166+
n => {
167+
if (Dom.isText(n)) {
168+
values.push(n.nodeValue);
169+
}
170+
},
171+
node
172+
);
173+
174+
expect(values.join('')).equals('123456789');
175+
176+
values.length = 0;
177+
178+
Dom.find(
179+
node.lastChild,
180+
n => {
181+
if (Dom.isText(n)) {
182+
values.push(n.nodeValue);
183+
}
184+
},
185+
node,
186+
false
187+
);
188+
189+
expect(values.join('')).equals('876543210');
190+
});
191+
});
192+
});
193+
194+
describe('Method between', function () {
195+
it('Should call callback for all nodes between start and end', () => {
196+
const node = document.createElement('div');
197+
198+
node.innerHTML =
199+
'<p>1<span id="s"></span>2</p><p>3</p><p>4<span id="e"></span>5</p>';
200+
201+
Dom.between(
202+
node.querySelector('#s'),
203+
node.querySelector('#e'),
204+
iterate
205+
);
206+
207+
expect(names.toString()).equals('2,P,3,P,4');
208+
});
209+
210+
it('Should stop iterating when callback returns true', () => {
211+
const node = document.createElement('div');
212+
213+
node.innerHTML =
214+
'<p>1<span id="s"></span>2</p><p>3</p><p>4<span id="e"></span>5</p>';
215+
216+
Dom.between(
217+
node.querySelector('#s'),
218+
node.querySelector('#e'),
219+
elm => {
220+
iterate(elm);
221+
return Dom.isText(elm) && elm.nodeValue === '3';
222+
}
223+
);
224+
225+
expect(names.toString()).equals('2,P,3');
226+
});
227+
228+
it('Should not iterate outside when end is an ancestor of start', () => {
229+
const node = document.createElement('div');
230+
231+
node.innerHTML = '<p>1<span id="s"></span></p><p>2</p>';
232+
233+
Dom.between(node.querySelector('#s'), node.firstChild, iterate);
234+
235+
expect(names.toString()).equals('');
236+
});
152237
});
153238
});
154239

@@ -280,6 +365,72 @@ describe('Test Dom module', function () {
280365
});
281366
});
282367

368+
describe('Method isFragment', function () {
369+
it('Should return true for document fragments', function () {
370+
expect(Dom.isFragment(document.createDocumentFragment())).is.true;
371+
372+
// fragment inside an inert document
373+
const template = document.createElement('template');
374+
template.innerHTML = '<p>test</p>';
375+
expect(Dom.isFragment(template.content)).is.true;
376+
});
377+
378+
it('Should return false for other values', function () {
379+
expect(Dom.isFragment(document.createElement('div'))).is.false;
380+
expect(Dom.isFragment(document.createTextNode('test'))).is.false;
381+
expect(Dom.isFragment(document)).is.false;
382+
expect(Dom.isFragment(null)).is.false;
383+
expect(Dom.isFragment('')).is.false;
384+
});
385+
});
386+
387+
describe('Method replace', function () {
388+
it('Should replace one tag with another keeping content and attributes', function () {
389+
const editor = getJodit();
390+
const div = document.createElement('div');
391+
div.innerHTML = '<span data-x="1">content</span>';
392+
393+
const strong = Dom.replace(
394+
div.firstChild,
395+
'strong',
396+
editor.createInside,
397+
true
398+
);
399+
400+
expect(div.innerHTML).equals('<strong data-x="1">content</strong>');
401+
expect(strong.tagName).equals('STRONG');
402+
});
403+
404+
it('Should replace tag with ready element', function () {
405+
const div = document.createElement('div');
406+
div.innerHTML = '<span>content</span>';
407+
408+
const em = document.createElement('em');
409+
const result = Dom.replace(div.firstChild, em);
410+
411+
expect(result).equals(em);
412+
expect(div.innerHTML).equals('<em>content</em>');
413+
});
414+
});
415+
416+
describe('Method replaceTemporaryFromString', function () {
417+
it('Should remove temporary wrappers and keep their content', function () {
418+
expect(
419+
Dom.replaceTemporaryFromString(
420+
'<p>a <span data-jodit-temp="true">b</span> c</p>'
421+
)
422+
).equals('<p>a b c</p>');
423+
});
424+
425+
it('Should support the attribute without a value', function () {
426+
expect(
427+
Dom.replaceTemporaryFromString(
428+
'<p><span data-jodit-temp>b</span></p>'
429+
)
430+
).equals('<p>b</p>');
431+
});
432+
});
433+
283434
describe('Method isOrContains', function () {
284435
it('Should return true if element inside root', function () {
285436
const node = document.createElement('div');
@@ -663,5 +814,69 @@ describe('Test Dom module', function () {
663814
walker.setWork(div);
664815
});
665816
});
817+
818+
describe('Repeated setWork', () => {
819+
it('should process only the last tree and emit end once', done => {
820+
const walker = new LazyWalker(new Async());
821+
const names = [];
822+
let endCount = 0;
823+
824+
walker
825+
.on('visit', node => {
826+
names.push(node.nodeName.toLowerCase());
827+
})
828+
.on('end', () => {
829+
endCount += 1;
830+
expect(endCount).eq(1);
831+
expect(names).deep.eq(['p', '#text']);
832+
done();
833+
});
834+
835+
const div1 = document.createElement('div');
836+
div1.innerHTML = '<span>a</span>';
837+
838+
const div2 = document.createElement('div');
839+
div2.innerHTML = '<p>b</p>';
840+
841+
walker.setWork(div1);
842+
walker.setWork(div2);
843+
});
844+
});
845+
846+
describe('Restart after break', () => {
847+
it('should reset the affect flag between passes', done => {
848+
const asyncM = new Async();
849+
const walker = new LazyWalker(asyncM, {
850+
timeoutChunkSize: 1,
851+
timeout: 100
852+
});
853+
854+
const div = document.createElement('div');
855+
div.innerHTML = '<b>1</b><i>2</i><u>3</u>';
856+
857+
let pass = 1;
858+
859+
// the first pass "affects" nodes, the second one - does not
860+
walker.on('visit', () => pass === 1);
861+
862+
walker.on('break', () => {
863+
asyncM.setTimeout(() => {
864+
pass = 2;
865+
walker.setWork(div);
866+
}, 10);
867+
});
868+
869+
walker.on('end', affect => {
870+
expect(pass).eq(2);
871+
expect(affect).is.false;
872+
done();
873+
});
874+
875+
walker.setWork(div);
876+
877+
// break the first pass in the middle, between two chunks
878+
asyncM.setTimeout(() => walker.break(), 150);
879+
});
880+
});
666881
});
667882
});

0 commit comments

Comments
 (0)