splitdelta.js 33 KB

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