8
0

treewalker.js 30 KB

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