treewalker.js 30 KB

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