attributedelta.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329
  1. /**
  2. * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
  3. * For licensing, see LICENSE.md.
  4. */
  5. /* bender-tags: treemodel, delta */
  6. 'use strict';
  7. import coreTestUtils from '/tests/core/_utils/utils.js';
  8. import Document from '/ckeditor5/core/treemodel/document.js';
  9. import Text from '/ckeditor5/core/treemodel/text.js';
  10. import Attribute from '/ckeditor5/core/treemodel/attribute.js';
  11. import Range from '/ckeditor5/core/treemodel/range.js';
  12. import Position from '/ckeditor5/core/treemodel/position.js';
  13. import Element from '/ckeditor5/core/treemodel/element.js';
  14. const getIteratorCount = coreTestUtils.getIteratorCount;
  15. describe( 'Batch', () => {
  16. let doc, root, batch;
  17. beforeEach( () => {
  18. doc = new Document();
  19. root = doc.createRoot( 'root' );
  20. batch = doc.batch();
  21. } );
  22. function getOperationsCount() {
  23. let count = 0;
  24. for ( let delta of batch.deltas ) {
  25. count += getIteratorCount( delta.operations );
  26. }
  27. return count;
  28. }
  29. describe( 'change attribute on node', () => {
  30. let node, text, textNode;
  31. beforeEach( () => {
  32. node = new Element( 'p', [ new Attribute( 'a', 1 ) ] );
  33. text = new Text( 'c', [ new Attribute( 'a', 1 ) ] );
  34. root.insertChildren( 0, [ node, text ] );
  35. textNode = root.getChild( 1 );
  36. } );
  37. describe( 'setAttr', () => {
  38. it( 'should create the attribute on element', () => {
  39. batch.setAttr( 'b', 2, node );
  40. expect( getOperationsCount() ).to.equal( 1 );
  41. expect( node.attrs.getValue( 'b' ) ).to.equal( 2 );
  42. } );
  43. it( 'should change the attribute of element', () => {
  44. batch.setAttr( 'a', 2, node );
  45. expect( getOperationsCount() ).to.equal( 1 );
  46. expect( node.attrs.getValue( 'a' ) ).to.equal( 2 );
  47. } );
  48. it( 'should create the attribute on text node', () => {
  49. batch.setAttr( 'b', 2, textNode );
  50. expect( getOperationsCount() ).to.equal( 1 );
  51. expect( root.getChild( 1 ).attrs.getValue( 'b' ) ).to.equal( 2 );
  52. } );
  53. it( 'should change the attribute of text node', () => {
  54. batch.setAttr( 'a', 2, textNode );
  55. expect( getOperationsCount() ).to.equal( 1 );
  56. expect( root.getChild( 1 ).attrs.getValue( 'a' ) ).to.equal( 2 );
  57. } );
  58. it( 'should do nothing if the attribute value is the same', () => {
  59. batch.setAttr( 'a', 1, node );
  60. expect( getOperationsCount() ).to.equal( 0 );
  61. expect( node.attrs.getValue( 'a' ) ).to.equal( 1 );
  62. } );
  63. it( 'should be chainable', () => {
  64. const chain = batch.setAttr( 'b', 2, node );
  65. expect( chain ).to.equal( batch );
  66. } );
  67. } );
  68. describe( 'removeAttr', () => {
  69. it( 'should remove the attribute from element', () => {
  70. batch.removeAttr( 'a', node );
  71. expect( getOperationsCount() ).to.equal( 1 );
  72. expect( node.attrs.getValue( 'a' ) ).to.be.null;
  73. } );
  74. it( 'should remove the attribute from character', () => {
  75. batch.removeAttr( 'a', textNode );
  76. expect( getOperationsCount() ).to.equal( 1 );
  77. expect( root.getChild( 1 ).attrs.getValue( 'a' ) ).to.be.null;
  78. } );
  79. it( 'should do nothing if the attribute is not set', () => {
  80. batch.removeAttr( 'b', node );
  81. expect( getOperationsCount() ).to.equal( 0 );
  82. } );
  83. it( 'should be chainable', () => {
  84. const chain = batch.removeAttr( 'a', node );
  85. expect( chain ).to.equal( batch );
  86. } );
  87. } );
  88. } );
  89. describe( 'change attribute on range', () => {
  90. beforeEach( () => {
  91. root.insertChildren( 0, [
  92. new Text( 'xxx', [ new Attribute( 'a', 1 ) ] ),
  93. 'xxx',
  94. new Text( 'xxx', [ new Attribute( 'a', 1 ) ] ),
  95. new Text( 'xxx', [ new Attribute( 'a', 2 ) ] ),
  96. 'xxx',
  97. new Text( 'xxx', [ new Attribute( 'a', 1 ) ] ),
  98. new Element( 'e', [ new Attribute( 'a', 2 ) ], 'xxx' ),
  99. 'xxx'
  100. ] );
  101. } );
  102. function getRange( startIndex, endIndex ) {
  103. return new Range(
  104. Position.createFromParentAndOffset( root, startIndex ),
  105. Position.createFromParentAndOffset( root, endIndex )
  106. );
  107. }
  108. function getChangesAttrsCount() {
  109. let count = 0;
  110. for ( let delta of batch.deltas ) {
  111. for ( let operation of delta.operations ) {
  112. count += getIteratorCount( operation.range.getAllNodes() );
  113. }
  114. }
  115. return count;
  116. }
  117. function getCompressedAttrs() {
  118. // default: 111---111222---1112------
  119. const range = Range.createFromElement( root );
  120. return Array.from( range.getAllNodes() ).map( node => node.attrs.getValue( 'a' ) || '-' ).join( '' );
  121. }
  122. describe( 'setAttr', () => {
  123. it( 'should set the attribute on the range', () => {
  124. batch.setAttr( 'a', 3, getRange( 3, 6 ) );
  125. expect( getOperationsCount() ).to.equal( 1 );
  126. expect( getChangesAttrsCount() ).to.equal( 3 );
  127. expect( getCompressedAttrs() ).to.equal( '111333111222---1112------' );
  128. } );
  129. it( 'should split the operations if parts of the range have different attributes', () => {
  130. batch.setAttr( 'a', 3, getRange( 4, 14 ) );
  131. expect( getOperationsCount() ).to.equal( 4 );
  132. expect( getChangesAttrsCount() ).to.equal( 10 );
  133. expect( getCompressedAttrs() ).to.equal( '111-3333333333-1112------' );
  134. } );
  135. it( 'should split the operations if parts of the part of the range have the attribute', () => {
  136. batch.setAttr( 'a', 2, getRange( 4, 14 ) );
  137. expect( getOperationsCount() ).to.equal( 3 );
  138. expect( getChangesAttrsCount() ).to.equal( 7 );
  139. expect( getCompressedAttrs() ).to.equal( '111-2222222222-1112------' );
  140. } );
  141. it( 'should strip the range if the beginning have the attribute', () => {
  142. batch.setAttr( 'a', 1, getRange( 1, 5 ) );
  143. expect( getOperationsCount() ).to.equal( 1 );
  144. expect( getChangesAttrsCount() ).to.equal( 2 );
  145. expect( getCompressedAttrs() ).to.equal( '11111-111222---1112------' );
  146. } );
  147. it( 'should strip the range if the ending have the attribute', () => {
  148. batch.setAttr( 'a', 1, getRange( 13, 17 ) );
  149. expect( getOperationsCount() ).to.equal( 1 );
  150. expect( getChangesAttrsCount() ).to.equal( 2 );
  151. expect( getCompressedAttrs() ).to.equal( '111---111222-111112------' );
  152. } );
  153. it( 'should do nothing if the range has attribute', () => {
  154. batch.setAttr( 'a', 1, getRange( 0, 3 ) );
  155. expect( getOperationsCount() ).to.equal( 0 );
  156. expect( getCompressedAttrs() ).to.equal( '111---111222---1112------' );
  157. } );
  158. it( 'should not check range\'s start position node when creating operations', () => {
  159. let range = new Range(
  160. new Position( root, [ 18, 1 ] ),
  161. new Position( root, [ 19 ] )
  162. );
  163. batch.setAttr( 'a', 1, range );
  164. expect( getOperationsCount() ).to.equal( 1 );
  165. expect( getChangesAttrsCount() ).to.equal( 2 );
  166. expect( getCompressedAttrs() ).to.equal( '111---111222---1112-11---' );
  167. } );
  168. it( 'should not change elements attribute if range contains closing tag', () => {
  169. let range = new Range(
  170. new Position( root, [ 18, 1 ] ),
  171. new Position( root, [ 21 ] )
  172. );
  173. batch.setAttr( 'a', 1, range );
  174. expect( getOperationsCount() ).to.equal( 1 );
  175. expect( getChangesAttrsCount() ).to.equal( 4 );
  176. expect( getCompressedAttrs() ).to.equal( '111---111222---1112-1111-' );
  177. } );
  178. it( 'should not create an operation if the range contains only closing tag', () => {
  179. let range = new Range(
  180. new Position( root, [ 18, 3 ] ),
  181. new Position( root, [ 19 ] )
  182. );
  183. batch.setAttr( 'a', 3, range );
  184. expect( getOperationsCount() ).to.equal( 0 );
  185. expect( getCompressedAttrs() ).to.equal( '111---111222---1112------' );
  186. } );
  187. it( 'should not create an operation if is collapsed', () => {
  188. batch.setAttr( 'a', 1, getRange( 3, 3 ) );
  189. expect( getOperationsCount() ).to.equal( 0 );
  190. expect( getCompressedAttrs() ).to.equal( '111---111222---1112------' );
  191. } );
  192. it( 'should create a proper operations for the mixed range', () => {
  193. batch.setAttr( 'a', 1, getRange( 0, 20 ) );
  194. expect( getOperationsCount() ).to.equal( 5 );
  195. expect( getChangesAttrsCount() ).to.equal( 14 );
  196. expect( getCompressedAttrs() ).to.equal( '11111111111111111111111--' );
  197. } );
  198. it( 'should be chainable', () => {
  199. const chain = batch.setAttr( 'a', 3, getRange( 3, 6 ) );
  200. expect( chain ).to.equal( batch );
  201. } );
  202. } );
  203. describe( 'removeAttr', () => {
  204. it( 'should remove the attribute on the range', () => {
  205. batch.removeAttr( 'a', getRange( 0, 2 ) );
  206. expect( getOperationsCount() ).to.equal( 1 );
  207. expect( getChangesAttrsCount() ).to.equal( 2 );
  208. expect( getCompressedAttrs() ).to.equal( '--1---111222---1112------' );
  209. } );
  210. it( 'should split the operations if parts of the range have different attributes', () => {
  211. batch.removeAttr( 'a', getRange( 7, 11 ) );
  212. expect( getOperationsCount() ).to.equal( 2 );
  213. expect( getChangesAttrsCount() ).to.equal( 4 );
  214. expect( getCompressedAttrs() ).to.equal( '111---1----2---1112------' );
  215. } );
  216. it( 'should split the operations if parts of the part of the range have no attribute', () => {
  217. batch.removeAttr( 'a', getRange( 1, 7 ) );
  218. expect( getOperationsCount() ).to.equal( 2 );
  219. expect( getChangesAttrsCount() ).to.equal( 3 );
  220. expect( getCompressedAttrs() ).to.equal( '1------11222---1112------' );
  221. } );
  222. it( 'should strip the range if the beginning have no attribute', () => {
  223. batch.removeAttr( 'a', getRange( 4, 12 ) );
  224. expect( getOperationsCount() ).to.equal( 2 );
  225. expect( getChangesAttrsCount() ).to.equal( 6 );
  226. expect( getCompressedAttrs() ).to.equal( '111------------1112------' );
  227. } );
  228. it( 'should strip the range if the ending have no attribute', () => {
  229. batch.removeAttr( 'a', getRange( 7, 15 ) );
  230. expect( getOperationsCount() ).to.equal( 2 );
  231. expect( getChangesAttrsCount() ).to.equal( 5 );
  232. expect( getCompressedAttrs() ).to.equal( '111---1--------1112------' );
  233. } );
  234. it( 'should do nothing if the range has no attribute', () => {
  235. batch.removeAttr( 'a', getRange( 4, 5 ) );
  236. expect( getOperationsCount() ).to.equal( 0 );
  237. expect( getCompressedAttrs() ).to.equal( '111---111222---1112------' );
  238. } );
  239. it( 'should not check range\'s start position node when creating operations', () => {
  240. let range = new Range(
  241. new Position( root, [ 18, 3 ] ),
  242. new Position( root, [ 19 ] )
  243. );
  244. batch.removeAttr( 'a', range );
  245. expect( getOperationsCount() ).to.equal( 0 );
  246. expect( getChangesAttrsCount() ).to.equal( 0 );
  247. expect( getCompressedAttrs() ).to.equal( '111---111222---1112------' );
  248. } );
  249. it( 'should not apply operation twice in the range contains opening and closing tags', () => {
  250. batch.removeAttr( 'a', getRange( 18, 22 ) );
  251. expect( getOperationsCount() ).to.equal( 1 );
  252. expect( getChangesAttrsCount() ).to.equal( 1 );
  253. expect( getCompressedAttrs() ).to.equal( '111---111222---111-------' );
  254. } );
  255. it( 'should not create an operation if range is collapsed', () => {
  256. batch.removeAttr( 'a', getRange( 3, 3 ) );
  257. expect( getOperationsCount() ).to.equal( 0 );
  258. expect( getCompressedAttrs() ).to.equal( '111---111222---1112------' );
  259. } );
  260. it( 'should create a proper operations for the mixed range', () => {
  261. batch.removeAttr( 'a', getRange( 3, 15 ) );
  262. expect( getOperationsCount() ).to.equal( 2 );
  263. expect( getChangesAttrsCount() ).to.equal( 6 );
  264. expect( getCompressedAttrs() ).to.equal( '111------------1112------' );
  265. } );
  266. it( 'should be chainable', () => {
  267. const chain = batch.removeAttr( 'a', getRange( 0, 2 ) );
  268. expect( chain ).to.equal( batch );
  269. } );
  270. } );
  271. } );
  272. } );