changeoperation.js 44 KB

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