attributeoperation.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368
  1. /**
  2. * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
  3. * For licensing, see LICENSE.md.
  4. */
  5. /* bender-tags: treemodel, operation */
  6. 'use strict';
  7. import Document from '/ckeditor5/engine/treemodel/document.js';
  8. import Element from '/ckeditor5/engine/treemodel/element.js';
  9. import AttributeOperation from '/ckeditor5/engine/treemodel/operation/attributeoperation.js';
  10. import Position from '/ckeditor5/engine/treemodel/position.js';
  11. import Range from '/ckeditor5/engine/treemodel/range.js';
  12. import Text from '/ckeditor5/engine/treemodel/text.js';
  13. import CKEditorError from '/ckeditor5/utils/ckeditorerror.js';
  14. describe( 'AttributeOperation', () => {
  15. let doc, root;
  16. beforeEach( () => {
  17. doc = new Document();
  18. root = doc.createRoot( 'root' );
  19. } );
  20. describe( 'type', () => {
  21. it( 'should be addAttribute for adding attribute', () => {
  22. const op = new AttributeOperation(
  23. new Range( new Position( root, [ 0 ] ), new Position( root, [ 2 ] ) ),
  24. 'key',
  25. null,
  26. 'newValue',
  27. doc.version
  28. );
  29. expect( op.type ).to.equal( 'addAttribute' );
  30. } );
  31. it( 'should be removeAttribute for removing attribute', () => {
  32. const op = new AttributeOperation(
  33. new Range( new Position( root, [ 0 ] ), new Position( root, [ 2 ] ) ),
  34. 'key',
  35. 'oldValue',
  36. null,
  37. doc.version
  38. );
  39. expect( op.type ).to.equal( 'removeAttribute' );
  40. } );
  41. it( 'should be changeAttribute for removing attribute', () => {
  42. const op = new AttributeOperation(
  43. new Range( new Position( root, [ 0 ] ), new Position( root, [ 2 ] ) ),
  44. 'key',
  45. 'oldValue',
  46. 'newValue',
  47. doc.version
  48. );
  49. expect( op.type ).to.equal( 'changeAttribute' );
  50. } );
  51. } );
  52. it( 'should insert attribute to the set of nodes', () => {
  53. root.insertChildren( 0, 'bar' );
  54. doc.applyOperation(
  55. new AttributeOperation(
  56. new Range( new Position( root, [ 0 ] ), new Position( root, [ 2 ] ) ),
  57. 'isNew',
  58. null,
  59. true,
  60. doc.version
  61. )
  62. );
  63. expect( doc.version ).to.equal( 1 );
  64. expect( root.getChildCount() ).to.equal( 3 );
  65. expect( root.getChild( 0 ).hasAttribute( 'isNew' ) ).to.be.true;
  66. expect( root.getChild( 1 ).hasAttribute( 'isNew' ) ).to.be.true;
  67. expect( root.getChild( 2 )._attrs.size ).to.equal( 0 );
  68. } );
  69. it( 'should add attribute to the existing attributes', () => {
  70. root.insertChildren( 0, new Text( 'x', { foo: true, bar: true } ) );
  71. doc.applyOperation(
  72. new AttributeOperation(
  73. new Range( new Position( root, [ 0 ] ), new Position( root, [ 1 ] ) ),
  74. 'isNew',
  75. null,
  76. true,
  77. doc.version
  78. )
  79. );
  80. expect( doc.version ).to.equal( 1 );
  81. expect( root.getChildCount() ).to.equal( 1 );
  82. expect( root.getChild( 0 )._attrs.size ).to.equal( 3 );
  83. expect( root.getChild( 0 ).hasAttribute( 'isNew' ) ).to.be.true;
  84. expect( root.getChild( 0 ).hasAttribute( 'foo' ) ).to.be.true;
  85. expect( root.getChild( 0 ).hasAttribute( 'bar' ) ).to.be.true;
  86. } );
  87. it( 'should change attribute to the set of nodes', () => {
  88. root.insertChildren( 0, new Text( 'bar', { isNew: false } ) );
  89. doc.applyOperation(
  90. new AttributeOperation(
  91. new Range( new Position( root, [ 0 ] ), new Position( root, [ 2 ] ) ),
  92. 'isNew',
  93. false,
  94. true,
  95. doc.version
  96. )
  97. );
  98. expect( doc.version ).to.equal( 1 );
  99. expect( root.getChildCount() ).to.equal( 3 );
  100. expect( root.getChild( 0 )._attrs.size ).to.equal( 1 );
  101. expect( root.getChild( 0 ).getAttribute( 'isNew' ) ).to.be.true;
  102. expect( root.getChild( 1 )._attrs.size ).to.equal( 1 );
  103. expect( root.getChild( 1 ).getAttribute( 'isNew' ) ).to.be.true;
  104. expect( root.getChild( 2 )._attrs.size ).to.equal( 1 );
  105. expect( root.getChild( 2 ).getAttribute( 'isNew' ) ).to.be.false;
  106. } );
  107. it( 'should change attribute in the middle of existing attributes', () => {
  108. root.insertChildren( 0, new Text( 'x', { foo: true, x: 1, bar: true } ) );
  109. doc.applyOperation(
  110. new AttributeOperation(
  111. new Range( new Position( root, [ 0 ] ), new Position( root, [ 1 ] ) ),
  112. 'x',
  113. 1,
  114. 2,
  115. doc.version
  116. )
  117. );
  118. expect( doc.version ).to.equal( 1 );
  119. expect( root.getChildCount() ).to.equal( 1 );
  120. expect( root.getChild( 0 )._attrs.size ).to.equal( 3 );
  121. expect( root.getChild( 0 ).getAttribute( 'foo' ) ).to.be.true;
  122. expect( root.getChild( 0 ).getAttribute( 'x' ) ).to.equal( 2 );
  123. expect( root.getChild( 0 ).getAttribute( 'bar' ) ).to.be.true;
  124. } );
  125. it( 'should remove attribute', () => {
  126. root.insertChildren( 0, new Text( 'x', { foo: true, x: true, bar: true } ) );
  127. doc.applyOperation(
  128. new AttributeOperation(
  129. new Range( new Position( root, [ 0 ] ), new Position( root, [ 1 ] ) ),
  130. 'x',
  131. true,
  132. null,
  133. doc.version
  134. )
  135. );
  136. expect( doc.version ).to.equal( 1 );
  137. expect( root.getChildCount() ).to.equal( 1 );
  138. expect( root.getChild( 0 )._attrs.size ).to.equal( 2 );
  139. expect( root.getChild( 0 ).hasAttribute( 'foo' ) ).to.be.true;
  140. expect( root.getChild( 0 ).hasAttribute( 'bar' ) ).to.be.true;
  141. } );
  142. it( 'should create an AttributeOperation as a reverse', () => {
  143. let range = new Range( new Position( root, [ 0 ] ), new Position( root, [ 3 ] ) );
  144. let operation = new AttributeOperation( range, 'x', 'old', 'new', doc.version );
  145. let reverse = operation.getReversed();
  146. expect( reverse ).to.be.an.instanceof( AttributeOperation );
  147. expect( reverse.baseVersion ).to.equal( 1 );
  148. expect( reverse.range.isEqual( range ) ).to.be.true;
  149. expect( reverse.key ).to.equal( 'x' );
  150. expect( reverse.oldValue ).to.equal( 'new' );
  151. expect( reverse.newValue ).to.equal( 'old' );
  152. } );
  153. it( 'should undo adding attribute by applying reverse operation', () => {
  154. root.insertChildren( 0, 'bar' );
  155. let operation = new AttributeOperation(
  156. new Range( new Position( root, [ 0 ] ), new Position( root, [ 3 ] ) ),
  157. 'isNew',
  158. null,
  159. true,
  160. doc.version
  161. );
  162. let reverse = operation.getReversed();
  163. doc.applyOperation( operation );
  164. doc.applyOperation( reverse );
  165. expect( doc.version ).to.equal( 2 );
  166. expect( root.getChildCount() ).to.equal( 3 );
  167. expect( root.getChild( 0 )._attrs.size ).to.equal( 0 );
  168. expect( root.getChild( 1 )._attrs.size ).to.equal( 0 );
  169. expect( root.getChild( 2 )._attrs.size ).to.equal( 0 );
  170. } );
  171. it( 'should not set attribute of element if change range starts in the middle of that element', () => {
  172. let eleA = new Element( 'a', [], 'abc' );
  173. let eleB = new Element( 'b', [], 'xyz' );
  174. root.insertChildren( 0, [ eleA, eleB ] );
  175. doc.applyOperation(
  176. new AttributeOperation(
  177. new Range( new Position( root, [ 0, 2 ] ), new Position( root, [ 1, 2 ] ) ),
  178. 'foo',
  179. null,
  180. true,
  181. doc.version
  182. )
  183. );
  184. expect( root.getChild( 0 ).hasAttribute( 'foo' ) ).to.be.false;
  185. } );
  186. it( 'should not remove attribute of element if change range starts in the middle of that element', () => {
  187. let fooAttr = { foo: true };
  188. let eleA = new Element( 'a', fooAttr, 'abc' );
  189. let eleB = new Element( 'b', fooAttr, 'xyz' );
  190. root.insertChildren( 0, [ eleA, eleB ] );
  191. doc.applyOperation(
  192. new AttributeOperation(
  193. new Range( new Position( root, [ 0, 3 ] ), new Position( root, [ 1, 0 ] ) ),
  194. 'foo',
  195. true,
  196. null,
  197. doc.version
  198. )
  199. );
  200. expect( root.getChild( 0 ).hasAttribute( 'foo' ) ).to.be.true;
  201. } );
  202. it( 'should undo changing attribute by applying reverse operation', () => {
  203. root.insertChildren( 0, new Text( 'bar', { isNew: false } ) );
  204. let operation = new AttributeOperation(
  205. new Range( new Position( root, [ 0 ] ), new Position( root, [ 3 ] ) ),
  206. 'isNew',
  207. false,
  208. true,
  209. doc.version
  210. );
  211. let reverse = operation.getReversed();
  212. doc.applyOperation( operation );
  213. doc.applyOperation( reverse );
  214. expect( doc.version ).to.equal( 2 );
  215. expect( root.getChildCount() ).to.equal( 3 );
  216. expect( root.getChild( 0 )._attrs.size ).to.equal( 1 );
  217. expect( root.getChild( 0 ).getAttribute( 'isNew' ) ).to.be.false;
  218. expect( root.getChild( 1 )._attrs.size ).to.equal( 1 );
  219. expect( root.getChild( 1 ).getAttribute( 'isNew' ) ).to.be.false;
  220. expect( root.getChild( 2 )._attrs.size ).to.equal( 1 );
  221. expect( root.getChild( 2 ).getAttribute( 'isNew' ) ).to.be.false;
  222. } );
  223. it( 'should undo remove attribute by applying reverse operation', () => {
  224. root.insertChildren( 0, new Text( 'bar', { foo: true } ) );
  225. let operation = new AttributeOperation(
  226. new Range( new Position( root, [ 0 ] ), new Position( root, [ 3 ] ) ),
  227. 'foo',
  228. true,
  229. null,
  230. doc.version
  231. );
  232. let reverse = operation.getReversed();
  233. doc.applyOperation( operation );
  234. doc.applyOperation( reverse );
  235. expect( doc.version ).to.equal( 2 );
  236. expect( root.getChildCount() ).to.equal( 3 );
  237. expect( root.getChild( 0 )._attrs.size ).to.equal( 1 );
  238. expect( root.getChild( 0 ).getAttribute( 'foo' ) ).to.be.true;
  239. expect( root.getChild( 1 )._attrs.size ).to.equal( 1 );
  240. expect( root.getChild( 1 ).getAttribute( 'foo' ) ).to.be.true;
  241. expect( root.getChild( 2 )._attrs.size ).to.equal( 1 );
  242. expect( root.getChild( 2 ).getAttribute( 'foo' ) ).to.be.true;
  243. } );
  244. it( 'should throw an error when one try to remove and the attribute does not exists', () => {
  245. root.insertChildren( 0, 'x' );
  246. expect( () => {
  247. doc.applyOperation(
  248. new AttributeOperation(
  249. new Range( new Position( root, [ 0 ] ), new Position( root, [ 1 ] ) ),
  250. 'foo',
  251. true,
  252. null,
  253. doc.version
  254. )
  255. );
  256. } ).to.throw( CKEditorError, /operation-attribute-no-attr-to-remove/ );
  257. } );
  258. it( 'should throw an error when one try to insert and the attribute already exists', () => {
  259. root.insertChildren( 0, new Text( 'x', { x: 1 } ) );
  260. expect( () => {
  261. doc.applyOperation(
  262. new AttributeOperation(
  263. new Range( new Position( root, [ 0 ] ), new Position( root, [ 1 ] ) ),
  264. 'x',
  265. null,
  266. 2,
  267. doc.version
  268. )
  269. );
  270. } ).to.throw( CKEditorError, /operation-attribute-attr-exists/ );
  271. } );
  272. it( 'should create an AttributeOperation with the same parameters when cloned', () => {
  273. let range = new Range( new Position( root, [ 0 ] ), new Position( root, [ 1 ] ) );
  274. let baseVersion = doc.version;
  275. let op = new AttributeOperation( range, 'foo', 'old', 'new', baseVersion );
  276. let clone = op.clone();
  277. // New instance rather than a pointer to the old instance.
  278. expect( clone ).not.to.be.equal( op );
  279. expect( clone ).to.be.instanceof( AttributeOperation );
  280. expect( clone.range.isEqual( range ) ).to.be.true;
  281. expect( clone.key ).to.equal( 'foo' );
  282. expect( clone.oldValue ).to.equal( 'old' );
  283. expect( clone.newValue ).to.equal( 'new' );
  284. expect( clone.baseVersion ).to.equal( baseVersion );
  285. } );
  286. it( 'should merge characters in node list', () => {
  287. let attrA = { foo: 'a' };
  288. let attrB = { foo: 'b' };
  289. root.insertChildren( 0, new Text( 'abc', attrA ) );
  290. root.insertChildren( 3, new Text( 'xyz', attrB ) );
  291. doc.applyOperation(
  292. new AttributeOperation(
  293. new Range( new Position( root, [ 1 ] ), new Position( root, [ 3 ] ) ),
  294. 'foo',
  295. 'a',
  296. 'b',
  297. doc.version
  298. )
  299. );
  300. expect( root._children._nodes[ 0 ].text ).to.equal( 'a' );
  301. expect( root._children._nodes[ 1 ].text ).to.equal( 'bcxyz' );
  302. } );
  303. } );