treewalker.js 29 KB

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