enableenginedebug.js 40 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177
  1. /**
  2. * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
  3. * For licensing, see LICENSE.md.
  4. */
  5. import { default as enableEngineDebug, disableEngineDebug } from '../../src/dev-utils/enableenginedebug';
  6. import Editor from '@ckeditor/ckeditor5-core/src/editor/editor';
  7. import StandardEditor from '@ckeditor/ckeditor5-core/src/editor/standardeditor';
  8. import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
  9. import ModelPosition from '../../src/model/position';
  10. import ModelRange from '../../src/model/range';
  11. import ModelText from '../../src/model/text';
  12. import ModelTextProxy from '../../src/model/textproxy';
  13. import ModelElement from '../../src/model/element';
  14. import AttributeOperation from '../../src/model/operation/attributeoperation';
  15. import DetachOperation from '../../src/model/operation/detachoperation';
  16. import InsertOperation from '../../src/model/operation/insertoperation';
  17. import MarkerOperation from '../../src/model/operation/markeroperation';
  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 RootAttributeOperation from '../../src/model/operation/rootattributeoperation';
  22. import RemoveOperation from '../../src/model/operation/removeoperation';
  23. import DeltaFactory from '../../src/model/delta/deltafactory';
  24. import Delta from '../../src/model/delta/delta';
  25. import AttributeDelta from '../../src/model/delta/attributedelta';
  26. import InsertDelta from '../../src/model/delta/insertdelta';
  27. import MarkerDelta from '../../src/model/delta/markerdelta';
  28. import MergeDelta from '../../src/model/delta/mergedelta';
  29. import MoveDelta from '../../src/model/delta/movedelta';
  30. import RenameDelta from '../../src/model/delta/renamedelta';
  31. import RootAttributeDelta from '../../src/model/delta/rootattributedelta';
  32. import SplitDelta from '../../src/model/delta/splitdelta';
  33. import UnwrapDelta from '../../src/model/delta/unwrapdelta';
  34. import WrapDelta from '../../src/model/delta/wrapdelta';
  35. import deltaTransform from '../../src/model/delta/transform';
  36. import Model from '../../src/model/model';
  37. import ModelDocumentFragment from '../../src/model/documentfragment';
  38. import ViewDocument from '../../src/view/document';
  39. import ViewAttributeElement from '../../src/view/attributeelement';
  40. import ViewContainerElement from '../../src/view/containerelement';
  41. import ViewText from '../../src/view/text';
  42. import ViewTextProxy from '../../src/view/textproxy';
  43. import ViewDocumentFragment from '../../src/view/documentfragment';
  44. import ViewElement from '../../src/view/element';
  45. import createViewRoot from '../view/_utils/createroot';
  46. import testUtils from '@ckeditor/ckeditor5-core/tests/_utils/utils';
  47. testUtils.createSinonSandbox();
  48. /* global document */
  49. describe( 'enableEngineDebug', () => {
  50. afterEach( () => {
  51. disableEngineDebug();
  52. } );
  53. it( 'should return plugin class', () => {
  54. const result = enableEngineDebug();
  55. expect( result.prototype ).to.be.instanceof( Plugin );
  56. } );
  57. it( 'should not throw when called multiple times', () => {
  58. enableEngineDebug();
  59. enableEngineDebug();
  60. const result = enableEngineDebug();
  61. expect( result.prototype ).to.be.instanceof( Plugin );
  62. } );
  63. } );
  64. describe( 'disableEngineDebug', () => {
  65. it( 'restores modified stubs', () => {
  66. expect( ModelPosition.prototype.log ).to.equal( undefined, 'Initial value (model/position)' );
  67. expect( ModelElement.prototype.printTree ).to.equal( undefined, 'Initial value (model/element)' );
  68. expect( Delta.prototype.log ).to.equal( undefined, 'Initial value (model/delta/delta)' );
  69. expect( ViewElement.prototype.printTree ).to.equal( undefined, 'Initial value (view/element)' );
  70. expect( Model.prototype.createReplayer ).to.equal( undefined, 'Initial value (model/document)' );
  71. expect( Editor.prototype.logDocuments ).to.equal( undefined, 'Initial value (core~editor/editor)' );
  72. enableEngineDebug();
  73. expect( ModelPosition.prototype.log ).to.be.a( 'function', 'After enabling engine debug (model/position)' );
  74. expect( ModelElement.prototype.printTree ).to.be.a( 'function', 'After enabling engine debug (model/element)' );
  75. expect( Delta.prototype.log ).to.be.a( 'function', 'After enabling engine debug (model/delta/delta)' );
  76. expect( ViewElement.prototype.printTree ).to.be.a( 'function', 'After enabling engine debug (view/element)' );
  77. expect( Model.prototype.createReplayer ).to.be.a( 'function', 'After enabling engine debug (model/document)' );
  78. expect( Editor.prototype.logDocuments ).to.be.a( 'function', 'After enabling engine debug (core~editor/editor)' );
  79. disableEngineDebug();
  80. expect( ModelPosition.prototype.log ).to.equal( undefined, 'After disabling engine debug (model/position)' );
  81. expect( ModelElement.prototype.printTree ).to.equal( undefined, 'After disabling engine debug (model/element)' );
  82. expect( Delta.prototype.log ).to.equal( undefined, 'After disabling engine debug (model/delta/delta)' );
  83. expect( ViewElement.prototype.printTree ).to.equal( undefined, 'After disabling engine debug (view/element)' );
  84. expect( Model.prototype.createReplayer ).to.equal( undefined, 'After disabling engine debug (model/document)' );
  85. expect( Editor.prototype.logDocuments ).to.equal( undefined, 'After disabling engine debug (core~editor/editor)' );
  86. } );
  87. } );
  88. describe( 'debug tools', () => {
  89. let DebugPlugin, log, error;
  90. class TestEditor extends StandardEditor {}
  91. before( () => {
  92. log = sinon.spy();
  93. error = sinon.spy();
  94. DebugPlugin = enableEngineDebug( { log, error } );
  95. } );
  96. after( () => {
  97. disableEngineDebug();
  98. } );
  99. afterEach( () => {
  100. log.reset();
  101. } );
  102. describe( 'should provide logging tools', () => {
  103. let model, modelDoc, modelRoot, modelElement, modelDocFrag;
  104. beforeEach( () => {
  105. model = new Model();
  106. modelDoc = model.document;
  107. modelRoot = modelDoc.createRoot();
  108. modelElement = new ModelElement( 'paragraph', null, new ModelText( 'foo' ) );
  109. modelDocFrag = new ModelDocumentFragment( [ new ModelText( 'bar' ) ] );
  110. } );
  111. it( 'for ModelText', () => {
  112. const foo = new ModelText( 'foo', { foo: 'bar' } );
  113. expect( foo.toString() ).to.equal( '#foo' );
  114. foo.log();
  115. expect( log.calledWithExactly( 'ModelText: #foo' ) ).to.be.true;
  116. foo.logExtended();
  117. expect( log.calledWithExactly( 'ModelText: #foo, attrs: {"foo":"bar"}' ) ).to.be.true;
  118. } );
  119. it( 'for ModelTextProxy', () => {
  120. const foo = new ModelText( 'foo', { foo: 'bar' } );
  121. const proxy = new ModelTextProxy( foo, 1, 1 );
  122. expect( proxy.toString() ).to.equal( '#o' );
  123. proxy.log();
  124. expect( log.calledWithExactly( 'ModelTextProxy: #o' ) ).to.be.true;
  125. proxy.logExtended();
  126. expect( log.calledWithExactly( 'ModelTextProxy: #o, attrs: {"foo":"bar"}' ) ).to.be.true;
  127. } );
  128. it( 'for ModelElement', () => {
  129. const paragraph = new ModelElement( 'paragraph', { foo: 'bar' }, new ModelText( 'foo' ) );
  130. expect( paragraph.toString() ).to.equal( '<paragraph>' );
  131. paragraph.log();
  132. expectLog( 'ModelElement: <paragraph>' );
  133. paragraph.logExtended();
  134. expectLog( 'ModelElement: <paragraph>, 1 children, attrs: {"foo":"bar"}' );
  135. sinon.spy( paragraph, 'logExtended' );
  136. paragraph.logAll();
  137. expect( paragraph.logExtended.called ).to.be.true;
  138. expectLog( 'ModelText: #foo' );
  139. } );
  140. it( 'for ModelRootElement', () => {
  141. modelRoot.log();
  142. expectLog( 'ModelRootElement: main' );
  143. } );
  144. it( 'for ModelDocumentFragment', () => {
  145. modelDocFrag.log();
  146. expectLog( 'ModelDocumentFragment: documentFragment' );
  147. } );
  148. it( 'for ModelPosition', () => {
  149. const posInRoot = new ModelPosition( modelRoot, [ 0, 1, 0 ] );
  150. const posInElement = new ModelPosition( modelElement, [ 0 ] );
  151. const posInDocFrag = new ModelPosition( modelDocFrag, [ 2, 3 ] );
  152. expect( posInRoot.toString() ).to.equal( 'main [ 0, 1, 0 ]' );
  153. expect( posInElement.toString() ).to.equal( '<paragraph> [ 0 ]' );
  154. expect( posInDocFrag.toString() ).to.equal( 'documentFragment [ 2, 3 ]' );
  155. posInRoot.log();
  156. expectLog( 'ModelPosition: main [ 0, 1, 0 ]' );
  157. } );
  158. it( 'for ModelRange', () => {
  159. const rangeInRoot = ModelRange.createIn( modelRoot );
  160. const rangeInElement = ModelRange.createIn( modelElement );
  161. const rangeInDocFrag = ModelRange.createIn( modelDocFrag );
  162. expect( rangeInRoot.toString() ).to.equal( 'main [ 0 ] - [ 0 ]' );
  163. expect( rangeInElement.toString() ).to.equal( '<paragraph> [ 0 ] - [ 3 ]' );
  164. expect( rangeInDocFrag.toString() ).to.equal( 'documentFragment [ 0 ] - [ 3 ]' );
  165. rangeInRoot.log();
  166. expectLog( 'ModelRange: main [ 0 ] - [ 0 ]' );
  167. } );
  168. it( 'for ViewText', () => {
  169. const foo = new ViewText( 'foo' );
  170. expect( foo.toString() ).to.equal( '#foo' );
  171. foo.log();
  172. expect( log.calledWithExactly( 'ViewText: #foo' ) ).to.be.true;
  173. foo.logExtended();
  174. expect( log.calledWithExactly( 'ViewText: #foo' ) ).to.be.true;
  175. } );
  176. it( 'for ViewTextProxy', () => {
  177. const foo = new ViewText( 'foo', { foo: 'bar' } );
  178. const proxy = new ViewTextProxy( foo, 1, 1 );
  179. expect( proxy.toString() ).to.equal( '#o' );
  180. proxy.log();
  181. expect( log.calledWithExactly( 'ViewTextProxy: #o' ) ).to.be.true;
  182. proxy.logExtended();
  183. expect( log.calledWithExactly( 'ViewTextProxy: #o' ) ).to.be.true;
  184. } );
  185. describe( 'for operations', () => {
  186. beforeEach( () => {
  187. modelRoot.appendChildren( [ new ModelText( 'foobar' ) ] );
  188. } );
  189. it( 'AttributeOperation', () => {
  190. const op = new AttributeOperation( ModelRange.createIn( modelRoot ), 'key', null, { foo: 'bar' }, 0 );
  191. expect( op.toString() ).to.equal( 'AttributeOperation( 0 ): "key": null -> {"foo":"bar"}, main [ 0 ] - [ 6 ]' );
  192. op.log();
  193. expect( log.calledWithExactly( op.toString() ) ).to.be.true;
  194. } );
  195. it( 'DetachOperation (text node)', () => {
  196. const op = new DetachOperation( ModelPosition.createAt( modelRoot, 0 ), 3 );
  197. expect( op.toString() ).to.equal( 'DetachOperation( null ): #foo -> main [ 0 ] - [ 3 ]' );
  198. op.log();
  199. expect( log.calledWithExactly( op.toString() ) ).to.be.true;
  200. } );
  201. it( 'DetachOperation (element)', () => {
  202. const element = new ModelElement( 'element' );
  203. modelRoot.insertChildren( 0, element );
  204. const op = new DetachOperation( ModelPosition.createBefore( element ), 1 );
  205. expect( op.toString() ).to.equal( 'DetachOperation( null ): <element> -> main [ 0 ] - [ 1 ]' );
  206. op.log();
  207. expect( log.calledWithExactly( op.toString() ) ).to.be.true;
  208. } );
  209. it( 'DetachOperation (multiple nodes)', () => {
  210. const element = new ModelElement( 'element' );
  211. modelRoot.insertChildren( 0, element );
  212. const op = new DetachOperation( ModelPosition.createBefore( element ), 2 );
  213. expect( op.toString() ).to.equal( 'DetachOperation( null ): [ 2 ] -> main [ 0 ] - [ 2 ]' );
  214. op.log();
  215. expect( log.calledWithExactly( op.toString() ) ).to.be.true;
  216. } );
  217. it( 'InsertOperation (text node)', () => {
  218. const op = new InsertOperation( ModelPosition.createAt( modelRoot, 3 ), [ new ModelText( 'abc' ) ], 0 );
  219. expect( op.toString() ).to.equal( 'InsertOperation( 0 ): #abc -> main [ 3 ]' );
  220. op.log();
  221. expect( log.calledWithExactly( op.toString() ) ).to.be.true;
  222. } );
  223. it( 'InsertOperation (element)', () => {
  224. const op = new InsertOperation( ModelPosition.createAt( modelRoot, 3 ), [ new ModelElement( 'paragraph' ) ], 0 );
  225. expect( op.toString() ).to.equal( 'InsertOperation( 0 ): <paragraph> -> main [ 3 ]' );
  226. op.log();
  227. expect( log.calledWithExactly( op.toString() ) ).to.be.true;
  228. } );
  229. it( 'InsertOperation (multiple nodes)', () => {
  230. const nodes = [ new ModelText( 'x' ), new ModelElement( 'y' ), new ModelText( 'z' ) ];
  231. const op = new InsertOperation( ModelPosition.createAt( modelRoot, 3 ), nodes, 0 );
  232. expect( op.toString() ).to.equal( 'InsertOperation( 0 ): [ 3 ] -> main [ 3 ]' );
  233. op.log();
  234. expect( log.calledWithExactly( op.toString() ) ).to.be.true;
  235. } );
  236. it( 'MarkerOperation', () => {
  237. const op = new MarkerOperation( 'marker', null, ModelRange.createIn( modelRoot ), modelDoc.markers, 0 );
  238. expect( op.toString() ).to.equal( 'MarkerOperation( 0 ): "marker": null -> main [ 0 ] - [ 6 ]' );
  239. op.log();
  240. expect( log.calledWithExactly( op.toString() ) ).to.be.true;
  241. } );
  242. it( 'MoveOperation', () => {
  243. const op = new MoveOperation( ModelPosition.createAt( modelRoot, 1 ), 2, ModelPosition.createAt( modelRoot, 6 ), 0 );
  244. expect( op.toString() ).to.equal( 'MoveOperation( 0 ): main [ 1 ] - [ 3 ] -> main [ 6 ]' );
  245. op.log();
  246. expect( log.calledWithExactly( op.toString() ) ).to.be.true;
  247. } );
  248. it( 'MoveOperation sticky', () => {
  249. const op = new MoveOperation( ModelPosition.createAt( modelRoot, 1 ), 2, ModelPosition.createAt( modelRoot, 6 ), 0 );
  250. op.isSticky = true;
  251. expect( op.toString() ).to.equal( 'MoveOperation( 0 ): main [ 1 ] - [ 3 ] -> main [ 6 ] (sticky)' );
  252. op.log();
  253. expect( log.calledWithExactly( op.toString() ) ).to.be.true;
  254. } );
  255. it( 'NoOperation', () => {
  256. const op = new NoOperation( 0 );
  257. expect( op.toString() ).to.equal( 'NoOperation( 0 )' );
  258. op.log();
  259. expect( log.calledWithExactly( op.toString() ) ).to.be.true;
  260. } );
  261. it( 'RenameOperation', () => {
  262. const op = new RenameOperation( ModelPosition.createAt( modelRoot, 1 ), 'old', 'new', 0 );
  263. expect( op.toString() ).to.equal( 'RenameOperation( 0 ): main [ 1 ]: "old" -> "new"' );
  264. op.log();
  265. expect( log.calledWithExactly( op.toString() ) ).to.be.true;
  266. } );
  267. it( 'RootAttributeOperation', () => {
  268. const op = new RootAttributeOperation( modelRoot, 'key', 'old', null, 0 );
  269. expect( op.toString() ).to.equal( 'RootAttributeOperation( 0 ): "key": "old" -> null, main' );
  270. op.log();
  271. expect( log.calledWithExactly( op.toString() ) ).to.be.true;
  272. } );
  273. } );
  274. describe( 'for deltas', () => {
  275. it( 'Delta', () => {
  276. const delta = new Delta();
  277. const op = { log: sinon.spy() };
  278. delta.addOperation( op );
  279. sinon.spy( delta, 'log' );
  280. delta.logAll();
  281. expect( op.log.called ).to.be.true;
  282. expect( delta.log.called ).to.be.true;
  283. } );
  284. it( 'AttributeDelta', () => {
  285. modelRoot.appendChildren( new ModelText( 'foobar' ) );
  286. const delta = new AttributeDelta();
  287. const op = new AttributeOperation( ModelRange.createIn( modelRoot ), 'key', null, { foo: 'bar' }, 0 );
  288. delta.addOperation( op );
  289. expect( delta.toString() ).to.equal( 'AttributeDelta( 0 ): "key": -> {"foo":"bar"}, main [ 0 ] - [ 6 ], 1 ops' );
  290. delta.log();
  291. expect( log.calledWithExactly( delta.toString() ) ).to.be.true;
  292. } );
  293. it( 'InsertDelta (text node)', () => {
  294. const delta = new InsertDelta();
  295. const op = new InsertOperation( ModelPosition.createAt( modelRoot, 3 ), [ new ModelText( 'abc' ) ], 0 );
  296. delta.addOperation( op );
  297. expect( delta.toString() ).to.equal( 'InsertDelta( 0 ): #abc -> main [ 3 ]' );
  298. delta.log();
  299. expect( log.calledWithExactly( delta.toString() ) ).to.be.true;
  300. } );
  301. it( 'InsertDelta (element)', () => {
  302. const delta = new InsertDelta();
  303. const op = new InsertOperation( ModelPosition.createAt( modelRoot, 3 ), [ new ModelElement( 'paragraph' ) ], 0 );
  304. delta.addOperation( op );
  305. expect( delta.toString() ).to.equal( 'InsertDelta( 0 ): <paragraph> -> main [ 3 ]' );
  306. delta.log();
  307. expect( log.calledWithExactly( delta.toString() ) ).to.be.true;
  308. } );
  309. it( 'InsertDelta (multiple nodes)', () => {
  310. const delta = new InsertDelta();
  311. const nodes = [ new ModelText( 'x' ), new ModelElement( 'y' ), new ModelText( 'z' ) ];
  312. const op = new InsertOperation( ModelPosition.createAt( modelRoot, 3 ), nodes, 0 );
  313. delta.addOperation( op );
  314. expect( delta.toString() ).to.equal( 'InsertDelta( 0 ): [ 3 ] -> main [ 3 ]' );
  315. delta.log();
  316. expect( log.calledWithExactly( delta.toString() ) ).to.be.true;
  317. } );
  318. it( 'MarkerDelta', () => {
  319. modelRoot.appendChildren( new ModelText( 'foobar' ) );
  320. const delta = new MarkerDelta();
  321. const op = new MarkerOperation( 'marker', null, ModelRange.createIn( modelRoot ), modelDoc.markers, 0 );
  322. delta.addOperation( op );
  323. expect( delta.toString() ).to.equal( 'MarkerDelta( 0 ): "marker": null -> main [ 0 ] - [ 6 ]' );
  324. delta.log();
  325. expect( log.calledWithExactly( delta.toString() ) ).to.be.true;
  326. } );
  327. it( 'MergeDelta', () => {
  328. const otherRoot = modelDoc.createRoot( '$root', 'otherRoot' );
  329. const firstEle = new ModelElement( 'paragraph' );
  330. const removedEle = new ModelElement( 'paragraph', null, [ new ModelText( 'foo' ) ] );
  331. otherRoot.appendChildren( [ firstEle, removedEle ] );
  332. const graveyard = modelDoc.graveyard;
  333. const delta = new MergeDelta();
  334. const move = new MoveOperation( ModelPosition.createAt( removedEle, 0 ), 3, ModelPosition.createAt( firstEle, 0 ), 0 );
  335. const remove = new RemoveOperation(
  336. ModelPosition.createBefore( removedEle ),
  337. 1,
  338. ModelPosition.createAt( graveyard, 0 ),
  339. 1
  340. );
  341. delta.addOperation( move );
  342. delta.addOperation( remove );
  343. expect( delta.toString() ).to.equal( 'MergeDelta( 0 ): otherRoot [ 1 ]' );
  344. delta.log();
  345. expect( log.calledWithExactly( delta.toString() ) ).to.be.true;
  346. } );
  347. it( 'MergeDelta - NoOperation as second operation', () => {
  348. const otherRoot = modelDoc.createRoot( '$root', 'otherRoot' );
  349. const firstEle = new ModelElement( 'paragraph' );
  350. const removedEle = new ModelElement( 'paragraph', null, [ new ModelText( 'foo' ) ] );
  351. otherRoot.appendChildren( [ firstEle, removedEle ] );
  352. const delta = new MergeDelta();
  353. const move = new MoveOperation( ModelPosition.createAt( removedEle, 0 ), 3, ModelPosition.createAt( firstEle, 0 ), 0 );
  354. delta.addOperation( move );
  355. delta.addOperation( new NoOperation( 1 ) );
  356. expect( delta.toString() ).to.equal( 'MergeDelta( 0 ): (move from otherRoot [ 1, 0 ])' );
  357. delta.log();
  358. expect( log.calledWithExactly( delta.toString() ) ).to.be.true;
  359. } );
  360. it( 'MoveDelta', () => {
  361. const delta = new MoveDelta();
  362. const move1 = new MoveOperation( ModelPosition.createAt( modelRoot, 0 ), 1, ModelPosition.createAt( modelRoot, 3 ), 0 );
  363. const move2 = new MoveOperation( ModelPosition.createAt( modelRoot, 1 ), 1, ModelPosition.createAt( modelRoot, 6 ), 0 );
  364. delta.addOperation( move1 );
  365. delta.addOperation( move2 );
  366. expect( delta.toString() ).to.equal( 'MoveDelta( 0 ): main [ 0 ] - [ 1 ] -> main [ 3 ]; main [ 1 ] - [ 2 ] -> main [ 6 ]' );
  367. delta.log();
  368. expect( log.calledWithExactly( delta.toString() ) ).to.be.true;
  369. } );
  370. it( 'RenameDelta', () => {
  371. const delta = new RenameDelta();
  372. const op = new RenameOperation( ModelPosition.createAt( modelRoot, 1 ), 'old', 'new', 0 );
  373. delta.addOperation( op );
  374. expect( delta.toString() ).to.equal( 'RenameDelta( 0 ): main [ 1 ]: "old" -> "new"' );
  375. delta.log();
  376. expect( log.calledWithExactly( delta.toString() ) ).to.be.true;
  377. } );
  378. it( 'RootAttributeDelta', () => {
  379. const delta = new RootAttributeDelta();
  380. const op = new RootAttributeOperation( modelRoot, 'key', 'old', null, 0 );
  381. delta.addOperation( op );
  382. expect( delta.toString() ).to.equal( 'RootAttributeDelta( 0 ): "key": "old" -> null, main' );
  383. delta.log();
  384. expect( log.calledWithExactly( delta.toString() ) ).to.be.true;
  385. } );
  386. it( 'SplitDelta', () => {
  387. const otherRoot = modelDoc.createRoot( 'main', 'otherRoot' );
  388. const splitEle = new ModelElement( 'paragraph', null, [ new ModelText( 'foo' ) ] );
  389. otherRoot.appendChildren( [ splitEle ] );
  390. const delta = new SplitDelta();
  391. const insert = new InsertOperation( ModelPosition.createAt( otherRoot, 1 ), [ new ModelElement( 'paragraph' ) ], 0 );
  392. const move = new MoveOperation( ModelPosition.createAt( splitEle, 1 ), 2, new ModelPosition( otherRoot, [ 1, 0 ] ), 1 );
  393. delta.addOperation( insert );
  394. delta.addOperation( move );
  395. expect( delta.toString() ).to.equal( 'SplitDelta( 0 ): otherRoot [ 0, 1 ]' );
  396. delta.log();
  397. expect( log.calledWithExactly( delta.toString() ) ).to.be.true;
  398. } );
  399. it( 'SplitDelta - NoOperation as second operation', () => {
  400. const otherRoot = modelDoc.createRoot( 'main', 'otherRoot' );
  401. const splitEle = new ModelElement( 'paragraph', null, [ new ModelText( 'foo' ) ] );
  402. otherRoot.appendChildren( [ splitEle ] );
  403. const delta = new SplitDelta();
  404. const insert = new InsertOperation( ModelPosition.createAt( otherRoot, 1 ), [ new ModelElement( 'paragraph' ) ], 0 );
  405. const move = new NoOperation( 1 );
  406. delta.addOperation( insert );
  407. delta.addOperation( move );
  408. expect( delta.toString() ).to.equal( 'SplitDelta( 0 ): (clone to otherRoot [ 1 ])' );
  409. delta.log();
  410. expect( log.calledWithExactly( delta.toString() ) ).to.be.true;
  411. } );
  412. it( 'SplitDelta - NoOperation as second operation, MoveOperation as first operation', () => {
  413. const otherRoot = modelDoc.createRoot( 'main', 'otherRoot' );
  414. const delta = new SplitDelta();
  415. const insert = new MoveOperation( ModelPosition.createAt( modelRoot, 1 ), 1, ModelPosition.createAt( otherRoot, 1 ), 0 );
  416. const move = new NoOperation( 1 );
  417. delta.addOperation( insert );
  418. delta.addOperation( move );
  419. expect( delta.toString() ).to.equal( 'SplitDelta( 0 ): (clone to otherRoot [ 1 ])' );
  420. delta.log();
  421. expect( log.calledWithExactly( delta.toString() ) ).to.be.true;
  422. } );
  423. it( 'UnwrapDelta', () => {
  424. const otherRoot = modelDoc.createRoot( 'main', 'otherRoot' );
  425. const unwrapEle = new ModelElement( 'paragraph', null, [ new ModelText( 'foo' ) ] );
  426. otherRoot.appendChildren( [ unwrapEle ] );
  427. const graveyard = modelDoc.graveyard;
  428. const delta = new UnwrapDelta();
  429. const move = new MoveOperation( ModelPosition.createAt( unwrapEle, 0 ), 3, ModelPosition.createAt( otherRoot, 0 ), 0 );
  430. const remove = new RemoveOperation( ModelPosition.createAt( otherRoot, 3 ), 1, ModelPosition.createAt( graveyard, 0 ), 1 );
  431. delta.addOperation( move );
  432. delta.addOperation( remove );
  433. expect( delta.toString() ).to.equal( 'UnwrapDelta( 0 ): otherRoot [ 0 ]' );
  434. delta.log();
  435. expect( log.calledWithExactly( delta.toString() ) ).to.be.true;
  436. } );
  437. it( 'WrapDelta', () => {
  438. const delta = new WrapDelta();
  439. const insert = new InsertOperation( ModelPosition.createAt( modelRoot, 6 ), new ModelElement( 'paragraph' ), 0 );
  440. const move = new MoveOperation( ModelPosition.createAt( modelRoot, 0 ), 6, new ModelPosition( modelRoot, [ 1, 0 ] ), 1 );
  441. delta.addOperation( insert );
  442. delta.addOperation( move );
  443. expect( delta.toString() ).to.equal( 'WrapDelta( 0 ): main [ 0 ] - [ 6 ] -> <paragraph>' );
  444. delta.log();
  445. expect( log.calledWithExactly( delta.toString() ) ).to.be.true;
  446. } );
  447. } );
  448. it( 'for applied operations', () => {
  449. const delta = new InsertDelta();
  450. const op = new InsertOperation( ModelPosition.createAt( modelRoot, 0 ), [ new ModelText( 'foo' ) ], 0 );
  451. delta.addOperation( op );
  452. model.applyOperation( op );
  453. expect( log.calledWithExactly( 'Applying InsertOperation( 0 ): #foo -> main [ 0 ]' ) ).to.be.true;
  454. } );
  455. } );
  456. describe( 'should provide tree printing tools', () => {
  457. it( 'for model', () => {
  458. const model = new Model();
  459. const modelDoc = model.document;
  460. const modelRoot = modelDoc.createRoot();
  461. modelRoot.appendChildren( [
  462. new ModelElement( 'paragraph', { foo: 'bar' }, [
  463. new ModelText( 'This is ' ), new ModelText( 'bold', { bold: true } ), new ModelText( '.' )
  464. ] ),
  465. new ModelElement( 'listItem', { type: 'numbered', indent: 0 }, new ModelText( 'One.' ) ),
  466. ] );
  467. const modelRootTree = modelRoot.printTree();
  468. expect( modelRootTree ).to.equal(
  469. '<main>' +
  470. '\n\t<paragraph foo="bar">' +
  471. '\n\t\tThis is ' +
  472. '\n\t\t<$text bold=true>bold</$text>' +
  473. '\n\t\t.' +
  474. '\n\t</paragraph>' +
  475. '\n\t<listItem type="numbered" indent=0>' +
  476. '\n\t\tOne.' +
  477. '\n\t</listItem>' +
  478. '\n</main>'
  479. );
  480. modelRoot.logTree();
  481. expect( log.calledWithExactly( modelRootTree ) ).to.be.true;
  482. const modelParagraph = modelRoot.getChild( 0 );
  483. const modelParagraphTree = modelParagraph.printTree();
  484. expect( modelParagraphTree ).to.equal(
  485. '<paragraph foo="bar">' +
  486. '\n\tThis is ' +
  487. '\n\t<$text bold=true>bold</$text>' +
  488. '\n\t.' +
  489. '\n</paragraph>'
  490. );
  491. log.reset();
  492. modelParagraph.logTree();
  493. expect( log.calledWithExactly( modelParagraphTree ) ).to.be.true;
  494. const modelDocFrag = new ModelDocumentFragment( [
  495. new ModelText( 'This is ' ), new ModelText( 'bold', { bold: true } ), new ModelText( '.' ),
  496. new ModelElement( 'paragraph', { foo: 'bar' }, [
  497. new ModelText( 'This is ' ), new ModelText( 'bold', { bold: true } ), new ModelText( '.' )
  498. ] )
  499. ] );
  500. const modelDocFragTree = modelDocFrag.printTree();
  501. expect( modelDocFragTree ).to.equal(
  502. 'ModelDocumentFragment: [' +
  503. '\n\tThis is ' +
  504. '\n\t<$text bold=true>bold</$text>' +
  505. '\n\t.' +
  506. '\n\t<paragraph foo="bar">' +
  507. '\n\t\tThis is ' +
  508. '\n\t\t<$text bold=true>bold</$text>' +
  509. '\n\t\t.' +
  510. '\n\t</paragraph>' +
  511. '\n]'
  512. );
  513. log.reset();
  514. modelDocFrag.logTree();
  515. expect( log.calledWithExactly( modelDocFragTree ) ).to.be.true;
  516. } );
  517. it( 'for view', () => {
  518. const viewDoc = new ViewDocument();
  519. const viewRoot = createViewRoot( viewDoc );
  520. viewRoot.appendChildren( [
  521. new ViewContainerElement( 'p', { foo: 'bar' }, [
  522. new ViewText( 'This is ' ), new ViewAttributeElement( 'b', null, new ViewText( 'bold' ) ), new ViewText( '.' )
  523. ] ),
  524. new ViewContainerElement( 'ol', null, [
  525. new ViewContainerElement( 'li', null, new ViewText( 'One.' ) )
  526. ] )
  527. ] );
  528. const viewRootTree = viewRoot.printTree();
  529. expect( viewRootTree ).to.equal(
  530. '<div>' +
  531. '\n\t<p foo="bar">' +
  532. '\n\t\tThis is ' +
  533. '\n\t\t<b>' +
  534. '\n\t\t\tbold' +
  535. '\n\t\t</b>' +
  536. '\n\t\t.' +
  537. '\n\t</p>' +
  538. '\n\t<ol>' +
  539. '\n\t\t<li>' +
  540. '\n\t\t\tOne.' +
  541. '\n\t\t</li>' +
  542. '\n\t</ol>' +
  543. '\n</div>'
  544. );
  545. viewRoot.logTree();
  546. expect( log.calledWithExactly( viewRootTree ) ).to.be.true;
  547. const viewParagraph = viewRoot.getChild( 0 );
  548. const viewParagraphTree = viewParagraph.printTree();
  549. expect( viewParagraphTree ).to.equal(
  550. '<p foo="bar">' +
  551. '\n\tThis is ' +
  552. '\n\t<b>' +
  553. '\n\t\tbold' +
  554. '\n\t</b>' +
  555. '\n\t.' +
  556. '\n</p>'
  557. );
  558. log.reset();
  559. viewParagraph.logTree();
  560. expect( log.calledWithExactly( viewParagraphTree ) ).to.be.true;
  561. const viewDocFrag = new ViewDocumentFragment( [
  562. new ViewText( 'Text.' ),
  563. new ViewContainerElement( 'p', { foo: 'bar' }, [
  564. new ViewText( 'This is ' ), new ViewAttributeElement( 'b', null, new ViewText( 'bold' ) ), new ViewText( '.' )
  565. ] )
  566. ] );
  567. const viewDocFragTree = viewDocFrag.printTree();
  568. expect( viewDocFragTree ).to.equal(
  569. 'ViewDocumentFragment: [' +
  570. '\n\tText.' +
  571. '\n\t<p foo="bar">' +
  572. '\n\t\tThis is ' +
  573. '\n\t\t<b>' +
  574. '\n\t\t\tbold' +
  575. '\n\t\t</b>' +
  576. '\n\t\t.' +
  577. '\n\t</p>' +
  578. '\n]'
  579. );
  580. log.reset();
  581. viewDocFrag.logTree();
  582. expect( log.calledWithExactly( viewDocFragTree ) ).to.be.true;
  583. } );
  584. } );
  585. describe( 'should store model and view trees state', () => {
  586. let editor;
  587. beforeEach( () => {
  588. const div = document.createElement( 'div' );
  589. return TestEditor.create( div, {
  590. plugins: [ DebugPlugin ]
  591. } ).then( _editor => {
  592. editor = _editor;
  593. } );
  594. } );
  595. it( 'should store model and view state after each applied operation', () => {
  596. const model = editor.model;
  597. const modelDoc = model.document;
  598. const modelRoot = modelDoc.getRoot();
  599. const viewDoc = editor.editing.view;
  600. model.change( () => {
  601. const insert = new InsertOperation( ModelPosition.createAt( modelRoot, 0 ), new ModelText( 'foobar' ), 0 );
  602. model.applyOperation( wrapInDelta( insert ) );
  603. const graveyard = modelDoc.graveyard;
  604. const remove = new RemoveOperation( ModelPosition.createAt( modelRoot, 1 ), 2, ModelPosition.createAt( graveyard, 0 ), 1 );
  605. model.applyOperation( wrapInDelta( remove ) );
  606. } );
  607. log.reset();
  608. modelDoc.log( 0 );
  609. expectLog(
  610. '<$graveyard></$graveyard>' +
  611. '\n<main></main>'
  612. );
  613. modelDoc.log( 1 );
  614. expectLog(
  615. '<$graveyard></$graveyard>' +
  616. '\n<main>' +
  617. '\n\tfoobar' +
  618. '\n</main>'
  619. );
  620. modelDoc.log( 2 );
  621. expectLog(
  622. '<$graveyard>' +
  623. '\n\too' +
  624. '\n</$graveyard>' +
  625. '\n<main>' +
  626. '\n\tfbar' +
  627. '\n</main>'
  628. );
  629. modelDoc.log();
  630. expectLog(
  631. '<$graveyard>' +
  632. '\n\too' +
  633. '\n</$graveyard>' +
  634. '\n<main>' +
  635. '\n\tfbar' +
  636. '\n</main>'
  637. );
  638. viewDoc.log( 0 );
  639. expectLog(
  640. '<$root></$root>'
  641. );
  642. viewDoc.log( 2 );
  643. expectLog(
  644. '<$root>' +
  645. '\n\tfbar' +
  646. '\n</$root>'
  647. );
  648. sinon.spy( modelDoc, 'log' );
  649. sinon.spy( viewDoc, 'log' );
  650. editor.logModel( 1 );
  651. expect( modelDoc.log.calledWithExactly( 1 ), 1 ).to.be.true;
  652. editor.logView( 2 );
  653. expect( viewDoc.log.calledWithExactly( 2 ), 2 ).to.be.true;
  654. modelDoc.log.reset();
  655. viewDoc.log.reset();
  656. editor.logModel();
  657. expect( modelDoc.log.calledWithExactly( 2 ), 3 ).to.be.true;
  658. modelDoc.log.reset();
  659. viewDoc.log.reset();
  660. editor.logDocuments();
  661. expect( modelDoc.log.calledWithExactly( 2 ), 4 ).to.be.true;
  662. expect( viewDoc.log.calledWithExactly( 2 ), 5 ).to.be.true;
  663. modelDoc.log.reset();
  664. viewDoc.log.reset();
  665. editor.logDocuments( 1 );
  666. expect( modelDoc.log.calledWithExactly( 1 ), 6 ).to.be.true;
  667. expect( viewDoc.log.calledWithExactly( 1 ), 7 ).to.be.true;
  668. } );
  669. it( 'should remove old states', () => {
  670. const model = editor.model;
  671. const modelDoc = model.document;
  672. const modelRoot = model.document.getRoot();
  673. for ( let i = 0; i < 25; i++ ) {
  674. const insert = new InsertOperation( ModelPosition.createAt( modelRoot, 0 ), new ModelText( 'foobar' ), modelDoc.version );
  675. model.applyOperation( wrapInDelta( insert ) );
  676. }
  677. modelDoc.log( 0 );
  678. expectLog( 'Tree log unavailable for given version: 0' );
  679. } );
  680. } );
  681. describe( 'should provide methods for delta replayer', () => {
  682. it( 'getAppliedDeltas()', () => {
  683. const model = new Model();
  684. const modelDoc = model.document;
  685. expect( model.getAppliedDeltas() ).to.equal( '' );
  686. const otherRoot = modelDoc.createRoot( '$root', 'otherRoot' );
  687. const firstEle = new ModelElement( 'paragraph' );
  688. const removedEle = new ModelElement( 'paragraph', null, [ new ModelText( 'foo' ) ] );
  689. otherRoot.appendChildren( [ firstEle, removedEle ] );
  690. const delta = new MergeDelta();
  691. const graveyard = modelDoc.graveyard;
  692. const move = new MoveOperation( ModelPosition.createAt( removedEle, 0 ), 3, ModelPosition.createAt( firstEle, 0 ), 0 );
  693. const remove = new RemoveOperation( ModelPosition.createBefore( removedEle ), 1, ModelPosition.createAt( graveyard, 0 ), 1 );
  694. delta.addOperation( move );
  695. delta.addOperation( remove );
  696. model.applyOperation( move );
  697. model.applyOperation( remove );
  698. const stringifiedDeltas = model.getAppliedDeltas();
  699. expect( stringifiedDeltas ).to.equal( JSON.stringify( delta.toJSON() ) );
  700. } );
  701. it( 'createReplayer()', () => {
  702. const model = new Model();
  703. const modelDoc = model.document;
  704. const otherRoot = modelDoc.createRoot( '$root', 'otherRoot' );
  705. const firstEle = new ModelElement( 'paragraph' );
  706. const removedEle = new ModelElement( 'paragraph', null, [ new ModelText( 'foo' ) ] );
  707. otherRoot.appendChildren( [ firstEle, removedEle ] );
  708. const delta = new MergeDelta();
  709. const graveyard = modelDoc.graveyard;
  710. const move = new MoveOperation( ModelPosition.createAt( removedEle, 0 ), 3, ModelPosition.createAt( firstEle, 0 ), 0 );
  711. const remove = new RemoveOperation( ModelPosition.createBefore( removedEle ), 1, ModelPosition.createAt( graveyard, 0 ), 1 );
  712. delta.addOperation( move );
  713. delta.addOperation( remove );
  714. const stringifiedDeltas = JSON.stringify( delta.toJSON() );
  715. const deltaReplayer = model.createReplayer( stringifiedDeltas );
  716. expect( deltaReplayer.getDeltasToReplay() ).to.deep.equal( [ JSON.parse( stringifiedDeltas ) ] );
  717. } );
  718. } );
  719. describe( 'should provide debug tools for delta transformation', () => {
  720. let model, document, root, otherRoot;
  721. beforeEach( () => {
  722. model = new Model();
  723. document = model.document;
  724. root = document.createRoot();
  725. otherRoot = document.createRoot( 'other', 'other' );
  726. } );
  727. it( 'Delta#_saveHistory()', () => {
  728. const insertDeltaA = new InsertDelta();
  729. const insertOpA = new InsertOperation( ModelPosition.createAt( root, 0 ), new ModelText( 'a' ), 0 );
  730. insertDeltaA.addOperation( insertOpA );
  731. const insertDeltaB = new InsertDelta();
  732. const insertOpB = new InsertOperation( ModelPosition.createAt( root, 0 ), new ModelText( 'a' ), 0 );
  733. insertDeltaB.addOperation( insertOpB );
  734. const insertDeltaFinalA = new InsertDelta();
  735. const insertOpFinalA = new InsertOperation( ModelPosition.createAt( root, 0 ), new ModelText( 'a' ), 1 );
  736. insertDeltaFinalA.addOperation( insertOpFinalA );
  737. const insertDeltaFinalAJsonWithoutHistory = JSON.stringify( insertDeltaFinalA );
  738. insertDeltaFinalA._saveHistory( {
  739. before: insertDeltaA,
  740. transformedBy: insertDeltaB,
  741. wasImportant: true,
  742. resultIndex: 0,
  743. resultsTotal: 1
  744. } );
  745. const insertDeltaC = new InsertDelta();
  746. const insertOpC = new InsertOperation( ModelPosition.createAt( root, 0 ), new ModelText( 'a' ), 1 );
  747. insertDeltaC.addOperation( insertOpC );
  748. const insertDeltaFinalB = new InsertDelta();
  749. const insertOpFinalB = new InsertOperation( ModelPosition.createAt( root, 0 ), new ModelText( 'a' ), 2 );
  750. insertDeltaFinalB.addOperation( insertOpFinalB );
  751. insertDeltaFinalB._saveHistory( {
  752. before: insertDeltaFinalA,
  753. transformedBy: insertDeltaC,
  754. wasImportant: false,
  755. resultIndex: 1,
  756. resultsTotal: 3
  757. } );
  758. expect( insertDeltaA.history ).to.be.undefined;
  759. expect( insertDeltaB.history ).to.be.undefined;
  760. expect( insertDeltaFinalA.history ).not.to.be.undefined;
  761. expect( insertDeltaFinalA.history.length ).to.equal( 1 );
  762. expect( insertDeltaFinalA.history[ 0 ] ).to.deep.equal( {
  763. before: JSON.stringify( insertDeltaA ),
  764. transformedBy: JSON.stringify( insertDeltaB ),
  765. wasImportant: true,
  766. resultIndex: 0,
  767. resultsTotal: 1
  768. } );
  769. expect( insertDeltaFinalB.history ).not.to.be.undefined;
  770. expect( insertDeltaFinalB.history.length ).to.equal( 2 );
  771. expect( insertDeltaFinalB.history[ 0 ] ).to.deep.equal( {
  772. before: JSON.stringify( insertDeltaA ),
  773. transformedBy: JSON.stringify( insertDeltaB ),
  774. wasImportant: true,
  775. resultIndex: 0,
  776. resultsTotal: 1
  777. } );
  778. expect( insertDeltaFinalB.history[ 1 ] ).to.deep.equal( {
  779. before: insertDeltaFinalAJsonWithoutHistory,
  780. transformedBy: JSON.stringify( insertDeltaC ),
  781. wasImportant: false,
  782. resultIndex: 1,
  783. resultsTotal: 3
  784. } );
  785. } );
  786. it( 'save delta history on deltaTransform.transform', () => {
  787. const deltaA = new MoveDelta();
  788. const opA = new MoveOperation( ModelPosition.createAt( root, 4 ), 4, ModelPosition.createAt( otherRoot, 4 ), 0 );
  789. deltaA.addOperation( opA );
  790. const deltaB = new InsertDelta();
  791. const opB = new InsertOperation( ModelPosition.createAt( root, 0 ), new ModelText( 'a' ), 0 );
  792. deltaB.addOperation( opB );
  793. const deltaC = new MoveDelta();
  794. const opC = new MoveOperation( ModelPosition.createAt( root, 4 ), 2, ModelPosition.createAt( root, 0 ), 1 );
  795. deltaC.addOperation( opC );
  796. let result = deltaTransform.transform( deltaA, deltaB, { document, wasAffected: new Map() } );
  797. expect( result[ 0 ].history ).not.to.be.undefined;
  798. expect( result[ 0 ].history.length ).to.equal( 1 );
  799. expect( result[ 0 ].history[ 0 ] ).to.deep.equal( {
  800. before: JSON.stringify( deltaA ),
  801. transformedBy: JSON.stringify( deltaB ),
  802. wasImportant: false,
  803. resultIndex: 0,
  804. resultsTotal: 1
  805. } );
  806. const firstResultWithoutHistory = result[ 0 ].clone();
  807. delete firstResultWithoutHistory.history;
  808. result = deltaTransform.transform( result[ 0 ], deltaC, {
  809. isStrong: true,
  810. document,
  811. wasAffected: new Map()
  812. } );
  813. expect( result[ 0 ].history ).not.to.be.undefined;
  814. expect( result[ 0 ].history.length ).to.equal( 2 );
  815. expect( result[ 0 ].history[ 0 ] ).to.deep.equal( {
  816. before: JSON.stringify( deltaA ),
  817. transformedBy: JSON.stringify( deltaB ),
  818. wasImportant: false,
  819. resultIndex: 0,
  820. resultsTotal: 1
  821. } );
  822. expect( result[ 0 ].history[ 1 ] ).to.deep.equal( {
  823. before: JSON.stringify( firstResultWithoutHistory ),
  824. transformedBy: JSON.stringify( deltaC ),
  825. wasImportant: true,
  826. resultIndex: 0,
  827. resultsTotal: 2
  828. } );
  829. } );
  830. it( 'recreate delta using Delta#history', () => {
  831. const deltaA = new MoveDelta();
  832. const opA = new MoveOperation( ModelPosition.createAt( root, 4 ), 4, ModelPosition.createAt( otherRoot, 4 ), 0 );
  833. deltaA.addOperation( opA );
  834. const deltaB = new InsertDelta();
  835. const opB = new InsertOperation( ModelPosition.createAt( root, 0 ), new ModelText( 'a' ), 0 );
  836. deltaB.addOperation( opB );
  837. const deltaC = new MoveDelta();
  838. const opC = new MoveOperation( ModelPosition.createAt( root, 4 ), 2, ModelPosition.createAt( root, 0 ), 1 );
  839. deltaC.addOperation( opC );
  840. let original = deltaTransform.transform( deltaA, deltaB, { document, wasAffected: new Map() } );
  841. original = deltaTransform.transform( original[ 0 ], deltaC, {
  842. isStrong: true,
  843. document,
  844. wasAffected: new Map()
  845. } )[ 0 ];
  846. const history = original.history;
  847. let recreated = deltaTransform.transform(
  848. DeltaFactory.fromJSON( JSON.parse( history[ 0 ].before ), document ),
  849. DeltaFactory.fromJSON( JSON.parse( history[ 0 ].transformedBy ), document ),
  850. { isStrong: history[ 0 ].wasImportant, document, wasAffected: new Map() }
  851. );
  852. recreated = recreated[ history[ 0 ].resultIndex ];
  853. recreated = deltaTransform.transform(
  854. recreated,
  855. DeltaFactory.fromJSON( JSON.parse( history[ 1 ].transformedBy ), document ),
  856. { isStrong: history[ 1 ].wasImportant, document, wasAffected: new Map() }
  857. );
  858. recreated = recreated[ history[ 1 ].resultIndex ];
  859. delete original.history;
  860. delete recreated.history;
  861. expect( JSON.stringify( recreated ) ).to.equal( JSON.stringify( original ) );
  862. } );
  863. describe( 'provide additional logging when transformation crashes', () => {
  864. it( 'with more important delta A', () => {
  865. const deltaA = new MoveDelta();
  866. const opA = new MoveOperation( ModelPosition.createAt( root, 4 ), 4, ModelPosition.createAt( otherRoot, 4 ), 0 );
  867. deltaA.addOperation( opA );
  868. const deltaB = new InsertDelta();
  869. const opB = new InsertOperation( ModelPosition.createAt( root, 0 ), new ModelText( 'a' ), 0 );
  870. deltaB.addOperation( opB );
  871. testUtils.sinon.stub( deltaTransform, 'defaultTransform' ).throws( new Error() );
  872. expect( () => deltaTransform.transform( deltaA, deltaB, { isStrong: true } ) ).to.throw( Error );
  873. expect( error.calledWith( deltaA.toString() + ' (important)' ) ).to.be.true;
  874. expect( error.calledWith( deltaB.toString() ) ).to.be.true;
  875. } );
  876. it( 'with more important delta B', () => {
  877. const deltaA = new MoveDelta();
  878. const opA = new MoveOperation( ModelPosition.createAt( root, 4 ), 4, ModelPosition.createAt( otherRoot, 4 ), 0 );
  879. deltaA.addOperation( opA );
  880. const deltaB = new InsertDelta();
  881. const opB = new InsertOperation( ModelPosition.createAt( root, 0 ), new ModelText( 'a' ), 0 );
  882. deltaB.addOperation( opB );
  883. testUtils.sinon.stub( deltaTransform, 'defaultTransform' ).throws( new Error() );
  884. expect( () => deltaTransform.transform( deltaA, deltaB, { isStrong: false } ) ).to.throw( Error );
  885. expect( error.calledWith( deltaA.toString() ) ).to.be.true;
  886. expect( error.calledWith( deltaB.toString() + ' (important)' ) ).to.be.true;
  887. } );
  888. } );
  889. } );
  890. function expectLog( expectedLogMsg ) {
  891. expect( log.calledWithExactly( expectedLogMsg ) ).to.be.true;
  892. log.reset();
  893. }
  894. function wrapInDelta( op ) {
  895. const delta = new Delta();
  896. delta.addOperation( op );
  897. return op;
  898. }
  899. } );