8
0

treewalker.js 30 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178
  1. /**
  2. * @license Copyright (c) 2003-2020, CKSource - Frederico Knabben. All rights reserved.
  3. * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
  4. */
  5. import Document from '../../src/view/document';
  6. import DocumentFragment from '../../src/view/documentfragment';
  7. import AttributeElement from '../../src/view/attributeelement';
  8. import ContainerElement from '../../src/view/containerelement';
  9. import Text from '../../src/view/text';
  10. import TreeWalker from '../../src/view/treewalker';
  11. import Position from '../../src/view/position';
  12. import Range from '../../src/view/range';
  13. import createViewRoot from './_utils/createroot';
  14. import { expectToThrowCKEditorError } from '@ckeditor/ckeditor5-utils/tests/_utils/utils';
  15. import { StylesProcessor } from '../../src/view/stylesmap';
  16. describe( 'TreeWalker', () => {
  17. let doc, root, img1, paragraph, bold, textAbcd, charY, img2, charX, rootBeginning, rootEnding, stylesProcessor;
  18. before( () => {
  19. stylesProcessor = new StylesProcessor();
  20. doc = new Document( stylesProcessor );
  21. root = createViewRoot( doc );
  22. // root
  23. // |- img1
  24. // |- p
  25. // |- b
  26. // | |- abcd
  27. // |
  28. // |- Y
  29. // |
  30. // |- img2
  31. // |
  32. // |- X
  33. textAbcd = new Text( doc, 'abcd' );
  34. bold = new AttributeElement( doc, 'b', null, [ textAbcd ] );
  35. charY = new Text( doc, 'y' );
  36. img2 = new ContainerElement( doc, 'img2' );
  37. charX = new Text( doc, 'x' );
  38. paragraph = new ContainerElement( doc, 'p', null, [ bold, charY, img2, charX ] );
  39. img1 = new ContainerElement( doc, 'img1' );
  40. root._insertChild( 0, [ img1, paragraph ] );
  41. rootBeginning = new Position( root, 0 );
  42. rootEnding = new Position( root, 2 );
  43. } );
  44. describe( 'constructor()', () => {
  45. it( 'should throw if neither boundaries nor starting position is set', () => {
  46. expectToThrowCKEditorError( () => {
  47. new TreeWalker(); // eslint-disable-line no-new
  48. }, /^view-tree-walker-no-start-position/, null );
  49. expectToThrowCKEditorError( () => {
  50. new TreeWalker( {} ); // eslint-disable-line no-new
  51. }, /^view-tree-walker-no-start-position/, null );
  52. expectToThrowCKEditorError( () => {
  53. new TreeWalker( { singleCharacters: true } ); // eslint-disable-line no-new
  54. }, /^view-tree-walker-no-start-position/, null );
  55. } );
  56. it( 'should throw if walking direction is unknown', () => {
  57. expectToThrowCKEditorError( () => {
  58. new TreeWalker( { startPosition: rootBeginning, direction: 'unknown' } ); // eslint-disable-line no-new
  59. }, /^view-tree-walker-unknown-direction/, doc );
  60. } );
  61. } );
  62. describe( 'iterate from start position `startPosition`', () => {
  63. let expected;
  64. beforeEach( () => {
  65. expected = [
  66. {
  67. type: 'elementStart',
  68. item: img1,
  69. previousPosition: new Position( root, 0 ),
  70. nextPosition: new Position( img1, 0 )
  71. },
  72. {
  73. type: 'elementEnd',
  74. item: img1,
  75. previousPosition: new Position( img1, 0 ),
  76. nextPosition: new Position( root, 1 )
  77. },
  78. {
  79. type: 'elementStart',
  80. item: paragraph,
  81. previousPosition: new Position( root, 1 ),
  82. nextPosition: new Position( paragraph, 0 )
  83. },
  84. {
  85. type: 'elementStart',
  86. item: bold,
  87. previousPosition: new Position( paragraph, 0 ),
  88. nextPosition: new Position( bold, 0 )
  89. },
  90. {
  91. type: 'text',
  92. text: 'abcd',
  93. previousPosition: new Position( bold, 0 ),
  94. nextPosition: new Position( bold, 1 )
  95. },
  96. {
  97. type: 'elementEnd',
  98. item: bold,
  99. previousPosition: new Position( bold, 1 ),
  100. nextPosition: new Position( paragraph, 1 )
  101. },
  102. {
  103. type: 'text',
  104. text: 'y',
  105. previousPosition: new Position( paragraph, 1 ),
  106. nextPosition: new Position( paragraph, 2 )
  107. },
  108. {
  109. type: 'elementStart',
  110. item: img2,
  111. previousPosition: new Position( paragraph, 2 ),
  112. nextPosition: new Position( img2, 0 )
  113. },
  114. {
  115. type: 'elementEnd',
  116. item: img2,
  117. previousPosition: new Position( img2, 0 ),
  118. nextPosition: new Position( paragraph, 3 )
  119. },
  120. {
  121. type: 'text',
  122. text: 'x',
  123. previousPosition: new Position( paragraph, 3 ),
  124. nextPosition: new Position( paragraph, 4 )
  125. },
  126. {
  127. type: 'elementEnd',
  128. item: paragraph,
  129. previousPosition: new Position( paragraph, 4 ),
  130. nextPosition: new Position( root, 2 )
  131. }
  132. ];
  133. } );
  134. it( 'should provide iterator interface with default forward direction', () => {
  135. const iterator = new TreeWalker( { startPosition: rootBeginning } );
  136. let i = 0;
  137. for ( const value of iterator ) {
  138. expectValue( value, expected[ i++ ] );
  139. }
  140. expect( i ).to.equal( expected.length );
  141. } );
  142. it( 'should provide iterator interface with forward direction', () => {
  143. const iterator = new TreeWalker( { startPosition: rootBeginning, direction: 'forward' } );
  144. let i = 0;
  145. for ( const value of iterator ) {
  146. expectValue( value, expected[ i++ ] );
  147. }
  148. expect( i ).to.equal( expected.length );
  149. } );
  150. it( 'should provide iterator interface which backward direction', () => {
  151. const iterator = new TreeWalker( { startPosition: rootEnding, direction: 'backward' } );
  152. let i = expected.length;
  153. for ( const value of iterator ) {
  154. expectValue( value, expected[ --i ], { direction: 'backward' } );
  155. }
  156. expect( i ).to.equal( 0 );
  157. } );
  158. it( 'should start iterating at the startPosition witch is not a root bound', () => {
  159. const iterator = new TreeWalker( { startPosition: new Position( root, 1 ) } );
  160. let i = 2;
  161. for ( const value of iterator ) {
  162. expectValue( value, expected[ i++ ] );
  163. }
  164. expect( i ).to.equal( expected.length );
  165. } );
  166. it( 'should start iterating at the startPosition witch is not a root bound, going backward', () => {
  167. const expected = [
  168. {
  169. type: 'elementStart',
  170. item: img1,
  171. previousPosition: new Position( root, 0 ),
  172. nextPosition: new Position( img1, 0 )
  173. },
  174. {
  175. type: 'elementEnd',
  176. item: img1,
  177. previousPosition: new Position( img1, 0 ),
  178. nextPosition: new Position( root, 1 )
  179. }
  180. ];
  181. const iterator = new TreeWalker( { startPosition: new Position( root, 1 ), direction: 'backward' } );
  182. let i = expected.length;
  183. for ( const value of iterator ) {
  184. expectValue( value, expected[ --i ], { direction: 'backward' } );
  185. }
  186. expect( i ).to.equal( 0 );
  187. } );
  188. } );
  189. describe( 'iterate trough the range `boundary`', () => {
  190. describe( 'range starts between elements', () => {
  191. let expected, range;
  192. before( () => {
  193. expected = [
  194. {
  195. type: 'elementStart',
  196. item: paragraph,
  197. previousPosition: new Position( root, 1 ),
  198. nextPosition: new Position( paragraph, 0 )
  199. },
  200. {
  201. type: 'elementStart',
  202. item: bold,
  203. previousPosition: new Position( paragraph, 0 ),
  204. nextPosition: new Position( bold, 0 )
  205. },
  206. {
  207. type: 'text',
  208. text: 'abcd',
  209. previousPosition: new Position( bold, 0 ),
  210. nextPosition: new Position( bold, 1 )
  211. },
  212. {
  213. type: 'elementEnd',
  214. item: bold,
  215. previousPosition: new Position( bold, 1 ),
  216. nextPosition: new Position( paragraph, 1 )
  217. },
  218. {
  219. type: 'text',
  220. text: 'y',
  221. previousPosition: new Position( paragraph, 1 ),
  222. nextPosition: new Position( paragraph, 2 )
  223. },
  224. {
  225. type: 'elementStart',
  226. item: img2,
  227. previousPosition: new Position( paragraph, 2 ),
  228. nextPosition: new Position( img2, 0 )
  229. },
  230. {
  231. type: 'elementEnd',
  232. item: img2,
  233. previousPosition: new Position( img2, 0 ),
  234. nextPosition: new Position( paragraph, 3 )
  235. }
  236. ];
  237. range = Range._createFromParentsAndOffsets( root, 1, paragraph, 3 );
  238. } );
  239. it( 'should iterating over the range', () => {
  240. const iterator = new TreeWalker( { boundaries: range } );
  241. let i = 0;
  242. for ( const value of iterator ) {
  243. expectValue( value, expected[ i++ ] );
  244. }
  245. expect( i ).to.equal( expected.length );
  246. } );
  247. it( 'should iterating over the range going backward', () => {
  248. const iterator = new TreeWalker( { boundaries: range, direction: 'backward' } );
  249. let i = expected.length;
  250. for ( const value of iterator ) {
  251. expectValue( value, expected[ --i ], { direction: 'backward' } );
  252. }
  253. expect( i ).to.equal( 0 );
  254. } );
  255. } );
  256. describe( 'range starts inside the text', () => {
  257. let expected, range;
  258. before( () => {
  259. expected = [
  260. {
  261. type: 'text',
  262. text: 'bcd',
  263. previousPosition: new Position( textAbcd, 1 ),
  264. nextPosition: new Position( bold, 1 )
  265. },
  266. {
  267. type: 'elementEnd',
  268. item: bold,
  269. previousPosition: new Position( bold, 1 ),
  270. nextPosition: new Position( paragraph, 1 )
  271. },
  272. {
  273. type: 'text',
  274. text: 'y',
  275. previousPosition: new Position( paragraph, 1 ),
  276. nextPosition: new Position( paragraph, 2 )
  277. },
  278. {
  279. type: 'elementStart',
  280. item: img2,
  281. previousPosition: new Position( paragraph, 2 ),
  282. nextPosition: new Position( img2, 0 )
  283. },
  284. {
  285. type: 'elementEnd',
  286. item: img2,
  287. previousPosition: new Position( img2, 0 ),
  288. nextPosition: new Position( paragraph, 3 )
  289. }
  290. ];
  291. range = Range._createFromParentsAndOffsets( textAbcd, 1, paragraph, 3 );
  292. } );
  293. it( 'should return part of the text', () => {
  294. const iterator = new TreeWalker( { boundaries: range } );
  295. let i = 0;
  296. for ( const value of iterator ) {
  297. expectValue( value, expected[ i++ ] );
  298. }
  299. expect( i ).to.equal( expected.length );
  300. } );
  301. it( 'should return part of the text going backward', () => {
  302. const iterator = new TreeWalker( {
  303. boundaries: range,
  304. direction: 'backward'
  305. }
  306. );
  307. let i = expected.length;
  308. for ( const value of iterator ) {
  309. expectValue( value, expected[ --i ], { direction: 'backward' } );
  310. }
  311. expect( i ).to.equal( 0 );
  312. } );
  313. } );
  314. describe( 'range ends inside the text', () => {
  315. let expected, range;
  316. before( () => {
  317. expected = [
  318. {
  319. type: 'elementStart',
  320. item: img1,
  321. previousPosition: new Position( root, 0 ),
  322. nextPosition: new Position( img1, 0 )
  323. },
  324. {
  325. type: 'elementEnd',
  326. item: img1,
  327. previousPosition: new Position( img1, 0 ),
  328. nextPosition: new Position( root, 1 )
  329. },
  330. {
  331. type: 'elementStart',
  332. item: paragraph,
  333. previousPosition: new Position( root, 1 ),
  334. nextPosition: new Position( paragraph, 0 )
  335. },
  336. {
  337. type: 'elementStart',
  338. item: bold,
  339. previousPosition: new Position( paragraph, 0 ),
  340. nextPosition: new Position( bold, 0 )
  341. },
  342. {
  343. type: 'text',
  344. text: 'ab',
  345. previousPosition: new Position( bold, 0 ),
  346. nextPosition: new Position( textAbcd, 2 )
  347. }
  348. ];
  349. range = new Range( rootBeginning, new Position( textAbcd, 2 ) );
  350. } );
  351. it( 'should return part of the text', () => {
  352. const iterator = new TreeWalker( { boundaries: range } );
  353. let i = 0;
  354. for ( const value of iterator ) {
  355. expectValue( value, expected[ i++ ] );
  356. }
  357. expect( i ).to.equal( expected.length );
  358. } );
  359. it( 'should return part of the text going backward', () => {
  360. const iterator = new TreeWalker( {
  361. boundaries: range,
  362. startPosition: range.end,
  363. direction: 'backward'
  364. } );
  365. let i = expected.length;
  366. for ( const value of iterator ) {
  367. expectValue( value, expected[ --i ], { direction: 'backward' } );
  368. }
  369. expect( i ).to.equal( 0 );
  370. } );
  371. } );
  372. describe( 'range starts and ends inside the same text', () => {
  373. let expected, range;
  374. before( () => {
  375. expected = [
  376. {
  377. type: 'text',
  378. text: 'bc',
  379. previousPosition: new Position( textAbcd, 1 ),
  380. nextPosition: new Position( textAbcd, 3 )
  381. }
  382. ];
  383. range = new Range( new Position( textAbcd, 1 ), new Position( textAbcd, 3 ) );
  384. } );
  385. it( 'should return part of the text', () => {
  386. const iterator = new TreeWalker( { boundaries: range } );
  387. let i = 0;
  388. for ( const value of iterator ) {
  389. expectValue( value, expected[ i++ ] );
  390. }
  391. expect( i ).to.equal( expected.length );
  392. } );
  393. it( 'should return part of the text going backward', () => {
  394. const iterator = new TreeWalker( {
  395. boundaries: range,
  396. startPosition: range.end,
  397. direction: 'backward'
  398. } );
  399. let i = expected.length;
  400. for ( const value of iterator ) {
  401. expectValue( value, expected[ --i ], { direction: 'backward' } );
  402. }
  403. expect( i ).to.equal( 0 );
  404. } );
  405. } );
  406. describe( 'custom start position', () => {
  407. it( 'should iterating from the start position', () => {
  408. const expected = [
  409. {
  410. type: 'text',
  411. text: 'y',
  412. previousPosition: new Position( paragraph, 1 ),
  413. nextPosition: new Position( paragraph, 2 )
  414. },
  415. {
  416. type: 'elementStart',
  417. item: img2,
  418. previousPosition: new Position( paragraph, 2 ),
  419. nextPosition: new Position( img2, 0 )
  420. },
  421. {
  422. type: 'elementEnd',
  423. item: img2,
  424. previousPosition: new Position( img2, 0 ),
  425. nextPosition: new Position( paragraph, 3 )
  426. }
  427. ];
  428. const range = Range._createFromParentsAndOffsets( bold, 1, paragraph, 3 );
  429. const iterator = new TreeWalker( {
  430. boundaries: range,
  431. startPosition: new Position( paragraph, 1 )
  432. } );
  433. let i = 0;
  434. for ( const value of iterator ) {
  435. expectValue( value, expected[ i++ ] );
  436. }
  437. expect( i ).to.equal( expected.length );
  438. } );
  439. it( 'should iterating from the start position going backward', () => {
  440. const expected = [
  441. {
  442. type: 'text',
  443. text: 'bcd',
  444. previousPosition: new Position( textAbcd, 1 ),
  445. nextPosition: new Position( bold, 1 )
  446. },
  447. {
  448. type: 'elementEnd',
  449. item: bold,
  450. previousPosition: new Position( bold, 1 ),
  451. nextPosition: new Position( paragraph, 1 )
  452. },
  453. {
  454. type: 'text',
  455. text: 'y',
  456. previousPosition: new Position( paragraph, 1 ),
  457. nextPosition: new Position( paragraph, 2 )
  458. }
  459. ];
  460. const range = new Range( new Position( textAbcd, 1 ), new Position( paragraph, 3 ) );
  461. const iterator = new TreeWalker( {
  462. boundaries: range,
  463. startPosition: new Position( paragraph, 2 ),
  464. direction: 'backward'
  465. } );
  466. let i = expected.length;
  467. for ( const value of iterator ) {
  468. expectValue( value, expected[ --i ], { direction: 'backward' } );
  469. }
  470. expect( i ).to.equal( 0 );
  471. } );
  472. } );
  473. } );
  474. describe( 'iterate by every single characters `singleCharacter`', () => {
  475. describe( 'whole root', () => {
  476. let expected;
  477. before( () => {
  478. expected = [
  479. {
  480. type: 'elementStart',
  481. item: img1,
  482. previousPosition: new Position( root, 0 ),
  483. nextPosition: new Position( img1, 0 )
  484. },
  485. {
  486. type: 'elementEnd',
  487. item: img1,
  488. previousPosition: new Position( img1, 0 ),
  489. nextPosition: new Position( root, 1 )
  490. },
  491. {
  492. type: 'elementStart',
  493. item: paragraph,
  494. previousPosition: new Position( root, 1 ),
  495. nextPosition: new Position( paragraph, 0 )
  496. },
  497. {
  498. type: 'elementStart',
  499. item: bold,
  500. previousPosition: new Position( paragraph, 0 ),
  501. nextPosition: new Position( bold, 0 )
  502. },
  503. {
  504. type: 'text',
  505. text: 'a',
  506. previousPosition: new Position( bold, 0 ),
  507. nextPosition: new Position( textAbcd, 1 )
  508. },
  509. {
  510. type: 'text',
  511. text: 'b',
  512. previousPosition: new Position( textAbcd, 1 ),
  513. nextPosition: new Position( textAbcd, 2 )
  514. },
  515. {
  516. type: 'text',
  517. text: 'c',
  518. previousPosition: new Position( textAbcd, 2 ),
  519. nextPosition: new Position( textAbcd, 3 )
  520. },
  521. {
  522. type: 'text',
  523. text: 'd',
  524. previousPosition: new Position( textAbcd, 3 ),
  525. nextPosition: new Position( bold, 1 )
  526. },
  527. {
  528. type: 'elementEnd',
  529. item: bold,
  530. previousPosition: new Position( bold, 1 ),
  531. nextPosition: new Position( paragraph, 1 )
  532. },
  533. {
  534. type: 'text',
  535. text: 'y',
  536. previousPosition: new Position( paragraph, 1 ),
  537. nextPosition: new Position( paragraph, 2 )
  538. },
  539. {
  540. type: 'elementStart',
  541. item: img2,
  542. previousPosition: new Position( paragraph, 2 ),
  543. nextPosition: new Position( img2, 0 )
  544. },
  545. {
  546. type: 'elementEnd',
  547. item: img2,
  548. previousPosition: new Position( img2, 0 ),
  549. nextPosition: new Position( paragraph, 3 )
  550. },
  551. {
  552. type: 'text',
  553. text: 'x',
  554. previousPosition: new Position( paragraph, 3 ),
  555. nextPosition: new Position( paragraph, 4 )
  556. },
  557. {
  558. type: 'elementEnd',
  559. item: paragraph,
  560. previousPosition: new Position( paragraph, 4 ),
  561. nextPosition: new Position( root, 2 )
  562. }
  563. ];
  564. } );
  565. it( 'should return single characters', () => {
  566. const iterator = new TreeWalker( { startPosition: rootBeginning, singleCharacters: true } );
  567. let i = 0;
  568. for ( const value of iterator ) {
  569. expectValue( value, expected[ i++ ] );
  570. }
  571. expect( i ).to.equal( expected.length );
  572. } );
  573. it( 'should return single characters going backward', () => {
  574. const iterator = new TreeWalker( {
  575. startPosition: rootEnding,
  576. singleCharacters: true,
  577. direction: 'backward'
  578. } );
  579. let i = expected.length;
  580. for ( const value of iterator ) {
  581. expectValue( value, expected[ --i ], { direction: 'backward' } );
  582. }
  583. expect( i ).to.equal( 0 );
  584. } );
  585. } );
  586. describe( 'range', () => {
  587. let range, expected;
  588. before( () => {
  589. expected = [
  590. {
  591. type: 'text',
  592. text: 'a',
  593. previousPosition: new Position( bold, 0 ),
  594. nextPosition: new Position( textAbcd, 1 )
  595. },
  596. {
  597. type: 'text',
  598. text: 'b',
  599. previousPosition: new Position( textAbcd, 1 ),
  600. nextPosition: new Position( textAbcd, 2 )
  601. },
  602. {
  603. type: 'text',
  604. text: 'c',
  605. previousPosition: new Position( textAbcd, 2 ),
  606. nextPosition: new Position( textAbcd, 3 )
  607. },
  608. {
  609. type: 'text',
  610. text: 'd',
  611. previousPosition: new Position( textAbcd, 3 ),
  612. nextPosition: new Position( bold, 1 )
  613. },
  614. {
  615. type: 'elementEnd',
  616. item: bold,
  617. previousPosition: new Position( bold, 1 ),
  618. nextPosition: new Position( paragraph, 1 )
  619. },
  620. {
  621. type: 'text',
  622. text: 'y',
  623. previousPosition: new Position( paragraph, 1 ),
  624. nextPosition: new Position( paragraph, 2 )
  625. },
  626. {
  627. type: 'elementStart',
  628. item: img2,
  629. previousPosition: new Position( paragraph, 2 ),
  630. nextPosition: new Position( img2, 0 )
  631. }
  632. ];
  633. range = new Range( new Position( bold, 0 ), new Position( img2, 0 ) );
  634. } );
  635. it( 'should respect boundaries', () => {
  636. const iterator = new TreeWalker( { boundaries: range, singleCharacters: true } );
  637. let i = 0;
  638. for ( const value of iterator ) {
  639. expectValue( value, expected[ i++ ] );
  640. }
  641. expect( i ).to.equal( expected.length );
  642. } );
  643. it( 'should respect boundaries going backward', () => {
  644. const iterator = new TreeWalker( {
  645. boundaries: range,
  646. singleCharacters: true,
  647. direction: 'backward'
  648. } );
  649. let i = expected.length;
  650. for ( const value of iterator ) {
  651. expectValue( value, expected[ --i ], { direction: 'backward' } );
  652. }
  653. expect( i ).to.equal( 0 );
  654. } );
  655. } );
  656. } );
  657. describe( 'iterate omitting child nodes and elementEnd `shallow`', () => {
  658. let expected;
  659. before( () => {
  660. expected = [
  661. {
  662. type: 'elementStart',
  663. item: img1,
  664. previousPosition: new Position( root, 0 ),
  665. nextPosition: new Position( root, 1 )
  666. },
  667. {
  668. type: 'elementStart',
  669. item: paragraph,
  670. previousPosition: new Position( root, 1 ),
  671. nextPosition: new Position( root, 2 )
  672. }
  673. ];
  674. } );
  675. it( 'should not enter elements', () => {
  676. const iterator = new TreeWalker( { startPosition: rootBeginning, shallow: true } );
  677. let i = 0;
  678. for ( const value of iterator ) {
  679. expectValue( value, expected[ i++ ] );
  680. }
  681. expect( i ).to.equal( expected.length );
  682. } );
  683. it( 'should not enter elements going backward', () => {
  684. const iterator = new TreeWalker( { startPosition: rootEnding, shallow: true, direction: 'backward' } );
  685. let i = expected.length;
  686. for ( const value of iterator ) {
  687. expectValue( value, expected[ --i ], { direction: 'backward' } );
  688. }
  689. expect( i ).to.equal( 0 );
  690. } );
  691. } );
  692. describe( 'iterate omitting elementEnd `ignoreElementEnd`', () => {
  693. describe( 'merged text', () => {
  694. let expected;
  695. before( () => {
  696. expected = [
  697. {
  698. type: 'elementStart',
  699. item: img1,
  700. previousPosition: new Position( root, 0 ),
  701. nextPosition: new Position( img1, 0 )
  702. },
  703. {
  704. type: 'elementStart',
  705. item: paragraph,
  706. previousPosition: new Position( root, 1 ),
  707. nextPosition: new Position( paragraph, 0 )
  708. },
  709. {
  710. type: 'elementStart',
  711. item: bold,
  712. previousPosition: new Position( paragraph, 0 ),
  713. nextPosition: new Position( bold, 0 )
  714. },
  715. {
  716. type: 'text',
  717. text: 'abcd',
  718. previousPosition: new Position( bold, 0 ),
  719. nextPosition: new Position( bold, 1 )
  720. },
  721. {
  722. type: 'text',
  723. text: 'y',
  724. previousPosition: new Position( paragraph, 1 ),
  725. nextPosition: new Position( paragraph, 2 )
  726. },
  727. {
  728. type: 'elementStart',
  729. item: img2,
  730. previousPosition: new Position( paragraph, 2 ),
  731. nextPosition: new Position( img2, 0 )
  732. },
  733. {
  734. type: 'text',
  735. text: 'x',
  736. previousPosition: new Position( paragraph, 3 ),
  737. nextPosition: new Position( paragraph, 4 )
  738. }
  739. ];
  740. } );
  741. it( 'should iterate ignoring elementEnd', () => {
  742. const iterator = new TreeWalker( { startPosition: rootBeginning, ignoreElementEnd: true } );
  743. let i = 0;
  744. for ( const value of iterator ) {
  745. expectValue( value, expected[ i++ ] );
  746. }
  747. expect( i ).to.equal( expected.length );
  748. } );
  749. it( 'should iterate ignoring elementEnd going backward', () => {
  750. const iterator = new TreeWalker( {
  751. startPosition: rootEnding,
  752. ignoreElementEnd: true,
  753. direction: 'backward'
  754. } );
  755. let i = expected.length;
  756. for ( const value of iterator ) {
  757. expectValue( value, expected[ --i ], { direction: 'backward' } );
  758. }
  759. expect( i ).to.equal( 0 );
  760. } );
  761. } );
  762. describe( 'single character', () => {
  763. let expected;
  764. before( () => {
  765. expected = [
  766. {
  767. type: 'elementStart',
  768. item: img1,
  769. previousPosition: new Position( root, 0 ),
  770. nextPosition: new Position( img1, 0 )
  771. },
  772. {
  773. type: 'elementStart',
  774. item: paragraph,
  775. previousPosition: new Position( root, 1 ),
  776. nextPosition: new Position( paragraph, 0 )
  777. },
  778. {
  779. type: 'elementStart',
  780. item: bold,
  781. previousPosition: new Position( paragraph, 0 ),
  782. nextPosition: new Position( bold, 0 )
  783. },
  784. {
  785. type: 'text',
  786. text: 'a',
  787. previousPosition: new Position( bold, 0 ),
  788. nextPosition: new Position( textAbcd, 1 )
  789. },
  790. {
  791. type: 'text',
  792. text: 'b',
  793. previousPosition: new Position( textAbcd, 1 ),
  794. nextPosition: new Position( textAbcd, 2 )
  795. },
  796. {
  797. type: 'text',
  798. text: 'c',
  799. previousPosition: new Position( textAbcd, 2 ),
  800. nextPosition: new Position( textAbcd, 3 )
  801. },
  802. {
  803. type: 'text',
  804. text: 'd',
  805. previousPosition: new Position( textAbcd, 3 ),
  806. nextPosition: new Position( bold, 1 )
  807. },
  808. {
  809. type: 'text',
  810. text: 'y',
  811. previousPosition: new Position( paragraph, 1 ),
  812. nextPosition: new Position( paragraph, 2 )
  813. },
  814. {
  815. type: 'elementStart',
  816. item: img2,
  817. previousPosition: new Position( paragraph, 2 ),
  818. nextPosition: new Position( img2, 0 )
  819. },
  820. {
  821. type: 'text',
  822. text: 'x',
  823. previousPosition: new Position( paragraph, 3 ),
  824. nextPosition: new Position( paragraph, 4 )
  825. }
  826. ];
  827. } );
  828. it( 'should return single characters ignoring elementEnd', () => {
  829. const iterator = new TreeWalker( {
  830. startPosition: rootBeginning,
  831. singleCharacters: true,
  832. ignoreElementEnd: true
  833. } );
  834. let i = 0;
  835. for ( const value of iterator ) {
  836. expectValue( value, expected[ i++ ] );
  837. }
  838. expect( i ).to.equal( expected.length );
  839. } );
  840. it( 'should return single characters ignoring elementEnd going backward', () => {
  841. const iterator = new TreeWalker( {
  842. startPosition: rootEnding,
  843. singleCharacters: true,
  844. ignoreElementEnd: true,
  845. direction: 'backward'
  846. } );
  847. let i = expected.length;
  848. for ( const value of iterator ) {
  849. expectValue( value, expected[ --i ], { direction: 'backward' } );
  850. }
  851. expect( i ).to.equal( 0 );
  852. } );
  853. } );
  854. } );
  855. it( 'should not return elementEnd for a text node when iteration begins at the end of that text node', () => {
  856. const iterator = new TreeWalker( {
  857. startPosition: Position._createAt( textAbcd, 'end' )
  858. } );
  859. const step = iterator.next();
  860. expect( step.value.type ).to.equal( 'elementEnd' );
  861. expect( step.value.item ).to.equal( bold );
  862. } );
  863. it( 'should not return elementStart for a text node when iteration begins at the start of that text node', () => {
  864. const iterator = new TreeWalker( {
  865. startPosition: Position._createAt( textAbcd, 0 ),
  866. direction: 'backward'
  867. } );
  868. const step = iterator.next();
  869. expect( step.value.type ).to.equal( 'elementStart' );
  870. expect( step.value.item ).to.equal( bold );
  871. } );
  872. it( 'should iterate over document fragment', () => {
  873. const foo = new Text( doc, 'foo' );
  874. const bar = new Text( doc, 'bar' );
  875. const p = new ContainerElement( doc, 'p', null, foo );
  876. const b = new AttributeElement( doc, 'b', null, bar );
  877. const docFrag = new DocumentFragment( doc, [ p, b ] );
  878. const expected = [
  879. {
  880. type: 'elementStart',
  881. item: p,
  882. previousPosition: new Position( docFrag, 0 ),
  883. nextPosition: new Position( p, 0 )
  884. },
  885. {
  886. type: 'text',
  887. text: 'foo',
  888. previousPosition: new Position( p, 0 ),
  889. nextPosition: new Position( p, 1 )
  890. },
  891. {
  892. type: 'elementEnd',
  893. item: p,
  894. previousPosition: new Position( p, 1 ),
  895. nextPosition: new Position( docFrag, 1 )
  896. },
  897. {
  898. type: 'elementStart',
  899. item: b,
  900. previousPosition: new Position( docFrag, 1 ),
  901. nextPosition: new Position( b, 0 )
  902. },
  903. {
  904. type: 'text',
  905. text: 'bar',
  906. previousPosition: new Position( b, 0 ),
  907. nextPosition: new Position( b, 1 )
  908. },
  909. {
  910. type: 'elementEnd',
  911. item: b,
  912. previousPosition: new Position( b, 1 ),
  913. nextPosition: new Position( docFrag, 2 )
  914. }
  915. ];
  916. const iterator = new TreeWalker( { boundaries: Range._createIn( docFrag ) } );
  917. let i = 0;
  918. for ( const value of iterator ) {
  919. expectValue( value, expected[ i++ ] );
  920. }
  921. expect( i ).to.equal( expected.length );
  922. } );
  923. describe( 'skip', () => {
  924. describe( 'forward treewalker', () => {
  925. it( 'should jump over all text nodes', () => {
  926. const walker = new TreeWalker( {
  927. startPosition: new Position( paragraph, 0 )
  928. } );
  929. walker.skip( value => value.type == 'text' || value.item.name == 'b' );
  930. expect( walker.position.parent ).to.equal( paragraph );
  931. expect( walker.position.offset ).to.equal( 2 );
  932. } );
  933. it( 'should do not move if the condition is false', () => {
  934. const walker = new TreeWalker( {
  935. startPosition: new Position( bold, 0 )
  936. } );
  937. walker.skip( () => false );
  938. expect( walker.position.parent ).to.equal( bold );
  939. expect( walker.position.offset ).to.equal( 0 );
  940. } );
  941. it( 'should do not move if the condition is false and the position is in text node', () => {
  942. const walker = new TreeWalker( {
  943. startPosition: new Position( bold.getChild( 0 ), 2 )
  944. } );
  945. walker.skip( () => false );
  946. expect( walker.position.parent ).to.equal( bold.getChild( 0 ) );
  947. expect( walker.position.offset ).to.equal( 2 );
  948. } );
  949. it( 'should move to the end if the condition is true', () => {
  950. const walker = new TreeWalker( {
  951. startPosition: new Position( bold, 0 )
  952. } );
  953. walker.skip( () => true );
  954. expect( walker.position.parent ).to.equal( rootEnding.parent );
  955. expect( walker.position.offset ).to.equal( rootEnding.offset );
  956. } );
  957. } );
  958. describe( 'backward treewalker', () => {
  959. it( 'should jump over all text nodes', () => {
  960. const walker = new TreeWalker( {
  961. startPosition: new Position( bold.getChild( 0 ), 2 ),
  962. direction: 'backward'
  963. } );
  964. walker.skip( value => value.type == 'text' || value.item.name == 'b' );
  965. expect( walker.position.parent ).to.equal( paragraph );
  966. expect( walker.position.offset ).to.equal( 0 );
  967. } );
  968. it( 'should do not move if the condition is false', () => {
  969. const walker = new TreeWalker( {
  970. startPosition: new Position( bold, 0 ),
  971. direction: 'backward'
  972. } );
  973. walker.skip( () => false );
  974. expect( walker.position.parent ).to.equal( bold );
  975. expect( walker.position.offset ).to.equal( 0 );
  976. } );
  977. it( 'should move to the end if the condition is true', () => {
  978. const walker = new TreeWalker( {
  979. startPosition: new Position( bold, 0 ),
  980. direction: 'backward'
  981. } );
  982. walker.skip( () => true );
  983. expect( walker.position.parent ).to.equal( rootBeginning.parent );
  984. expect( walker.position.offset ).to.equal( rootBeginning.offset );
  985. } );
  986. } );
  987. } );
  988. } );
  989. function expectValue( value, expected, options = {} ) {
  990. let expectedPreviousPosition, expectedNextPosition;
  991. if ( options.direction == 'backward' ) {
  992. expectedNextPosition = expected.previousPosition;
  993. expectedPreviousPosition = expected.nextPosition;
  994. } else {
  995. expectedNextPosition = expected.nextPosition;
  996. expectedPreviousPosition = expected.previousPosition;
  997. }
  998. expect( value.type ).to.equal( expected.type );
  999. expect( value.previousPosition ).to.deep.equal( expectedPreviousPosition );
  1000. expect( value.nextPosition ).to.deep.equal( expectedNextPosition );
  1001. if ( value.type == 'text' ) {
  1002. expectText( value, expected );
  1003. } else if ( value.type == 'elementStart' ) {
  1004. expectStart( value, expected );
  1005. } else if ( value.type == 'elementEnd' ) {
  1006. expectEnd( value, expected );
  1007. }
  1008. }
  1009. function expectText( value, expected ) {
  1010. expect( value.item.data ).to.equal( expected.text );
  1011. expect( value.length ).to.equal( value.item.data.length );
  1012. }
  1013. function expectStart( value, expected ) {
  1014. expect( value.item ).to.equal( expected.item );
  1015. expect( value.length ).to.equal( 1 );
  1016. }
  1017. function expectEnd( value, expected ) {
  1018. expect( value.item ).to.equal( expected.item );
  1019. expect( value.length ).to.be.undefined;
  1020. }