treewalker.js 30 KB

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