transform.js 69 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640
  1. /**
  2. * @license Copyright (c) 2003-2020, CKSource - Frederico Knabben. All rights reserved.
  3. * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
  4. */
  5. /* globals console */
  6. import { transform, transformSets } from '../../../src/model/operation/transform';
  7. import Model from '../../../src/model/model';
  8. import RootElement from '../../../src/model/rootelement';
  9. import Node from '../../../src/model/node';
  10. import Position from '../../../src/model/position';
  11. import Range from '../../../src/model/range';
  12. import InsertOperation from '../../../src/model/operation/insertoperation';
  13. import AttributeOperation from '../../../src/model/operation/attributeoperation';
  14. import RootAttributeOperation from '../../../src/model/operation/rootattributeoperation';
  15. import MarkerOperation from '../../../src/model/operation/markeroperation';
  16. import MoveOperation from '../../../src/model/operation/moveoperation';
  17. import RenameOperation from '../../../src/model/operation/renameoperation';
  18. import NoOperation from '../../../src/model/operation/nooperation';
  19. describe( 'transform', () => {
  20. let model, doc, root, op, nodeA, nodeB, expected;
  21. beforeEach( () => {
  22. model = new Model();
  23. doc = model.document;
  24. root = doc.createRoot();
  25. nodeA = new Node();
  26. nodeB = new Node();
  27. } );
  28. afterEach( () => {
  29. sinon.restore();
  30. } );
  31. function expectOperation( op, params ) {
  32. for ( const i in params ) {
  33. if ( Object.prototype.hasOwnProperty.call( params, i ) ) {
  34. if ( i == 'type' ) {
  35. expect( op, 'type' ).to.be.instanceof( params[ i ] );
  36. } else if ( params[ i ] instanceof Array ) {
  37. expect( op[ i ].length, i ).to.equal( params[ i ].length );
  38. for ( let j = 0; j < params[ i ].length; j++ ) {
  39. expect( op[ i ][ j ] ).to.equal( params[ i ][ j ] );
  40. }
  41. } else if ( params[ i ] instanceof Position || params[ i ] instanceof Range ) {
  42. expect( op[ i ].isEqual( params[ i ] ), i ).to.be.true;
  43. } else {
  44. expect( op[ i ], i ).to.equal( params[ i ] );
  45. }
  46. }
  47. }
  48. }
  49. const strongContext = {
  50. aIsStrong: true
  51. };
  52. it( 'should throw an error when one of operations is invalid', () => {
  53. // Catches the 'Error during operation transformation!' warning in the CK_DEBUG mode.
  54. sinon.stub( console, 'warn' );
  55. const nodeA = new Node();
  56. const nodeB = new Node();
  57. const position = new Position( root, [ 0 ] );
  58. const a = new InsertOperation( position, [ nodeA ], 0 );
  59. const b = new InsertOperation( position, [ nodeB ], 0 );
  60. // Modify an operation so it will throw an error.
  61. a.position = null;
  62. expect( () => {
  63. transform( a, b, {
  64. aIsStrong: true,
  65. aWasUndone: false,
  66. bWasUndone: false,
  67. abRelation: null,
  68. baRelation: null
  69. } );
  70. } ).to.throw( TypeError );
  71. } );
  72. describe( 'InsertOperation', () => {
  73. let nodeC, nodeD, position;
  74. beforeEach( () => {
  75. nodeC = new Node();
  76. nodeD = new Node();
  77. position = new Position( root, [ 0, 2, 2 ] );
  78. op = new InsertOperation( position, [ nodeA, nodeB ], 0 );
  79. expected = {
  80. type: InsertOperation,
  81. position: position.clone()
  82. };
  83. } );
  84. describe( 'by InsertOperation', () => {
  85. it( 'target at different position: no position update', () => {
  86. const transformBy = new InsertOperation(
  87. new Position( root, [ 1, 3, 2 ] ),
  88. [ nodeC, nodeD ],
  89. 0
  90. );
  91. const transOp = transform( op, transformBy );
  92. expect( transOp.length ).to.equal( 1 );
  93. expectOperation( transOp[ 0 ], expected );
  94. } );
  95. it( 'target at offset before: increment offset', () => {
  96. const transformBy = new InsertOperation(
  97. new Position( root, [ 0, 2, 0 ] ),
  98. [ nodeC, nodeD ],
  99. 0
  100. );
  101. const transOp = transform( op, transformBy );
  102. expected.position.offset += 2;
  103. expect( transOp.length ).to.equal( 1 );
  104. expectOperation( transOp[ 0 ], expected );
  105. } );
  106. it( 'target at same offset and is important: increment offset', () => {
  107. const transformBy = new InsertOperation(
  108. new Position( root, [ 0, 2, 2 ] ),
  109. [ nodeC, nodeD ],
  110. 0
  111. );
  112. const transOp = transform( op, transformBy );
  113. expected.position.offset += 2;
  114. expect( transOp.length ).to.equal( 1 );
  115. expectOperation( transOp[ 0 ], expected );
  116. } );
  117. it( 'target at same offset and is less important: no position update', () => {
  118. const transformBy = new InsertOperation(
  119. new Position( root, [ 0, 2, 2 ] ),
  120. [ nodeC, nodeD ],
  121. 0
  122. );
  123. const transOp = transform( op, transformBy, strongContext );
  124. expect( transOp.length ).to.equal( 1 );
  125. expectOperation( transOp[ 0 ], expected );
  126. } );
  127. it( 'target at same offset and context.insertBefore = false: increment offset', () => {
  128. const transformBy = new InsertOperation(
  129. new Position( root, [ 0, 2, 2 ] ),
  130. [ nodeC, nodeD ],
  131. 0
  132. );
  133. const transOp = transform( op, transformBy );
  134. expected.position.offset += 2;
  135. expect( transOp.length ).to.equal( 1 );
  136. expectOperation( transOp[ 0 ], expected );
  137. } );
  138. it( 'target at offset after: no position update', () => {
  139. const transformBy = new InsertOperation(
  140. new Position( root, [ 0, 2, 3 ] ),
  141. [ nodeC, nodeD ],
  142. 0
  143. );
  144. const transOp = transform( op, transformBy );
  145. expect( transOp.length ).to.equal( 1 );
  146. expectOperation( transOp[ 0 ], expected );
  147. } );
  148. it( 'target before node from path: increment index on path', () => {
  149. const transformBy = new InsertOperation(
  150. new Position( root, [ 0, 1 ] ),
  151. [ nodeC, nodeD ],
  152. 0
  153. );
  154. const transOp = transform( op, transformBy );
  155. expected.position.path[ 1 ] += 2;
  156. expect( transOp.length ).to.equal( 1 );
  157. expectOperation( transOp[ 0 ], expected );
  158. } );
  159. it( 'target after node from path: no position update', () => {
  160. const transformBy = new InsertOperation(
  161. new Position( root, [ 0, 6 ] ),
  162. [ nodeC, nodeD ],
  163. 0
  164. );
  165. const transOp = transform( op, transformBy );
  166. expect( transOp.length ).to.equal( 1 );
  167. expectOperation( transOp[ 0 ], expected );
  168. } );
  169. } );
  170. describe( 'by AttributeOperation', () => {
  171. it( 'no position update', () => {
  172. const transformBy = new AttributeOperation(
  173. Range._createFromPositionAndShift( position, 2 ),
  174. 'foo',
  175. null,
  176. 'bar',
  177. 0
  178. );
  179. const transOp = transform( op, transformBy );
  180. expect( transOp.length ).to.equal( 1 );
  181. expectOperation( transOp[ 0 ], expected );
  182. } );
  183. } );
  184. describe( 'by RootAttributeOperation', () => {
  185. it( 'no position update', () => {
  186. const transformBy = new RootAttributeOperation(
  187. root,
  188. 'foo',
  189. null,
  190. 'bar',
  191. 0
  192. );
  193. const transOp = transform( op, transformBy );
  194. expect( transOp.length ).to.equal( 1 );
  195. expectOperation( transOp[ 0 ], expected );
  196. } );
  197. } );
  198. describe( 'by MoveOperation', () => {
  199. it( 'range and target are different than insert position: no position update', () => {
  200. const transformBy = new MoveOperation(
  201. new Position( root, [ 1, 3, 2 ] ),
  202. 2,
  203. new Position( root, [ 2, 1 ] ),
  204. 0
  205. );
  206. const transOp = transform( op, transformBy );
  207. expect( transOp.length ).to.equal( 1 );
  208. expectOperation( transOp[ 0 ], expected );
  209. } );
  210. it( 'range is before insert position offset: decrement offset', () => {
  211. const transformBy = new MoveOperation(
  212. new Position( root, [ 0, 2, 0 ] ),
  213. 1,
  214. new Position( root, [ 1, 1 ] ),
  215. 0
  216. );
  217. const transOp = transform( op, transformBy );
  218. expected.position.offset--;
  219. expect( transOp.length ).to.equal( 1 );
  220. expectOperation( transOp[ 0 ], expected );
  221. } );
  222. it( 'range offset is after insert position offset: no position update', () => {
  223. const transformBy = new MoveOperation(
  224. new Position( root, [ 0, 2, 5 ] ),
  225. 1,
  226. new Position( root, [ 1, 1 ] ),
  227. 0
  228. );
  229. const transOp = transform( op, transformBy );
  230. expect( transOp.length ).to.equal( 1 );
  231. expectOperation( transOp[ 0 ], expected );
  232. } );
  233. it( 'target offset before insert position offset: increment offset', () => {
  234. const transformBy = new MoveOperation(
  235. new Position( root, [ 1, 1 ] ),
  236. 2,
  237. new Position( root, [ 0, 2, 0 ] ),
  238. 0
  239. );
  240. const transOp = transform( op, transformBy );
  241. expected.position.offset += 2;
  242. expect( transOp.length ).to.equal( 1 );
  243. expectOperation( transOp[ 0 ], expected );
  244. } );
  245. it( 'target offset after insert position offset: no position update', () => {
  246. const transformBy = new MoveOperation(
  247. new Position( root, [ 1, 1 ] ),
  248. 2,
  249. new Position( root, [ 0, 2, 5 ] ),
  250. 0
  251. );
  252. const transOp = transform( op, transformBy );
  253. expect( transOp.length ).to.equal( 1 );
  254. expectOperation( transOp[ 0 ], expected );
  255. } );
  256. it( 'target offset same as insert position offset and is important: increment offset', () => {
  257. const transformBy = new MoveOperation(
  258. new Position( root, [ 1, 1 ] ),
  259. 2,
  260. new Position( root, [ 0, 2, 2 ] ),
  261. 0
  262. );
  263. const transOp = transform( op, transformBy );
  264. expected.position.offset += 2;
  265. expect( transOp.length ).to.equal( 1 );
  266. expectOperation( transOp[ 0 ], expected );
  267. } );
  268. it( 'target offset same as insert position offset and is less important: increment offset', () => {
  269. const transformBy = new MoveOperation(
  270. new Position( root, [ 1, 1 ] ),
  271. 2,
  272. new Position( root, [ 0, 2, 2 ] ),
  273. 0
  274. );
  275. const transOp = transform( op, transformBy, strongContext );
  276. expected.position.offset += 2;
  277. expect( transOp.length ).to.equal( 1 );
  278. expectOperation( transOp[ 0 ], expected );
  279. } );
  280. it( 'range is before node from insert position path: decrement index on path', () => {
  281. const transformBy = new MoveOperation(
  282. new Position( root, [ 0, 0 ] ),
  283. 1,
  284. new Position( root, [ 1, 0 ] ),
  285. 0
  286. );
  287. const transOp = transform( op, transformBy );
  288. expected.position.path[ 1 ] -= 1;
  289. expect( transOp.length ).to.equal( 1 );
  290. expectOperation( transOp[ 0 ], expected );
  291. } );
  292. it( 'range is after node from insert position path: no position update', () => {
  293. const transformBy = new MoveOperation(
  294. new Position( root, [ 0, 4 ] ),
  295. 2,
  296. new Position( root, [ 1, 0 ] ),
  297. 0
  298. );
  299. const transOp = transform( op, transformBy );
  300. expect( transOp.length ).to.equal( 1 );
  301. expectOperation( transOp[ 0 ], expected );
  302. } );
  303. it( 'target before node from insert position path: increment index on path', () => {
  304. const transformBy = new MoveOperation(
  305. new Position( root, [ 1, 0 ] ),
  306. 2,
  307. new Position( root, [ 0, 0 ] ),
  308. 0
  309. );
  310. const transOp = transform( op, transformBy );
  311. expected.position.path[ 1 ] += 2;
  312. expect( transOp.length ).to.equal( 1 );
  313. expectOperation( transOp[ 0 ], expected );
  314. } );
  315. it( 'target after node from insert position path: no position update', () => {
  316. const transformBy = new MoveOperation(
  317. new Position( root, [ 1, 0 ] ),
  318. 2,
  319. new Position( root, [ 0, 4 ] ),
  320. 0
  321. );
  322. const transOp = transform( op, transformBy );
  323. expect( transOp.length ).to.equal( 1 );
  324. expectOperation( transOp[ 0 ], expected );
  325. } );
  326. it( 'range has node that contains insert position: update position', () => {
  327. const transformBy = new MoveOperation(
  328. new Position( root, [ 0, 1 ] ),
  329. 2,
  330. new Position( root, [ 1, 1 ] ),
  331. 0
  332. );
  333. const transOp = transform( op, transformBy );
  334. expected.position.path = [ 1, 2, 2 ];
  335. expect( transOp.length ).to.equal( 1 );
  336. expectOperation( transOp[ 0 ], expected );
  337. } );
  338. it( 'range contains insert position (on same level): update position', () => {
  339. const transformBy = new MoveOperation(
  340. new Position( root, [ 0, 2, 0 ] ),
  341. 4,
  342. new Position( root, [ 1, 0 ] ),
  343. 0
  344. );
  345. const transOp = transform( op, transformBy );
  346. expected.position.path = [ 1, 2 ];
  347. expect( transOp.length ).to.equal( 1 );
  348. expectOperation( transOp[ 0 ], expected );
  349. } );
  350. } );
  351. describe( 'by NoOperation', () => {
  352. it( 'no operation update', () => {
  353. const transformBy = new NoOperation( 0 );
  354. const transOp = transform( op, transformBy );
  355. expect( transOp.length ).to.equal( 1 );
  356. expectOperation( transOp[ 0 ], expected );
  357. } );
  358. } );
  359. describe( 'by RenameOperation', () => {
  360. it( 'no position update', () => {
  361. const transformBy = new RenameOperation( new Position( root, [ 0, 2, 0 ] ), 'oldName', 'newName', 0 );
  362. const transOp = transform( op, transformBy );
  363. expect( transOp.length ).to.equal( 1 );
  364. expectOperation( transOp[ 0 ], expected );
  365. } );
  366. } );
  367. describe( 'by MarkerOperation', () => {
  368. it( 'no position update', () => {
  369. const newRange = new Range( new Position( root, [ 0, 2, 0 ] ), new Position( root, [ 0, 2, 4 ] ) );
  370. const transformBy = new MarkerOperation( 'name', null, newRange, model.markers, false, 0 );
  371. const transOp = transform( op, transformBy );
  372. expect( transOp.length ).to.equal( 1 );
  373. expectOperation( transOp[ 0 ], expected );
  374. } );
  375. } );
  376. } );
  377. describe( 'AttributeOperation', () => {
  378. let start, end, range;
  379. beforeEach( () => {
  380. start = new Position( root, [ 0, 2, 1 ] );
  381. end = new Position( root, [ 0, 2, 4 ] );
  382. range = new Range( start, end );
  383. op = new AttributeOperation( range, 'foo', 'abc', 'bar', 0 );
  384. expected = {
  385. type: AttributeOperation,
  386. range,
  387. key: 'foo',
  388. oldValue: 'abc',
  389. newValue: 'bar'
  390. };
  391. } );
  392. describe( 'by InsertOperation', () => {
  393. it( 'target at offset before: increment offset', () => {
  394. const transformBy = new InsertOperation(
  395. new Position( root, [ 0, 2, 0 ] ),
  396. [ nodeA, nodeB ],
  397. 0
  398. );
  399. const transOp = transform( op, transformBy );
  400. expected.range.start.offset += 2;
  401. expected.range.end.offset += 2;
  402. expect( transOp.length ).to.equal( 1 );
  403. expectOperation( transOp[ 0 ], expected );
  404. } );
  405. it( 'target at same offset: increment offset', () => {
  406. const transformBy = new InsertOperation(
  407. new Position( root, [ 0, 2, 1 ] ),
  408. [ nodeA, nodeB ],
  409. 0
  410. );
  411. const transOp = transform( op, transformBy );
  412. expected.range.start.offset += 2;
  413. expected.range.end.offset += 2;
  414. expect( transOp.length ).to.equal( 1 );
  415. expectOperation( transOp[ 0 ], expected );
  416. } );
  417. } );
  418. describe( 'by MoveOperation', () => {
  419. it( 'range offset is before change range start offset: decrement offset', () => {
  420. const transformBy = new MoveOperation(
  421. new Position( root, [ 0, 2, 0 ] ),
  422. 1,
  423. new Position( root, [ 2, 4, 1 ] ),
  424. 0
  425. );
  426. const transOp = transform( op, transformBy );
  427. expected.range.start.offset--;
  428. expected.range.end.offset--;
  429. expect( transOp.length ).to.equal( 1 );
  430. expectOperation( transOp[ 0 ], expected );
  431. } );
  432. it( 'target offset is before change range start offset: increment offset', () => {
  433. const transformBy = new MoveOperation(
  434. new Position( root, [ 2, 4, 1 ] ),
  435. 2,
  436. new Position( root, [ 0, 2, 0 ] ),
  437. 0
  438. );
  439. const transOp = transform( op, transformBy );
  440. expected.range.start.offset += 2;
  441. expected.range.end.offset += 2;
  442. expect( transOp.length ).to.equal( 1 );
  443. expectOperation( transOp[ 0 ], expected );
  444. } );
  445. it( 'range intersects on left with change range: split into two operations', () => {
  446. const transformBy = new MoveOperation(
  447. new Position( root, [ 0, 2, 2 ] ),
  448. 4,
  449. new Position( root, [ 2, 4, 1 ] ),
  450. 0
  451. );
  452. const transOp = transform( op, transformBy );
  453. expect( transOp.length ).to.equal( 2 );
  454. expected.range.end.offset -= 2;
  455. expectOperation( transOp[ 0 ], expected );
  456. expected.range.start.path = [ 2, 4, 1 ];
  457. expected.range.end.path = [ 2, 4, 3 ];
  458. expectOperation( transOp[ 1 ], expected );
  459. } );
  460. it( 'range intersects on right with change range: split into two operation', () => {
  461. const transformBy = new MoveOperation(
  462. new Position( root, [ 0, 2, 0 ] ),
  463. 2,
  464. new Position( root, [ 2, 4, 1 ] ),
  465. 0
  466. );
  467. const transOp = transform( op, transformBy );
  468. expect( transOp.length ).to.equal( 2 );
  469. expected.range.start.offset -= 1;
  470. expected.range.end.offset -= 2;
  471. expectOperation( transOp[ 0 ], expected );
  472. expected.range.start.path = [ 2, 4, 2 ];
  473. expected.range.end.path = [ 2, 4, 3 ];
  474. expectOperation( transOp[ 1 ], expected );
  475. } );
  476. it( 'range contains change range: update change range', () => {
  477. const transformBy = new MoveOperation(
  478. new Position( root, [ 0, 1 ] ),
  479. 3,
  480. new Position( root, [ 2, 4, 1 ] ),
  481. 0
  482. );
  483. const transOp = transform( op, transformBy );
  484. expected.range.start.path = [ 2, 4, 2, 1 ];
  485. expected.range.end.path = [ 2, 4, 2, 4 ];
  486. expect( transOp.length ).to.equal( 1 );
  487. expectOperation( transOp[ 0 ], expected );
  488. } );
  489. it( 'range is inside change range: split into two operations', () => {
  490. const transformBy = new MoveOperation(
  491. new Position( root, [ 0, 2, 2 ] ),
  492. 1,
  493. new Position( root, [ 2, 4, 1 ] ),
  494. 0
  495. );
  496. const transOp = transform( op, transformBy );
  497. expect( transOp.length ).to.equal( 3 );
  498. expected.range.start.path = [ 0, 2, 1 ];
  499. expected.range.end.path = [ 0, 2, 2 ];
  500. expectOperation( transOp[ 0 ], expected );
  501. expected.range.start.path = [ 0, 2, 2 ];
  502. expected.range.end.path = [ 0, 2, 3 ];
  503. expectOperation( transOp[ 1 ], expected );
  504. expected.range.start.path = [ 2, 4, 1 ];
  505. expected.range.end.path = [ 2, 4, 2 ];
  506. expectOperation( transOp[ 2 ], expected );
  507. } );
  508. it( 'range is same as change range: update change range', () => {
  509. const transformBy = new MoveOperation(
  510. new Position( root, [ 0, 2, 1 ] ),
  511. 3,
  512. new Position( root, [ 2, 4, 1 ] ),
  513. 0
  514. );
  515. const transOp = transform( op, transformBy );
  516. expected.range.start.path = [ 2, 4, 1 ];
  517. expected.range.end.path = [ 2, 4, 4 ];
  518. expect( transOp.length ).to.equal( 1 );
  519. expectOperation( transOp[ 0 ], expected );
  520. } );
  521. it( 'target inside change range: split into two operations', () => {
  522. const transformBy = new MoveOperation(
  523. new Position( root, [ 2, 4, 1 ] ),
  524. 2,
  525. new Position( root, [ 0, 2, 2 ] ),
  526. 0
  527. );
  528. const transOp = transform( op, transformBy );
  529. expect( transOp.length ).to.equal( 2 );
  530. expected.range.end.offset = 2;
  531. expectOperation( transOp[ 0 ], expected );
  532. expected.range.start.offset = 4;
  533. expected.range.end.offset = 6;
  534. expectOperation( transOp[ 1 ], expected );
  535. } );
  536. it( 'range intersects change range and target inside change range: split into three operations', () => {
  537. const transformBy = new MoveOperation(
  538. new Position( root, [ 0, 2, 0 ] ),
  539. 2,
  540. new Position( root, [ 0, 2, 3 ] ),
  541. 0
  542. );
  543. const transOp = transform( op, transformBy );
  544. expect( transOp.length ).to.equal( 3 );
  545. expected.range.start.path = [ 0, 2, 0 ];
  546. expected.range.end.path = [ 0, 2, 1 ];
  547. expectOperation( transOp[ 0 ], expected );
  548. expected.range.start.path = [ 0, 2, 3 ];
  549. expected.range.end.path = [ 0, 2, 4 ];
  550. expectOperation( transOp[ 1 ], expected );
  551. expected.range.start.path = [ 0, 2, 2 ];
  552. expected.range.end.path = [ 0, 2, 3 ];
  553. expectOperation( transOp[ 2 ], expected );
  554. } );
  555. } );
  556. } );
  557. describe( 'RootAttributeOperation', () => {
  558. const diffRoot = new RootElement( null );
  559. beforeEach( () => {
  560. expected = {
  561. type: RootAttributeOperation,
  562. key: 'foo',
  563. oldValue: 'abc',
  564. newValue: 'bar'
  565. };
  566. op = new RootAttributeOperation( root, 'foo', 'abc', 'bar', 0 );
  567. } );
  568. describe( 'by InsertOperation', () => {
  569. it( 'no operation update', () => {
  570. const transformBy = new InsertOperation(
  571. new Position( root, [ 0 ] ),
  572. 'a',
  573. 0
  574. );
  575. const transOp = transform( op, transformBy );
  576. expect( transOp.length ).to.equal( 1 );
  577. expectOperation( transOp[ 0 ], expected );
  578. } );
  579. } );
  580. describe( 'by AttributeOperation', () => {
  581. it( 'no operation update', () => {
  582. const transformBy = new AttributeOperation(
  583. new Range(
  584. new Position( root, [ 0 ] ),
  585. new Position( root, [ 1 ] )
  586. ),
  587. 'foo',
  588. 'bar',
  589. 'xyz',
  590. 0
  591. );
  592. const transOp = transform( op, transformBy );
  593. expect( transOp.length ).to.equal( 1 );
  594. expectOperation( transOp[ 0 ], expected );
  595. } );
  596. } );
  597. describe( 'by RootAttributeOperation', () => {
  598. it( 'changes different root: no operation update', () => {
  599. const transformBy = new RootAttributeOperation(
  600. diffRoot,
  601. 'foo',
  602. 'abc',
  603. 'xyz',
  604. 0
  605. );
  606. const transOp = transform( op, transformBy );
  607. expect( transOp.length ).to.equal( 1 );
  608. expectOperation( transOp[ 0 ], expected );
  609. } );
  610. it( 'changes different key: no operation update', () => {
  611. const transformBy = new RootAttributeOperation(
  612. root,
  613. 'abc',
  614. 'abc',
  615. 'xyz',
  616. 0
  617. );
  618. const transOp = transform( op, transformBy );
  619. expect( transOp.length ).to.equal( 1 );
  620. expectOperation( transOp[ 0 ], expected );
  621. } );
  622. it( 'sets same value for same key: convert to NoOperation', () => {
  623. const transformBy = new RootAttributeOperation(
  624. root,
  625. 'foo',
  626. 'abc',
  627. 'bar',
  628. 0
  629. );
  630. const transOp = transform( op, transformBy );
  631. expect( transOp.length ).to.equal( 1 );
  632. expectOperation( transOp[ 0 ], {
  633. type: NoOperation
  634. } );
  635. } );
  636. it( 'sets different value for same key on same root and is important: convert to NoOperation', () => {
  637. const transformBy = new RootAttributeOperation(
  638. root,
  639. 'foo',
  640. 'abc',
  641. 'xyz',
  642. 0
  643. );
  644. const transOp = transform( op, transformBy );
  645. expect( transOp.length ).to.equal( 1 );
  646. expectOperation( transOp[ 0 ], {
  647. type: NoOperation
  648. } );
  649. } );
  650. it( 'sets different value for same key on same root and is less important: change oldValue', () => {
  651. const transformBy = new RootAttributeOperation(
  652. root,
  653. 'foo',
  654. 'abc',
  655. 'xyz',
  656. 0
  657. );
  658. const transOp = transform( op, transformBy, strongContext );
  659. expected.oldValue = 'xyz';
  660. expect( transOp.length ).to.equal( 1 );
  661. expectOperation( transOp[ 0 ], expected );
  662. } );
  663. } );
  664. describe( 'by MoveOperation', () => {
  665. it( 'no operation update', () => {
  666. const transformBy = new MoveOperation(
  667. new Position( root, [ 0 ] ),
  668. 2,
  669. new Position( root, [ 1 ] ),
  670. 0
  671. );
  672. const transOp = transform( op, transformBy );
  673. expect( transOp.length ).to.equal( 1 );
  674. expectOperation( transOp[ 0 ], expected );
  675. } );
  676. } );
  677. describe( 'by NoOperation', () => {
  678. it( 'no operation update', () => {
  679. const transformBy = new NoOperation( 0 );
  680. const transOp = transform( op, transformBy );
  681. expect( transOp.length ).to.equal( 1 );
  682. expectOperation( transOp[ 0 ], expected );
  683. } );
  684. } );
  685. describe( 'by RenameOperation', () => {
  686. it( 'no position update', () => {
  687. const transformBy = new RenameOperation( new Position( root, [ 0 ] ), 'oldName', 'newName', 0 );
  688. const transOp = transform( op, transformBy );
  689. expect( transOp.length ).to.equal( 1 );
  690. expectOperation( transOp[ 0 ], expected );
  691. } );
  692. } );
  693. describe( 'by MarkerOperation', () => {
  694. it( 'no position update', () => {
  695. const newRange = new Range( new Position( root, [ 0, 2, 0 ] ), new Position( root, [ 0, 2, 8 ] ) );
  696. const transformBy = new MarkerOperation( 'name', null, newRange, model.markers, false, 0 );
  697. const transOp = transform( op, transformBy );
  698. expect( transOp.length ).to.equal( 1 );
  699. expectOperation( transOp[ 0 ], expected );
  700. } );
  701. } );
  702. } );
  703. describe( 'MoveOperation', () => {
  704. let sourcePosition, targetPosition, rangeEnd, howMany;
  705. beforeEach( () => {
  706. sourcePosition = new Position( root, [ 2, 2, 4 ] );
  707. targetPosition = new Position( root, [ 3, 3, 3 ] );
  708. howMany = 2;
  709. rangeEnd = sourcePosition.clone();
  710. rangeEnd.offset += howMany;
  711. op = new MoveOperation( sourcePosition, howMany, targetPosition, 0 );
  712. expected = {
  713. type: MoveOperation,
  714. sourcePosition: sourcePosition.clone(),
  715. targetPosition: targetPosition.clone(),
  716. howMany
  717. };
  718. } );
  719. describe( 'by InsertOperation', () => {
  720. it( 'target at different position than move range and target: no operation update', () => {
  721. const transformBy = new InsertOperation(
  722. new Position( root, [ 1, 3, 2 ] ),
  723. [ nodeA, nodeB ],
  724. 0
  725. );
  726. const transOp = transform( op, transformBy );
  727. expect( transOp.length ).to.equal( 1 );
  728. expectOperation( transOp[ 0 ], expected );
  729. } );
  730. it( 'target inside node from move range: no operation update', () => {
  731. const transformBy = new InsertOperation(
  732. new Position( root, [ 2, 2, 4, 1 ] ),
  733. [ nodeA, nodeB ],
  734. 0
  735. );
  736. const transOp = transform( op, transformBy );
  737. expect( transOp.length ).to.equal( 1 );
  738. expectOperation( transOp[ 0 ], expected );
  739. } );
  740. it( 'target at offset before range offset: increment range offset', () => {
  741. const transformBy = new InsertOperation(
  742. new Position( root, [ 2, 2, 0 ] ),
  743. [ nodeA, nodeB ],
  744. 0
  745. );
  746. const transOp = transform( op, transformBy );
  747. expected.sourcePosition.offset += 2;
  748. expect( transOp.length ).to.equal( 1 );
  749. expectOperation( transOp[ 0 ], expected );
  750. } );
  751. it( 'target at offset after range offset: no operation update', () => {
  752. const transformBy = new InsertOperation(
  753. new Position( root, [ 2, 2, 7 ] ),
  754. [ nodeA, nodeB ],
  755. 0
  756. );
  757. const transOp = transform( op, transformBy );
  758. expect( transOp.length ).to.equal( 1 );
  759. expectOperation( transOp[ 0 ], expected );
  760. } );
  761. it( 'target before node from range start path: increment index on range start path', () => {
  762. const transformBy = new InsertOperation(
  763. new Position( root, [ 2, 0 ] ),
  764. [ nodeA, nodeB ],
  765. 0
  766. );
  767. const transOp = transform( op, transformBy );
  768. expected.sourcePosition.path[ 1 ] += 2;
  769. expect( transOp.length ).to.equal( 1 );
  770. expectOperation( transOp[ 0 ], expected );
  771. } );
  772. it( 'target after node from range start path: no operation update', () => {
  773. const transformBy = new InsertOperation(
  774. new Position( root, [ 2, 3 ] ),
  775. [ nodeA, nodeB ],
  776. 0
  777. );
  778. const transOp = transform( op, transformBy );
  779. expect( transOp.length ).to.equal( 1 );
  780. expectOperation( transOp[ 0 ], expected );
  781. } );
  782. it( 'target offset before move target offset: increment target offset', () => {
  783. const transformBy = new InsertOperation(
  784. new Position( root, [ 3, 3, 2 ] ),
  785. [ nodeA, nodeB ],
  786. 0
  787. );
  788. const transOp = transform( op, transformBy );
  789. expected.targetPosition.offset += 2;
  790. expect( transOp.length ).to.equal( 1 );
  791. expectOperation( transOp[ 0 ], expected );
  792. } );
  793. it( 'target offset after move target offset: no operation update', () => {
  794. const transformBy = new InsertOperation(
  795. new Position( root, [ 3, 3, 4 ] ),
  796. [ nodeA, nodeB ],
  797. 0
  798. );
  799. const transOp = transform( op, transformBy );
  800. expect( transOp.length ).to.equal( 1 );
  801. expectOperation( transOp[ 0 ], expected );
  802. } );
  803. it( 'target before node from move target position path: increment index on move target position path', () => {
  804. const transformBy = new InsertOperation(
  805. new Position( root, [ 3, 3 ] ),
  806. [ nodeA, nodeB ],
  807. 0
  808. );
  809. const transOp = transform( op, transformBy );
  810. expected.targetPosition.path[ 1 ] += 2;
  811. expect( transOp.length ).to.equal( 1 );
  812. expectOperation( transOp[ 0 ], expected );
  813. } );
  814. it( 'target after node from move target position path: no operation update', () => {
  815. const transformBy = new InsertOperation(
  816. new Position( root, [ 3, 6 ] ),
  817. [ nodeA, nodeB ],
  818. 0
  819. );
  820. const transOp = transform( op, transformBy );
  821. expect( transOp.length ).to.equal( 1 );
  822. expectOperation( transOp[ 0 ], expected );
  823. } );
  824. it( 'target offset same as move target offset and is important: no operation update', () => {
  825. const transformBy = new InsertOperation(
  826. new Position( root, [ 3, 3, 3 ] ),
  827. [ nodeA, nodeB ],
  828. 0
  829. );
  830. const transOp = transform( op, transformBy );
  831. expect( transOp.length ).to.equal( 1 );
  832. expectOperation( transOp[ 0 ], expected );
  833. } );
  834. it( 'target offset same as move target offset and is less important: no operation update', () => {
  835. const transformBy = new InsertOperation(
  836. new Position( root, [ 3, 3, 3 ] ),
  837. [ nodeA, nodeB ],
  838. 0
  839. );
  840. const transOp = transform( op, transformBy, strongContext );
  841. expect( transOp.length ).to.equal( 1 );
  842. expectOperation( transOp[ 0 ], expected );
  843. } );
  844. it( 'target inside move range: expand range', () => {
  845. const transformBy = new InsertOperation(
  846. new Position( root, [ 2, 2, 5 ] ),
  847. [ nodeA, nodeB ],
  848. 0
  849. );
  850. const transOp = transform( op, transformBy );
  851. expect( transOp.length ).to.equal( 1 );
  852. expected.howMany = 4;
  853. expectOperation( transOp[ 0 ], expected );
  854. } );
  855. it( 'target at offset same as range end boundary: no operation update', () => {
  856. const transformBy = new InsertOperation(
  857. new Position( root, [ 2, 2, 6 ] ),
  858. [ nodeA, nodeB ],
  859. 0
  860. );
  861. const transOp = transform( op, transformBy );
  862. expect( transOp.length ).to.equal( 1 );
  863. expectOperation( transOp[ 0 ], expected );
  864. } );
  865. } );
  866. describe( 'by AttributeOperation', () => {
  867. it( 'no operation update', () => {
  868. const transformBy = new AttributeOperation(
  869. new Range( sourcePosition, rangeEnd ),
  870. 'abc',
  871. true,
  872. false,
  873. 0
  874. );
  875. const transOp = transform( op, transformBy );
  876. expect( transOp.length ).to.equal( 1 );
  877. expectOperation( transOp[ 0 ], expected );
  878. } );
  879. } );
  880. describe( 'by RootAttributeOperation', () => {
  881. it( 'no operation update', () => {
  882. const transformBy = new RootAttributeOperation(
  883. root,
  884. 'foo',
  885. null,
  886. 'bar',
  887. 0
  888. );
  889. const transOp = transform( op, transformBy );
  890. expect( transOp.length ).to.equal( 1 );
  891. expectOperation( transOp[ 0 ], expected );
  892. } );
  893. } );
  894. describe( 'by MoveOperation', () => {
  895. it( 'range and target different than transforming range and target: no operation update', () => {
  896. const transformBy = new MoveOperation(
  897. new Position( root, [ 1, 2 ] ),
  898. 3,
  899. new Position( root, [ 4, 1, 0 ] ),
  900. 0
  901. );
  902. const transOp = transform( op, transformBy );
  903. expect( transOp.length ).to.equal( 1 );
  904. expectOperation( transOp[ 0 ], expected );
  905. } );
  906. it( 'target offset before transforming range start offset: increment range offset', () => {
  907. const transformBy = new MoveOperation(
  908. new Position( root, [ 4, 1, 0 ] ),
  909. 2,
  910. new Position( root, [ 2, 2, 0 ] ),
  911. 0
  912. );
  913. const transOp = transform( op, transformBy );
  914. expected.sourcePosition.offset += 2;
  915. expect( transOp.length ).to.equal( 1 );
  916. expectOperation( transOp[ 0 ], expected );
  917. } );
  918. it( 'target offset after transforming range start offset: no operation update', () => {
  919. const transformBy = new MoveOperation(
  920. new Position( root, [ 4, 1, 0 ] ),
  921. 2,
  922. new Position( root, [ 2, 2, 7 ] ),
  923. 0
  924. );
  925. const transOp = transform( op, transformBy );
  926. expect( transOp.length ).to.equal( 1 );
  927. expectOperation( transOp[ 0 ], expected );
  928. } );
  929. it( 'range start offset before transforming range start offset: decrement range offset', () => {
  930. const transformBy = new MoveOperation(
  931. new Position( root, [ 2, 2, 0 ] ),
  932. 2,
  933. new Position( root, [ 4, 1, 0 ] ),
  934. 0
  935. );
  936. const transOp = transform( op, transformBy );
  937. expected.sourcePosition.offset -= 2;
  938. expect( transOp.length ).to.equal( 1 );
  939. expectOperation( transOp[ 0 ], expected );
  940. } );
  941. it( 'range start offset after transforming range start offset: no operation update', () => {
  942. const transformBy = new MoveOperation(
  943. new Position( root, [ 2, 2, 9 ] ),
  944. 2,
  945. new Position( root, [ 4, 1, 0 ] ),
  946. 0
  947. );
  948. const transOp = transform( op, transformBy, strongContext );
  949. expect( transOp.length ).to.equal( 1 );
  950. expectOperation( transOp[ 0 ], expected );
  951. } );
  952. it( 'target before node from transforming range start path: increment index on range start path', () => {
  953. const transformBy = new MoveOperation(
  954. new Position( root, [ 4, 1, 0 ] ),
  955. 2,
  956. new Position( root, [ 2, 1 ] ),
  957. 0
  958. );
  959. const transOp = transform( op, transformBy );
  960. expected.sourcePosition.path[ 1 ] += 2;
  961. expect( transOp.length ).to.equal( 1 );
  962. expectOperation( transOp[ 0 ], expected );
  963. } );
  964. it( 'target after node from transforming range start path: no operation update', () => {
  965. const transformBy = new MoveOperation(
  966. new Position( root, [ 4, 1, 0 ] ),
  967. 2,
  968. new Position( root, [ 2, 5 ] ),
  969. 0
  970. );
  971. const transOp = transform( op, transformBy );
  972. expect( transOp.length ).to.equal( 1 );
  973. expectOperation( transOp[ 0 ], expected );
  974. } );
  975. it( 'range before node from transforming range start path: decrement index on range start path', () => {
  976. const transformBy = new MoveOperation(
  977. new Position( root, [ 2, 0 ] ),
  978. 1,
  979. new Position( root, [ 4, 1, 0 ] ),
  980. 0
  981. );
  982. const transOp = transform( op, transformBy );
  983. expected.sourcePosition.path[ 1 ] -= 1;
  984. expect( transOp.length ).to.equal( 1 );
  985. expectOperation( transOp[ 0 ], expected );
  986. } );
  987. it( 'range after node from transforming range start path: no operation update', () => {
  988. const transformBy = new MoveOperation(
  989. new Position( root, [ 2, 3 ] ),
  990. 2,
  991. new Position( root, [ 4, 1, 0 ] ),
  992. 0
  993. );
  994. const transOp = transform( op, transformBy );
  995. expect( transOp.length ).to.equal( 1 );
  996. expectOperation( transOp[ 0 ], expected );
  997. } );
  998. it( 'target offset before transforming target offset: increment target offset', () => {
  999. const transformBy = new MoveOperation(
  1000. new Position( root, [ 4, 1, 0 ] ),
  1001. 2,
  1002. new Position( root, [ 3, 3, 0 ] ),
  1003. 0
  1004. );
  1005. const transOp = transform( op, transformBy );
  1006. expected.targetPosition.offset += 2;
  1007. expect( transOp.length ).to.equal( 1 );
  1008. expectOperation( transOp[ 0 ], expected );
  1009. } );
  1010. it( 'target offset after transforming target offset: no operation update', () => {
  1011. const transformBy = new MoveOperation(
  1012. new Position( root, [ 4, 1, 0 ] ),
  1013. 2,
  1014. new Position( root, [ 3, 3, 5 ] ),
  1015. 0
  1016. );
  1017. const transOp = transform( op, transformBy );
  1018. expect( transOp.length ).to.equal( 1 );
  1019. expectOperation( transOp[ 0 ], expected );
  1020. } );
  1021. it( 'range offset before transforming target offset: decrement target offset', () => {
  1022. const transformBy = new MoveOperation(
  1023. new Position( root, [ 3, 3, 0 ] ),
  1024. 2,
  1025. new Position( root, [ 4, 1, 0 ] ),
  1026. 0
  1027. );
  1028. const transOp = transform( op, transformBy );
  1029. expected.targetPosition.offset -= 2;
  1030. expect( transOp.length ).to.equal( 1 );
  1031. expectOperation( transOp[ 0 ], expected );
  1032. } );
  1033. it( 'range offset after transforming target offset: no operation update', () => {
  1034. const transformBy = new MoveOperation(
  1035. new Position( root, [ 3, 3, 5 ] ),
  1036. 2,
  1037. new Position( root, [ 4, 1, 0 ] ),
  1038. 0
  1039. );
  1040. const transOp = transform( op, transformBy );
  1041. expect( transOp.length ).to.equal( 1 );
  1042. expectOperation( transOp[ 0 ], expected );
  1043. } );
  1044. it( 'target before node from transforming target path: increment index on target path', () => {
  1045. const transformBy = new MoveOperation(
  1046. new Position( root, [ 4, 1, 0 ] ),
  1047. 2,
  1048. new Position( root, [ 3, 1 ] ),
  1049. 0
  1050. );
  1051. const transOp = transform( op, transformBy );
  1052. expected.targetPosition.path[ 1 ] += 2;
  1053. expect( transOp.length ).to.equal( 1 );
  1054. expectOperation( transOp[ 0 ], expected );
  1055. } );
  1056. it( 'target after node from transforming target path: no operation update', () => {
  1057. const transformBy = new MoveOperation(
  1058. new Position( root, [ 4, 1, 0 ] ),
  1059. 2,
  1060. new Position( root, [ 3, 5 ] ),
  1061. 0
  1062. );
  1063. const transOp = transform( op, transformBy );
  1064. expect( transOp.length ).to.equal( 1 );
  1065. expectOperation( transOp[ 0 ], expected );
  1066. } );
  1067. it( 'range before node from transforming target path: decrement index on target path', () => {
  1068. const transformBy = new MoveOperation(
  1069. new Position( root, [ 3, 0 ] ),
  1070. 2,
  1071. new Position( root, [ 4, 1, 0 ] ),
  1072. 0
  1073. );
  1074. const transOp = transform( op, transformBy );
  1075. expected.targetPosition.path[ 1 ] -= 2;
  1076. expect( transOp.length ).to.equal( 1 );
  1077. expectOperation( transOp[ 0 ], expected );
  1078. } );
  1079. it( 'range after node from transforming target path: no operation update', () => {
  1080. const transformBy = new MoveOperation(
  1081. new Position( root, [ 3, 5 ] ),
  1082. 2,
  1083. new Position( root, [ 4, 1, 0 ] ),
  1084. 0
  1085. );
  1086. const transOp = transform( op, transformBy );
  1087. expect( transOp.length ).to.equal( 1 );
  1088. expectOperation( transOp[ 0 ], expected );
  1089. } );
  1090. it( 'target inside transforming move range: split into two operations', () => {
  1091. const transformBy = new MoveOperation(
  1092. new Position( root, [ 4, 1, 0 ] ),
  1093. 2,
  1094. new Position( root, [ 2, 2, 5 ] ),
  1095. 0
  1096. );
  1097. const transOp = transform( op, transformBy );
  1098. expect( transOp.length ).to.equal( 2 );
  1099. expected.howMany = 1;
  1100. expectOperation( transOp[ 0 ], expected );
  1101. expected.sourcePosition.path = [ 2, 2, 6 ];
  1102. expected.targetPosition = targetPosition.getShiftedBy( 1 );
  1103. expectOperation( transOp[ 1 ], expected );
  1104. } );
  1105. it( 'target at start boundary of transforming move range: increment source offset', () => {
  1106. const transformBy = new MoveOperation(
  1107. new Position( root, [ 4, 1, 0 ] ),
  1108. 2,
  1109. new Position( root, [ 2, 2, 4 ] ),
  1110. 0
  1111. );
  1112. const transOp = transform( op, transformBy );
  1113. expect( transOp.length ).to.equal( 1 );
  1114. expected.sourcePosition.offset = 6;
  1115. expectOperation( transOp[ 0 ], expected );
  1116. } );
  1117. it( 'target at end boundary of transforming move range: no operation update', () => {
  1118. const transformBy = new MoveOperation(
  1119. new Position( root, [ 4, 1, 0 ] ),
  1120. 2,
  1121. new Position( root, [ 2, 2, 6 ] ),
  1122. 0
  1123. );
  1124. const transOp = transform( op, transformBy );
  1125. expect( transOp.length ).to.equal( 1 );
  1126. expectOperation( transOp[ 0 ], expected );
  1127. } );
  1128. it( 'target inside a node from transforming range: no operation update', () => {
  1129. const transformBy = new MoveOperation(
  1130. new Position( root, [ 4, 1, 0 ] ),
  1131. 2,
  1132. new Position( root, [ 2, 2, 5, 1 ] ),
  1133. 0
  1134. );
  1135. const transOp = transform( op, transformBy );
  1136. expect( transOp.length ).to.equal( 1 );
  1137. expectOperation( transOp[ 0 ], expected );
  1138. } );
  1139. it( 'range has node that contains transforming range: update range path', () => {
  1140. const transformBy = new MoveOperation(
  1141. new Position( root, [ 2, 1 ] ),
  1142. 3,
  1143. new Position( root, [ 4, 2 ] ),
  1144. 0
  1145. );
  1146. const transOp = transform( op, transformBy );
  1147. expected.sourcePosition.path = [ 4, 3, 4 ];
  1148. expect( transOp.length ).to.equal( 1 );
  1149. expectOperation( transOp[ 0 ], expected );
  1150. } );
  1151. it( 'range has node that contains transforming target: update target path', () => {
  1152. const transformBy = new MoveOperation(
  1153. new Position( root, [ 3, 2 ] ),
  1154. 3,
  1155. new Position( root, [ 0, 1 ] ),
  1156. 0
  1157. );
  1158. const transOp = transform( op, transformBy );
  1159. expected.targetPosition.path = [ 0, 2, 3 ];
  1160. expect( transOp.length ).to.equal( 1 );
  1161. expectOperation( transOp[ 0 ], expected );
  1162. } );
  1163. it( 'target inside a node from transforming range and vice versa: reverse transform-by operation', () => {
  1164. const transformBy = new MoveOperation(
  1165. new Position( root, [ 3, 2 ] ),
  1166. 3,
  1167. new Position( root, [ 2, 2, 5, 0 ] ),
  1168. 0
  1169. );
  1170. const transOp = transform( op, transformBy );
  1171. const reversed = transformBy.getReversed();
  1172. expected.sourcePosition = reversed.sourcePosition;
  1173. expected.targetPosition = reversed.targetPosition;
  1174. expected.howMany = reversed.howMany;
  1175. expect( transOp.length ).to.equal( 1 );
  1176. expectOperation( transOp[ 0 ], expected );
  1177. } );
  1178. it( 'range is same as transforming range and is important: convert to NoOperation', () => {
  1179. const transformBy = new MoveOperation(
  1180. op.sourcePosition,
  1181. op.howMany,
  1182. new Position( root, [ 4, 1, 0 ] ),
  1183. 0
  1184. );
  1185. const transOp = transform( op, transformBy );
  1186. expect( transOp.length ).to.equal( 1 );
  1187. expectOperation( transOp[ 0 ], {
  1188. type: NoOperation
  1189. } );
  1190. } );
  1191. it( 'range is same as transforming range and is less important: update range path', () => {
  1192. const transformBy = new MoveOperation(
  1193. op.sourcePosition,
  1194. op.howMany,
  1195. new Position( root, [ 4, 1, 0 ] ),
  1196. 0
  1197. );
  1198. const transOp = transform( op, transformBy, strongContext );
  1199. expected.sourcePosition.path = [ 4, 1, 0 ];
  1200. expect( transOp.length ).to.equal( 1 );
  1201. expectOperation( transOp[ 0 ], expected );
  1202. } );
  1203. it( 'range contains transforming range and is important: convert to NoOperation', () => {
  1204. const transformBy = new MoveOperation(
  1205. new Position( root, [ 2, 2, 3 ] ),
  1206. 4,
  1207. new Position( root, [ 4, 1, 0 ] ),
  1208. 0
  1209. );
  1210. const transOp = transform( op, transformBy );
  1211. expect( transOp.length ).to.equal( 1 );
  1212. expectOperation( transOp[ 0 ], {
  1213. type: NoOperation
  1214. } );
  1215. } );
  1216. it( 'range contains transforming range and is less important: update range path', () => {
  1217. const transformBy = new MoveOperation(
  1218. new Position( root, [ 2, 2, 3 ] ),
  1219. 4,
  1220. new Position( root, [ 4, 1, 0 ] ),
  1221. 0
  1222. );
  1223. const transOp = transform( op, transformBy, strongContext );
  1224. expected.sourcePosition.path = [ 4, 1, 1 ];
  1225. expect( transOp.length ).to.equal( 1 );
  1226. expectOperation( transOp[ 0 ], expected );
  1227. } );
  1228. it( 'range contains transforming range and target and is important: update range path and target', () => {
  1229. op.targetPosition.path = [ 2, 2, 7 ];
  1230. const transformBy = new MoveOperation(
  1231. new Position( root, [ 2, 2, 3 ] ),
  1232. 5,
  1233. new Position( root, [ 4, 1, 0 ] ),
  1234. 0
  1235. );
  1236. const transOp = transform( op, transformBy );
  1237. expect( transOp.length ).to.equal( 1 );
  1238. expected.sourcePosition.path = [ 4, 1, 1 ];
  1239. expected.targetPosition.path = [ 4, 1, 4 ];
  1240. expectOperation( transOp[ 0 ], expected );
  1241. } );
  1242. it( 'range contains transforming range and target and is less important: update range path and target', () => {
  1243. op.targetPosition.path = [ 2, 2, 7 ];
  1244. const transformBy = new MoveOperation(
  1245. new Position( root, [ 2, 2, 3 ] ),
  1246. 5,
  1247. new Position( root, [ 4, 1, 0 ] ),
  1248. 0
  1249. );
  1250. const transOp = transform( op, transformBy, strongContext );
  1251. expect( transOp.length ).to.equal( 1 );
  1252. expected.sourcePosition.path = [ 4, 1, 1 ];
  1253. expected.targetPosition.path = [ 4, 1, 4 ];
  1254. expectOperation( transOp[ 0 ], expected );
  1255. } );
  1256. it( 'range intersects on left side of transforming range and is important: shrink range', () => {
  1257. const transformBy = new MoveOperation(
  1258. new Position( root, [ 2, 2, 3 ] ),
  1259. 2,
  1260. new Position( root, [ 4, 1, 0 ] ),
  1261. 0
  1262. );
  1263. const transOp = transform( op, transformBy );
  1264. expected.sourcePosition.path = [ 2, 2, 3 ];
  1265. expected.howMany = 1;
  1266. expect( transOp.length ).to.equal( 1 );
  1267. expectOperation( transOp[ 0 ], expected );
  1268. } );
  1269. it( 'range intersects on left side of transforming range and is less important: split into two operations', () => {
  1270. // Get more test cases and better code coverage
  1271. const otherRoot = new RootElement( null );
  1272. const transformBy = new MoveOperation(
  1273. new Position( root, [ 2, 2, 3 ] ),
  1274. 2,
  1275. new Position( otherRoot, [ 4, 1, 0 ] ),
  1276. 0
  1277. );
  1278. const transOp = transform( op, transformBy, strongContext );
  1279. expect( transOp.length ).to.equal( 2 );
  1280. expected.sourcePosition = new Position( otherRoot, [ 4, 1, 1 ] );
  1281. expected.howMany = 1;
  1282. expectOperation( transOp[ 0 ], expected );
  1283. expected.sourcePosition = new Position( root, [ 2, 2, 3 ] );
  1284. expected.targetPosition = targetPosition.getShiftedBy( 1 );
  1285. expectOperation( transOp[ 1 ], expected );
  1286. } );
  1287. it( 'range intersects on right side of transforming range and is important: shrink range', () => {
  1288. const transformBy = new MoveOperation(
  1289. new Position( root, [ 2, 2, 5 ] ),
  1290. 2,
  1291. new Position( root, [ 4, 1, 0 ] ),
  1292. 0
  1293. );
  1294. const transOp = transform( op, transformBy );
  1295. expected.howMany = 1;
  1296. expect( transOp.length ).to.equal( 1 );
  1297. expectOperation( transOp[ 0 ], expected );
  1298. } );
  1299. it( 'range intersects on right side of transforming range and is less important: split into two operations', () => {
  1300. const transformBy = new MoveOperation(
  1301. new Position( root, [ 2, 2, 5 ] ),
  1302. 2,
  1303. new Position( root, [ 4, 1, 0 ] ),
  1304. 0
  1305. );
  1306. const transOp = transform( op, transformBy, strongContext );
  1307. expect( transOp.length ).to.equal( 2 );
  1308. expected.sourcePosition = sourcePosition;
  1309. expected.howMany = 1;
  1310. expectOperation( transOp[ 0 ], expected );
  1311. expected.sourcePosition = new Position( root, [ 4, 1, 0 ] );
  1312. expected.targetPosition = targetPosition.getShiftedBy( 1 );
  1313. expectOperation( transOp[ 1 ], expected );
  1314. } );
  1315. it( 'range intersects on left side, target inside transforming range and is important: split into two operations', () => {
  1316. op.howMany = 4;
  1317. const transformBy = new MoveOperation(
  1318. new Position( root, [ 2, 2, 3 ] ),
  1319. 2,
  1320. new Position( root, [ 2, 2, 6 ] ),
  1321. 0
  1322. );
  1323. const transOp = transform( op, transformBy );
  1324. expect( transOp.length ).to.equal( 2 );
  1325. expected.sourcePosition.path = [ 2, 2, 3 ];
  1326. expected.howMany = 1;
  1327. expectOperation( transOp[ 0 ], expected );
  1328. expected.sourcePosition.path = [ 2, 2, 5 ];
  1329. expected.howMany = 2;
  1330. expected.targetPosition = targetPosition.getShiftedBy( 1 );
  1331. expectOperation( transOp[ 1 ], expected );
  1332. } );
  1333. it( 'range intersects on left side, target inside transforming range and is less important: split into two operations', () => {
  1334. op.howMany = 4;
  1335. const transformBy = new MoveOperation(
  1336. new Position( root, [ 2, 2, 3 ] ),
  1337. 2,
  1338. new Position( root, [ 2, 2, 6 ] ),
  1339. 0
  1340. );
  1341. const transOp = transform( op, transformBy, strongContext );
  1342. expect( transOp.length ).to.equal( 3 );
  1343. expected.sourcePosition.path = [ 2, 2, 3 ];
  1344. expected.howMany = 1;
  1345. expectOperation( transOp[ 0 ], expected );
  1346. expected.sourcePosition.path = [ 2, 2, 4 ];
  1347. expected.howMany = 1;
  1348. expected.targetPosition = targetPosition.getShiftedBy( 1 );
  1349. expectOperation( transOp[ 1 ], expected );
  1350. expected.sourcePosition.path = [ 2, 2, 4 ];
  1351. expected.howMany = 2;
  1352. expected.targetPosition = targetPosition.getShiftedBy( 2 );
  1353. expectOperation( transOp[ 2 ], expected );
  1354. } );
  1355. it( 'range intersects, target inside transforming range and is important: split into two operations', () => {
  1356. op.targetPosition.path = [ 2, 2, 7 ];
  1357. const transformBy = new MoveOperation(
  1358. new Position( root, [ 2, 2, 5 ] ),
  1359. 4,
  1360. new Position( root, [ 4, 1, 0 ] ),
  1361. 0
  1362. );
  1363. const transOp = transform( op, transformBy, strongContext );
  1364. expect( transOp.length ).to.equal( 2 );
  1365. expected.sourcePosition.path = [ 2, 2, 4 ];
  1366. expected.targetPosition.path = [ 4, 1, 2 ];
  1367. expected.howMany = 1;
  1368. expectOperation( transOp[ 0 ], expected );
  1369. expected.sourcePosition.path = [ 4, 1, 0 ];
  1370. expected.targetPosition.path = [ 4, 1, 3 ];
  1371. expectOperation( transOp[ 1 ], expected );
  1372. } );
  1373. it( 'range intersects, target inside transforming range and is less important: shrink range', () => {
  1374. op.targetPosition.path = [ 2, 2, 7 ];
  1375. const transformBy = new MoveOperation(
  1376. new Position( root, [ 2, 2, 5 ] ),
  1377. 4,
  1378. new Position( root, [ 4, 1, 0 ] ),
  1379. 0
  1380. );
  1381. const transOp = transform( op, transformBy );
  1382. expect( transOp.length ).to.equal( 1 );
  1383. expected.sourcePosition.path = [ 2, 2, 4 ];
  1384. expected.targetPosition.path = [ 4, 1, 2 ];
  1385. expected.howMany = 1;
  1386. expectOperation( transOp[ 0 ], expected );
  1387. } );
  1388. it( 'range inside transforming range and is important: split into two operations', () => {
  1389. op.howMany = 4;
  1390. const transformBy = new MoveOperation(
  1391. new Position( root, [ 2, 2, 5 ] ),
  1392. 2,
  1393. new Position( root, [ 4, 1, 0 ] ),
  1394. 0
  1395. );
  1396. const transOp = transform( op, transformBy );
  1397. expect( transOp.length ).to.equal( 2 );
  1398. expected.sourcePosition.path = [ 2, 2, 4 ];
  1399. expected.howMany = 1;
  1400. expectOperation( transOp[ 0 ], expected );
  1401. expected.targetPosition = targetPosition.getShiftedBy( 1 );
  1402. expectOperation( transOp[ 1 ], expected );
  1403. } );
  1404. it( 'range inside transforming range and is less important: split into three operations', () => {
  1405. op.howMany = 4;
  1406. const transformBy = new MoveOperation(
  1407. new Position( root, [ 2, 2, 5 ] ),
  1408. 2,
  1409. new Position( root, [ 4, 1, 0 ] ),
  1410. 0
  1411. );
  1412. const transOp = transform( op, transformBy, strongContext );
  1413. expect( transOp.length ).to.equal( 3 );
  1414. expected.sourcePosition.path = [ 2, 2, 4 ];
  1415. expected.howMany = 1;
  1416. expectOperation( transOp[ 0 ], expected );
  1417. expected.sourcePosition.path = [ 4, 1, 0 ];
  1418. expected.targetPosition = targetPosition.getShiftedBy( 1 );
  1419. expected.howMany = 2;
  1420. expectOperation( transOp[ 1 ], expected );
  1421. expected.sourcePosition.path = [ 2, 2, 4 ];
  1422. expected.targetPosition = targetPosition.getShiftedBy( 3 );
  1423. expected.howMany = 1;
  1424. expectOperation( transOp[ 2 ], expected );
  1425. } );
  1426. it( 'range and target inside transforming range and is important: no operation update', () => {
  1427. op.howMany = 6;
  1428. const transformBy = new MoveOperation(
  1429. new Position( root, [ 2, 2, 5 ] ),
  1430. 2,
  1431. new Position( root, [ 2, 2, 9 ] ),
  1432. 0
  1433. );
  1434. const transOp = transform( op, transformBy );
  1435. expect( transOp.length ).to.equal( 1 );
  1436. expected.howMany = 6;
  1437. expectOperation( transOp[ 0 ], expected );
  1438. } );
  1439. it( 'range and target inside transforming range and is less important: no operation update', () => {
  1440. op.howMany = 6;
  1441. const transformBy = new MoveOperation(
  1442. new Position( root, [ 2, 2, 5 ] ),
  1443. 2,
  1444. new Position( root, [ 2, 2, 9 ] ),
  1445. 0
  1446. );
  1447. const transOp = transform( op, transformBy, strongContext );
  1448. expect( transOp.length ).to.equal( 1 );
  1449. expected.howMany = 6;
  1450. expectOperation( transOp[ 0 ], expected );
  1451. } );
  1452. } );
  1453. describe( 'by MoveOperation to graveyard', () => {
  1454. let transformBy;
  1455. beforeEach( () => {
  1456. transformBy = new MoveOperation(
  1457. op.sourcePosition.clone(),
  1458. op.howMany,
  1459. new Position( doc.graveyard, [ 0 ] ),
  1460. 0
  1461. );
  1462. } );
  1463. it( 'should skip context.aIsStrong and be less important than MoveOperation', () => {
  1464. const transOp = transform( op, transformBy, strongContext );
  1465. expect( transOp.length ).to.equal( 1 );
  1466. expectOperation( transOp[ 0 ], {
  1467. type: NoOperation
  1468. } );
  1469. } );
  1470. } );
  1471. describe( 'by NoOperation', () => {
  1472. it( 'no operation update', () => {
  1473. const transformBy = new NoOperation( 0 );
  1474. const transOp = transform( op, transformBy );
  1475. expect( transOp.length ).to.equal( 1 );
  1476. expectOperation( transOp[ 0 ], expected );
  1477. } );
  1478. } );
  1479. describe( 'by RenameOperation', () => {
  1480. it( 'no position update', () => {
  1481. const transformBy = new RenameOperation( new Position( root, [ 2, 2, 4 ] ), 'oldName', 'newName', 0 );
  1482. const transOp = transform( op, transformBy );
  1483. expect( transOp.length ).to.equal( 1 );
  1484. expectOperation( transOp[ 0 ], expected );
  1485. } );
  1486. } );
  1487. describe( 'by MarkerOperation', () => {
  1488. it( 'no position update', () => {
  1489. const newRange = new Range( new Position( root, [ 2, 2, 3 ] ), new Position( root, [ 2, 2, 8 ] ) );
  1490. const transformBy = new MarkerOperation( 'name', null, newRange, model.markers, false, 0 );
  1491. const transOp = transform( op, transformBy );
  1492. expect( transOp.length ).to.equal( 1 );
  1493. expectOperation( transOp[ 0 ], expected );
  1494. } );
  1495. } );
  1496. } );
  1497. describe( 'MoveOperation to graveyard', () => {
  1498. describe( 'by MoveOperation', () => {
  1499. it( 'should force removing content even if was less important', () => {
  1500. const op = new MoveOperation( new Position( root, [ 8 ] ), 2, new Position( doc.graveyard, [ 0 ] ), 0 );
  1501. const targetPosition = op.targetPosition.clone();
  1502. const transformBy = new MoveOperation( new Position( root, [ 8 ] ), 2, new Position( root, [ 1 ] ), 0 );
  1503. const sourcePosition = transformBy.targetPosition.clone();
  1504. const transOp = transform( op, transformBy );
  1505. expect( transOp.length ).to.equal( 1 );
  1506. expectOperation( transOp[ 0 ], {
  1507. type: MoveOperation,
  1508. howMany: 2,
  1509. sourcePosition,
  1510. targetPosition
  1511. } );
  1512. } );
  1513. } );
  1514. } );
  1515. describe( 'NoOperation', () => {
  1516. beforeEach( () => {
  1517. op = new NoOperation( 0 );
  1518. expected = {
  1519. type: NoOperation
  1520. };
  1521. } );
  1522. describe( 'by InsertOperation', () => {
  1523. it( 'no operation update', () => {
  1524. const transformBy = new InsertOperation(
  1525. new Position( root, [ 0 ] ),
  1526. 'a',
  1527. 0
  1528. );
  1529. const transOp = transform( op, transformBy );
  1530. expect( transOp.length ).to.equal( 1 );
  1531. expectOperation( transOp[ 0 ], expected );
  1532. } );
  1533. } );
  1534. describe( 'by AttributeOperation', () => {
  1535. it( 'no operation update', () => {
  1536. const transformBy = new AttributeOperation(
  1537. new Range(
  1538. new Position( root, [ 0 ] ),
  1539. new Position( root, [ 1 ] )
  1540. ),
  1541. 'foo',
  1542. 'bar',
  1543. 'xyz',
  1544. 0
  1545. );
  1546. const transOp = transform( op, transformBy );
  1547. expect( transOp.length ).to.equal( 1 );
  1548. expectOperation( transOp[ 0 ], expected );
  1549. } );
  1550. } );
  1551. describe( 'by RootAttributeOperation', () => {
  1552. it( 'no operation update', () => {
  1553. const transformBy = new RootAttributeOperation(
  1554. root,
  1555. 'foo',
  1556. null,
  1557. 'bar',
  1558. 0
  1559. );
  1560. const transOp = transform( op, transformBy );
  1561. expect( transOp.length ).to.equal( 1 );
  1562. expectOperation( transOp[ 0 ], expected );
  1563. } );
  1564. } );
  1565. describe( 'by MoveOperation', () => {
  1566. it( 'no operation update', () => {
  1567. const transformBy = new MoveOperation(
  1568. new Position( root, [ 0 ] ),
  1569. 2,
  1570. new Position( root, [ 1 ] ),
  1571. 0
  1572. );
  1573. const transOp = transform( op, transformBy );
  1574. expect( transOp.length ).to.equal( 1 );
  1575. expectOperation( transOp[ 0 ], expected );
  1576. } );
  1577. } );
  1578. describe( 'by NoOperation', () => {
  1579. it( 'no operation update', () => {
  1580. const transformBy = new NoOperation( 0 );
  1581. const transOp = transform( op, transformBy );
  1582. expect( transOp.length ).to.equal( 1 );
  1583. expectOperation( transOp[ 0 ], expected );
  1584. } );
  1585. } );
  1586. describe( 'by RenameOperation', () => {
  1587. it( 'no position update', () => {
  1588. const transformBy = new RenameOperation( new Position( root, [ 0, 2, 0 ] ), 'oldName', 'newName', 0 );
  1589. const transOp = transform( op, transformBy );
  1590. expect( transOp.length ).to.equal( 1 );
  1591. expectOperation( transOp[ 0 ], expected );
  1592. } );
  1593. } );
  1594. describe( 'by MarkerOperation', () => {
  1595. it( 'no position update', () => {
  1596. const newRange = new Range( new Position( root, [ 0, 2, 0 ] ), new Position( root, [ 0, 2, 8 ] ) );
  1597. const transformBy = new MarkerOperation( 'name', null, newRange, model.markers, false, 0 );
  1598. const transOp = transform( op, transformBy );
  1599. expect( transOp.length ).to.equal( 1 );
  1600. expectOperation( transOp[ 0 ], expected );
  1601. } );
  1602. } );
  1603. } );
  1604. describe( 'RenameOperation', () => {
  1605. beforeEach( () => {
  1606. const position = new Position( root, [ 0, 2, 2 ] );
  1607. op = new RenameOperation( position, 'oldName', 'newName', 0 );
  1608. expected = {
  1609. position,
  1610. oldName: 'oldName',
  1611. newName: 'newName'
  1612. };
  1613. } );
  1614. describe( 'by InsertOperation', () => {
  1615. it( 'target before renamed element: offset update', () => {
  1616. const transformBy = new InsertOperation(
  1617. new Position( root, [ 0, 2, 1 ] ),
  1618. [ nodeA, nodeB ],
  1619. 0
  1620. );
  1621. const transOp = transform( op, transformBy );
  1622. expect( transOp.length ).to.equal( 1 );
  1623. expected.position.offset = 4;
  1624. expectOperation( transOp[ 0 ], expected );
  1625. } );
  1626. it( 'target after renamed element: no change', () => {
  1627. const transformBy = new InsertOperation(
  1628. new Position( root, [ 0, 2, 3 ] ),
  1629. [ nodeA, nodeB ],
  1630. 0
  1631. );
  1632. const transOp = transform( op, transformBy );
  1633. expect( transOp.length ).to.equal( 1 );
  1634. expectOperation( transOp[ 0 ], expected );
  1635. } );
  1636. it( 'target before a node on path to renamed element: path update', () => {
  1637. const transformBy = new InsertOperation(
  1638. new Position( root, [ 0, 1 ] ),
  1639. [ nodeA, nodeB ],
  1640. 0
  1641. );
  1642. const transOp = transform( op, transformBy );
  1643. expect( transOp.length ).to.equal( 1 );
  1644. expected.position.path = [ 0, 4, 2 ];
  1645. expectOperation( transOp[ 0 ], expected );
  1646. } );
  1647. it( 'target after a node on path to renamed element: no change', () => {
  1648. const transformBy = new InsertOperation(
  1649. new Position( root, [ 0, 3 ] ),
  1650. [ nodeA, nodeB ],
  1651. 0
  1652. );
  1653. const transOp = transform( op, transformBy );
  1654. expect( transOp.length ).to.equal( 1 );
  1655. expectOperation( transOp[ 0 ], expected );
  1656. } );
  1657. } );
  1658. describe( 'by AttributeOperation', () => {
  1659. it( 'no operation update', () => {
  1660. const transformBy = new AttributeOperation(
  1661. new Range(
  1662. new Position( root, [ 0, 2, 1 ] ),
  1663. new Position( root, [ 1, 3 ] )
  1664. ),
  1665. 'foo',
  1666. 'bar',
  1667. 'xyz',
  1668. 0
  1669. );
  1670. const transOp = transform( op, transformBy );
  1671. expect( transOp.length ).to.equal( 1 );
  1672. expectOperation( transOp[ 0 ], expected );
  1673. } );
  1674. } );
  1675. describe( 'by RootAttributeOperation', () => {
  1676. it( 'no operation update', () => {
  1677. const transformBy = new RootAttributeOperation(
  1678. root,
  1679. 'foo',
  1680. null,
  1681. 'bar',
  1682. 0
  1683. );
  1684. const transOp = transform( op, transformBy );
  1685. expect( transOp.length ).to.equal( 1 );
  1686. expectOperation( transOp[ 0 ], expected );
  1687. } );
  1688. } );
  1689. describe( 'by MarkerOperation', () => {
  1690. it( 'no operation update', () => {
  1691. const newRange = new Range( new Position( root, [ 0, 2, 0 ] ), new Position( root, [ 0, 2, 8 ] ) );
  1692. const transformBy = new MarkerOperation( 'name', null, newRange, model.markers, false, 0 );
  1693. const transOp = transform( op, transformBy );
  1694. expect( transOp.length ).to.equal( 1 );
  1695. expectOperation( transOp[ 0 ], expected );
  1696. } );
  1697. } );
  1698. describe( 'by RenameOperation', () => {
  1699. it( 'different element: no change', () => {
  1700. const transformBy = new RenameOperation(
  1701. new Position( root, [ 0, 2, 1 ] ),
  1702. 'foo',
  1703. 'bar',
  1704. 0
  1705. );
  1706. const transOp = transform( op, transformBy );
  1707. expect( transOp.length ).to.equal( 1 );
  1708. expectOperation( transOp[ 0 ], expected );
  1709. } );
  1710. it( 'same element and is important: convert to NoOperation', () => {
  1711. const transformBy = new RenameOperation(
  1712. new Position( root, [ 0, 2, 2 ] ),
  1713. 'oldName',
  1714. 'otherName',
  1715. 0
  1716. );
  1717. const transOp = transform( op, transformBy );
  1718. expect( transOp.length ).to.equal( 1 );
  1719. expectOperation( transOp[ 0 ], {
  1720. type: NoOperation
  1721. } );
  1722. } );
  1723. it( 'same element and is not important: change old name to new name', () => {
  1724. const transformBy = new RenameOperation(
  1725. new Position( root, [ 0, 2, 2 ] ),
  1726. 'oldName',
  1727. 'otherName',
  1728. 0
  1729. );
  1730. const transOp = transform( op, transformBy, strongContext );
  1731. expect( transOp.length ).to.equal( 1 );
  1732. expected.oldName = 'otherName';
  1733. expectOperation( transOp[ 0 ], expected );
  1734. } );
  1735. } );
  1736. describe( 'by MoveOperation', () => {
  1737. it( 'moved range before renamed element: update offset', () => {
  1738. const transformBy = new MoveOperation(
  1739. new Position( root, [ 0, 2, 0 ] ),
  1740. 2,
  1741. new Position( root, [ 2, 5 ] ),
  1742. 0
  1743. );
  1744. const transOp = transform( op, transformBy );
  1745. expect( transOp.length ).to.equal( 1 );
  1746. expected.position.offset = 0;
  1747. expectOperation( transOp[ 0 ], expected );
  1748. } );
  1749. it( 'moved range before an element on path to renamed element: update path', () => {
  1750. const transformBy = new MoveOperation(
  1751. new Position( root, [ 0, 0 ] ),
  1752. 2,
  1753. new Position( root, [ 2, 5 ] ),
  1754. 0
  1755. );
  1756. const transOp = transform( op, transformBy );
  1757. expect( transOp.length ).to.equal( 1 );
  1758. expected.position.path = [ 0, 0, 2 ];
  1759. expectOperation( transOp[ 0 ], expected );
  1760. } );
  1761. it( 'moved range contains renamed element: update path', () => {
  1762. const transformBy = new MoveOperation(
  1763. new Position( root, [ 0, 2, 1 ] ),
  1764. 3,
  1765. new Position( root, [ 2, 5 ] ),
  1766. 0
  1767. );
  1768. const transOp = transform( op, transformBy );
  1769. expect( transOp.length ).to.equal( 1 );
  1770. expected.position.path = [ 2, 6 ];
  1771. expectOperation( transOp[ 0 ], expected );
  1772. } );
  1773. it( 'moved range contains renamed element parent: updated path', () => {
  1774. const transformBy = new MoveOperation(
  1775. new Position( root, [ 0, 1 ] ),
  1776. 3,
  1777. new Position( root, [ 2, 5 ] ),
  1778. 0
  1779. );
  1780. const transOp = transform( op, transformBy );
  1781. expect( transOp.length ).to.equal( 1 );
  1782. expected.position.path = [ 2, 6, 2 ];
  1783. expectOperation( transOp[ 0 ], expected );
  1784. } );
  1785. it( 'move target before renamed element: update offset', () => {
  1786. const transformBy = new MoveOperation(
  1787. new Position( root, [ 2, 5 ] ),
  1788. 2,
  1789. new Position( root, [ 0, 2, 1 ] ),
  1790. 0
  1791. );
  1792. const transOp = transform( op, transformBy );
  1793. expect( transOp.length ).to.equal( 1 );
  1794. expected.position.offset = 4;
  1795. expectOperation( transOp[ 0 ], expected );
  1796. } );
  1797. it( 'move target before an element on path to renamed element: update path', () => {
  1798. const transformBy = new MoveOperation(
  1799. new Position( root, [ 0, 2, 0 ] ),
  1800. 2,
  1801. new Position( root, [ 2, 5 ] ),
  1802. 0
  1803. );
  1804. const transOp = transform( op, transformBy );
  1805. expect( transOp.length ).to.equal( 1 );
  1806. expected.position.offset = 0;
  1807. expectOperation( transOp[ 0 ], expected );
  1808. } );
  1809. } );
  1810. } );
  1811. describe( 'MarkerOperation', () => {
  1812. let oldRange, newRange;
  1813. beforeEach( () => {
  1814. oldRange = new Range( Position._createAt( root, 1 ), Position._createAt( root, 4 ) );
  1815. newRange = new Range( Position._createAt( root, 10 ), Position._createAt( root, 12 ) );
  1816. op = new MarkerOperation( 'name', oldRange, newRange, model.markers, false, 0 );
  1817. expected = {
  1818. name: 'name',
  1819. oldRange,
  1820. newRange
  1821. };
  1822. } );
  1823. describe( 'by InsertOperation', () => {
  1824. it( 'insert position affecting oldRange: update oldRange', () => {
  1825. // Just CC things.
  1826. op.newRange = null;
  1827. const transformBy = new InsertOperation( Position._createAt( root, 0 ), [ nodeA, nodeB ], 0 );
  1828. const transOp = transform( op, transformBy );
  1829. expected.newRange = null;
  1830. expected.oldRange.start.offset = 3;
  1831. expected.oldRange.end.offset = 6;
  1832. expect( transOp.length ).to.equal( 1 );
  1833. expectOperation( transOp[ 0 ], expected );
  1834. } );
  1835. it( 'insert position affecting newRange: update newRange', () => {
  1836. // Just CC things.
  1837. op.oldRange = null;
  1838. const transformBy = new InsertOperation( Position._createAt( root, 8 ), [ nodeA, nodeB ], 0 );
  1839. const transOp = transform( op, transformBy );
  1840. expected.oldRange = null;
  1841. expected.newRange.start.offset = 12;
  1842. expected.newRange.end.offset = 14;
  1843. expect( transOp.length ).to.equal( 1 );
  1844. expectOperation( transOp[ 0 ], expected );
  1845. } );
  1846. } );
  1847. describe( 'by AttributeOperation', () => {
  1848. it( 'no operation update', () => {
  1849. const transformBy = new AttributeOperation(
  1850. new Range(
  1851. new Position( root, [ 2 ] ),
  1852. new Position( root, [ 11 ] )
  1853. ),
  1854. 'foo',
  1855. 'bar',
  1856. 'xyz',
  1857. 0
  1858. );
  1859. const transOp = transform( op, transformBy );
  1860. expect( transOp.length ).to.equal( 1 );
  1861. expectOperation( transOp[ 0 ], expected );
  1862. } );
  1863. } );
  1864. describe( 'by MoveOperation', () => {
  1865. it( 'moved range is before oldRange: update oldRange', () => {
  1866. // Just CC things.
  1867. op.newRange = null;
  1868. const transformBy = new MoveOperation( Position._createAt( root, 0 ), 1, Position._createAt( root, 20 ), 0 );
  1869. const transOp = transform( op, transformBy );
  1870. expected.newRange = null;
  1871. expected.oldRange.start.offset = 0;
  1872. expected.oldRange.end.offset = 3;
  1873. expect( transOp.length ).to.equal( 1 );
  1874. expectOperation( transOp[ 0 ], expected );
  1875. } );
  1876. it( 'moved range contains oldRange and is before newRange: update oldRange and newRange', () => {
  1877. const transformBy = new MoveOperation( Position._createAt( root, 2 ), 2, Position._createAt( root, 20 ), 0 );
  1878. const transOp = transform( op, transformBy );
  1879. expected.oldRange.start.offset = 1;
  1880. expected.oldRange.end.offset = 2;
  1881. expected.newRange.start.offset = 8;
  1882. expected.newRange.end.offset = 10;
  1883. expect( transOp.length ).to.equal( 1 );
  1884. expectOperation( transOp[ 0 ], expected );
  1885. } );
  1886. it( 'target position is inside newRange: update newRange', () => {
  1887. // Just CC things.
  1888. op.oldRange = null;
  1889. const transformBy = new MoveOperation( Position._createAt( root, 20 ), 2, Position._createAt( root, 11 ), 0 );
  1890. const transOp = transform( op, transformBy );
  1891. expected.oldRange = null;
  1892. expected.newRange.start.offset = 10;
  1893. expected.newRange.end.offset = 14;
  1894. expect( transOp.length ).to.equal( 1 );
  1895. expectOperation( transOp[ 0 ], expected );
  1896. } );
  1897. it( 'target position is inside oldRange and before newRange: update oldRange and newRange', () => {
  1898. const transformBy = new MoveOperation( Position._createAt( root, 20 ), 4, Position._createAt( root, 2 ), 0 );
  1899. const transOp = transform( op, transformBy );
  1900. expected.oldRange.start.offset = 1;
  1901. expected.oldRange.end.offset = 8;
  1902. expected.newRange.start.offset = 14;
  1903. expected.newRange.end.offset = 16;
  1904. expect( transOp.length ).to.equal( 1 );
  1905. expectOperation( transOp[ 0 ], expected );
  1906. } );
  1907. } );
  1908. describe( 'by RootAttributeOperation', () => {
  1909. it( 'no operation update', () => {
  1910. const transformBy = new RootAttributeOperation(
  1911. root,
  1912. 'foo',
  1913. null,
  1914. 'bar',
  1915. 0
  1916. );
  1917. const transOp = transform( op, transformBy );
  1918. expect( transOp.length ).to.equal( 1 );
  1919. expectOperation( transOp[ 0 ], expected );
  1920. } );
  1921. } );
  1922. describe( 'by RenameOperation', () => {
  1923. it( 'no operation update', () => {
  1924. const transformBy = new RenameOperation( new Position( root, [ 1 ] ), 'oldName', 'newName', 0 );
  1925. const transOp = transform( op, transformBy );
  1926. expect( transOp.length ).to.equal( 1 );
  1927. expectOperation( transOp[ 0 ], expected );
  1928. } );
  1929. } );
  1930. describe( 'by MarkerOperation', () => {
  1931. it( 'different marker name: no operation update', () => {
  1932. const transformBy = new MarkerOperation( 'otherName', oldRange, newRange, model.markers, false, 0 );
  1933. const transOp = transform( op, transformBy );
  1934. expect( transOp.length ).to.equal( 1 );
  1935. expectOperation( transOp[ 0 ], expected );
  1936. } );
  1937. it( 'same marker name and is important: convert to NoOperation', () => {
  1938. const anotherRange = new Range( Position._createAt( root, 2 ), Position._createAt( root, 2 ) );
  1939. const transformBy = new MarkerOperation( 'name', oldRange, anotherRange, model.markers, false, 0 );
  1940. const transOp = transform( op, transformBy );
  1941. expect( transOp.length ).to.equal( 1 );
  1942. expectOperation( transOp[ 0 ], {
  1943. type: NoOperation
  1944. } );
  1945. } );
  1946. it( 'same marker name and is less important: update oldRange parameter', () => {
  1947. const anotherRange = new Range( Position._createAt( root, 2 ), Position._createAt( root, 2 ) );
  1948. const transformBy = new MarkerOperation( 'name', oldRange, anotherRange, model.markers, false, 0 );
  1949. const transOp = transform( op, transformBy, strongContext );
  1950. expected.oldRange = anotherRange;
  1951. expect( transOp.length ).to.equal( 1 );
  1952. expectOperation( transOp[ 0 ], expected );
  1953. } );
  1954. } );
  1955. } );
  1956. } );
  1957. describe( 'transformSets', () => {
  1958. let model, doc, root, node;
  1959. beforeEach( () => {
  1960. model = new Model();
  1961. doc = model.document;
  1962. root = doc.createRoot();
  1963. node = new Node();
  1964. } );
  1965. it( 'originalOperations should correctly link transformed operations with original operations #1', () => {
  1966. const position = new Position( root, [ 0 ] );
  1967. const a = new InsertOperation( position, [ node ], 0 );
  1968. const { operationsA, originalOperations } = transformSets( [ a ], [], {
  1969. document: doc,
  1970. useRelations: false,
  1971. padWithNoOps: false
  1972. } );
  1973. expect( originalOperations.get( operationsA[ 0 ] ) ).to.equal( a );
  1974. } );
  1975. it( 'originalOperations should correctly link transformed operations with original operations #2', () => {
  1976. const position = new Position( root, [ 0 ] );
  1977. const b = new InsertOperation( position, [ node ], 0 );
  1978. const { operationsB, originalOperations } = transformSets( [], [ b ], {
  1979. document: doc,
  1980. useRelations: false,
  1981. padWithNoOps: false
  1982. } );
  1983. expect( originalOperations.get( operationsB[ 0 ] ) ).to.equal( b );
  1984. } );
  1985. it( 'originalOperations should correctly link transformed operations with original operations #3', () => {
  1986. const position = new Position( root, [ 4 ] );
  1987. const a = new InsertOperation( position, [ node ], 0 );
  1988. const b = new AttributeOperation(
  1989. new Range(
  1990. new Position( root, [ 2 ] ),
  1991. new Position( root, [ 11 ] )
  1992. ),
  1993. 'foo',
  1994. 'bar',
  1995. 'xyz',
  1996. 0
  1997. );
  1998. const { operationsA, operationsB, originalOperations } = transformSets( [ a ], [ b ], {
  1999. document: doc,
  2000. useRelations: false,
  2001. padWithNoOps: false
  2002. } );
  2003. expect( originalOperations.get( operationsA[ 0 ] ) ).to.equal( a );
  2004. expect( originalOperations.get( operationsB[ 0 ] ) ).to.equal( b );
  2005. expect( originalOperations.get( operationsB[ 1 ] ) ).to.equal( b );
  2006. } );
  2007. } );