enableenginedebug.js 40 KB

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