8
0

changeoperation.js 32 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129
  1. /**
  2. * @license Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
  3. * For licensing, see LICENSE.md.
  4. */
  5. /* bender-tags: document */
  6. /* bender-include: ../../_tools/tools.js */
  7. /* global describe, before, beforeEach, it, expect */
  8. /* bender-include: ../../_tools/tools.js */
  9. 'use strict';
  10. const getIteratorCount = bender.tools.core.getIteratorCount;
  11. const modules = bender.amd.require(
  12. 'document/document',
  13. 'document/node',
  14. 'document/operation/changeoperation',
  15. 'document/operation/insertoperation',
  16. 'document/operation/moveoperation',
  17. 'document/operation/nooperation',
  18. 'document/position',
  19. 'document/range',
  20. 'document/character',
  21. 'document/attribute',
  22. 'document/nodelist',
  23. 'document/text',
  24. 'ckeditorerror'
  25. );
  26. describe( 'ChangeOperation', () => {
  27. let Document, Node, ChangeOperation, InsertOperation, MoveOperation, NoOperation, Position, Range,
  28. Character, Attribute, NodeList, Text, CKEditorError;
  29. before( () => {
  30. Document = modules[ 'document/document' ];
  31. Node = modules[ 'document/node' ];
  32. ChangeOperation = modules[ 'document/operation/changeoperation' ];
  33. InsertOperation = modules[ 'document/operation/insertoperation' ];
  34. MoveOperation = modules[ 'document/operation/moveoperation' ];
  35. NoOperation = modules[ 'document/operation/nooperation' ];
  36. Position = modules[ 'document/position' ];
  37. Range = modules[ 'document/range' ];
  38. Character = modules[ 'document/character' ];
  39. Attribute = modules[ 'document/attribute' ];
  40. NodeList = modules[ 'document/nodelist' ];
  41. Text = modules[ 'document/text' ];
  42. CKEditorError = modules.ckeditorerror;
  43. } );
  44. let doc, root;
  45. beforeEach( () => {
  46. doc = new Document();
  47. root = doc.createRoot( 'root' );
  48. } );
  49. it( 'should insert attribute to the set of nodes', () => {
  50. let newAttr = new Attribute( 'isNew', true );
  51. root.insertChildren( 0, 'bar' );
  52. doc.applyOperation(
  53. new ChangeOperation(
  54. new Range( new Position( [ 0 ], root ), new Position( [ 2 ], root ) ),
  55. null,
  56. newAttr,
  57. doc.version
  58. )
  59. );
  60. expect( doc.version ).to.equal( 1 );
  61. expect( root.getChildCount() ).to.equal( 3 );
  62. expect( root.getChild( 0 ).hasAttr( newAttr ) ).to.be.true;
  63. expect( root.getChild( 1 ).hasAttr( newAttr ) ).to.be.true;
  64. expect( getIteratorCount( root.getChild( 2 ).getAttrs() ) ).to.equal( 0 );
  65. } );
  66. it( 'should add attribute to the existing attributes', () => {
  67. let newAttr = new Attribute( 'isNew', true );
  68. let fooAttr = new Attribute( 'foo', true );
  69. let barAttr = new Attribute( 'bar', true );
  70. root.insertChildren( 0, new Character( 'x', [ fooAttr, barAttr ] ) );
  71. doc.applyOperation(
  72. new ChangeOperation(
  73. new Range( new Position( [ 0 ], root ), new Position( [ 1 ], root ) ),
  74. null,
  75. newAttr,
  76. doc.version
  77. )
  78. );
  79. expect( doc.version ).to.equal( 1 );
  80. expect( root.getChildCount() ).to.equal( 1 );
  81. expect( getIteratorCount( root.getChild( 0 ).getAttrs() ) ).to.equal( 3 );
  82. expect( root.getChild( 0 ).hasAttr( newAttr ) ).to.be.true;
  83. expect( root.getChild( 0 ).hasAttr( fooAttr ) ).to.be.true;
  84. expect( root.getChild( 0 ).hasAttr( barAttr ) ).to.be.true;
  85. } );
  86. it( 'should change attribute to the set of nodes', () => {
  87. let oldAttr = new Attribute( 'isNew', false );
  88. let newAttr = new Attribute( 'isNew', true );
  89. root.insertChildren( 0, new Text( 'bar', [ oldAttr ] ) );
  90. doc.applyOperation(
  91. new ChangeOperation(
  92. new Range( new Position( [ 0 ], root ), new Position( [ 2 ], root ) ),
  93. oldAttr,
  94. newAttr,
  95. doc.version
  96. )
  97. );
  98. expect( doc.version ).to.equal( 1 );
  99. expect( root.getChildCount() ).to.equal( 3 );
  100. expect( getIteratorCount( root.getChild( 0 ).getAttrs() ) ).to.equal( 1 );
  101. expect( root.getChild( 0 ).hasAttr( newAttr ) ).to.be.true;
  102. expect( getIteratorCount( root.getChild( 1 ).getAttrs() ) ).to.equal( 1 );
  103. expect( root.getChild( 1 ).hasAttr( newAttr ) ).to.be.true;
  104. expect( getIteratorCount( root.getChild( 2 ).getAttrs() ) ).to.equal( 1 );
  105. expect( root.getChild( 2 ).hasAttr( oldAttr ) ).to.be.true;
  106. } );
  107. it( 'should change attribute in the middle of existing attributes', () => {
  108. let fooAttr = new Attribute( 'foo', true );
  109. let x1Attr = new Attribute( 'x', 1 );
  110. let x2Attr = new Attribute( 'x', 2 );
  111. let barAttr = new Attribute( 'bar', true );
  112. root.insertChildren( 0, new Character( 'x', [ fooAttr, x1Attr, barAttr ] ) );
  113. doc.applyOperation(
  114. new ChangeOperation(
  115. new Range( new Position( [ 0 ], root ), new Position( [ 1 ], root ) ),
  116. x1Attr,
  117. x2Attr,
  118. doc.version
  119. )
  120. );
  121. expect( doc.version ).to.equal( 1 );
  122. expect( root.getChildCount() ).to.equal( 1 );
  123. expect( getIteratorCount( root.getChild( 0 ).getAttrs() ) ).to.equal( 3 );
  124. expect( root.getChild( 0 ).hasAttr( fooAttr ) ).to.be.true;
  125. expect( root.getChild( 0 ).hasAttr( x2Attr ) ).to.be.true;
  126. expect( root.getChild( 0 ).hasAttr( barAttr ) ).to.be.true;
  127. } );
  128. it( 'should remove attribute', () => {
  129. let fooAttr = new Attribute( 'foo', true );
  130. let xAttr = new Attribute( 'x', true );
  131. let barAttr = new Attribute( 'bar', true );
  132. root.insertChildren( 0, new Character( 'x', [ fooAttr, xAttr, barAttr ] ) );
  133. doc.applyOperation(
  134. new ChangeOperation(
  135. new Range( new Position( [ 0 ], root ), new Position( [ 1 ], root ) ),
  136. xAttr,
  137. null,
  138. doc.version
  139. )
  140. );
  141. expect( doc.version ).to.equal( 1 );
  142. expect( root.getChildCount() ).to.equal( 1 );
  143. expect( getIteratorCount( root.getChild( 0 ).getAttrs() ) ).to.equal( 2 );
  144. expect( root.getChild( 0 ).hasAttr( fooAttr ) ).to.be.true;
  145. expect( root.getChild( 0 ).hasAttr( barAttr ) ).to.be.true;
  146. } );
  147. it( 'should create a change operation as a reverse', () => {
  148. let oldAttr = new Attribute( 'x', 'old' );
  149. let newAttr = new Attribute( 'x', 'new' );
  150. let range = new Range( new Position( [ 0 ], root ), new Position( [ 3 ], root ) );
  151. let operation = new ChangeOperation( range, oldAttr, newAttr, doc.version );
  152. let reverse = operation.getReversed();
  153. expect( reverse ).to.be.an.instanceof( ChangeOperation );
  154. expect( reverse.baseVersion ).to.equal( 1 );
  155. expect( reverse.range ).to.equal( range );
  156. expect( reverse.oldAttr ).to.equal( newAttr );
  157. expect( reverse.newAttr ).to.equal( oldAttr );
  158. } );
  159. it( 'should undo adding attribute by applying reverse operation', () => {
  160. let newAttr = new Attribute( 'isNew', true );
  161. root.insertChildren( 0, 'bar' );
  162. let operation = new ChangeOperation(
  163. new Range( new Position( [ 0 ], root ), new Position( [ 3 ], root ) ),
  164. null,
  165. newAttr,
  166. doc.version
  167. );
  168. let reverse = operation.getReversed();
  169. doc.applyOperation( operation );
  170. doc.applyOperation( reverse );
  171. expect( doc.version ).to.equal( 2 );
  172. expect( root.getChildCount() ).to.equal( 3 );
  173. expect( getIteratorCount( root.getChild( 0 ).getAttrs() ) ).to.equal( 0 );
  174. expect( getIteratorCount( root.getChild( 1 ).getAttrs() ) ).to.equal( 0 );
  175. expect( getIteratorCount( root.getChild( 2 ).getAttrs() ) ).to.equal( 0 );
  176. } );
  177. it( 'should undo changing attribute by applying reverse operation', () => {
  178. let oldAttr = new Attribute( 'isNew', false );
  179. let newAttr = new Attribute( 'isNew', true );
  180. root.insertChildren( 0, new Text( 'bar', [ oldAttr ] ) );
  181. let operation = new ChangeOperation(
  182. new Range( new Position( [ 0 ], root ), new Position( [ 3 ], root ) ),
  183. oldAttr,
  184. newAttr,
  185. doc.version
  186. );
  187. let reverse = operation.getReversed();
  188. doc.applyOperation( operation );
  189. doc.applyOperation( reverse );
  190. expect( doc.version ).to.equal( 2 );
  191. expect( root.getChildCount() ).to.equal( 3 );
  192. expect( getIteratorCount( root.getChild( 0 ).getAttrs() ) ).to.equal( 1 );
  193. expect( root.getChild( 0 ).hasAttr( oldAttr ) ).to.be.true;
  194. expect( getIteratorCount( root.getChild( 1 ).getAttrs() ) ).to.equal( 1 );
  195. expect( root.getChild( 1 ).hasAttr( oldAttr ) ).to.be.true;
  196. expect( getIteratorCount( root.getChild( 2 ).getAttrs() ) ).to.equal( 1 );
  197. expect( root.getChild( 2 ).hasAttr( oldAttr ) ).to.be.true;
  198. } );
  199. it( 'should undo remove attribute by applying reverse operation', () => {
  200. let fooAttr = new Attribute( 'foo', false );
  201. root.insertChildren( 0, new Text( 'bar', [ fooAttr ] ) );
  202. let operation = new ChangeOperation(
  203. new Range( new Position( [ 0 ], root ), new Position( [ 3 ], root ) ),
  204. fooAttr,
  205. null,
  206. doc.version
  207. );
  208. let reverse = operation.getReversed();
  209. doc.applyOperation( operation );
  210. doc.applyOperation( reverse );
  211. expect( doc.version ).to.equal( 2 );
  212. expect( root.getChildCount() ).to.equal( 3 );
  213. expect( getIteratorCount( root.getChild( 0 ).getAttrs() ) ).to.equal( 1 );
  214. expect( root.getChild( 0 ).hasAttr( fooAttr ) ).to.be.true;
  215. expect( getIteratorCount( root.getChild( 1 ).getAttrs() ) ).to.equal( 1 );
  216. expect( root.getChild( 1 ).hasAttr( fooAttr ) ).to.be.true;
  217. expect( getIteratorCount( root.getChild( 2 ).getAttrs() ) ).to.equal( 1 );
  218. expect( root.getChild( 2 ).hasAttr( fooAttr ) ).to.be.true;
  219. } );
  220. it( 'should throw an error when one try to change and the new and old attributes have different keys', () => {
  221. let fooAttr = new Attribute( 'foo', true );
  222. let barAttr = new Attribute( 'bar', true );
  223. root.insertChildren( 0, 'x' );
  224. expect(
  225. () => {
  226. doc.applyOperation(
  227. new ChangeOperation(
  228. new Range( new Position( [ 0 ], root ), new Position( [ 1 ], root ) ),
  229. fooAttr,
  230. barAttr,
  231. doc.version
  232. )
  233. );
  234. }
  235. ).to.throw( CKEditorError, /operation-change-different-keys/ );
  236. } );
  237. it( 'should create operation with the same parameters when cloned', () => {
  238. let range = new Range( new Position( [ 0 ], root ), new Position( [ 1 ], root ) );
  239. let oldAttr = new Attribute( 'foo', 'old' );
  240. let newAttr = new Attribute( 'foo', 'bar' );
  241. let baseVersion = doc.version;
  242. let op = new ChangeOperation( range, oldAttr, newAttr, baseVersion );
  243. let clone = op.clone();
  244. // New instance rather than a pointer to the old instance.
  245. expect( clone ).not.to.be.equal( op );
  246. expect( clone ).to.be.instanceof( ChangeOperation );
  247. expect( clone.range.isEqual( range ) ).to.be.true;
  248. expect( clone.oldAttr.isEqual( oldAttr ) ).to.be.true;
  249. expect( clone.newAttr.isEqual( newAttr ) ).to.be.true;
  250. expect( clone.baseVersion ).to.equal( baseVersion );
  251. } );
  252. describe( 'have conflicting attributes', () => {
  253. it( 'if it sets an attribute that is removed by the other operation', () => {
  254. let op = new ChangeOperation(
  255. new Range( new Position( [ 0 ], root ), new Position( [ 1 ], root ) ),
  256. new Attribute( 'foo', 'old' ),
  257. new Attribute( 'foo', 'bar' ),
  258. doc.version
  259. );
  260. let op2 = new ChangeOperation(
  261. new Range( new Position( [ 0 ], root ), new Position( [ 1 ], root ) ),
  262. new Attribute( 'foo', 'old' ),
  263. null,
  264. doc.version
  265. );
  266. let conflicts = op.conflictsAttributesWith( op2 );
  267. expect( conflicts ).to.be.true;
  268. } );
  269. it( 'if it removes an attribute that is set by the other operation', () => {
  270. let op = new ChangeOperation(
  271. new Range( new Position( [ 0 ], root ), new Position( [ 1 ], root ) ),
  272. new Attribute( 'foo', 'old' ),
  273. null,
  274. doc.version
  275. );
  276. let op2 = new ChangeOperation(
  277. new Range( new Position( [ 0 ], root ), new Position( [ 1 ], root ) ),
  278. new Attribute( 'foo', 'old' ),
  279. new Attribute( 'foo', 'bar' ),
  280. doc.version
  281. );
  282. let conflicts = op.conflictsAttributesWith( op2 );
  283. expect( conflicts ).to.be.true;
  284. } );
  285. it( 'if it sets different value than the other operation', () => {
  286. let op = new ChangeOperation(
  287. new Range( new Position( [ 0 ], root ), new Position( [ 1 ], root ) ),
  288. new Attribute( 'foo', 'old' ),
  289. new Attribute( 'foo', 'bar' ),
  290. doc.version
  291. );
  292. let op2 = new ChangeOperation(
  293. new Range( new Position( [ 0 ], root ), new Position( [ 1 ], root ) ),
  294. new Attribute( 'foo', 'old' ),
  295. new Attribute( 'foo', 'xyz' ),
  296. doc.version
  297. );
  298. let conflicts = op.conflictsAttributesWith( op2 );
  299. expect( conflicts ).to.be.true;
  300. } );
  301. it( 'if it sets a value for an attribute that is being removed by the operation', () => {
  302. let op = new ChangeOperation(
  303. new Range( new Position( [ 0 ], root ), new Position( [ 1 ], root ) ),
  304. null,
  305. new Attribute( 'foo', 'bar' ),
  306. doc.version
  307. );
  308. let op2 = new ChangeOperation(
  309. new Range( new Position( [ 0 ], root ), new Position( [ 1 ], root ) ),
  310. new Attribute( 'foo', 'old' ),
  311. null,
  312. doc.version
  313. );
  314. let conflicts = op.conflictsAttributesWith( op2 );
  315. expect( conflicts ).to.be.true;
  316. } );
  317. } );
  318. describe( 'getTransformedBy', () => {
  319. let nodeA, nodeB, start, end, range, oldAttr, newAttr, op, baseVersion, expected;
  320. let expectOperation;
  321. beforeEach( () => {
  322. nodeA = new Node();
  323. nodeB = new Node();
  324. baseVersion = doc.version;
  325. oldAttr = new Attribute( 'foo', 'abc' );
  326. newAttr = new Attribute( 'foo', 'bar' );
  327. expected = {
  328. type: ChangeOperation,
  329. oldAttr: oldAttr,
  330. newAttr: newAttr,
  331. baseVersion: baseVersion + 1
  332. };
  333. expectOperation = bender.tools.operations.expectOperation( Position, Range );
  334. } );
  335. describe( 'on multi-level range', () => {
  336. beforeEach( () => {
  337. start = new Position( [ 1, 2 ], root );
  338. end = new Position( [ 2, 2, 4 ], root );
  339. range = new Range( start, end );
  340. op = new ChangeOperation( range, oldAttr, newAttr, baseVersion );
  341. expected.range = new Range( start.clone(), end.clone() );
  342. } );
  343. describe( 'InsertOperation', () => {
  344. it( 'target at different position: no operation update', () => {
  345. let transformBy = new InsertOperation(
  346. new Position( [ 3, 3, 2 ], root ),
  347. [ nodeA, nodeB ],
  348. baseVersion
  349. );
  350. let transOp = op.getTransformedBy( transformBy );
  351. expectOperation( transOp[ 0 ], expected );
  352. } );
  353. it( 'target at offset before: increment offset', () => {
  354. let transformBy = new InsertOperation(
  355. new Position( [ 1, 1 ], root ),
  356. [ nodeA, nodeB ],
  357. baseVersion
  358. );
  359. let transOp = op.getTransformedBy( transformBy );
  360. expected.range.start.offset += 2;
  361. expectOperation( transOp[ 0 ], expected );
  362. } );
  363. it( 'target at same offset: increment offset', () => {
  364. let transformBy = new InsertOperation(
  365. new Position( [ 1, 2 ], root ),
  366. [ nodeA, nodeB ],
  367. baseVersion
  368. );
  369. let transOp = op.getTransformedBy( transformBy );
  370. expected.range.start.offset += 2;
  371. expectOperation( transOp[ 0 ], expected );
  372. } );
  373. it( 'target at offset after: no operation update', () => {
  374. let transformBy = new InsertOperation(
  375. new Position( [ 3, 2 ], root ),
  376. [ nodeA, nodeB ],
  377. baseVersion
  378. );
  379. let transOp = op.getTransformedBy( transformBy );
  380. expectOperation( transOp[ 0 ], expected );
  381. } );
  382. it( 'target before node from path: increment index on path', () => {
  383. let transformBy = new InsertOperation(
  384. new Position( [ 0 ], root ),
  385. [ nodeA, nodeB ],
  386. baseVersion
  387. );
  388. let transOp = op.getTransformedBy( transformBy );
  389. expected.range.start.path[ 0 ] += 2;
  390. expected.range.end.path[ 0 ] += 2;
  391. expectOperation( transOp[ 0 ], expected );
  392. } );
  393. it( 'target after node from path: no position change', () => {
  394. let transformBy = new InsertOperation(
  395. new Position( [ 2, 6 ], root ),
  396. [ nodeA, nodeB ],
  397. baseVersion
  398. );
  399. let transOp = op.getTransformedBy( transformBy );
  400. expectOperation( transOp[ 0 ], expected );
  401. } );
  402. it( 'target inside change range: split into two operations', () => {
  403. let transformBy = new InsertOperation(
  404. new Position( [ 1, 3, 1 ], root ),
  405. [ nodeA, nodeB ],
  406. baseVersion
  407. );
  408. let transOp = op.getTransformedBy( transformBy );
  409. expect( transOp ).to.be.instanceof( Array );
  410. expect( transOp.length ).to.equal( 2 );
  411. expected.range.start.path = [ 1, 3, 3 ];
  412. expectOperation( transOp[ 0 ], expected );
  413. expected.range.start = op.range.start;
  414. expected.range.end.path = [ 1, 3, 1 ];
  415. expected.baseVersion++;
  416. expectOperation( transOp[ 1 ], expected );
  417. } );
  418. } );
  419. describe( 'ChangeOperation', () => {
  420. it( 'attributes not conflicting: no operation update', () => {
  421. let transformBy = new ChangeOperation(
  422. range.clone(),
  423. new Attribute( 'abc', true ),
  424. new Attribute( 'abc', false ),
  425. baseVersion
  426. );
  427. let transOp = op.getTransformedBy( transformBy );
  428. expectOperation( transOp[ 0 ], expected );
  429. } );
  430. it( 'is less important: no operation update', () => {
  431. let transformBy = new ChangeOperation(
  432. new Range( new Position( [ 1, 1, 4 ], root ), new Position( [ 3 ], root ) ),
  433. oldAttr,
  434. null,
  435. baseVersion
  436. );
  437. let transOp = op.getTransformedBy( transformBy, true );
  438. expectOperation( transOp[ 0 ], expected );
  439. } );
  440. it( 'range contains original range: convert to NoOperation', () => {
  441. let transformBy = new ChangeOperation(
  442. new Range( new Position( [ 1, 1, 4 ], root ), new Position( [ 3 ], root ) ),
  443. oldAttr,
  444. null,
  445. baseVersion
  446. );
  447. let transOp = op.getTransformedBy( transformBy );
  448. expectOperation( transOp[ 0 ], {
  449. type: NoOperation,
  450. baseVersion: baseVersion + 1
  451. } );
  452. } );
  453. // [ original range < ] range transformed by >
  454. it( 'range intersects on left: shrink original range', () => {
  455. let transformBy = new ChangeOperation(
  456. new Range( new Position( [ 1, 4, 2 ], root ), new Position( [ 3 ], root ) ),
  457. oldAttr,
  458. null,
  459. baseVersion
  460. );
  461. let transOp = op.getTransformedBy( transformBy );
  462. expected.range.end = new Position( [ 1, 4, 2 ], root );
  463. expectOperation( transOp[ 0 ], expected );
  464. } );
  465. // [ range transformed by < ] original range >
  466. it( 'range intersects on right: shrink original range', () => {
  467. let transformBy = new ChangeOperation(
  468. new Range( new Position( [ 1 ], root ), new Position( [ 2, 1 ], root ) ),
  469. oldAttr,
  470. null,
  471. baseVersion
  472. );
  473. let transOp = op.getTransformedBy( transformBy );
  474. expected.range.start = new Position( [ 2, 1 ], root );
  475. expectOperation( transOp[ 0 ], expected );
  476. } );
  477. it( 'range is inside original range: split original range', () => {
  478. let transformBy = new ChangeOperation(
  479. new Range( new Position( [ 1, 4, 1 ], root ), new Position( [ 2, 1 ], root ) ),
  480. oldAttr,
  481. null,
  482. baseVersion
  483. );
  484. let transOp = op.getTransformedBy( transformBy );
  485. expect( transOp ).to.be.instanceof( Array );
  486. expect( transOp.length ).to.equal( 2 );
  487. expected.range.end.path = [ 1, 4, 1 ];
  488. expectOperation( transOp[ 0 ], expected );
  489. expected.range.start.path = [ 2, 1 ];
  490. expected.range.end = op.range.end;
  491. expected.baseVersion++;
  492. expectOperation( transOp[ 1 ], expected );
  493. } );
  494. } );
  495. describe( 'MoveOperation', () => {
  496. it( 'range and target are different than change range: no operation update', () => {
  497. let transformBy = new MoveOperation(
  498. new Position( [ 1, 1, 2 ], root ),
  499. new Position( [ 3, 4 ], root ),
  500. 2,
  501. baseVersion
  502. );
  503. let transOp = op.getTransformedBy( transformBy );
  504. expectOperation( transOp[ 0 ], expected );
  505. } );
  506. it( 'range offset is before change range start offset: decrement offset', () => {
  507. let transformBy = new MoveOperation(
  508. new Position( [ 1, 0 ], root ),
  509. new Position( [ 3, 4, 1 ], root ),
  510. 2,
  511. baseVersion
  512. );
  513. let transOp = op.getTransformedBy( transformBy );
  514. expected.range.start.offset -= 2;
  515. expectOperation( transOp[ 0 ], expected );
  516. } );
  517. it( 'target offset is before change range start offset: increment offset', () => {
  518. let transformBy = new MoveOperation(
  519. new Position( [ 3, 4, 1 ], root ),
  520. new Position( [ 1, 0 ], root ),
  521. 2,
  522. baseVersion
  523. );
  524. let transOp = op.getTransformedBy( transformBy );
  525. expected.range.start.offset += 2;
  526. expectOperation( transOp[ 0 ], expected );
  527. } );
  528. it( 'range is before node from path to change range: decrement index on path', () => {
  529. let transformBy = new MoveOperation(
  530. new Position( [ 0 ], root ),
  531. new Position( [ 2, 4, 1 ], root ),
  532. 1,
  533. baseVersion
  534. );
  535. let transOp = op.getTransformedBy( transformBy );
  536. expected.range.start.path[ 0 ]--;
  537. expected.range.end.path[ 0 ]--;
  538. expectOperation( transOp[ 0 ], expected );
  539. } );
  540. it( 'range is after node from path to change range: no position change', () => {
  541. let transformBy = new MoveOperation(
  542. new Position( [ 3 ], root ),
  543. new Position( [ 0, 1 ], root ),
  544. 2,
  545. baseVersion
  546. );
  547. let transOp = op.getTransformedBy( transformBy );
  548. expectOperation( transOp[ 0 ], expected );
  549. } );
  550. it( 'target before node from path to change range: increment index on path', () => {
  551. let transformBy = new MoveOperation(
  552. new Position( [ 3, 4, 1 ], root ),
  553. new Position( [ 1, 0 ], root ),
  554. 2,
  555. baseVersion
  556. );
  557. let transOp = op.getTransformedBy( transformBy );
  558. expected.range.start.path[ 1 ] += 2;
  559. expectOperation( transOp[ 0 ], expected );
  560. } );
  561. it( 'target after node from path to change range: no position change', () => {
  562. let transformBy = new MoveOperation(
  563. new Position( [ 3, 4, 1 ], root ),
  564. new Position( [ 3 ], root ),
  565. 2,
  566. baseVersion
  567. );
  568. let transOp = op.getTransformedBy( transformBy );
  569. expectOperation( transOp[ 0 ], expected );
  570. } );
  571. it( 'range intersects on left with change range: split into two operations', () => {
  572. let transformBy = new MoveOperation(
  573. new Position( [ 2, 1 ], root ),
  574. new Position( [ 4 ], root ),
  575. 3,
  576. baseVersion
  577. );
  578. let transOp = op.getTransformedBy( transformBy );
  579. expect( transOp ).to.be.instanceof( Array );
  580. expect( transOp.length ).to.equal( 2 );
  581. expected.range.end.path = [ 2, 1 ];
  582. expectOperation( transOp[ 0 ], expected );
  583. expected.range.start.path = [ 4 ];
  584. expected.range.end.path = [ 5, 4 ];
  585. expected.baseVersion++;
  586. expectOperation( transOp[ 1 ], expected );
  587. } );
  588. it( 'range intersects on right with change range: split into two operation', () => {
  589. let transformBy = new MoveOperation(
  590. new Position( [ 1, 1 ], root ),
  591. new Position( [ 0, 0 ], root ),
  592. 3,
  593. baseVersion
  594. );
  595. let transOp = op.getTransformedBy( transformBy );
  596. expect( transOp ).to.be.instanceof( Array );
  597. expect( transOp.length ).to.equal( 2 );
  598. expected.range.start.path = [ 1, 1 ];
  599. expectOperation( transOp[ 0 ], expected );
  600. expected.range.start.path = [ 0, 1 ];
  601. expected.range.end.path = [ 0, 3 ];
  602. expected.baseVersion++;
  603. expectOperation( transOp[ 1 ], expected );
  604. } );
  605. it( 'range contains change range: update change range', () => {
  606. let transformBy = new MoveOperation(
  607. new Position( [ 1 ], root ),
  608. new Position( [ 3, 4, 1 ], root ),
  609. 2,
  610. baseVersion
  611. );
  612. let transOp = op.getTransformedBy( transformBy );
  613. expected.range.start.path = [ 1, 4, 1, 2 ];
  614. expected.range.end.path = [ 1, 4, 2, 2, 4 ];
  615. expectOperation( transOp[ 0 ], expected );
  616. } );
  617. it( 'range is inside change range: split into two operations', () => {
  618. let transformBy = new MoveOperation(
  619. new Position( [ 1, 4 ], root ),
  620. new Position( [ 3, 2 ], root ),
  621. 2,
  622. baseVersion
  623. );
  624. let transOp = op.getTransformedBy( transformBy );
  625. expect( transOp ).to.be.instanceof( Array );
  626. expect( transOp.length ).to.equal( 2 );
  627. expectOperation( transOp[ 0 ], expected );
  628. expected.range.start.path = [ 3, 2 ];
  629. expected.range.end.path = [ 3, 4 ];
  630. expected.baseVersion++;
  631. expectOperation( transOp[ 1 ], expected );
  632. } );
  633. it( 'target inside change range: split into two operations', () => {
  634. let transformBy = new MoveOperation(
  635. new Position( [ 3, 4, 1 ], root ),
  636. new Position( [ 1, 4 ], root ),
  637. 2,
  638. baseVersion
  639. );
  640. let transOp = op.getTransformedBy( transformBy );
  641. expect( transOp ).to.be.instanceof( Array );
  642. expect( transOp.length ).to.equal( 2 );
  643. expected.range.start.path = [ 1, 6 ];
  644. expectOperation( transOp[ 0 ], expected );
  645. expected.range.start = op.range.start;
  646. expected.range.end.path = [ 1, 4 ];
  647. expected.baseVersion++;
  648. expectOperation( transOp[ 1 ], expected );
  649. } );
  650. it( 'range intersects change range and target inside change range: split into three operations', () => {
  651. let transformBy = new MoveOperation(
  652. new Position( [ 1, 1 ], root ),
  653. new Position( [ 2 ], root ),
  654. 3,
  655. baseVersion
  656. );
  657. let transOp = op.getTransformedBy( transformBy );
  658. expect( transOp ).to.be.instanceof( Array );
  659. expect( transOp.length ).to.equal( 3 );
  660. expected.range.start.path = [ 5 ];
  661. expected.range.end.path = [ 5, 2, 4 ];
  662. expectOperation( transOp[ 0 ], expected );
  663. expected.range.start.path = [ 1, 1 ];
  664. expected.range.end.path = [ 2 ];
  665. expected.baseVersion++;
  666. expectOperation( transOp[ 1 ], expected );
  667. expected.range.start.path = [ 3 ];
  668. expected.range.end.path = [ 5 ];
  669. expected.baseVersion++;
  670. expectOperation( transOp[ 2 ], expected );
  671. } );
  672. } );
  673. describe( 'NoOperation', () => {
  674. it( 'no operation update', () => {
  675. let transformBy = new NoOperation( baseVersion );
  676. let transOp = op.getTransformedBy( transformBy );
  677. expectOperation( transOp[ 0 ], expected );
  678. } );
  679. } );
  680. } );
  681. // Some extra cases for a ChangeOperation that operates on single tree level range.
  682. // This means that the change range start and end differs only on offset value.
  683. describe( 'on single-level range', () => {
  684. beforeEach( () => {
  685. start = new Position( [ 0, 2, 1 ], root );
  686. end = new Position( [ 0, 2, 4 ], root );
  687. range = new Range( start, end );
  688. op = new ChangeOperation( range, oldAttr, newAttr, baseVersion );
  689. expected.range = new Range( start.clone(), end.clone() );
  690. } );
  691. describe( 'InsertOperation', () => {
  692. it( 'target at offset before: increment offset', () => {
  693. let transformBy = new InsertOperation(
  694. new Position( [ 0, 2, 0 ], root ),
  695. [ nodeA, nodeB ],
  696. baseVersion
  697. );
  698. let transOp = op.getTransformedBy( transformBy );
  699. expected.range.start.offset += 2;
  700. expected.range.end.offset += 2;
  701. expectOperation( transOp[ 0 ], expected );
  702. } );
  703. it( 'target at same offset: increment offset', () => {
  704. let transformBy = new InsertOperation(
  705. new Position( [ 0, 2, 1 ], root ),
  706. [ nodeA, nodeB ],
  707. baseVersion
  708. );
  709. let transOp = op.getTransformedBy( transformBy );
  710. expected.range.start.offset += 2;
  711. expected.range.end.offset += 2;
  712. expectOperation( transOp[ 0 ], expected );
  713. } );
  714. } );
  715. describe( 'MoveOperation', () => {
  716. it( 'range offset is before change range start offset: decrement offset', () => {
  717. let transformBy = new MoveOperation(
  718. new Position( [ 0, 2, 0 ], root ),
  719. new Position( [ 2, 4, 1 ], root ),
  720. 1,
  721. baseVersion
  722. );
  723. let transOp = op.getTransformedBy( transformBy );
  724. expected.range.start.offset--;
  725. expected.range.end.offset--;
  726. expectOperation( transOp[ 0 ], expected );
  727. } );
  728. it( 'target offset is before change range start offset: increment offset', () => {
  729. let transformBy = new MoveOperation(
  730. new Position( [ 2, 4, 1 ], root ),
  731. new Position( [ 0, 2, 0 ], root ),
  732. 2,
  733. baseVersion
  734. );
  735. let transOp = op.getTransformedBy( transformBy );
  736. expected.range.start.offset += 2;
  737. expected.range.end.offset += 2;
  738. expectOperation( transOp[ 0 ], expected );
  739. } );
  740. it( 'range intersects on left with change range: split into two operations', () => {
  741. let transformBy = new MoveOperation(
  742. new Position( [ 0, 2, 2 ], root ),
  743. new Position( [ 2, 4, 1 ], root ),
  744. 4,
  745. baseVersion
  746. );
  747. let transOp = op.getTransformedBy( transformBy );
  748. expect( transOp ).to.be.instanceof( Array );
  749. expect( transOp.length ).to.equal( 2 );
  750. expected.range.end.offset -= 2;
  751. expectOperation( transOp[ 0 ], expected );
  752. expected.range.start.path = [ 2, 4, 1 ];
  753. expected.range.end.path = [ 2, 4, 3 ];
  754. expected.baseVersion++;
  755. expectOperation( transOp[ 1 ], expected );
  756. } );
  757. it( 'range intersects on right with change range: split into two operation', () => {
  758. let transformBy = new MoveOperation(
  759. new Position( [ 0, 2, 0 ], root ),
  760. new Position( [ 2, 4, 1 ], root ),
  761. 2,
  762. baseVersion
  763. );
  764. let transOp = op.getTransformedBy( transformBy );
  765. expect( transOp ).to.be.instanceof( Array );
  766. expect( transOp.length ).to.equal( 2 );
  767. expected.range.start.offset -= 1;
  768. expected.range.end.offset -= 2;
  769. expectOperation( transOp[ 0 ], expected );
  770. expected.range.start.path = [ 2, 4, 2 ];
  771. expected.range.end.path = [ 2, 4, 3 ];
  772. expected.baseVersion++;
  773. expectOperation( transOp[ 1 ], expected );
  774. } );
  775. it( 'range contains change range: update change range', () => {
  776. let transformBy = new MoveOperation(
  777. new Position( [ 0, 1 ], root ),
  778. new Position( [ 2, 4, 1 ], root ),
  779. 3,
  780. baseVersion
  781. );
  782. let transOp = op.getTransformedBy( transformBy );
  783. expected.range.start.path = [ 2, 4, 2, 1 ];
  784. expected.range.end.path = [ 2, 4, 2, 4 ];
  785. expectOperation( transOp[ 0 ], expected );
  786. } );
  787. it( 'range is inside change range: split into two operations', () => {
  788. let transformBy = new MoveOperation(
  789. new Position( [ 0, 2, 2 ], root ),
  790. new Position( [ 2, 4, 1 ], root ),
  791. 1,
  792. baseVersion
  793. );
  794. let transOp = op.getTransformedBy( transformBy );
  795. expect( transOp ).to.be.instanceof( Array );
  796. expect( transOp.length ).to.equal( 2 );
  797. expected.range.end.offset--;
  798. expectOperation( transOp[ 0 ], expected );
  799. expected.range.start.path = [ 2, 4, 1 ];
  800. expected.range.end.path = [ 2, 4, 2 ];
  801. expected.baseVersion++;
  802. expectOperation( transOp[ 1 ], expected );
  803. } );
  804. it( 'range is same as change range: update change range', () => {
  805. let transformBy = new MoveOperation(
  806. new Position( [ 0, 2, 1 ], root ),
  807. new Position( [ 2, 4, 1 ], root ),
  808. 3,
  809. baseVersion
  810. );
  811. let transOp = op.getTransformedBy( transformBy );
  812. expected.range.start = new Position( [ 2, 4, 1 ], root );
  813. expected.range.end = new Position( [ 2, 4, 4 ], root );
  814. expectOperation( transOp[ 0 ], expected );
  815. } );
  816. it( 'target inside change range: split into two operations', () => {
  817. let transformBy = new MoveOperation(
  818. new Position( [ 2, 4, 1 ], root ),
  819. new Position( [ 0, 2, 2 ], root ),
  820. 2,
  821. baseVersion
  822. );
  823. let transOp = op.getTransformedBy( transformBy );
  824. expect( transOp ).to.be.instanceof( Array );
  825. expect( transOp.length ).to.equal( 2 );
  826. expected.range.start.offset = 4;
  827. expected.range.end.offset = 6;
  828. expectOperation( transOp[ 0 ], expected );
  829. expected.range.start.offset = op.range.start.offset;
  830. expected.range.end.offset = 2;
  831. expected.baseVersion++;
  832. expectOperation( transOp[ 1 ], expected );
  833. } );
  834. it( 'range intersects change range and target inside change range: split into three operations', () => {
  835. let transformBy = new MoveOperation(
  836. new Position( [ 0, 2, 0 ], root ),
  837. new Position( [ 0, 2, 3 ], root ),
  838. 2,
  839. baseVersion
  840. );
  841. let transOp = op.getTransformedBy( transformBy );
  842. expect( transOp ).to.be.instanceof( Array );
  843. expect( transOp.length ).to.equal( 3 );
  844. expected.range.start.offset = 3;
  845. expected.range.end.offset = 4;
  846. expectOperation( transOp[ 0 ], expected );
  847. expected.range.start.offset = 0;
  848. expected.range.end.offset = 1;
  849. expected.baseVersion++;
  850. expectOperation( transOp[ 1 ], expected );
  851. expected.range.start.offset = 2;
  852. expected.range.end.offset = 3;
  853. expected.baseVersion++;
  854. expectOperation( transOp[ 2 ], expected );
  855. } );
  856. } );
  857. } );
  858. } );
  859. } );