splitdelta.js 33 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088
  1. /**
  2. * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
  3. * For licensing, see LICENSE.md.
  4. */
  5. import transformations from '../../../../src/model/delta/basic-transformations'; // eslint-disable-line no-unused-vars
  6. import deltaTransform from '../../../../src/model/delta/transform';
  7. const transform = deltaTransform.transform;
  8. import Element from '../../../../src/model/element';
  9. import Position from '../../../../src/model/position';
  10. import Range from '../../../../src/model/range';
  11. import Delta from '../../../../src/model/delta/delta';
  12. import SplitDelta from '../../../../src/model/delta/splitdelta';
  13. import AttributeDelta from '../../../../src/model/delta/attributedelta';
  14. import RenameDelta from '../../../../src/model/delta/renamedelta';
  15. import InsertOperation from '../../../../src/model/operation/insertoperation';
  16. import AttributeOperation from '../../../../src/model/operation/attributeoperation';
  17. import ReinsertOperation from '../../../../src/model/operation/reinsertoperation';
  18. import MoveOperation from '../../../../src/model/operation/moveoperation';
  19. import NoOperation from '../../../../src/model/operation/nooperation';
  20. import RenameOperation from '../../../../src/model/operation/renameoperation';
  21. import { getNodesAndText } from '../../../../tests/model/_utils/utils';
  22. import {
  23. applyDelta,
  24. expectDelta,
  25. getFilledDocument,
  26. getSplitDelta,
  27. getWrapDelta,
  28. getUnwrapDelta,
  29. getRemoveDelta
  30. } from '../../../../tests/model/delta/transform/_utils/utils';
  31. describe( 'transform', () => {
  32. let doc, root, gy, baseVersion, context;
  33. beforeEach( () => {
  34. doc = getFilledDocument();
  35. root = doc.getRoot();
  36. gy = doc.graveyard;
  37. baseVersion = doc.version;
  38. context = { isStrong: false };
  39. } );
  40. describe( 'SplitDelta by', () => {
  41. let splitDelta, splitPosition;
  42. beforeEach( () => {
  43. splitPosition = new Position( root, [ 3, 3, 3, 3 ] );
  44. splitDelta = getSplitDelta( splitPosition, new Element( 'p' ), 9, baseVersion );
  45. } );
  46. describe( 'SplitDelta', () => {
  47. it( 'split in same parent and offset - isStrong = false', () => {
  48. const splitDeltaB = getSplitDelta( splitPosition, new Element( 'p' ), 9, baseVersion );
  49. const transformed = transform( splitDelta, splitDeltaB, context );
  50. baseVersion = splitDeltaB.operations.length;
  51. expect( transformed.length ).to.equal( 1 );
  52. expectDelta( transformed[ 0 ], {
  53. type: SplitDelta,
  54. operations: [
  55. {
  56. type: InsertOperation,
  57. position: new Position( root, [ 3, 3, 4 ] ),
  58. baseVersion
  59. },
  60. {
  61. type: NoOperation,
  62. baseVersion: baseVersion + 1
  63. }
  64. ]
  65. } );
  66. // Test if deltas do what they should after applying transformed delta.
  67. applyDelta( splitDeltaB, doc );
  68. applyDelta( transformed[ 0 ], doc );
  69. const nodesAndText = getNodesAndText( Range.createFromPositionAndShift( new Position( root, [ 3, 3, 0 ] ), 6 ) );
  70. expect( nodesAndText ).to.equal( 'XXXXXabcdXPabcPPPPfoobarxyzP' );
  71. } );
  72. it( 'split in same parent and offset - isStrong = true', () => {
  73. const splitDeltaB = getSplitDelta( splitPosition, new Element( 'p' ), 9, baseVersion );
  74. context.isStrong = true;
  75. const transformed = transform( splitDelta, splitDeltaB, context );
  76. baseVersion = splitDeltaB.operations.length;
  77. expect( transformed.length ).to.equal( 1 );
  78. expectDelta( transformed[ 0 ], {
  79. type: SplitDelta,
  80. operations: [
  81. {
  82. type: InsertOperation,
  83. position: new Position( root, [ 3, 3, 4 ] ),
  84. baseVersion
  85. },
  86. {
  87. type: NoOperation,
  88. baseVersion: baseVersion + 1
  89. }
  90. ]
  91. } );
  92. // Test if deltas do what they should after applying transformed delta.
  93. applyDelta( splitDeltaB, doc );
  94. applyDelta( transformed[ 0 ], doc );
  95. const nodesAndText = getNodesAndText( Range.createFromPositionAndShift( new Position( root, [ 3, 3, 0 ] ), 6 ) );
  96. expect( nodesAndText ).to.equal( 'XXXXXabcdXPabcPPPPfoobarxyzP' );
  97. } );
  98. it( 'should not change context.insertBefore if it was set', () => {
  99. // SplitDelta x SplitDelta transformation case sets `context.insertBefore` on its own if it is not set.
  100. // It is to achieve better transformation results from UX point of view.
  101. // However, if `context.insertBefore` was already set, it should not be changed. This might be important for undo.
  102. const splitDeltaB = getSplitDelta( splitPosition, new Element( 'p' ), 9, baseVersion );
  103. const transformed = transform( splitDelta, splitDeltaB, {
  104. isStrong: false,
  105. insertBefore: true,
  106. bWasUndone: true // If `insertBefore` was set it means that delta `b` had to be undone.
  107. } );
  108. baseVersion = splitDeltaB.operations.length;
  109. expect( transformed.length ).to.equal( 1 );
  110. expectDelta( transformed[ 0 ], {
  111. type: SplitDelta,
  112. operations: [
  113. {
  114. type: InsertOperation,
  115. // If `context.insertBefore` would not be set, the offset would be 4. See an example above.
  116. position: new Position( root, [ 3, 3, 5 ] ),
  117. baseVersion
  118. },
  119. {
  120. type: NoOperation,
  121. baseVersion: baseVersion + 1
  122. }
  123. ]
  124. } );
  125. } );
  126. it( 'split in same parent, incoming delta splits closer', () => {
  127. const splitDeltaB = getSplitDelta( new Position( root, [ 3, 3, 3, 5 ] ), new Element( 'p' ), 7, baseVersion );
  128. const transformed = transform( splitDelta, splitDeltaB, context );
  129. baseVersion = splitDeltaB.operations.length;
  130. expect( transformed.length ).to.equal( 1 );
  131. expectDelta( transformed[ 0 ], {
  132. type: SplitDelta,
  133. operations: [
  134. {
  135. type: InsertOperation,
  136. position: new Position( root, [ 3, 3, 4 ] ),
  137. baseVersion
  138. },
  139. {
  140. type: MoveOperation,
  141. sourcePosition: new Position( root, [ 3, 3, 3, 3 ] ),
  142. howMany: 2,
  143. targetPosition: new Position( root, [ 3, 3, 4, 0 ] ),
  144. baseVersion: baseVersion + 1
  145. }
  146. ]
  147. } );
  148. // Test if deltas do what they should after applying transformed delta.
  149. applyDelta( splitDeltaB, doc );
  150. applyDelta( transformed[ 0 ], doc );
  151. const nodesAndText = getNodesAndText( Range.createFromPositionAndShift( new Position( root, [ 3, 3, 0 ] ), 6 ) );
  152. // P element is correctly split, there are three P elements, letters in P elements are in correct order.
  153. expect( nodesAndText ).to.equal( 'XXXXXabcdXPabcPPfoPPobarxyzP' );
  154. } );
  155. it( 'split in same parent, incoming delta splits closer, split deltas have reinsert operations', () => {
  156. let reOp = new ReinsertOperation(
  157. new Position( gy, [ 1 ] ),
  158. 1,
  159. Position.createFromPosition( splitDelta.operations[ 0 ].position ),
  160. splitDelta.operations[ 0 ].baseVersion
  161. );
  162. splitDelta.operations[ 0 ] = reOp;
  163. const splitDeltaB = getSplitDelta( new Position( root, [ 3, 3, 3, 5 ] ), new Element( 'p' ), 7, baseVersion );
  164. reOp = new ReinsertOperation(
  165. new Position( gy, [ 0 ] ),
  166. 1,
  167. Position.createFromPosition( splitDeltaB.operations[ 0 ].position ),
  168. splitDeltaB.operations[ 0 ].baseVersion
  169. );
  170. splitDeltaB.operations[ 0 ] = reOp;
  171. const transformed = transform( splitDelta, splitDeltaB, context );
  172. baseVersion = splitDeltaB.operations.length;
  173. expect( transformed.length ).to.equal( 1 );
  174. expectDelta( transformed[ 0 ], {
  175. type: SplitDelta,
  176. operations: [
  177. {
  178. type: ReinsertOperation,
  179. sourcePosition: new Position( gy, [ 0 ] ),
  180. howMany: 1,
  181. targetPosition: new Position( root, [ 3, 3, 4 ] ),
  182. baseVersion
  183. },
  184. {
  185. type: MoveOperation,
  186. sourcePosition: new Position( root, [ 3, 3, 3, 3 ] ),
  187. howMany: 2,
  188. targetPosition: new Position( root, [ 3, 3, 4, 0 ] ),
  189. baseVersion: baseVersion + 1
  190. }
  191. ]
  192. } );
  193. } );
  194. it( 'split in same parent, incoming delta splits further', () => {
  195. const splitDeltaB = getSplitDelta( new Position( root, [ 3, 3, 3, 1 ] ), new Element( 'p' ), 11, baseVersion );
  196. const transformed = transform( splitDelta, splitDeltaB, context );
  197. baseVersion = splitDeltaB.operations.length;
  198. expect( transformed.length ).to.equal( 1 );
  199. expectDelta( transformed[ 0 ], {
  200. type: SplitDelta,
  201. operations: [
  202. {
  203. type: InsertOperation,
  204. position: new Position( root, [ 3, 3, 5 ] ),
  205. baseVersion
  206. },
  207. {
  208. type: MoveOperation,
  209. sourcePosition: new Position( root, [ 3, 3, 4, 2 ] ),
  210. howMany: 9,
  211. targetPosition: new Position( root, [ 3, 3, 5, 0 ] ),
  212. baseVersion: baseVersion + 1
  213. }
  214. ]
  215. } );
  216. // Test if deltas do what they should after applying transformed delta.
  217. applyDelta( splitDeltaB, doc );
  218. applyDelta( transformed[ 0 ], doc );
  219. const nodesAndText = getNodesAndText( Range.createFromPositionAndShift( new Position( root, [ 3, 3, 0 ] ), 6 ) );
  220. // P element is correctly split, there are three P elements, letters in P elements are in correct order.
  221. expect( nodesAndText ).to.equal( 'XXXXXabcdXPaPPbcPPfoobarxyzP' );
  222. } );
  223. it( 'split in same parent, incoming delta splits further, split deltas have reinsert operations', () => {
  224. let reOp = new ReinsertOperation(
  225. new Position( gy, [ 1 ] ),
  226. 1,
  227. Position.createFromPosition( splitDelta.operations[ 0 ].position ),
  228. splitDelta.operations[ 0 ].baseVersion
  229. );
  230. splitDelta.operations[ 0 ] = reOp;
  231. const splitDeltaB = getSplitDelta( new Position( root, [ 3, 3, 3, 1 ] ), new Element( 'p' ), 11, baseVersion );
  232. reOp = new ReinsertOperation(
  233. new Position( gy, [ 0 ] ),
  234. 1,
  235. Position.createFromPosition( splitDeltaB.operations[ 0 ].position ),
  236. splitDeltaB.operations[ 0 ].baseVersion
  237. );
  238. splitDeltaB.operations[ 0 ] = reOp;
  239. const transformed = transform( splitDelta, splitDeltaB, context );
  240. baseVersion = splitDeltaB.operations.length;
  241. expect( transformed.length ).to.equal( 1 );
  242. expectDelta( transformed[ 0 ], {
  243. type: SplitDelta,
  244. operations: [
  245. {
  246. type: ReinsertOperation,
  247. sourcePosition: new Position( gy, [ 0 ] ),
  248. howMany: 1,
  249. targetPosition: new Position( root, [ 3, 3, 5 ] ),
  250. baseVersion
  251. },
  252. {
  253. type: MoveOperation,
  254. sourcePosition: new Position( root, [ 3, 3, 4, 2 ] ),
  255. howMany: 9,
  256. targetPosition: new Position( root, [ 3, 3, 5, 0 ] ),
  257. baseVersion: baseVersion + 1
  258. }
  259. ]
  260. } );
  261. } );
  262. it( 'split in split parent', () => {
  263. const splitDeltaB = getSplitDelta( new Position( root, [ 3, 3, 3 ] ), new Element( 'div' ), 1, baseVersion );
  264. const transformed = transform( splitDelta, splitDeltaB, context );
  265. baseVersion = splitDeltaB.operations.length;
  266. expect( transformed.length ).to.equal( 1 );
  267. expectDelta( transformed[ 0 ], {
  268. type: SplitDelta,
  269. operations: [
  270. {
  271. type: InsertOperation,
  272. position: new Position( root, [ 3, 4, 1 ] ),
  273. baseVersion
  274. },
  275. {
  276. type: MoveOperation,
  277. sourcePosition: new Position( root, [ 3, 4, 0, 3 ] ),
  278. howMany: 9,
  279. targetPosition: new Position( root, [ 3, 4, 1, 0 ] ),
  280. baseVersion: baseVersion + 1
  281. }
  282. ]
  283. } );
  284. // Test if deltas do what they should after applying transformed delta.
  285. applyDelta( splitDeltaB, doc );
  286. applyDelta( transformed[ 0 ], doc );
  287. const nodesAndText = getNodesAndText( Range.createFromPositionAndShift( new Position( root, [ 3, 3 ] ), 2 ) );
  288. // DIV and P elements are correctly split.
  289. expect( nodesAndText ).to.equal( 'DIVXXXXXabcdXDIVDIVPabcPPfoobarxyzPDIV' );
  290. } );
  291. it( 'should use default algorithm and not throw if transformed split delta has NoOperation', () => {
  292. splitDelta.operations[ 1 ] = new NoOperation( 1 );
  293. const splitDeltaB = getSplitDelta( new Position( root, [ 3, 3, 3, 1 ] ), new Element( 'p' ), 11, baseVersion );
  294. const transformed = transform( splitDelta, splitDeltaB, context );
  295. baseVersion = splitDeltaB.operations.length;
  296. expect( transformed.length ).to.equal( 1 );
  297. expectDelta( transformed[ 0 ], {
  298. type: SplitDelta,
  299. operations: [
  300. {
  301. type: InsertOperation,
  302. position: new Position( root, [ 3, 3, 5 ] ),
  303. baseVersion
  304. },
  305. {
  306. type: NoOperation,
  307. baseVersion: baseVersion + 1
  308. }
  309. ]
  310. } );
  311. } );
  312. it( 'should use default algorithm and not throw if split delta to transform by has NoOperation', () => {
  313. const splitDeltaB = getSplitDelta( new Position( root, [ 3, 3, 3, 1 ] ), new Element( 'p' ), 11, baseVersion );
  314. splitDeltaB.operations[ 1 ] = new NoOperation( 1 );
  315. const transformed = transform( splitDelta, splitDeltaB, context );
  316. baseVersion = splitDeltaB.operations.length;
  317. expect( transformed.length ).to.equal( 1 );
  318. expectDelta( transformed[ 0 ], {
  319. type: SplitDelta,
  320. operations: [
  321. {
  322. type: InsertOperation,
  323. position: new Position( root, [ 3, 3, 5 ] ),
  324. baseVersion
  325. },
  326. {
  327. type: MoveOperation,
  328. sourcePosition: new Position( root, [ 3, 3, 3, 3 ] ),
  329. howMany: 9,
  330. targetPosition: new Position( root, [ 3, 3, 5, 0 ] ),
  331. baseVersion: baseVersion + 1
  332. }
  333. ]
  334. } );
  335. } );
  336. } );
  337. describe( 'UnwrapDelta', () => {
  338. it( 'split position directly in unwrapped node', () => {
  339. const unwrapDelta = getUnwrapDelta( new Position( root, [ 3, 3, 3 ] ), 12, baseVersion );
  340. const transformed = transform( splitDelta, unwrapDelta, context );
  341. baseVersion = unwrapDelta.operations.length;
  342. expect( transformed.length ).to.equal( 1 );
  343. expectDelta( transformed[ 0 ], {
  344. type: Delta,
  345. operations: [
  346. {
  347. type: NoOperation,
  348. baseVersion
  349. }
  350. ]
  351. } );
  352. // Test if deltas do what they should after applying transformed delta.
  353. applyDelta( unwrapDelta, doc );
  354. applyDelta( transformed[ 0 ], doc );
  355. const nodesAndText = getNodesAndText( Range.createFromPositionAndShift( new Position( root, [ 3, 3 ] ), 1 ) );
  356. // UnwrapDelta is applied. SplitDelta is discarded.
  357. expect( nodesAndText ).to.equal( 'DIVXXXXXabcdXabcfoobarxyzDIV' );
  358. } );
  359. it( 'split position indirectly in unwrapped node', () => {
  360. const unwrapDelta = getUnwrapDelta( new Position( root, [ 3, 3 ] ), 4, baseVersion );
  361. const transformed = transform( splitDelta, unwrapDelta, context );
  362. expect( transformed.length ).to.equal( 1 );
  363. baseVersion = unwrapDelta.operations.length;
  364. expectDelta( transformed[ 0 ], {
  365. type: SplitDelta,
  366. operations: [
  367. {
  368. type: InsertOperation,
  369. position: new Position( root, [ 3, 7 ] ),
  370. baseVersion
  371. },
  372. {
  373. type: MoveOperation,
  374. sourcePosition: new Position( root, [ 3, 6, 3 ] ),
  375. howMany: 9,
  376. targetPosition: new Position( root, [ 3, 7, 0 ] ),
  377. baseVersion: baseVersion + 1
  378. }
  379. ]
  380. } );
  381. // Test if deltas do what they should after applying transformed delta.
  382. applyDelta( unwrapDelta, doc );
  383. applyDelta( transformed[ 0 ], doc );
  384. const nodesAndText = getNodesAndText( Range.createFromPositionAndShift( new Position( root, [ 3 ] ), 1 ) );
  385. // UnwrapDelta and SplitDelta are correctly applied.
  386. expect( nodesAndText ).to.equal( 'DIVXXXXXaXXXXXXabcdXPabcPPfoobarxyzPDIV' );
  387. } );
  388. it( 'should use default algorithm and not throw if split delta has NoOperation', () => {
  389. splitDelta.operations[ 1 ] = new NoOperation( 1 );
  390. const unwrapDelta = getUnwrapDelta( new Position( root, [ 3, 3 ] ), 4, baseVersion );
  391. const transformed = transform( splitDelta, unwrapDelta, context );
  392. expect( transformed.length ).to.equal( 1 );
  393. baseVersion = unwrapDelta.operations.length;
  394. expectDelta( transformed[ 0 ], {
  395. type: SplitDelta,
  396. operations: [
  397. {
  398. type: InsertOperation,
  399. position: new Position( root, [ 3, 7 ] ),
  400. baseVersion
  401. },
  402. {
  403. type: NoOperation,
  404. baseVersion: baseVersion + 1
  405. }
  406. ]
  407. } );
  408. } );
  409. } );
  410. describe( 'WrapDelta', () => {
  411. it( 'split position is between wrapped nodes', () => {
  412. const wrapRange = new Range( new Position( root, [ 3, 3, 3, 1 ] ), new Position( root, [ 3, 3, 3, 5 ] ) );
  413. const wrapElement = new Element( 'E' );
  414. const wrapDelta = getWrapDelta( wrapRange, wrapElement, baseVersion );
  415. const transformed = transform( splitDelta, wrapDelta, context );
  416. baseVersion = wrapDelta.operations.length;
  417. expect( transformed.length ).to.equal( 1 );
  418. expectDelta( transformed[ 0 ], {
  419. type: Delta,
  420. operations: [
  421. {
  422. type: NoOperation,
  423. baseVersion
  424. }
  425. ]
  426. } );
  427. // Test if deltas do what they should after applying transformed delta.
  428. applyDelta( wrapDelta, doc );
  429. applyDelta( transformed[ 0 ], doc );
  430. const nodesAndText = getNodesAndText( Range.createFromPositionAndShift( new Position( root, [ 3, 3, 3 ] ), 1 ) );
  431. // WrapDelta is applied. SplitDelta is discarded.
  432. expect( nodesAndText ).to.equal( 'PaEbcfoEobarxyzP' );
  433. } );
  434. it( 'split position is before wrapped nodes', () => {
  435. const wrapRange = new Range( new Position( root, [ 3, 3, 3, 5 ] ), new Position( root, [ 3, 3, 3, 7 ] ) );
  436. const wrapElement = new Element( 'E' );
  437. const wrapDelta = getWrapDelta( wrapRange, wrapElement, baseVersion );
  438. const transformed = transform( splitDelta, wrapDelta, context );
  439. expect( transformed.length ).to.equal( 1 );
  440. baseVersion = wrapDelta.operations.length;
  441. expectDelta( transformed[ 0 ], {
  442. type: SplitDelta,
  443. operations: [
  444. {
  445. type: InsertOperation,
  446. position: new Position( root, [ 3, 3, 4 ] ),
  447. baseVersion
  448. },
  449. {
  450. type: MoveOperation,
  451. sourcePosition: new Position( root, [ 3, 3, 3, 3 ] ),
  452. howMany: 8,
  453. targetPosition: new Position( root, [ 3, 3, 4, 0 ] ),
  454. baseVersion: baseVersion + 1
  455. }
  456. ]
  457. } );
  458. // Test if deltas do what they should after applying transformed delta.
  459. applyDelta( wrapDelta, doc );
  460. applyDelta( transformed[ 0 ], doc );
  461. const nodesAndText = getNodesAndText( Range.createFromPositionAndShift( new Position( root, [ 3, 3, 3 ] ), 2 ) );
  462. // WrapDelta and SplitDelta are correctly applied.
  463. expect( nodesAndText ).to.equal( 'PabcPPfoEobEarxyzP' );
  464. } );
  465. it( 'split position is inside wrapped node', () => {
  466. const wrapRange = new Range( new Position( root, [ 3, 3, 2 ] ), new Position( root, [ 3, 3, 4 ] ) );
  467. const wrapElement = new Element( 'E' );
  468. const wrapDelta = getWrapDelta( wrapRange, wrapElement, baseVersion );
  469. const transformed = transform( splitDelta, wrapDelta, context );
  470. expect( transformed.length ).to.equal( 1 );
  471. baseVersion = wrapDelta.operations.length;
  472. expectDelta( transformed[ 0 ], {
  473. type: SplitDelta,
  474. operations: [
  475. {
  476. type: InsertOperation,
  477. position: new Position( root, [ 3, 3, 2, 2 ] ),
  478. baseVersion
  479. },
  480. {
  481. type: MoveOperation,
  482. sourcePosition: new Position( root, [ 3, 3, 2, 1, 3 ] ),
  483. howMany: 9,
  484. targetPosition: new Position( root, [ 3, 3, 2, 2, 0 ] ),
  485. baseVersion: baseVersion + 1
  486. }
  487. ]
  488. } );
  489. // Test if deltas do what they should after applying transformed delta.
  490. applyDelta( wrapDelta, doc );
  491. applyDelta( transformed[ 0 ], doc );
  492. const nodesAndText = getNodesAndText( Range.createFromPositionAndShift( new Position( root, [ 3, 3, 2 ] ), 1 ) );
  493. // WrapDelta and SplitDelta are correctly applied.
  494. expect( nodesAndText ).to.equal( 'EXabcdXPabcPPfoobarxyzPE' );
  495. } );
  496. it( 'should use default algorithm and not throw if split delta has NoOperation', () => {
  497. splitDelta.operations[ 1 ] = new NoOperation( 1 );
  498. const wrapRange = new Range( new Position( root, [ 3, 3, 2 ] ), new Position( root, [ 3, 3, 4 ] ) );
  499. const wrapElement = new Element( 'E' );
  500. const wrapDelta = getWrapDelta( wrapRange, wrapElement, baseVersion );
  501. const transformed = transform( splitDelta, wrapDelta, context );
  502. expect( transformed.length ).to.equal( 1 );
  503. baseVersion = wrapDelta.operations.length;
  504. expectDelta( transformed[ 0 ], {
  505. type: SplitDelta,
  506. operations: [
  507. {
  508. type: InsertOperation,
  509. position: new Position( root, [ 3, 3, 3 ] ),
  510. baseVersion
  511. },
  512. {
  513. type: NoOperation,
  514. baseVersion: baseVersion + 1
  515. }
  516. ]
  517. } );
  518. } );
  519. } );
  520. describe( 'AttributeDelta', () => {
  521. it( 'attribute changed on split element', () => {
  522. const attributeDelta = new AttributeDelta();
  523. attributeDelta.addOperation( new AttributeOperation(
  524. Range.createFromParentsAndOffsets( root, 0, root, 2 ), 'key', 'oldValue', 'newValue', baseVersion
  525. ) );
  526. attributeDelta.addOperation( new AttributeOperation(
  527. Range.createFromPositionAndShift( new Position( root, [ 3, 3, 2 ] ), 3 ), 'key', null, 'newValue', baseVersion + 1
  528. ) );
  529. const transformed = transform( splitDelta, attributeDelta, context );
  530. baseVersion = attributeDelta.operations.length;
  531. expect( transformed.length ).to.equal( 1 );
  532. expectDelta( transformed[ 0 ], {
  533. type: SplitDelta,
  534. operations: [
  535. {
  536. type: InsertOperation,
  537. position: new Position( root, [ 3, 3, 4 ] ),
  538. baseVersion
  539. },
  540. {
  541. type: MoveOperation,
  542. sourcePosition: new Position( root, [ 3, 3, 3, 3 ] ),
  543. howMany: 9,
  544. targetPosition: new Position( root, [ 3, 3, 4, 0 ] ),
  545. baseVersion: baseVersion + 1
  546. }
  547. ]
  548. } );
  549. expect( transformed[ 0 ].operations[ 0 ].nodes.getNode( 0 ).getAttribute( 'key' ) ).to.equal( 'newValue' );
  550. } );
  551. it( 'attribute removed from split element', () => {
  552. splitDelta.operations[ 0 ].nodes.getNode( 0 ).setAttribute( 'key', 'oldValue' );
  553. const attributeDelta = new AttributeDelta();
  554. attributeDelta.addOperation( new AttributeOperation(
  555. Range.createFromParentsAndOffsets( root, 0, root, 2 ), 'key', 'otherValue', null, baseVersion
  556. ) );
  557. attributeDelta.addOperation( new AttributeOperation(
  558. Range.createFromPositionAndShift( new Position( root, [ 3, 3, 2 ] ), 3 ), 'key', 'oldValue', null, baseVersion + 1
  559. ) );
  560. const transformed = transform( splitDelta, attributeDelta, context );
  561. baseVersion = attributeDelta.operations.length;
  562. expect( transformed.length ).to.equal( 1 );
  563. expectDelta( transformed[ 0 ], {
  564. type: SplitDelta,
  565. operations: [
  566. {
  567. type: InsertOperation,
  568. position: new Position( root, [ 3, 3, 4 ] ),
  569. baseVersion
  570. },
  571. {
  572. type: MoveOperation,
  573. sourcePosition: new Position( root, [ 3, 3, 3, 3 ] ),
  574. howMany: 9,
  575. targetPosition: new Position( root, [ 3, 3, 4, 0 ] ),
  576. baseVersion: baseVersion + 1
  577. }
  578. ]
  579. } );
  580. expect( transformed[ 0 ].operations[ 0 ].nodes.getNode( 0 ).hasAttribute( 'key' ) ).to.be.false;
  581. } );
  582. it( 'attribute changed on split element that is reinserted from graveyard', () => {
  583. splitDelta.operations[ 0 ] = new ReinsertOperation(
  584. new Position( gy, [ 1 ] ),
  585. 1,
  586. new Position( root, [ 3, 3, 4 ] ),
  587. baseVersion
  588. );
  589. const attributeDelta = new AttributeDelta();
  590. attributeDelta.addOperation( new AttributeOperation(
  591. Range.createFromParentsAndOffsets( root, 0, root, 4 ), 'key', 'oldValue', 'newValue', baseVersion
  592. ) );
  593. const transformed = transform( splitDelta, attributeDelta, context );
  594. baseVersion = attributeDelta.operations.length;
  595. expect( transformed.length ).to.equal( 1 );
  596. expectDelta( transformed[ 0 ], {
  597. type: SplitDelta,
  598. operations: [
  599. {
  600. type: ReinsertOperation,
  601. sourcePosition: new Position( gy, [ 1 ] ),
  602. howMany: 1,
  603. targetPosition: new Position( root, [ 3, 3, 4 ] ),
  604. baseVersion
  605. },
  606. {
  607. type: MoveOperation,
  608. sourcePosition: new Position( root, [ 3, 3, 3, 3 ] ),
  609. howMany: 9,
  610. targetPosition: new Position( root, [ 3, 3, 4, 0 ] ),
  611. baseVersion: baseVersion + 1
  612. }
  613. ]
  614. } );
  615. } );
  616. it( 'should use default algorithm and not throw if split delta has NoOperation', () => {
  617. splitDelta.operations[ 1 ] = new NoOperation( 1 );
  618. const attributeDelta = new AttributeDelta();
  619. attributeDelta.addOperation( new AttributeOperation(
  620. Range.createFromParentsAndOffsets( root, 0, root, 4 ), 'key', 'oldValue', 'newValue', baseVersion
  621. ) );
  622. const transformed = transform( splitDelta, attributeDelta, context );
  623. baseVersion = attributeDelta.operations.length;
  624. expect( transformed.length ).to.equal( 1 );
  625. expectDelta( transformed[ 0 ], {
  626. type: SplitDelta,
  627. operations: [
  628. {
  629. type: InsertOperation,
  630. position: new Position( root, [ 3, 3, 4 ] ),
  631. baseVersion
  632. },
  633. {
  634. type: NoOperation,
  635. baseVersion: baseVersion + 1
  636. }
  637. ]
  638. } );
  639. } );
  640. } );
  641. describe( 'RenameDelta', () => {
  642. it( 'renamed split element', () => {
  643. const renameDelta = new RenameDelta();
  644. renameDelta.addOperation( new RenameOperation(
  645. new Position( root, [ 3, 3, 3 ] ), 'h2', 'li', baseVersion
  646. ) );
  647. const transformed = transform( splitDelta, renameDelta, context );
  648. baseVersion = renameDelta.operations.length;
  649. expect( transformed.length ).to.equal( 2 );
  650. expectDelta( transformed[ 0 ], {
  651. type: SplitDelta,
  652. operations: [
  653. {
  654. type: InsertOperation,
  655. position: new Position( root, [ 3, 3, 4 ] ),
  656. baseVersion
  657. },
  658. {
  659. type: MoveOperation,
  660. sourcePosition: new Position( root, [ 3, 3, 3, 3 ] ),
  661. howMany: 9,
  662. targetPosition: new Position( root, [ 3, 3, 4, 0 ] ),
  663. baseVersion: baseVersion + 1
  664. }
  665. ]
  666. } );
  667. expectDelta( transformed[ 1 ], {
  668. type: RenameDelta,
  669. operations: [
  670. {
  671. type: RenameOperation,
  672. position: new Position( root, [ 3, 3, 4 ] ),
  673. oldName: 'p', // `oldName` taken from SplitDelta.
  674. newName: 'li',
  675. baseVersion: baseVersion + 2
  676. }
  677. ]
  678. } );
  679. } );
  680. it( 'split element is different than renamed element', () => {
  681. const renameDelta = new RenameDelta();
  682. renameDelta.addOperation( new RenameOperation(
  683. new Position( root, [ 4 ] ), 'p', 'li', baseVersion
  684. ) );
  685. const transformed = transform( splitDelta, renameDelta, context );
  686. baseVersion = renameDelta.operations.length;
  687. expect( transformed.length ).to.equal( 1 );
  688. expectDelta( transformed[ 0 ], {
  689. type: SplitDelta,
  690. operations: [
  691. {
  692. type: InsertOperation,
  693. position: new Position( root, [ 3, 3, 4 ] ),
  694. baseVersion
  695. },
  696. {
  697. type: MoveOperation,
  698. sourcePosition: new Position( root, [ 3, 3, 3, 3 ] ),
  699. howMany: 9,
  700. targetPosition: new Position( root, [ 3, 3, 4, 0 ] ),
  701. baseVersion: baseVersion + 1
  702. }
  703. ]
  704. } );
  705. } );
  706. it( 'renamed split element that is reinserted from graveyard', () => {
  707. splitDelta.operations[ 0 ] = new ReinsertOperation(
  708. new Position( gy, [ 1 ] ),
  709. 1,
  710. new Position( root, [ 3, 3, 4 ] ),
  711. baseVersion
  712. );
  713. const renameDelta = new RenameDelta();
  714. renameDelta.addOperation( new RenameOperation(
  715. new Position( root, [ 3, 3, 3 ] ), 'p', 'li', baseVersion
  716. ) );
  717. context.aWasUndone = true;
  718. const transformed = transform( splitDelta, renameDelta, context );
  719. baseVersion = renameDelta.operations.length;
  720. expect( transformed.length ).to.equal( 1 );
  721. expectDelta( transformed[ 0 ], {
  722. type: SplitDelta,
  723. operations: [
  724. {
  725. type: ReinsertOperation,
  726. sourcePosition: new Position( gy, [ 1 ] ),
  727. howMany: 1,
  728. targetPosition: new Position( root, [ 3, 3, 4 ] ),
  729. baseVersion
  730. },
  731. {
  732. type: MoveOperation,
  733. sourcePosition: new Position( root, [ 3, 3, 3, 3 ] ),
  734. howMany: 9,
  735. targetPosition: new Position( root, [ 3, 3, 4, 0 ] ),
  736. baseVersion: baseVersion + 1
  737. }
  738. ]
  739. } );
  740. } );
  741. it( 'should not throw if clone operation is NoOperation and use default transformation in that case', () => {
  742. const noOpSplitDelta = new SplitDelta();
  743. noOpSplitDelta.addOperation( new NoOperation( 0 ) );
  744. noOpSplitDelta.addOperation( new MoveOperation( new Position( root, [ 1, 2 ] ), 3, new Position( root, [ 2, 0 ] ), 1 ) );
  745. const renameDelta = new RenameDelta();
  746. renameDelta.addOperation( new RenameOperation(
  747. new Position( root, [ 1 ] ),
  748. 'p',
  749. 'li',
  750. baseVersion
  751. ) );
  752. const transformed = transform( noOpSplitDelta, renameDelta, context );
  753. expect( transformed.length ).to.equal( 1 );
  754. expectDelta( transformed[ 0 ], {
  755. type: SplitDelta,
  756. operations: [
  757. {
  758. type: NoOperation,
  759. baseVersion: 1
  760. },
  761. {
  762. type: MoveOperation,
  763. sourcePosition: new Position( root, [ 1, 2 ] ),
  764. howMany: 3,
  765. targetPosition: new Position( root, [ 2, 0 ] ),
  766. baseVersion: 2
  767. }
  768. ]
  769. } );
  770. } );
  771. } );
  772. describe( 'RemoveDelta', () => {
  773. it( 'node inside the removed range was a node that has been split', () => {
  774. splitPosition = new Position( root, [ 3, 3, 2, 2 ] );
  775. splitDelta = getSplitDelta( splitPosition, new Element( 'x' ), 2, baseVersion );
  776. const removePosition = new Position( root, [ 3, 3, 1 ] );
  777. const removeDelta = getRemoveDelta( removePosition, 3, baseVersion );
  778. const removeOperation = removeDelta.operations[ 0 ];
  779. const transformed = transform( splitDelta, removeDelta, context );
  780. expect( transformed.length ).to.equal( 1 );
  781. baseVersion = removeDelta.operations.length;
  782. const newInsertPosition = removeOperation.targetPosition.getShiftedBy( 2 );
  783. const newMoveSourcePosition = removeOperation.targetPosition.getShiftedBy( 1 );
  784. newMoveSourcePosition.path.push( 2 );
  785. const newMoveTargetPosition = Position.createAt( newInsertPosition );
  786. newMoveTargetPosition.path.push( 0 );
  787. expectDelta( transformed[ 0 ], {
  788. type: SplitDelta,
  789. operations: [
  790. {
  791. type: InsertOperation,
  792. position: newInsertPosition,
  793. baseVersion
  794. },
  795. {
  796. type: MoveOperation,
  797. sourcePosition: newMoveSourcePosition,
  798. howMany: 2,
  799. targetPosition: newMoveTargetPosition,
  800. baseVersion: baseVersion + 1
  801. }
  802. ]
  803. } );
  804. } );
  805. it( 'last node in the removed range was a node that has been split', () => {
  806. const removePosition = new Position( root, [ 3, 3, 2 ] );
  807. const removeDelta = getRemoveDelta( removePosition, 2, baseVersion );
  808. const removeOperation = removeDelta.operations[ 0 ];
  809. const transformed = transform( splitDelta, removeDelta, context );
  810. expect( transformed.length ).to.equal( 1 );
  811. baseVersion = removeDelta.operations.length;
  812. const newInsertPosition = removeOperation.targetPosition.getShiftedBy( 2 );
  813. const newMoveSourcePosition = removeOperation.targetPosition.getShiftedBy( 1 );
  814. newMoveSourcePosition.path.push( 3 );
  815. const newMoveTargetPosition = Position.createAt( newInsertPosition );
  816. newMoveTargetPosition.path.push( 0 );
  817. expectDelta( transformed[ 0 ], {
  818. type: SplitDelta,
  819. operations: [
  820. {
  821. type: InsertOperation,
  822. position: newInsertPosition,
  823. baseVersion
  824. },
  825. {
  826. type: MoveOperation,
  827. sourcePosition: newMoveSourcePosition,
  828. howMany: 9,
  829. targetPosition: newMoveTargetPosition,
  830. baseVersion: baseVersion + 1
  831. }
  832. ]
  833. } );
  834. } );
  835. it( 'split node has been removed - undo context', () => {
  836. const removePosition = new Position( root, [ 3, 3, 3 ] );
  837. const removeDelta = getRemoveDelta( removePosition, 1, baseVersion );
  838. context.bWasUndone = true;
  839. const transformed = transform( splitDelta, removeDelta, context );
  840. expect( transformed.length ).to.equal( 1 );
  841. baseVersion = removeDelta.operations.length;
  842. expectDelta( transformed[ 0 ], {
  843. type: SplitDelta,
  844. operations: [
  845. {
  846. type: InsertOperation,
  847. position: splitDelta.operations[ 0 ].position.getShiftedBy( -1 ),
  848. baseVersion
  849. },
  850. {
  851. type: ReinsertOperation,
  852. sourcePosition: new Position( gy, [ 0, 3 ] ),
  853. howMany: splitDelta.operations[ 1 ].howMany,
  854. targetPosition: new Position( root, [ 3, 3, 3, 0 ] ),
  855. baseVersion: baseVersion + 1
  856. }
  857. ]
  858. } );
  859. } );
  860. it( 'should not throw if clone operation is NoOperation and use default transformation in that case', () => {
  861. const noOpSplitDelta = new SplitDelta();
  862. noOpSplitDelta.addOperation( new NoOperation( 0 ) );
  863. noOpSplitDelta.addOperation( new MoveOperation( new Position( root, [ 1, 2 ] ), 3, new Position( root, [ 2, 0 ] ), 1 ) );
  864. const removeDelta = getRemoveDelta( new Position( root, [ 0 ] ), 1, 0 );
  865. const transformed = transform( noOpSplitDelta, removeDelta, context );
  866. expect( transformed.length ).to.equal( 1 );
  867. expectDelta( transformed[ 0 ], {
  868. type: SplitDelta,
  869. operations: [
  870. {
  871. type: NoOperation,
  872. baseVersion: 1
  873. },
  874. {
  875. type: MoveOperation,
  876. sourcePosition: new Position( root, [ 0, 2 ] ),
  877. howMany: 3,
  878. targetPosition: new Position( root, [ 1, 0 ] ),
  879. baseVersion: 2
  880. }
  881. ]
  882. } );
  883. } );
  884. } );
  885. } );
  886. } );