treewalker.js 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350
  1. /**
  2. * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
  3. * For licensing, see LICENSE.md.
  4. */
  5. /* bender-tags: treemodel */
  6. 'use strict';
  7. import Document from '/ckeditor5/core/treemodel/document.js';
  8. import Element from '/ckeditor5/core/treemodel/element.js';
  9. import Text from '/ckeditor5/core/treemodel/text.js';
  10. import TreeWalker from '/ckeditor5/core/treemodel/treewalker.js';
  11. import Position from '/ckeditor5/core/treemodel/position.js';
  12. import Range from '/ckeditor5/core/treemodel/range.js';
  13. import CKEditorError from '/ckeditor5/core/ckeditorerror.js';
  14. describe( 'TreeWalker', () => {
  15. let expectedSingle, expected, expectedIgnoreEnd, expectedSingleIgnoreEnd;
  16. let expectedShallow, expectedCroppedStart, expectedCroppedEnd;
  17. let doc, root, img1, paragraph, b, a, r, img2, x;
  18. let rootBeginning;
  19. before( () => {
  20. doc = new Document();
  21. root = doc.createRoot( 'root' );
  22. // root
  23. // |- img1
  24. // |- p
  25. // |- B -bold
  26. // |- A -bold
  27. // |- R
  28. // |
  29. // |- img2
  30. // |
  31. // |- X
  32. b = new Text( 'b', { bold: true } );
  33. a = new Text( 'a', { bold: true } );
  34. r = new Text( 'r' );
  35. img2 = new Element( 'img2' );
  36. x = new Text( 'x' );
  37. paragraph = new Element( 'p', [], [ b, a, r, img2, x ] );
  38. img1 = new Element( 'img1' );
  39. root.insertChildren( 0, [ img1, paragraph ] );
  40. rootBeginning = new Position( root, [ 0 ] );
  41. expected = [
  42. { type: 'ELEMENT_START', item: img1 },
  43. { type: 'ELEMENT_END', item: img1 },
  44. { type: 'ELEMENT_START', item: paragraph },
  45. { type: 'TEXT', text: 'ba', attrs: [ [ 'bold', true ] ] },
  46. { type: 'TEXT', text: 'r', attrs: [] },
  47. { type: 'ELEMENT_START', item: img2 },
  48. { type: 'ELEMENT_END', item: img2 },
  49. { type: 'TEXT', text: 'x', attrs: [] },
  50. { type: 'ELEMENT_END', item: paragraph }
  51. ];
  52. expectedCroppedEnd = [
  53. { type: 'ELEMENT_START', item: img1 },
  54. { type: 'ELEMENT_END', item: img1 },
  55. { type: 'ELEMENT_START', item: paragraph },
  56. { type: 'TEXT', text: 'b', attrs: [ [ 'bold', true ] ] }
  57. ];
  58. expectedCroppedStart = [
  59. { type: 'TEXT', text: 'a', attrs: [ [ 'bold', true ] ] },
  60. { type: 'TEXT', text: 'r', attrs: [] },
  61. { type: 'ELEMENT_START', item: img2 },
  62. { type: 'ELEMENT_END', item: img2 },
  63. { type: 'TEXT', text: 'x', attrs: [] },
  64. { type: 'ELEMENT_END', item: paragraph }
  65. ];
  66. expectedSingle = [
  67. { type: 'ELEMENT_START', item: img1 },
  68. { type: 'ELEMENT_END', item: img1 },
  69. { type: 'ELEMENT_START', item: paragraph },
  70. { type: 'CHARACTER', text: 'b', attrs: [ [ 'bold', true ] ] },
  71. { type: 'CHARACTER', text: 'a', attrs: [ [ 'bold', true ] ] },
  72. { type: 'CHARACTER', text: 'r', attrs: [] },
  73. { type: 'ELEMENT_START', item: img2 },
  74. { type: 'ELEMENT_END', item: img2 },
  75. { type: 'CHARACTER', text: 'x', attrs: [] },
  76. { type: 'ELEMENT_END', item: paragraph }
  77. ];
  78. expectedIgnoreEnd = [
  79. { type: 'ELEMENT_START', item: img1 },
  80. { type: 'ELEMENT_START', item: paragraph },
  81. { type: 'TEXT', text: 'ba', attrs: [ [ 'bold', true ] ] },
  82. { type: 'TEXT', text: 'r', attrs: [] },
  83. { type: 'ELEMENT_START', item: img2 },
  84. { type: 'TEXT', text: 'x', attrs: [] }
  85. ];
  86. expectedSingleIgnoreEnd = [
  87. { type: 'ELEMENT_START', item: img1 },
  88. { type: 'ELEMENT_START', item: paragraph },
  89. { type: 'CHARACTER', text: 'b', attrs: [ [ 'bold', true ] ] },
  90. { type: 'CHARACTER', text: 'a', attrs: [ [ 'bold', true ] ] },
  91. { type: 'CHARACTER', text: 'r', attrs: [] },
  92. { type: 'ELEMENT_START', item: img2 },
  93. { type: 'CHARACTER', text: 'x', attrs: [] }
  94. ];
  95. expectedShallow = [
  96. { type: 'ELEMENT_START', item: img1 },
  97. { type: 'ELEMENT_START', item: paragraph }
  98. ];
  99. } );
  100. function expectItem( item, expected, options ) {
  101. expect( item.done ).to.be.false;
  102. expectValue( item.value, expected, options );
  103. }
  104. function expectValue( value, expected, options ) {
  105. expect( value.type ).to.equal( expected.type );
  106. if ( value.type == 'TEXT' ) {
  107. expectText( value, expected, options );
  108. } else if ( value.type == 'CHARACTER' ) {
  109. expectCharacter( value, expected, options );
  110. } else if ( value.type == 'ELEMENT_START' ) {
  111. expectStart( value, expected, options );
  112. } else if ( value.type == 'ELEMENT_END' ) {
  113. expectEnd( value, expected, options );
  114. }
  115. }
  116. function expectText( value, expected ) {
  117. expect( value.item.text ).to.equal( expected.text );
  118. expect( Array.from( value.item.first._attrs ) ).to.deep.equal( expected.attrs );
  119. expect( value.length ).to.equal( value.item.text.length );
  120. expect( value.previousPosition ).to.deep.equal( Position.createBefore( value.item.first ) );
  121. expect( value.nextPosition ).to.deep.equal( Position.createAfter( value.item.last ) );
  122. }
  123. function expectCharacter( value, expected ) {
  124. expect( value.item.character ).to.equal( expected.text );
  125. expect( Array.from( value.item._attrs ) ).to.deep.equal( expected.attrs );
  126. expect( value.length ).to.equal( value.item.character.length );
  127. expect( value.previousPosition ).to.deep.equal( Position.createBefore( value.item ) );
  128. expect( value.nextPosition ).to.deep.equal( Position.createAfter( value.item ) );
  129. }
  130. function expectStart( value, expected, options ) {
  131. expect( value.item ).to.equal( expected.item );
  132. expect( value.length ).to.equal( 1 );
  133. expect( value.previousPosition ).to.deep.equal( Position.createBefore( value.item ) );
  134. if ( options && options.shallow ) {
  135. expect( value.previousPosition ).to.deep.equal( Position.createBefore( value.item ) );
  136. } else {
  137. expect( value.nextPosition ).to.deep.equal( Position.createFromParentAndOffset( value.item, 0 ) );
  138. }
  139. }
  140. function expectEnd( value, expected ) {
  141. expect( value.item ).to.equal( expected.item );
  142. expect( value.length ).to.be.undefined;
  143. expect( value.previousPosition ).to.deep.equal(
  144. Position.createFromParentAndOffset( value.item, value.item.getChildCount() ) );
  145. expect( value.nextPosition ).to.deep.equal( Position.createAfter( value.item ) );
  146. }
  147. it( 'should provide iterator methods', () => {
  148. let iterator = new TreeWalker( { startPosition: rootBeginning } );
  149. for ( let i = 0; i <= 8; i++ ) {
  150. expectItem( iterator.next(), expected[ i ] );
  151. }
  152. expect( iterator.next() ).to.have.property( 'done' ).that.is.true;
  153. } );
  154. it( 'should provide iterator interface', () => {
  155. let iterator = new TreeWalker( { startPosition: rootBeginning } );
  156. let i = 0;
  157. for ( let value of iterator ) {
  158. expectValue( value, expected[ i ] );
  159. i++;
  160. }
  161. expect( i ).to.equal( expected.length );
  162. } );
  163. it( 'should start at the startPosition', () => {
  164. let iterator = new TreeWalker( { startPosition: new Position( root, [ 1 ] ) } );
  165. let i = 2;
  166. for ( let value of iterator ) {
  167. expectValue( value, expected[ i ] );
  168. i++;
  169. }
  170. expect( i ).to.equal( expected.length );
  171. } );
  172. it( 'should iterating over the range', () => {
  173. let start = new Position( root, [ 1 ] );
  174. let end = new Position( root, [ 1, 4 ] );
  175. let range = new Range( start, end );
  176. let iterator = new TreeWalker( { boundaries: range } );
  177. let i = 2;
  178. for ( let value of iterator ) {
  179. expectValue( value, expected[ i ] );
  180. i++;
  181. }
  182. expect( i ).to.equal( 7 );
  183. } );
  184. it( 'should start iterating at startPosition even if the range is defined', () => {
  185. let start = new Position( root, [ 1 ] );
  186. let end = new Position( root, [ 1, 4 ] );
  187. let range = new Range( start, end );
  188. let iterator = new TreeWalker( { boundaries: range } );
  189. let i = 2;
  190. for ( let value of iterator ) {
  191. expectValue( value, expected[ i ] );
  192. i++;
  193. }
  194. expect( i ).to.equal( 7 );
  195. } );
  196. it( 'should return part of the text if range starts inside the text', () => {
  197. let iterator = new TreeWalker( { startPosition: new Position( root, [ 1, 1 ] ) } );
  198. let i = 0;
  199. for ( let value of iterator ) {
  200. expectValue( value, expectedCroppedStart[ i ] );
  201. i++;
  202. }
  203. expect( i ).to.equal( expectedCroppedStart.length );
  204. } );
  205. it( 'should return part of the text if range ends inside the text', () => {
  206. let start = rootBeginning;
  207. let end = new Position( root, [ 1, 1 ] );
  208. let range = new Range( start, end );
  209. let iterator = new TreeWalker( { boundaries: range } );
  210. let i = 0;
  211. for ( let value of iterator ) {
  212. expectValue( value, expectedCroppedEnd[ i ] );
  213. i++;
  214. }
  215. expect( i ).to.equal( expectedCroppedEnd.length );
  216. } );
  217. describe( 'singleCharacters', () => {
  218. it( 'should return single characters', () => {
  219. let iterator = new TreeWalker( { startPosition: rootBeginning, singleCharacters: true } );
  220. let i = 0;
  221. for ( let value of iterator ) {
  222. expectValue( value, expectedSingle[ i ] );
  223. i++;
  224. }
  225. expect( i ).to.equal( expectedSingle.length );
  226. } );
  227. it( 'should respect boundaries', () => {
  228. let start = new Position( root, [ 1, 0 ] ); // p, 0
  229. let end = new Position( root, [ 1, 3, 0 ] ); // img, 0
  230. let iterator = new TreeWalker( { boundaries: new Range( start, end ), singleCharacters: true } );
  231. let i = 3;
  232. for ( let value of iterator ) {
  233. expectValue( value, expectedSingle[ i ] );
  234. i++;
  235. }
  236. expect( i ).to.equal( 7 );
  237. } );
  238. } );
  239. describe( 'shallow', () => {
  240. it( 'should not enter elements', () => {
  241. let iterator = new TreeWalker( { startPosition: rootBeginning, shallow: true } );
  242. let i = 0;
  243. for ( let value of iterator ) {
  244. expectValue( value, expectedShallow[ i ], { shallow: true } );
  245. i++;
  246. }
  247. expect( i ).to.equal( expectedShallow.length );
  248. } );
  249. } );
  250. describe( 'ignoreElementEnd', () => {
  251. it( 'should iterate ignoring ELEMENT_END', () => {
  252. let iterator = new TreeWalker( { startPosition: rootBeginning, ignoreElementEnd: true } );
  253. let i = 0;
  254. for ( let value of iterator ) {
  255. expectValue( value, expectedIgnoreEnd[ i ] );
  256. i++;
  257. }
  258. expect( i ).to.equal( expectedIgnoreEnd.length );
  259. } );
  260. it( 'should return single characters ignoring ELEMENT_END', () => {
  261. let iterator = new TreeWalker( { startPosition: rootBeginning, singleCharacters: true, ignoreElementEnd: true } );
  262. let i = 0;
  263. for ( let value of iterator ) {
  264. expectValue( value, expectedSingleIgnoreEnd[ i ] );
  265. i++;
  266. }
  267. expect( i ).to.equal( expectedSingleIgnoreEnd.length );
  268. } );
  269. } );
  270. it( 'should throw if neither boundaries nor starting position is set', () => {
  271. expect( () => {
  272. new TreeWalker();
  273. } ).to.throw( CKEditorError, /^tree-walker-no-start-position/ );
  274. expect( () => {
  275. new TreeWalker( {} );
  276. } ).to.throw( CKEditorError, /^tree-walker-no-start-position/ );
  277. expect( () => {
  278. new TreeWalker( { mergeCharacters: true } );
  279. } ).to.throw( CKEditorError, /^tree-walker-no-start-position/ );
  280. } );
  281. } );