enableenginedebug.js 39 KB

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