8
0

changeoperation.js 44 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537
  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. /* global describe, before, beforeEach, it, expect */
  7. /* bender-include: ../../_tools/tools.js */
  8. 'use strict';
  9. const getIteratorCount = bender.tools.core.getIteratorCount;
  10. const modules = bender.amd.require(
  11. 'document/document',
  12. 'document/node',
  13. 'document/operation/changeoperation',
  14. 'document/operation/insertoperation',
  15. 'document/operation/moveoperation',
  16. 'document/operation/nooperation',
  17. 'document/position',
  18. 'document/range',
  19. 'document/character',
  20. 'document/attribute',
  21. 'document/nodelist',
  22. 'document/text',
  23. 'ckeditorerror'
  24. );
  25. describe( 'ChangeOperation', () => {
  26. let Document, Node, ChangeOperation, InsertOperation, MoveOperation, NoOperation, Position, Range, Character, Attribute, NodeList, Text, CKEditorError;
  27. before( () => {
  28. Document = modules[ 'document/document' ];
  29. Node = modules[ 'document/node' ];
  30. ChangeOperation = modules[ 'document/operation/changeoperation' ];
  31. InsertOperation = modules[ 'document/operation/insertoperation' ];
  32. MoveOperation = modules[ 'document/operation/moveoperation' ];
  33. NoOperation = modules[ 'document/operation/nooperation' ];
  34. Position = modules[ 'document/position' ];
  35. Range = modules[ 'document/range' ];
  36. Character = modules[ 'document/character' ];
  37. Attribute = modules[ 'document/attribute' ];
  38. NodeList = modules[ 'document/nodelist' ];
  39. Text = modules[ 'document/text' ];
  40. CKEditorError = modules.ckeditorerror;
  41. } );
  42. let doc, root;
  43. beforeEach( () => {
  44. doc = new Document();
  45. root = doc.createRoot( 'root' );
  46. } );
  47. it( 'should insert attribute to the set of nodes', () => {
  48. let newAttr = new Attribute( 'isNew', true );
  49. root.insertChildren( 0, 'bar' );
  50. doc.applyOperation(
  51. new ChangeOperation(
  52. new Range( new Position( [ 0 ], root ), new Position( [ 2 ], root ) ),
  53. null,
  54. newAttr,
  55. doc.version
  56. )
  57. );
  58. expect( doc.version ).to.equal( 1 );
  59. expect( root.getChildCount() ).to.equal( 3 );
  60. expect( root.getChild( 0 ).hasAttr( newAttr ) ).to.be.true;
  61. expect( root.getChild( 1 ).hasAttr( newAttr ) ).to.be.true;
  62. expect( getIteratorCount( root.getChild( 2 ).getAttrs() ) ).to.equal( 0 );
  63. } );
  64. it( 'should add attribute to the existing attributes', () => {
  65. let newAttr = new Attribute( 'isNew', true );
  66. let fooAttr = new Attribute( 'foo', true );
  67. let barAttr = new Attribute( 'bar', true );
  68. root.insertChildren( 0, new Character( 'x', [ fooAttr, barAttr ] ) );
  69. doc.applyOperation(
  70. new ChangeOperation(
  71. new Range( new Position( [ 0 ], root ), new Position( [ 1 ], root ) ),
  72. null,
  73. newAttr,
  74. doc.version
  75. )
  76. );
  77. expect( doc.version ).to.equal( 1 );
  78. expect( root.getChildCount() ).to.equal( 1 );
  79. expect( getIteratorCount( root.getChild( 0 ).getAttrs() ) ).to.equal( 3 );
  80. expect( root.getChild( 0 ).hasAttr( newAttr ) ).to.be.true;
  81. expect( root.getChild( 0 ).hasAttr( fooAttr ) ).to.be.true;
  82. expect( root.getChild( 0 ).hasAttr( barAttr ) ).to.be.true;
  83. } );
  84. it( 'should change attribute to the set of nodes', () => {
  85. let oldAttr = new Attribute( 'isNew', false );
  86. let newAttr = new Attribute( 'isNew', true );
  87. root.insertChildren( 0, new Text( 'bar', [ oldAttr ] ) );
  88. doc.applyOperation(
  89. new ChangeOperation(
  90. new Range( new Position( [ 0 ], root ), new Position( [ 2 ], root ) ),
  91. oldAttr,
  92. newAttr,
  93. doc.version
  94. )
  95. );
  96. expect( doc.version ).to.equal( 1 );
  97. expect( root.getChildCount() ).to.equal( 3 );
  98. expect( getIteratorCount( root.getChild( 0 ).getAttrs() ) ).to.equal( 1 );
  99. expect( root.getChild( 0 ).hasAttr( newAttr ) ).to.be.true;
  100. expect( getIteratorCount( root.getChild( 1 ).getAttrs() ) ).to.equal( 1 );
  101. expect( root.getChild( 1 ).hasAttr( newAttr ) ).to.be.true;
  102. expect( getIteratorCount( root.getChild( 2 ).getAttrs() ) ).to.equal( 1 );
  103. expect( root.getChild( 2 ).hasAttr( oldAttr ) ).to.be.true;
  104. } );
  105. it( 'should change attribute in the middle of existing attributes', () => {
  106. let fooAttr = new Attribute( 'foo', true );
  107. let x1Attr = new Attribute( 'x', 1 );
  108. let x2Attr = new Attribute( 'x', 2 );
  109. let barAttr = new Attribute( 'bar', true );
  110. root.insertChildren( 0, new Character( 'x', [ fooAttr, x1Attr, barAttr ] ) );
  111. doc.applyOperation(
  112. new ChangeOperation(
  113. new Range( new Position( [ 0 ], root ), new Position( [ 1 ], root ) ),
  114. x1Attr,
  115. x2Attr,
  116. doc.version
  117. )
  118. );
  119. expect( doc.version ).to.equal( 1 );
  120. expect( root.getChildCount() ).to.equal( 1 );
  121. expect( getIteratorCount( root.getChild( 0 ).getAttrs() ) ).to.equal( 3 );
  122. expect( root.getChild( 0 ).hasAttr( fooAttr ) ).to.be.true;
  123. expect( root.getChild( 0 ).hasAttr( x2Attr ) ).to.be.true;
  124. expect( root.getChild( 0 ).hasAttr( barAttr ) ).to.be.true;
  125. } );
  126. it( 'should remove attribute', () => {
  127. let fooAttr = new Attribute( 'foo', true );
  128. let xAttr = new Attribute( 'x', true );
  129. let barAttr = new Attribute( 'bar', true );
  130. root.insertChildren( 0, new Character( 'x', [ fooAttr, xAttr, barAttr ] ) );
  131. doc.applyOperation(
  132. new ChangeOperation(
  133. new Range( new Position( [ 0 ], root ), new Position( [ 1 ], root ) ),
  134. xAttr,
  135. null,
  136. doc.version
  137. )
  138. );
  139. expect( doc.version ).to.equal( 1 );
  140. expect( root.getChildCount() ).to.equal( 1 );
  141. expect( getIteratorCount( root.getChild( 0 ).getAttrs() ) ).to.equal( 2 );
  142. expect( root.getChild( 0 ).hasAttr( fooAttr ) ).to.be.true;
  143. expect( root.getChild( 0 ).hasAttr( barAttr ) ).to.be.true;
  144. } );
  145. it( 'should create a change operation as a reverse', () => {
  146. let oldAttr = new Attribute( 'x', 'old' );
  147. let newAttr = new Attribute( 'x', 'new' );
  148. let range = new Range( new Position( [ 0 ], root ), new Position( [ 3 ], root ) );
  149. let operation = new ChangeOperation( range, oldAttr, newAttr, doc.version );
  150. let reverse = operation.getReversed();
  151. expect( reverse ).to.be.an.instanceof( ChangeOperation );
  152. expect( reverse.baseVersion ).to.equal( 1 );
  153. expect( reverse.range ).to.equal( range );
  154. expect( reverse.oldAttr ).to.equal( newAttr );
  155. expect( reverse.newAttr ).to.equal( oldAttr );
  156. } );
  157. it( 'should undo adding attribute by applying reverse operation', () => {
  158. let newAttr = new Attribute( 'isNew', true );
  159. root.insertChildren( 0, 'bar' );
  160. let operation = new ChangeOperation(
  161. new Range( new Position( [ 0 ], root ), new Position( [ 3 ], root ) ),
  162. null,
  163. newAttr,
  164. doc.version
  165. );
  166. let reverse = operation.getReversed();
  167. doc.applyOperation( operation );
  168. doc.applyOperation( reverse );
  169. expect( doc.version ).to.equal( 2 );
  170. expect( root.getChildCount() ).to.equal( 3 );
  171. expect( getIteratorCount( root.getChild( 0 ).getAttrs() ) ).to.equal( 0 );
  172. expect( getIteratorCount( root.getChild( 1 ).getAttrs() ) ).to.equal( 0 );
  173. expect( getIteratorCount( root.getChild( 2 ).getAttrs() ) ).to.equal( 0 );
  174. } );
  175. it( 'should undo changing attribute by applying reverse operation', () => {
  176. let oldAttr = new Attribute( 'isNew', false );
  177. let newAttr = new Attribute( 'isNew', true );
  178. root.insertChildren( 0, new Text( 'bar', [ oldAttr ] ) );
  179. let operation = new ChangeOperation(
  180. new Range( new Position( [ 0 ], root ), new Position( [ 3 ], root ) ),
  181. oldAttr,
  182. newAttr,
  183. doc.version
  184. );
  185. let reverse = operation.getReversed();
  186. doc.applyOperation( operation );
  187. doc.applyOperation( reverse );
  188. expect( doc.version ).to.equal( 2 );
  189. expect( root.getChildCount() ).to.equal( 3 );
  190. expect( getIteratorCount( root.getChild( 0 ).getAttrs() ) ).to.equal( 1 );
  191. expect( root.getChild( 0 ).hasAttr( oldAttr ) ).to.be.true;
  192. expect( getIteratorCount( root.getChild( 1 ).getAttrs() ) ).to.equal( 1 );
  193. expect( root.getChild( 1 ).hasAttr( oldAttr ) ).to.be.true;
  194. expect( getIteratorCount( root.getChild( 2 ).getAttrs() ) ).to.equal( 1 );
  195. expect( root.getChild( 2 ).hasAttr( oldAttr ) ).to.be.true;
  196. } );
  197. it( 'should undo remove attribute by applying reverse operation', () => {
  198. let fooAttr = new Attribute( 'foo', false );
  199. root.insertChildren( 0, new Text( 'bar', [ fooAttr ] ) );
  200. let operation = new ChangeOperation(
  201. new Range( new Position( [ 0 ], root ), new Position( [ 3 ], root ) ),
  202. fooAttr,
  203. null,
  204. doc.version
  205. );
  206. let reverse = operation.getReversed();
  207. doc.applyOperation( operation );
  208. doc.applyOperation( reverse );
  209. expect( doc.version ).to.equal( 2 );
  210. expect( root.getChildCount() ).to.equal( 3 );
  211. expect( getIteratorCount( root.getChild( 0 ).getAttrs() ) ).to.equal( 1 );
  212. expect( root.getChild( 0 ).hasAttr( fooAttr ) ).to.be.true;
  213. expect( getIteratorCount( root.getChild( 1 ).getAttrs() ) ).to.equal( 1 );
  214. expect( root.getChild( 1 ).hasAttr( fooAttr ) ).to.be.true;
  215. expect( getIteratorCount( root.getChild( 2 ).getAttrs() ) ).to.equal( 1 );
  216. expect( root.getChild( 2 ).hasAttr( fooAttr ) ).to.be.true;
  217. } );
  218. it( 'should throw an error when one try to change and the new and old attributes have different keys', () => {
  219. let fooAttr = new Attribute( 'foo', true );
  220. let barAttr = new Attribute( 'bar', true );
  221. root.insertChildren( 0, 'x' );
  222. expect(
  223. () => {
  224. doc.applyOperation(
  225. new ChangeOperation(
  226. new Range( new Position( [ 0 ], root ), new Position( [ 1 ], root ) ),
  227. fooAttr,
  228. barAttr,
  229. doc.version
  230. )
  231. );
  232. }
  233. ).to.throw( CKEditorError, /operation-change-different-keys/ );
  234. } );
  235. it( 'should create operation with the same parameters when cloned', () => {
  236. let range = new Range( new Position( [ 0 ], root ), new Position( [ 1 ], root ) );
  237. let oldAttr = new Attribute( 'foo', 'old' );
  238. let newAttr = new Attribute( 'foo', 'bar' );
  239. let baseVersion = doc.version;
  240. let op = new ChangeOperation( range, oldAttr, newAttr, baseVersion );
  241. let clone = op.clone();
  242. // New instance rather than a pointer to the old instance.
  243. expect( clone ).not.to.be.equal( op );
  244. expect( clone ).to.be.instanceof( ChangeOperation );
  245. expect( clone.range.isEqual( range ) ).to.be.true;
  246. expect( clone.oldAttr.isEqual( oldAttr ) ).to.be.true;
  247. expect( clone.newAttr.isEqual( newAttr ) ).to.be.true;
  248. expect( clone.baseVersion ).to.equal( baseVersion );
  249. } );
  250. describe( 'have conflicting attributes', () => {
  251. it( 'if it sets an attribute that is removed by the other operation', () => {
  252. let op = new ChangeOperation(
  253. new Range( new Position( [ 0 ], root ), new Position( [ 1 ], root ) ),
  254. new Attribute( 'foo', 'old' ),
  255. new Attribute( 'foo', 'bar' ),
  256. doc.version
  257. );
  258. let op2 = new ChangeOperation(
  259. new Range( new Position( [ 0 ], root ), new Position( [ 1 ], root ) ),
  260. new Attribute( 'foo', 'old' ),
  261. null,
  262. doc.version
  263. );
  264. let conflicts = op.conflictsAttributesWith( op2 );
  265. expect( conflicts ).to.be.true;
  266. } );
  267. it( 'if it removes an attribute that is set by the other operation', () => {
  268. let op = new ChangeOperation(
  269. new Range( new Position( [ 0 ], root ), new Position( [ 1 ], root ) ),
  270. new Attribute( 'foo', 'old' ),
  271. null,
  272. doc.version
  273. );
  274. let op2 = new ChangeOperation(
  275. new Range( new Position( [ 0 ], root ), new Position( [ 1 ], root ) ),
  276. new Attribute( 'foo', 'old' ),
  277. new Attribute( 'foo', 'bar' ),
  278. doc.version
  279. );
  280. let conflicts = op.conflictsAttributesWith( op2 );
  281. expect( conflicts ).to.be.true;
  282. } );
  283. it( 'if it sets different value than the other operation', () => {
  284. let op = new ChangeOperation(
  285. new Range( new Position( [ 0 ], root ), new Position( [ 1 ], root ) ),
  286. new Attribute( 'foo', 'old' ),
  287. new Attribute( 'foo', 'bar' ),
  288. doc.version
  289. );
  290. let op2 = new ChangeOperation(
  291. new Range( new Position( [ 0 ], root ), new Position( [ 1 ], root ) ),
  292. new Attribute( 'foo', 'old' ),
  293. new Attribute( 'foo', 'xyz' ),
  294. doc.version
  295. );
  296. let conflicts = op.conflictsAttributesWith( op2 );
  297. expect( conflicts ).to.be.true;
  298. } );
  299. it( 'if it sets a value for an attribute that is being removed by the operation', () => {
  300. let op = new ChangeOperation(
  301. new Range( new Position( [ 0 ], root ), new Position( [ 1 ], root ) ),
  302. null,
  303. new Attribute( 'foo', 'bar' ),
  304. doc.version
  305. );
  306. let op2 = new ChangeOperation(
  307. new Range( new Position( [ 0 ], root ), new Position( [ 1 ], root ) ),
  308. new Attribute( 'foo', 'old' ),
  309. null,
  310. doc.version
  311. );
  312. let conflicts = op.conflictsAttributesWith( op2 );
  313. expect( conflicts ).to.be.true;
  314. } );
  315. } );
  316. describe( 'getTransformedBy', () => {
  317. let nodeA, nodeB, start, end, range, oldAttr, newAttr, op, baseVersion, expected;
  318. beforeEach( () => {
  319. nodeA = new Node();
  320. nodeB = new Node();
  321. baseVersion = doc.version;
  322. oldAttr = new Attribute( 'foo', 'abc' );
  323. newAttr = new Attribute( 'foo', 'bar' );
  324. expected = {
  325. type: ChangeOperation,
  326. oldAttr: oldAttr,
  327. newAttr: newAttr,
  328. baseVersion: baseVersion + 1
  329. };
  330. } );
  331. function expectOperation( op, params ) {
  332. for ( let i in params ) {
  333. if ( params.hasOwnProperty( i ) ) {
  334. if ( i == 'type' ) {
  335. expect( op ).to.be.instanceof( params[ i ] );
  336. }
  337. else if ( params[ i ] instanceof Array ) {
  338. expect( op[ i ].length ).to.equal( params[ i ].length );
  339. for ( let j = 0; j < params[ i ].length; j++ ) {
  340. expect( op[ i ][ j ] ).to.equal( params[ i ][ j ] );
  341. }
  342. } else if ( params[ i ] instanceof Position || params[ i ] instanceof Range ) {
  343. expect( op[ i ].isEqual( params[ i ] ) ).to.be.true;
  344. } else {
  345. expect( op[ i ] ).to.equal( params[ i ] );
  346. }
  347. }
  348. }
  349. }
  350. describe( 'single-level range', () => {
  351. beforeEach( () => {
  352. start = new Position( [ 0, 2, 1 ], root );
  353. end = new Position( [ 0, 2, 4 ], root );
  354. range = new Range( start, end );
  355. op = new ChangeOperation( range, oldAttr, newAttr, baseVersion );
  356. expected.range = new Range( start.clone(), end.clone() );
  357. } );
  358. describe( 'InsertOperation', () => {
  359. it( 'should not change when positions are different', () => {
  360. let transformBy = new InsertOperation(
  361. new Position( [ 1, 3, 2 ], root ),
  362. [ nodeA, nodeB ],
  363. baseVersion
  364. );
  365. let transOp = op.getTransformedBy( transformBy );
  366. expectOperation( transOp[ 0 ], expected );
  367. } );
  368. it( 'should increment offset if addresses are same and offset is after applied operation', () => {
  369. let transformBy = new InsertOperation(
  370. new Position( [ 0, 2, 0 ], root ),
  371. [ nodeA, nodeB ],
  372. baseVersion
  373. );
  374. let transOp = op.getTransformedBy( transformBy );
  375. expected.range.start.offset += 2;
  376. expected.range.end.offset += 2;
  377. expectOperation( transOp[ 0 ], expected );
  378. } );
  379. it( 'should increment offset if positions are same', () => {
  380. let transformBy = new InsertOperation(
  381. new Position( [ 0, 2, 1 ], root ),
  382. [ nodeA, nodeB ],
  383. baseVersion
  384. );
  385. let transOp = op.getTransformedBy( transformBy );
  386. expected.range.start.offset += 2;
  387. expected.range.end.offset += 2;
  388. expectOperation( transOp[ 0 ], expected );
  389. } );
  390. it( 'should not increment offset if addresses are same and offset is before applied operation', () => {
  391. let transformBy = new InsertOperation(
  392. new Position( [ 0, 2, 6 ], root ),
  393. [ nodeA, nodeB ],
  394. baseVersion
  395. );
  396. let transOp = op.getTransformedBy( transformBy );
  397. expectOperation( transOp[ 0 ], expected );
  398. } );
  399. it( 'should update address at node(i) if applied operation\'s address was a prefix and its offset is before node(i)', () => {
  400. let transformBy = new InsertOperation(
  401. new Position( [ 0, 1 ], root ),
  402. [ nodeA, nodeB ],
  403. baseVersion
  404. );
  405. let transOp = op.getTransformedBy( transformBy );
  406. expected.range.start.path[ 1 ] += 2;
  407. expected.range.end.path[ 1 ] += 2;
  408. expectOperation( transOp[ 0 ], expected );
  409. } );
  410. it( 'should not update address at node(i) if applied operation\'s address was a prefix and its offset is after node(i)', () => {
  411. let transformBy = new InsertOperation(
  412. new Position( [ 0, 6 ], root ),
  413. [ nodeA, nodeB ],
  414. baseVersion
  415. );
  416. let transOp = op.getTransformedBy( transformBy );
  417. expectOperation( transOp[ 0 ], expected );
  418. } );
  419. it( 'should be split into two operations if insert was inside the range of incoming change operation', () => {
  420. let transformBy = new InsertOperation(
  421. new Position( [ 0, 2, 2 ], root ),
  422. [ nodeA, nodeB ],
  423. baseVersion
  424. );
  425. let transOp = op.getTransformedBy( transformBy );
  426. expect( transOp ).to.be.instanceof( Array );
  427. expect( transOp.length ).to.equal( 2 );
  428. expected.range.start.offset = 4;
  429. expected.range.end.offset = 6;
  430. expectOperation( transOp[ 0 ], expected );
  431. expected.range.start.offset = op.range.start.offset;
  432. expected.range.end.offset = 2;
  433. expected.baseVersion++;
  434. expectOperation( transOp[ 1 ], expected );
  435. } );
  436. } );
  437. describe( 'ChangeOperation', () => {
  438. it( 'should remain the same if attributes are not conflicting', () => {
  439. let transformBy = new ChangeOperation(
  440. range.clone(),
  441. new Attribute( 'abc', true ),
  442. new Attribute( 'abc', false ),
  443. baseVersion
  444. );
  445. let transOp = op.getTransformedBy( transformBy );
  446. expectOperation( transOp[ 0 ], expected );
  447. } );
  448. describe( 'when attributes are conflicting', () => {
  449. describe( 'when incoming range and on-site range are the same', () => {
  450. it( 'should remain the same if it is stronger', () => {
  451. let transformBy = new ChangeOperation(
  452. range.clone(),
  453. oldAttr,
  454. null,
  455. baseVersion
  456. );
  457. let transOp = op.getTransformedBy( transformBy, true );
  458. expectOperation( transOp[ 0 ], expected );
  459. } );
  460. it( 'should become do-nothing operation if it is weaker', () => {
  461. let transformBy = new ChangeOperation(
  462. range.clone(),
  463. oldAttr,
  464. null,
  465. baseVersion
  466. );
  467. let transOp = op.getTransformedBy( transformBy );
  468. expectOperation( transOp[ 0 ], {
  469. type: NoOperation,
  470. baseVersion: baseVersion + 1
  471. } );
  472. } );
  473. } );
  474. describe( 'when incoming range is contained by on-site range', () => {
  475. it( 'should remain the same if it is stronger', () => {
  476. let transformRange = range.clone();
  477. transformRange.start.offset--;
  478. transformRange.end.offset++;
  479. let transformBy = new ChangeOperation(
  480. transformRange,
  481. oldAttr,
  482. null,
  483. baseVersion
  484. );
  485. let transOp = op.getTransformedBy( transformBy, true );
  486. expectOperation( transOp[ 0 ], expected );
  487. } );
  488. it( 'should become do-nothing operation if it has lower site id', () => {
  489. let transformRange = range.clone();
  490. transformRange.start.offset--;
  491. transformRange.end.offset++;
  492. let transformBy = new ChangeOperation(
  493. transformRange,
  494. oldAttr,
  495. null,
  496. baseVersion
  497. );
  498. let transOp = op.getTransformedBy( transformBy );
  499. expectOperation( transOp[ 0 ], {
  500. type: NoOperation,
  501. baseVersion: baseVersion + 1
  502. } );
  503. } );
  504. } );
  505. // [ incoming range < ] on site range >
  506. describe( 'when incoming range intersects on right-side with on-site range', () => {
  507. it( 'should remain the same if it is stronger', () => {
  508. let transformRange = range.clone();
  509. transformRange.start.offset++;
  510. transformRange.end.offset++;
  511. let transformBy = new ChangeOperation(
  512. transformRange,
  513. oldAttr,
  514. null,
  515. baseVersion
  516. );
  517. let transOp = op.getTransformedBy( transformBy, true );
  518. expectOperation( transOp[ 0 ], expected );
  519. } );
  520. it( 'should get shrunk if it is weaker', () => {
  521. let transformRange = range.clone();
  522. transformRange.start.offset++;
  523. transformRange.end.offset++;
  524. let transformBy = new ChangeOperation(
  525. transformRange,
  526. oldAttr,
  527. null,
  528. baseVersion
  529. );
  530. let transOp = op.getTransformedBy( transformBy );
  531. expected.range.end.offset = 2;
  532. expectOperation( transOp[ 0 ], expected );
  533. } );
  534. } );
  535. // [ on site range < ] incoming range >
  536. describe( 'when incoming range intersects on left-side with on-site range', () => {
  537. it( 'should remain the same if it is stronger', () => {
  538. let transformRange = range.clone();
  539. transformRange.start.offset--;
  540. transformRange.end.offset--;
  541. let transformBy = new ChangeOperation(
  542. transformRange,
  543. oldAttr,
  544. null,
  545. baseVersion
  546. );
  547. let transOp = op.getTransformedBy( transformBy, true );
  548. expectOperation( transOp[ 0 ], expected );
  549. } );
  550. it( 'should get shrunk if it is weaker', () => {
  551. let transformRange = range.clone();
  552. transformRange.start.offset--;
  553. transformRange.end.offset--;
  554. let transformBy = new ChangeOperation(
  555. transformRange,
  556. oldAttr,
  557. null,
  558. baseVersion
  559. );
  560. let transOp = op.getTransformedBy( transformBy );
  561. expected.range.start.offset = 3;
  562. expectOperation( transOp[ 0 ], expected );
  563. } );
  564. } );
  565. describe( 'when incoming range contains on-site range', () => {
  566. it( 'should remain the same if it is stronger', () => {
  567. let transformRange = range.clone();
  568. transformRange.start.offset++;
  569. transformRange.end.offset--;
  570. let transformBy = new ChangeOperation(
  571. transformRange,
  572. oldAttr,
  573. null,
  574. baseVersion
  575. );
  576. let transOp = op.getTransformedBy( transformBy, true );
  577. expectOperation( transOp[ 0 ], expected );
  578. } );
  579. it( 'should get split if it is weaker', () => {
  580. let transformRange = range.clone();
  581. transformRange.start.offset++;
  582. transformRange.end.offset--;
  583. let transformBy = new ChangeOperation(
  584. transformRange,
  585. oldAttr,
  586. null,
  587. baseVersion
  588. );
  589. let transOp = op.getTransformedBy( transformBy );
  590. expect( transOp ).to.be.instanceof( Array );
  591. expect( transOp.length ).to.equal( 2 );
  592. expected.range.end.offset = 2;
  593. expectOperation( transOp[ 0 ], expected );
  594. expected.range.start.offset = 3;
  595. expected.range.end.offset = 4;
  596. expected.baseVersion++;
  597. expectOperation( transOp[ 1 ], expected );
  598. } );
  599. } );
  600. } );
  601. } );
  602. describe( 'MoveOperation', () => {
  603. it( 'should not update address or offset if change target is in different path than move origin and destination', () => {
  604. let transformBy = new MoveOperation(
  605. new Position( [ 1, 2 ], root ),
  606. new Position( [ 2, 4 ], root ),
  607. 2,
  608. baseVersion
  609. );
  610. let transOp = op.getTransformedBy( transformBy );
  611. expectOperation( transOp[ 0 ], expected );
  612. } );
  613. it( 'should have it\'s address merged with destination address if change was inside moved node sub-tree', () => {
  614. let transformBy = new MoveOperation(
  615. new Position( [ 0, 1 ], root ),
  616. new Position( [ 2, 4, 1 ], root ),
  617. 3,
  618. baseVersion
  619. );
  620. let transOp = op.getTransformedBy( transformBy );
  621. expected.range.start.path = [ 2, 4, 2, 1 ];
  622. expected.range.end.path = [ 2, 4, 2, 4 ];
  623. expectOperation( transOp[ 0 ], expected );
  624. } );
  625. it( 'should decrement offset if address is same as move origin and change offset is after moved node offset', () => {
  626. let transformBy = new MoveOperation(
  627. new Position( [ 0, 2, 0 ], root ),
  628. new Position( [ 2, 4, 1 ], root ),
  629. 1,
  630. baseVersion
  631. );
  632. let transOp = op.getTransformedBy( transformBy );
  633. expected.range.start.offset--;
  634. expected.range.end.offset--;
  635. expectOperation( transOp[ 0 ], expected );
  636. } );
  637. it( 'should increment offset if address is same as move destination and change offset is after move-to offset', () => {
  638. let transformBy = new MoveOperation(
  639. new Position( [ 2, 4, 1 ], root ),
  640. new Position( [ 0, 2, 0 ], root ),
  641. 2,
  642. baseVersion
  643. );
  644. let transOp = op.getTransformedBy( transformBy );
  645. expected.range.start.offset += 2;
  646. expected.range.end.offset += 2;
  647. expectOperation( transOp[ 0 ], expected );
  648. } );
  649. it( 'should update address if moved node is before a node from change path', () => {
  650. let transformBy = new MoveOperation(
  651. new Position( [ 0, 0 ], root ),
  652. new Position( [ 2, 4, 1 ], root ),
  653. 2,
  654. baseVersion
  655. );
  656. let transOp = op.getTransformedBy( transformBy );
  657. expected.range.start.path[ 1 ] -= 2;
  658. expected.range.end.path[ 1 ] -= 2;
  659. expectOperation( transOp[ 0 ], expected );
  660. } );
  661. it( 'should not update address if moved node is after a node from change path', () => {
  662. let transformBy = new MoveOperation(
  663. new Position( [ 0, 4 ], root ),
  664. new Position( [ 2, 4, 1 ], root ),
  665. 2,
  666. baseVersion
  667. );
  668. let transOp = op.getTransformedBy( transformBy );
  669. expectOperation( transOp[ 0 ], expected );
  670. } );
  671. it( 'should update address if move-in destination is before a node from change path', () => {
  672. let transformBy = new MoveOperation(
  673. new Position( [ 2, 4, 1 ], root ),
  674. new Position( [ 0, 0 ], root ),
  675. 2,
  676. baseVersion
  677. );
  678. let transOp = op.getTransformedBy( transformBy );
  679. expected.range.start.path[ 1 ] += 2;
  680. expected.range.end.path[ 1 ] += 2;
  681. expectOperation( transOp[ 0 ], expected );
  682. } );
  683. it( 'should not update address if move-in destination is after a node from change path', () => {
  684. let transformBy = new MoveOperation(
  685. new Position( [ 2, 4, 1 ], root ),
  686. new Position( [ 0, 4 ], root ),
  687. 2,
  688. baseVersion
  689. );
  690. let transOp = op.getTransformedBy( transformBy );
  691. expectOperation( transOp[ 0 ], expected );
  692. } );
  693. it( 'should get split into two ranges and one of them have it\'s address merged if change range intersects on right-side with moved range', () => {
  694. let transformBy = new MoveOperation(
  695. new Position( [ 0, 2, 2 ], root ),
  696. new Position( [ 2, 4, 1 ], root ),
  697. 4,
  698. baseVersion
  699. );
  700. let transOp = op.getTransformedBy( transformBy );
  701. expect( transOp ).to.be.instanceof( Array );
  702. expect( transOp.length ).to.equal( 2 );
  703. expected.range.end.offset -= 2;
  704. expectOperation( transOp[ 0 ], expected );
  705. expected.range.start.path = [ 2, 4, 1 ];
  706. expected.range.end.path = [ 2, 4, 3 ];
  707. expected.baseVersion++;
  708. expectOperation( transOp[ 1 ], expected );
  709. } );
  710. it( 'should get split into two ranges and one of them have it\'s address merged if change range intersects on left-side with moved range', () => {
  711. let transformBy = new MoveOperation(
  712. new Position( [ 0, 2, 0 ], root ),
  713. new Position( [ 2, 4, 1 ], root ),
  714. 2,
  715. baseVersion
  716. );
  717. let transOp = op.getTransformedBy( transformBy );
  718. expect( transOp ).to.be.instanceof( Array );
  719. expect( transOp.length ).to.equal( 2 );
  720. expected.range.start.offset -= 1;
  721. expected.range.end.offset -= 2;
  722. expectOperation( transOp[ 0 ], expected );
  723. expected.range.start.path = [ 2, 4, 2 ];
  724. expected.range.end.path = [ 2, 4, 3 ];
  725. expected.baseVersion++;
  726. expectOperation( transOp[ 1 ], expected );
  727. } );
  728. it( 'should get split into two ranges and one of them have it\'s address merged if change range contains moved range', () => {
  729. let transformBy = new MoveOperation(
  730. new Position( [ 0, 2, 2 ], root ),
  731. new Position( [ 2, 4, 1 ], root ),
  732. 1,
  733. baseVersion
  734. );
  735. let transOp = op.getTransformedBy( transformBy );
  736. expect( transOp ).to.be.instanceof( Array );
  737. expect( transOp.length ).to.equal( 2 );
  738. expected.range.end.offset--;
  739. expectOperation( transOp[ 0 ], expected );
  740. expected.range.start.path = [ 2, 4, 1 ];
  741. expected.range.end.path = [ 2, 4, 2 ];
  742. expected.baseVersion++;
  743. expectOperation( transOp[ 1 ], expected );
  744. } );
  745. it( 'should have it\'s address set to destination address and offset updated if change range is same as move range', () => {
  746. let transformBy = new MoveOperation(
  747. new Position( [ 0, 2, 1 ], root ),
  748. new Position( [ 2, 4, 1 ], root ),
  749. 3,
  750. baseVersion
  751. );
  752. let transOp = op.getTransformedBy( transformBy );
  753. expected.range.start = new Position( [ 2, 4, 1 ], root );
  754. expected.range.end = new Position( [ 2, 4, 4 ], root );
  755. expectOperation( transOp[ 0 ], expected );
  756. } );
  757. it( 'should get split into two ranges if move-in destination is inside change range', () => {
  758. let transformBy = new MoveOperation(
  759. new Position( [ 2, 4, 1 ], root ),
  760. new Position( [ 0, 2, 2 ], 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 = 4;
  768. expected.range.end.offset = 6;
  769. expectOperation( transOp[ 0 ], expected );
  770. expected.range.start.offset = op.range.start.offset;
  771. expected.range.end.offset = 2;
  772. expected.baseVersion++;
  773. expectOperation( transOp[ 1 ], expected );
  774. } );
  775. it( 'should get split into three ranges if moved range intersects change range and move-in destination is inside change range', () => {
  776. let transformBy = new MoveOperation(
  777. new Position( [ 0, 2, 0 ], root ),
  778. new Position( [ 0, 2, 3 ], root ),
  779. 2,
  780. baseVersion
  781. );
  782. let transOp = op.getTransformedBy( transformBy );
  783. expect( transOp ).to.be.instanceof( Array );
  784. expect( transOp.length ).to.equal( 3 );
  785. expected.range.start.offset = 3;
  786. expected.range.end.offset = 4;
  787. expectOperation( transOp[ 0 ], expected );
  788. expected.range.start.offset = 0;
  789. expected.range.end.offset = 1;
  790. expected.baseVersion++;
  791. expectOperation( transOp[ 1 ], expected );
  792. expected.range.start.offset = 2;
  793. expected.range.end.offset = 3;
  794. expected.baseVersion++;
  795. expectOperation( transOp[ 2 ], expected );
  796. } );
  797. } );
  798. describe( 'NoOperation', () => {
  799. it( 'should not get updated', () => {
  800. let transformBy = new NoOperation( baseVersion );
  801. let transOp = op.getTransformedBy( transformBy );
  802. expectOperation( transOp[ 0 ], expected );
  803. } );
  804. } );
  805. } );
  806. describe( 'multi-level range', () => {
  807. beforeEach( () => {
  808. start = new Position( [ 1, 2 ], root );
  809. end = new Position( [ 2, 2, 4 ], root );
  810. range = new Range( start, end );
  811. op = new ChangeOperation( range, oldAttr, newAttr, baseVersion );
  812. expected.range = new Range( start.clone(), end.clone() );
  813. } );
  814. describe( 'InsertOperation', () => {
  815. it( 'should not change when positions are different', () => {
  816. let transformBy = new InsertOperation(
  817. new Position( [ 3, 3, 2 ], root ),
  818. [ nodeA, nodeB ],
  819. baseVersion
  820. );
  821. let transOp = op.getTransformedBy( transformBy );
  822. expectOperation( transOp[ 0 ], expected );
  823. } );
  824. it( 'should increment offset if addresses are same and offset is after applied operation', () => {
  825. let transformBy = new InsertOperation(
  826. new Position( [ 1, 1 ], root ),
  827. [ nodeA, nodeB ],
  828. baseVersion
  829. );
  830. let transOp = op.getTransformedBy( transformBy );
  831. expected.range.start.offset += 2;
  832. expectOperation( transOp[ 0 ], expected );
  833. } );
  834. it( 'should increment offset if positions are same', () => {
  835. let transformBy = new InsertOperation(
  836. new Position( [ 1, 2 ], root ),
  837. [ nodeA, nodeB ],
  838. baseVersion
  839. );
  840. let transOp = op.getTransformedBy( transformBy );
  841. expected.range.start.offset += 2;
  842. expectOperation( transOp[ 0 ], expected );
  843. } );
  844. it( 'should not increment offset if insert position is after change range', () => {
  845. let transformBy = new InsertOperation(
  846. new Position( [ 3, 2 ], root ),
  847. [ nodeA, nodeB ],
  848. baseVersion
  849. );
  850. let transOp = op.getTransformedBy( transformBy );
  851. expectOperation( transOp[ 0 ], expected );
  852. } );
  853. it( 'should update address at node(i) if applied operation\'s address was a prefix and its offset is before node(i)', () => {
  854. let transformBy = new InsertOperation(
  855. new Position( [ 0 ], root ),
  856. [ nodeA, nodeB ],
  857. baseVersion
  858. );
  859. let transOp = op.getTransformedBy( transformBy );
  860. expected.range.start.path[ 0 ] += 2;
  861. expected.range.end.path[ 0 ] += 2;
  862. expectOperation( transOp[ 0 ], expected );
  863. } );
  864. it( 'should not update address at node(i) if applied operation\'s address was a prefix and its offset is after node(i)', () => {
  865. let transformBy = new InsertOperation(
  866. new Position( [ 2, 6 ], root ),
  867. [ nodeA, nodeB ],
  868. baseVersion
  869. );
  870. let transOp = op.getTransformedBy( transformBy );
  871. expectOperation( transOp[ 0 ], expected );
  872. } );
  873. it( 'should be split into two operations if insert was inside the range of incoming change operation', () => {
  874. let transformBy = new InsertOperation(
  875. new Position( [ 1, 3, 1 ], root ),
  876. [ nodeA, nodeB ],
  877. baseVersion
  878. );
  879. let transOp = op.getTransformedBy( transformBy );
  880. expect( transOp ).to.be.instanceof( Array );
  881. expect( transOp.length ).to.equal( 2 );
  882. expected.range.start.path = [ 1, 3, 3 ];
  883. expectOperation( transOp[ 0 ], expected );
  884. expected.range.start = op.range.start;
  885. expected.range.end.path = [ 1, 3, 1 ];
  886. expected.baseVersion++;
  887. expectOperation( transOp[ 1 ], expected );
  888. } );
  889. } );
  890. describe( 'ChangeOperation', () => {
  891. describe( 'when attributes are conflicting', () => {
  892. describe( 'when incoming range is contained by on-site range', () => {
  893. it( 'should remain the same if it is stronger', () => {
  894. let transformBy = new ChangeOperation(
  895. new Range( new Position( [ 1, 1, 4 ], root ), new Position( [ 3 ], root ) ),
  896. oldAttr,
  897. null,
  898. baseVersion
  899. );
  900. let transOp = op.getTransformedBy( transformBy, true );
  901. expectOperation( transOp[ 0 ], expected );
  902. } );
  903. it( 'should become do-nothing operation if it has lower site id', () => {
  904. let transformBy = new ChangeOperation(
  905. new Range( new Position( [ 1, 1, 4 ], root ), new Position( [ 3 ], root ) ),
  906. oldAttr,
  907. null,
  908. baseVersion
  909. );
  910. let transOp = op.getTransformedBy( transformBy );
  911. expectOperation( transOp[ 0 ], {
  912. type: NoOperation,
  913. baseVersion: baseVersion + 1
  914. } );
  915. } );
  916. } );
  917. // [ incoming range < ] on site range >
  918. describe( 'when incoming range intersects on right-side with on-site range', () => {
  919. it( 'should remain the same if it is stronger', () => {
  920. let transformBy = new ChangeOperation(
  921. new Range( new Position( [ 1, 4, 2 ], root ), new Position( [ 3 ], root ) ),
  922. oldAttr,
  923. null,
  924. baseVersion
  925. );
  926. let transOp = op.getTransformedBy( transformBy, true );
  927. expectOperation( transOp[ 0 ], expected );
  928. } );
  929. it( 'should get shrunk if it is weaker', () => {
  930. let transformBy = new ChangeOperation(
  931. new Range( new Position( [ 1, 4, 2 ], root ), new Position( [ 3 ], root ) ),
  932. oldAttr,
  933. null,
  934. baseVersion
  935. );
  936. let transOp = op.getTransformedBy( transformBy );
  937. expected.range.end = new Position( [ 1, 4, 2 ], root );
  938. expectOperation( transOp[ 0 ], expected );
  939. } );
  940. } );
  941. // [ on site range < ] incoming range >
  942. describe( 'when incoming range intersects on left-side with on-site range', () => {
  943. it( 'should remain the same if it is stronger', () => {
  944. let transformBy = new ChangeOperation(
  945. new Range( new Position( [ 1 ], root ), new Position( [ 2, 1 ], root ) ),
  946. oldAttr,
  947. null,
  948. baseVersion
  949. );
  950. let transOp = op.getTransformedBy( transformBy, true );
  951. expectOperation( transOp[ 0 ], expected );
  952. } );
  953. it( 'should get shrunk if it is weaker', () => {
  954. let transformBy = new ChangeOperation(
  955. new Range( new Position( [ 1 ], root ), new Position( [ 2, 1 ], root ) ),
  956. oldAttr,
  957. null,
  958. baseVersion
  959. );
  960. let transOp = op.getTransformedBy( transformBy );
  961. expected.range.start = new Position( [ 2, 1 ], root );
  962. expectOperation( transOp[ 0 ], expected );
  963. } );
  964. } );
  965. describe( 'when incoming range contains on-site range', () => {
  966. it( 'should remain the same if it is stronger', () => {
  967. let transformBy = new ChangeOperation(
  968. new Range( new Position( [ 1, 4, 1 ], root ), new Position( [ 2, 1 ], root ) ),
  969. oldAttr,
  970. null,
  971. baseVersion
  972. );
  973. let transOp = op.getTransformedBy( transformBy, true );
  974. expectOperation( transOp[ 0 ], expected );
  975. } );
  976. it( 'should get split if it is weaker', () => {
  977. let transformBy = new ChangeOperation(
  978. new Range( new Position( [ 1, 4, 1 ], root ), new Position( [ 2, 1 ], root ) ),
  979. oldAttr,
  980. null,
  981. baseVersion
  982. );
  983. let transOp = op.getTransformedBy( transformBy );
  984. expect( transOp ).to.be.instanceof( Array );
  985. expect( transOp.length ).to.equal( 2 );
  986. expected.range.end.path = [ 1, 4, 1 ];
  987. expectOperation( transOp[ 0 ], expected );
  988. expected.range.start.path = [ 2, 1 ];
  989. expected.range.end = op.range.end;
  990. expected.baseVersion++;
  991. expectOperation( transOp[ 1 ], expected );
  992. } );
  993. } );
  994. } );
  995. } );
  996. describe( 'MoveOperation', () => {
  997. it( 'should not update address or offset if change target is in different path than move origin and destination', () => {
  998. let transformBy = new MoveOperation(
  999. new Position( [ 1, 1, 2 ], root ),
  1000. new Position( [ 3, 4 ], root ),
  1001. 2,
  1002. baseVersion
  1003. );
  1004. let transOp = op.getTransformedBy( transformBy );
  1005. expectOperation( transOp[ 0 ], expected );
  1006. } );
  1007. it( 'should have it\'s address merged with destination address if change was inside moved node sub-tree', () => {
  1008. let transformBy = new MoveOperation(
  1009. new Position( [ 1 ], root ),
  1010. new Position( [ 3, 4, 1 ], root ),
  1011. 2,
  1012. baseVersion
  1013. );
  1014. let transOp = op.getTransformedBy( transformBy );
  1015. expected.range.start.path = [ 1, 4, 1, 2 ];
  1016. expected.range.end.path = [ 1, 4, 2, 2, 4 ];
  1017. expectOperation( transOp[ 0 ], expected );
  1018. } );
  1019. it( 'should decrement offset if address is same as move origin and change offset is after moved node offset', () => {
  1020. let transformBy = new MoveOperation(
  1021. new Position( [ 1, 0 ], root ),
  1022. new Position( [ 3, 4, 1 ], root ),
  1023. 2,
  1024. baseVersion
  1025. );
  1026. let transOp = op.getTransformedBy( transformBy );
  1027. expected.range.start.offset -= 2;
  1028. expectOperation( transOp[ 0 ], expected );
  1029. } );
  1030. it( 'should increment offset if address is same as move destination and change offset is after move-to offset', () => {
  1031. let transformBy = new MoveOperation(
  1032. new Position( [ 3, 4, 1 ], root ),
  1033. new Position( [ 1, 0 ], root ),
  1034. 2,
  1035. baseVersion
  1036. );
  1037. let transOp = op.getTransformedBy( transformBy );
  1038. expected.range.start.offset += 2;
  1039. expectOperation( transOp[ 0 ], expected );
  1040. } );
  1041. it( 'should update address if moved node is before a node from change path', () => {
  1042. let transformBy = new MoveOperation(
  1043. new Position( [ 0 ], root ),
  1044. new Position( [ 2, 4, 1 ], root ),
  1045. 1,
  1046. baseVersion
  1047. );
  1048. let transOp = op.getTransformedBy( transformBy );
  1049. expected.range.start.path[ 0 ]--;
  1050. expected.range.end.path[ 0 ]--;
  1051. expectOperation( transOp[ 0 ], expected );
  1052. } );
  1053. it( 'should not update address if moved node is after a node from change path', () => {
  1054. let transformBy = new MoveOperation(
  1055. new Position( [ 3 ], root ),
  1056. new Position( [ 0, 1 ], root ),
  1057. 2,
  1058. baseVersion
  1059. );
  1060. let transOp = op.getTransformedBy( transformBy );
  1061. expectOperation( transOp[ 0 ], expected );
  1062. } );
  1063. it( 'should update address if move-in destination is before a node from change path', () => {
  1064. let transformBy = new MoveOperation(
  1065. new Position( [ 3, 4, 1 ], root ),
  1066. new Position( [ 1, 0 ], root ),
  1067. 2,
  1068. baseVersion
  1069. );
  1070. let transOp = op.getTransformedBy( transformBy );
  1071. expected.range.start.path[ 1 ] += 2;
  1072. expectOperation( transOp[ 0 ], expected );
  1073. } );
  1074. it( 'should not update address if move-in destination is after a node from change path', () => {
  1075. let transformBy = new MoveOperation(
  1076. new Position( [ 3, 4, 1 ], root ),
  1077. new Position( [ 3 ], root ),
  1078. 2,
  1079. baseVersion
  1080. );
  1081. let transOp = op.getTransformedBy( transformBy );
  1082. expectOperation( transOp[ 0 ], expected );
  1083. } );
  1084. it( 'should get split into two ranges and one of them have it\'s address merged if change range intersects on right-side with moved range', () => {
  1085. let transformBy = new MoveOperation(
  1086. new Position( [ 2, 1 ], root ),
  1087. new Position( [ 4 ], root ),
  1088. 3,
  1089. baseVersion
  1090. );
  1091. let transOp = op.getTransformedBy( transformBy );
  1092. expect( transOp ).to.be.instanceof( Array );
  1093. expect( transOp.length ).to.equal( 2 );
  1094. expected.range.end.path = [ 2, 1 ];
  1095. expectOperation( transOp[ 0 ], expected );
  1096. expected.range.start.path = [ 4 ];
  1097. expected.range.end.path = [ 5, 4 ];
  1098. expected.baseVersion++;
  1099. expectOperation( transOp[ 1 ], expected );
  1100. } );
  1101. it( 'should get split into two ranges and one of them have it\'s address merged if change range intersects on left-side with moved range', () => {
  1102. let transformBy = new MoveOperation(
  1103. new Position( [ 1, 1 ], root ),
  1104. new Position( [ 0, 0 ], root ),
  1105. 3,
  1106. baseVersion
  1107. );
  1108. let transOp = op.getTransformedBy( transformBy );
  1109. expect( transOp ).to.be.instanceof( Array );
  1110. expect( transOp.length ).to.equal( 2 );
  1111. expected.range.start.path = [ 1, 1 ];
  1112. expectOperation( transOp[ 0 ], expected );
  1113. expected.range.start.path = [ 0, 1 ];
  1114. expected.range.end.path = [ 0, 3 ];
  1115. expected.baseVersion++;
  1116. expectOperation( transOp[ 1 ], expected );
  1117. } );
  1118. it( 'should get split into two ranges and one of them have it\'s address merged if change range contains moved range', () => {
  1119. let transformBy = new MoveOperation(
  1120. new Position( [ 1, 4 ], root ),
  1121. new Position( [ 3, 2 ], root ),
  1122. 2,
  1123. baseVersion
  1124. );
  1125. let transOp = op.getTransformedBy( transformBy );
  1126. expect( transOp ).to.be.instanceof( Array );
  1127. expect( transOp.length ).to.equal( 2 );
  1128. expectOperation( transOp[ 0 ], expected );
  1129. expected.range.start.path = [ 3, 2 ];
  1130. expected.range.end.path = [ 3, 4 ];
  1131. expected.baseVersion++;
  1132. expectOperation( transOp[ 1 ], expected );
  1133. } );
  1134. it( 'should get split into two ranges if move-in destination is inside change range', () => {
  1135. let transformBy = new MoveOperation(
  1136. new Position( [ 3, 4, 1 ], root ),
  1137. new Position( [ 1, 4 ], root ),
  1138. 2,
  1139. baseVersion
  1140. );
  1141. let transOp = op.getTransformedBy( transformBy );
  1142. expect( transOp ).to.be.instanceof( Array );
  1143. expect( transOp.length ).to.equal( 2 );
  1144. expected.range.start.path = [ 1, 6 ];
  1145. expectOperation( transOp[ 0 ], expected );
  1146. expected.range.start = op.range.start;
  1147. expected.range.end.path = [ 1, 4 ];
  1148. expected.baseVersion++;
  1149. expectOperation( transOp[ 1 ], expected );
  1150. } );
  1151. it( 'should get split into three ranges if moved range intersects change range and move-in destination is inside change range', () => {
  1152. let transformBy = new MoveOperation(
  1153. new Position( [ 1, 1 ], root ),
  1154. new Position( [ 2 ], root ),
  1155. 3,
  1156. baseVersion
  1157. );
  1158. let transOp = op.getTransformedBy( transformBy );
  1159. expect( transOp ).to.be.instanceof( Array );
  1160. expect( transOp.length ).to.equal( 3 );
  1161. expected.range.start.path = [ 5 ];
  1162. expected.range.end.path = [ 5, 2, 4 ];
  1163. expectOperation( transOp[ 0 ], expected );
  1164. expected.range.start.path = [ 1, 1 ];
  1165. expected.range.end.path = [ 2 ];
  1166. expected.baseVersion++;
  1167. expectOperation( transOp[ 1 ], expected );
  1168. expected.range.start.path = [ 3 ];
  1169. expected.range.end.path = [ 5 ];
  1170. expected.baseVersion++;
  1171. expectOperation( transOp[ 2 ], expected );
  1172. } );
  1173. } );
  1174. } );
  1175. } );
  1176. } );