8
0

writer.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396
  1. /**
  2. * @license Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
  3. * For licensing, see LICENSE.md.
  4. */
  5. /* bender-tags: treeview */
  6. 'use strict';
  7. import Writer from '/ckeditor5/core/treeview/writer.js';
  8. import Element from '/ckeditor5/core/treeview/element.js';
  9. import Text from '/ckeditor5/core/treeview/text.js';
  10. import Position from '/ckeditor5/core/treeview/position.js';
  11. describe( 'Writer', () => {
  12. describe( 'isContainer', () => {
  13. it( 'should return true for container elements', () => {
  14. const containerElement = new Element( 'p' );
  15. const attributeElement = new Element( 'b' );
  16. const writer = new Writer();
  17. writer._priorities.set( attributeElement, 1 );
  18. expect( writer.isContainer( containerElement ) ).to.be.true;
  19. expect( writer.isContainer( attributeElement ) ).to.be.false;
  20. } );
  21. } );
  22. describe( 'isAttribute', () => {
  23. it( 'should return true for container elements', () => {
  24. const containerElement = new Element( 'p' );
  25. const attributeElement = new Element( 'b' );
  26. const writer = new Writer();
  27. writer._priorities.set( attributeElement, 1 );
  28. expect( writer.isAttribute( containerElement ) ).to.be.false;
  29. expect( writer.isAttribute( attributeElement ) ).to.be.true;
  30. } );
  31. } );
  32. describe( 'setPriority', () => {
  33. it( 'sets node priority', () => {
  34. const writer = new Writer();
  35. const nodeMock = {};
  36. writer.setPriority( nodeMock, 10 );
  37. expect( writer._priorities.get( nodeMock ) ).to.equal( 10 );
  38. } );
  39. } );
  40. describe( 'getPriority', () => {
  41. it( 'gets node priority', () => {
  42. const writer = new Writer();
  43. const nodeMock = {};
  44. writer._priorities.set( nodeMock, 12 );
  45. expect( writer.getPriority( nodeMock ) ).to.equal( 12 );
  46. } );
  47. } );
  48. describe( 'breakAttributes', () => {
  49. // Start position is at the begin of the text node.
  50. // After breaking it should be at the begin of the 'p' element.
  51. it( '<p>|foobar</p>', () => {
  52. const writer = new Writer();
  53. const text = new Text( 'foobar' );
  54. const element = new Element( 'p', {}, [ text ] );
  55. const position = new Position( text, 0 );
  56. const newPosition = writer.breakAttributes( position );
  57. const newParent = newPosition.parent;
  58. expect( newPosition.offset ).to.equal( 0 );
  59. expect( newParent ).to.equal( element );
  60. expect( newParent.getChildCount() ).to.equal( 1 );
  61. expect( newParent.getChild( 0 ) ).to.equal( text );
  62. } );
  63. // Start position is located in the middle of text node.
  64. // After breaking it should be located inside 'p' element, between two text nodes 'foo' and 'bar'.
  65. it( '<p>foo|bar</p>', () => {
  66. const writer = new Writer();
  67. const text = new Text( 'foobar' );
  68. const element = new Element( 'p', {}, [ text ] );
  69. const position = new Position( text, 3 );
  70. const newPosition = writer.breakAttributes( position );
  71. const newParent = newPosition.parent;
  72. expect( newPosition.offset ).to.equal( 1 );
  73. expect( newParent ).to.equal( element );
  74. expect( newParent.getChildCount() ).to.equal( 2 );
  75. expect( newParent.getChild( 0 ) ).to.be.instanceOf( Text );
  76. expect( newParent.getChild( 1 ) ).to.be.instanceOf( Text );
  77. expect( newParent.getChild( 0 ).data ).to.equal( 'foo' );
  78. expect( newParent.getChild( 1 ).data ).to.equal( 'bar' );
  79. } );
  80. // Start position is at the end of the text node.
  81. // After breaking it should be at the end of the 'p' element.
  82. it( '<p>foobar|</p>', () => {
  83. const writer = new Writer();
  84. const text = new Text( 'foobar' );
  85. const element = new Element( 'p', {}, [ text ] );
  86. const position = new Position( text, 6 );
  87. const newPosition = writer.breakAttributes( position );
  88. const newParent = newPosition.parent;
  89. expect( newPosition.offset ).to.equal( 1 );
  90. expect( newParent ).to.equal( element );
  91. expect( newParent.getChildCount() ).to.equal( 1 );
  92. expect( newParent.getChild( 0 ) ).to.equal( text );
  93. } );
  94. // <p><b>foo|bar</b></p> -> <p><b>foo</b>|<b>bar</b></p>
  95. it( '<p><b>foo|bar</b></p>', () => {
  96. const writer = new Writer();
  97. const text = new Text( 'foobar' );
  98. const b = new Element( 'b', { attribute: 'value' }, [ text ] );
  99. const p = new Element( 'p', {}, [ b ] );
  100. const position = new Position( text, 3 );
  101. writer.setPriority( b, 1 );
  102. const newPosition = writer.breakAttributes( position );
  103. const parent = newPosition.parent;
  104. expect( parent ).to.equal( p );
  105. expect( newPosition.offset ).to.equal( 1 );
  106. expect( p.getChildCount() ).to.equal( 2 );
  107. const child1 = p.getChild( 0 );
  108. const child2 = p.getChild( 1 );
  109. expect( child1 ).to.be.instanceOf( Element );
  110. expect( child1.same( b ) ).to.be.true;
  111. expect( writer.getPriority( child1 ) ).to.equal( 1 );
  112. expect( child1.getChildCount() ).to.equal( 1 );
  113. expect( child1.getChild( 0 ).data ).to.equal( 'foo' );
  114. expect( child2 ).to.be.instanceOf( Element );
  115. expect( child2.same( b ) ).to.be.true;
  116. expect( writer.getPriority( child2 ) ).to.equal( 1 );
  117. expect( child2.getChildCount() ).to.equal( 1 );
  118. expect( child2.getChild( 0 ).data ).to.equal( 'bar' );
  119. } );
  120. // <p><b><u>|foobar</u></b></p> -> <p>|<b><u>foobar</u></b></p>
  121. it( '<p><b><u>|foobar</u></b></p>', () => {
  122. const writer = new Writer();
  123. const text = new Text( 'foobar' );
  124. const u = new Element( 'u', { foo: 'bar' }, [ text ] );
  125. const b = new Element( 'b', { baz: 'qux' }, [ u ] );
  126. const p = new Element( 'p', {}, [ b ] );
  127. const position = new Position( text, 0 );
  128. writer.setPriority( u, 10 );
  129. writer.setPriority( b, 5 );
  130. const newPosition = writer.breakAttributes( position );
  131. const parent = newPosition.parent;
  132. expect( parent ).to.equal( p );
  133. expect( newPosition.offset ).to.equal( 0 );
  134. expect( p.getChildCount() ).to.equal( 1 );
  135. const b1 = p.getChild( 0 );
  136. expect( b1 ).to.equal( b );
  137. expect( writer.getPriority( b1 ) ).to.equal( 5 );
  138. expect( b1.getChildCount() ).to.equal( 1 );
  139. const u1 = b.getChild( 0 );
  140. expect( u1 ).to.equal( u );
  141. expect( writer.getPriority( u1 ) ).to.equal( 10 );
  142. expect( u1.getChildCount() ).to.equal( 1 );
  143. const text1 = u1.getChild( 0 );
  144. expect( text1 ).to.equal( text );
  145. expect( text1.data ).to.equal( 'foobar' );
  146. } );
  147. // <p><b><u>foo|bar</u></b></p> -> <p><b><u>foo</u></b>|<b></u>bar</u></b></p>
  148. it( '<p><b><u>foo|bar</u></b></p>', () => {
  149. const writer = new Writer();
  150. const text = new Text( 'foobar' );
  151. const u = new Element( 'u', { foo: 'bar' }, [ text ] );
  152. const b = new Element( 'b', { baz: 'qux' }, [ u ] );
  153. const p = new Element( 'p', {}, [ b ] );
  154. const position = new Position( text, 3 );
  155. writer.setPriority( u, 1 );
  156. writer.setPriority( b, 1 );
  157. const newPosition = writer.breakAttributes( position );
  158. const parent = newPosition.parent;
  159. expect( parent ).to.equal( p );
  160. expect( newPosition.offset ).to.equal( 1 );
  161. expect( parent.getChildCount() ).to.equal( 2 );
  162. const b1 = parent.getChild( 0 );
  163. const b2 = parent.getChild( 1 );
  164. expect( b1.same( b ) ).to.be.true;
  165. expect( b2.same( b ) ).to.be.true;
  166. expect( b1.getChildCount() ).to.equal( 1 );
  167. expect( b2.getChildCount() ).to.equal( 1 );
  168. const u1 = b1.getChild( 0 );
  169. const u2 = b2.getChild( 0 );
  170. expect( u1.same( u ) ).to.be.true;
  171. expect( u2.same( u ) ).to.be.true;
  172. expect( u1.getChildCount() ).to.equal( 1 );
  173. expect( u2.getChildCount() ).to.equal( 1 );
  174. expect( u1.getChild( 0 ).data ).to.equal( 'foo' );
  175. expect( u2.getChild( 0 ).data ).to.equal( 'bar' );
  176. } );
  177. // <p><b><u>foobar|</u></b></p> -> <p><b><u>foobar</u></b>|</p>
  178. it( '<p><b><u>foobar|</u></b></p>', () => {
  179. const writer = new Writer();
  180. const text = new Text( 'foobar' );
  181. const u = new Element( 'u', { foo: 'bar' }, [ text ] );
  182. const b = new Element( 'b', { baz: 'qux' }, [ u ] );
  183. const p = new Element( 'p', {}, [ b ] );
  184. const position = new Position( text, 6 );
  185. writer.setPriority( u, 1 );
  186. writer.setPriority( b, 1 );
  187. const newPosition = writer.breakAttributes( position );
  188. const parent = newPosition.parent;
  189. expect( parent ).to.equal( p );
  190. expect( newPosition.offset ).to.equal( 1 );
  191. expect( p.getChildCount() ).to.equal( 1 );
  192. const b1 = p.getChild( 0 );
  193. expect( b1.same( b ) ).to.be.true;
  194. expect( b1.getChildCount() ).to.equal( 1 );
  195. const u1 = b.getChild( 0 );
  196. expect( u1.same( u ) ).to.be.true;
  197. expect( u1.getChildCount() ).to.equal( 1 );
  198. const text1 = u1.getChild( 0 );
  199. expect( text1.same( text ) ).to.be.true;
  200. } );
  201. } );
  202. describe( 'mergeAttributes', () => {
  203. // <p>{fo|obar}</p>
  204. it( 'should not merge if inside text node', () => {
  205. const writer = new Writer();
  206. const text = new Text( 'foobar' );
  207. const p = new Element( 'p', [], [ text ] );
  208. const position = new Position( text, 2 );
  209. const newPosition = writer.mergeAttributes( position );
  210. expect( newPosition.isEqual( position ) ).to.be.true;
  211. expect( p.getChildCount() ).to.equal( 1 );
  212. const text1 = p.getChild( 0 );
  213. expect( text1 ).to.equal( text );
  214. expect( text1.data ).to.equal( 'foobar' );
  215. } );
  216. it( 'should return same position when inside empty container', () => {
  217. // <p>|</p>
  218. const writer = new Writer();
  219. const p = new Element( 'p' );
  220. const position = new Position( p, 0 );
  221. const newPosition = writer.mergeAttributes( position );
  222. expect( newPosition.isEqual( position ) ).to.be.true;
  223. } );
  224. it( 'should not merge when position is placed at the beginning of the container', () => {
  225. // <p>|<b></b></p>
  226. const writer = new Writer();
  227. const b = new Element( 'b' );
  228. const p = new Element( 'p', {}, [ b ] );
  229. const position = new Position( p, 0 );
  230. writer.setPriority( b, 1 );
  231. const newPosition = writer.mergeAttributes( position );
  232. expect( newPosition.isEqual( position ) );
  233. expect( p.getChildCount() ).to.equal( 1 );
  234. expect( p.getChild( 0 ) ).to.equal( b );
  235. } );
  236. it( 'should not merge when position is placed at the end of the container', () => {
  237. // <p><b></b>|</p>
  238. const writer = new Writer();
  239. const b = new Element( 'b' );
  240. const p = new Element( 'p', {}, [ b ] );
  241. const position = new Position( p, 1 );
  242. writer.setPriority( b, 1 );
  243. const newPosition = writer.mergeAttributes( position );
  244. expect( newPosition.isEqual( position ) );
  245. expect( p.getChildCount() ).to.equal( 1 );
  246. expect( p.getChild( 0 ) ).to.equal( b );
  247. } );
  248. it( 'should merge when placed between two text nodes', () => {
  249. // <p>{foo}|{bar}</p>
  250. // <p>{foo|bar}</p>
  251. const writer = new Writer();
  252. const text1 = new Text( 'foo' );
  253. const text2 = new Text( 'bar' );
  254. const p = new Element( 'p', {}, [ text1, text2 ] );
  255. const position = new Position( p, 1 );
  256. const newPosition = writer.mergeAttributes( position );
  257. expect( newPosition.parent ).is.equal( text1 );
  258. expect( newPosition.offset ).is.equal( 3 );
  259. expect( text1.data ).to.equal( 'foobar' );
  260. expect( p.getChildCount() ).to.equal( 1 );
  261. expect( p.getChild( 0 ) ).to.equal( text1 );
  262. } );
  263. it( 'should merge when placed between similar attribute nodes', () => {
  264. // <p><b foo="bar"></b>|<b foo="bar"></b></p>
  265. // <p><b foo="bar">|</b></p>
  266. const writer = new Writer();
  267. const b1 = new Element( 'b', { foo: 'bar' } );
  268. const b2 = new Element( 'b', { foo: 'bar' } );
  269. const p = new Element( 'p', {}, [ b1, b2 ] );
  270. const position = new Position( p, 1 );
  271. writer.setPriority( b1, 1 );
  272. writer.setPriority( b2, 1 );
  273. const newPosition = writer.mergeAttributes( position );
  274. expect( newPosition.parent ).to.equal( b1 );
  275. expect( newPosition.offset ).to.equal( 0 );
  276. expect( p.getChildCount() ).to.equal( 1 );
  277. expect( p.getChild( 0 ) ).to.equal( b1 );
  278. } );
  279. it( 'should not merge when placed between non-similar attribute nodes', () => {
  280. // <p><b foo="bar"></b>|<b foo="baz"></b></p>
  281. // <p><b foo="bar"></b>|<b foo="baz"></b></p>
  282. const writer = new Writer();
  283. const b1 = new Element( 'b', { foo: 'bar' } );
  284. const b2 = new Element( 'b', { foo: 'baz' } );
  285. const p = new Element( 'p', {}, [ b1, b2 ] );
  286. const position = new Position( p, 1 );
  287. writer.setPriority( b1, 1 );
  288. writer.setPriority( b2, 1 );
  289. const newPosition = writer.mergeAttributes( position );
  290. expect( newPosition.isEqual( position ) ).to.be.true;
  291. expect( p.getChildCount() ).to.equal( 2 );
  292. expect( p.getChild( 0 ) ).to.equal( b1 );
  293. expect( p.getChild( 1 ) ).to.equal( b2 );
  294. } );
  295. it( 'should not merge when placed between similar attribute nodes with different priority', () => {
  296. const writer = new Writer();
  297. const b1 = new Element( 'b', { foo: 'bar' } );
  298. const b2 = new Element( 'b', { foo: 'bar' } );
  299. const p = new Element( 'p', {}, [ b1, b2 ] );
  300. const position = new Position( p, 1 );
  301. writer.setPriority( b1, 1 );
  302. writer.setPriority( b2, 2 );
  303. const newPosition = writer.mergeAttributes( position );
  304. expect( newPosition.isEqual( position ) ).to.be.true;
  305. expect( p.getChildCount() ).to.equal( 2 );
  306. expect( p.getChild( 0 ) ).to.equal( b1 );
  307. expect( p.getChild( 1 ) ).to.equal( b2 );
  308. } );
  309. it( 'should merge attribute nodes and their contents if possible', () => {
  310. // <p><b foo="bar">{foo}</b>|<b foo="bar">{bar}</b></p>
  311. // <p><b foo="bar">{foo}|{bar}</b></p>
  312. // <p><b foo="bar">{foo|bar}</b></p>
  313. const writer = new Writer();
  314. const text1 = new Text( 'foo' );
  315. const text2 = new Text( 'bar' );
  316. const b1 = new Element( 'b', { foo: 'bar' }, [ text1 ] );
  317. const b2 = new Element( 'b', { foo: 'bar' }, [ text2 ] );
  318. const p = new Element( 'p', {}, [ b1, b2 ] );
  319. const position = new Position( p, 1 );
  320. writer.setPriority( b1, 1 );
  321. writer.setPriority( b2, 1 );
  322. const newPosition = writer.mergeAttributes( position );
  323. expect( newPosition.parent ).to.equal( text1 );
  324. expect( newPosition.offset ).to.equal( 3 );
  325. expect( p.getChildCount() ).to.equal( 1 );
  326. expect( p.getChild( 0 ) ).to.equal( b1 );
  327. expect( b1.getChildCount() ).to.equal( 1 );
  328. expect( b1.getChild( 0 ) ).to.equal( text1 );
  329. expect( text1.data ).to.equal( 'foobar' );
  330. } );
  331. } );
  332. } );