8
0

enableenginedebug.js 40 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188
  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. const insert = new InsertOperation( ModelPosition.createAt( modelRoot, 0 ), new ModelText( 'foobar' ), 0 );
  606. model.applyOperation( wrapInDelta( insert ) );
  607. const graveyard = modelDoc.graveyard;
  608. const remove = new RemoveOperation( ModelPosition.createAt( modelRoot, 1 ), 2, ModelPosition.createAt( graveyard, 0 ), 1 );
  609. model.applyOperation( wrapInDelta( remove ) );
  610. log.reset();
  611. modelDoc.log( 0 );
  612. expectLog(
  613. '<$graveyard></$graveyard>' +
  614. '\n<main></main>'
  615. );
  616. modelDoc.log( 1 );
  617. expectLog(
  618. '<$graveyard></$graveyard>' +
  619. '\n<main>' +
  620. '\n\tfoobar' +
  621. '\n</main>'
  622. );
  623. modelDoc.log( 2 );
  624. expectLog(
  625. '<$graveyard>' +
  626. '\n\too' +
  627. '\n</$graveyard>' +
  628. '\n<main>' +
  629. '\n\tfbar' +
  630. '\n</main>'
  631. );
  632. modelDoc.log();
  633. expectLog(
  634. '<$graveyard>' +
  635. '\n\too' +
  636. '\n</$graveyard>' +
  637. '\n<main>' +
  638. '\n\tfbar' +
  639. '\n</main>'
  640. );
  641. viewDoc.log( 0 );
  642. expectLog(
  643. '<div></div>'
  644. );
  645. viewDoc.log( 1 );
  646. expectLog(
  647. '<div>' +
  648. '\n\tfoobar' +
  649. '\n</div>'
  650. );
  651. viewDoc.log( 2 );
  652. expectLog(
  653. '<div>' +
  654. '\n\tfbar' +
  655. '\n</div>'
  656. );
  657. sinon.spy( modelDoc, 'log' );
  658. sinon.spy( viewDoc, 'log' );
  659. editor.logModel( 1 );
  660. expect( modelDoc.log.calledWithExactly( 1 ), 1 ).to.be.true;
  661. editor.logView( 2 );
  662. expect( viewDoc.log.calledWithExactly( 2 ), 2 ).to.be.true;
  663. modelDoc.log.reset();
  664. viewDoc.log.reset();
  665. editor.logModel();
  666. expect( modelDoc.log.calledWithExactly( 2 ), 3 ).to.be.true;
  667. modelDoc.log.reset();
  668. viewDoc.log.reset();
  669. editor.logDocuments();
  670. expect( modelDoc.log.calledWithExactly( 2 ), 4 ).to.be.true;
  671. expect( viewDoc.log.calledWithExactly( 2 ), 5 ).to.be.true;
  672. modelDoc.log.reset();
  673. viewDoc.log.reset();
  674. editor.logDocuments( 1 );
  675. expect( modelDoc.log.calledWithExactly( 1 ), 6 ).to.be.true;
  676. expect( viewDoc.log.calledWithExactly( 1 ), 7 ).to.be.true;
  677. } );
  678. it( 'should remove old states', () => {
  679. const model = editor.model;
  680. const modelDoc = model.document;
  681. const modelRoot = model.document.getRoot();
  682. for ( let i = 0; i < 25; i++ ) {
  683. const insert = new InsertOperation( ModelPosition.createAt( modelRoot, 0 ), new ModelText( 'foobar' ), modelDoc.version );
  684. model.applyOperation( wrapInDelta( insert ) );
  685. }
  686. modelDoc.log( 0 );
  687. expectLog( 'Tree log unavailable for given version: 0' );
  688. } );
  689. } );
  690. describe( 'should provide methods for delta replayer', () => {
  691. it( 'getAppliedDeltas()', () => {
  692. const model = new Model();
  693. const modelDoc = model.document;
  694. expect( model.getAppliedDeltas() ).to.equal( '' );
  695. const otherRoot = modelDoc.createRoot( '$root', 'otherRoot' );
  696. const firstEle = new ModelElement( 'paragraph' );
  697. const removedEle = new ModelElement( 'paragraph', null, [ new ModelText( 'foo' ) ] );
  698. otherRoot.appendChildren( [ firstEle, removedEle ] );
  699. const delta = new MergeDelta();
  700. const graveyard = modelDoc.graveyard;
  701. const move = new MoveOperation( ModelPosition.createAt( removedEle, 0 ), 3, ModelPosition.createAt( firstEle, 0 ), 0 );
  702. const remove = new RemoveOperation( ModelPosition.createBefore( removedEle ), 1, ModelPosition.createAt( graveyard, 0 ), 1 );
  703. delta.addOperation( move );
  704. delta.addOperation( remove );
  705. model.applyOperation( move );
  706. model.applyOperation( remove );
  707. const stringifiedDeltas = model.getAppliedDeltas();
  708. expect( stringifiedDeltas ).to.equal( JSON.stringify( delta.toJSON() ) );
  709. } );
  710. it( 'createReplayer()', () => {
  711. const model = new Model();
  712. const modelDoc = model.document;
  713. const otherRoot = modelDoc.createRoot( '$root', 'otherRoot' );
  714. const firstEle = new ModelElement( 'paragraph' );
  715. const removedEle = new ModelElement( 'paragraph', null, [ new ModelText( 'foo' ) ] );
  716. otherRoot.appendChildren( [ firstEle, removedEle ] );
  717. const delta = new MergeDelta();
  718. const graveyard = modelDoc.graveyard;
  719. const move = new MoveOperation( ModelPosition.createAt( removedEle, 0 ), 3, ModelPosition.createAt( firstEle, 0 ), 0 );
  720. const remove = new RemoveOperation( ModelPosition.createBefore( removedEle ), 1, ModelPosition.createAt( graveyard, 0 ), 1 );
  721. delta.addOperation( move );
  722. delta.addOperation( remove );
  723. const stringifiedDeltas = JSON.stringify( delta.toJSON() );
  724. const deltaReplayer = model.createReplayer( stringifiedDeltas );
  725. expect( deltaReplayer.getDeltasToReplay() ).to.deep.equal( [ JSON.parse( stringifiedDeltas ) ] );
  726. } );
  727. } );
  728. describe( 'should provide debug tools for delta transformation', () => {
  729. let model, document, root, otherRoot;
  730. beforeEach( () => {
  731. model = new Model();
  732. document = model.document;
  733. root = document.createRoot();
  734. otherRoot = document.createRoot( 'other', 'other' );
  735. } );
  736. it( 'Delta#_saveHistory()', () => {
  737. const insertDeltaA = new InsertDelta();
  738. const insertOpA = new InsertOperation( ModelPosition.createAt( root, 0 ), new ModelText( 'a' ), 0 );
  739. insertDeltaA.addOperation( insertOpA );
  740. const insertDeltaB = new InsertDelta();
  741. const insertOpB = new InsertOperation( ModelPosition.createAt( root, 0 ), new ModelText( 'a' ), 0 );
  742. insertDeltaB.addOperation( insertOpB );
  743. const insertDeltaFinalA = new InsertDelta();
  744. const insertOpFinalA = new InsertOperation( ModelPosition.createAt( root, 0 ), new ModelText( 'a' ), 1 );
  745. insertDeltaFinalA.addOperation( insertOpFinalA );
  746. const insertDeltaFinalAJsonWithoutHistory = JSON.stringify( insertDeltaFinalA );
  747. insertDeltaFinalA._saveHistory( {
  748. before: insertDeltaA,
  749. transformedBy: insertDeltaB,
  750. wasImportant: true,
  751. resultIndex: 0,
  752. resultsTotal: 1
  753. } );
  754. const insertDeltaC = new InsertDelta();
  755. const insertOpC = new InsertOperation( ModelPosition.createAt( root, 0 ), new ModelText( 'a' ), 1 );
  756. insertDeltaC.addOperation( insertOpC );
  757. const insertDeltaFinalB = new InsertDelta();
  758. const insertOpFinalB = new InsertOperation( ModelPosition.createAt( root, 0 ), new ModelText( 'a' ), 2 );
  759. insertDeltaFinalB.addOperation( insertOpFinalB );
  760. insertDeltaFinalB._saveHistory( {
  761. before: insertDeltaFinalA,
  762. transformedBy: insertDeltaC,
  763. wasImportant: false,
  764. resultIndex: 1,
  765. resultsTotal: 3
  766. } );
  767. expect( insertDeltaA.history ).to.be.undefined;
  768. expect( insertDeltaB.history ).to.be.undefined;
  769. expect( insertDeltaFinalA.history ).not.to.be.undefined;
  770. expect( insertDeltaFinalA.history.length ).to.equal( 1 );
  771. expect( insertDeltaFinalA.history[ 0 ] ).to.deep.equal( {
  772. before: JSON.stringify( insertDeltaA ),
  773. transformedBy: JSON.stringify( insertDeltaB ),
  774. wasImportant: true,
  775. resultIndex: 0,
  776. resultsTotal: 1
  777. } );
  778. expect( insertDeltaFinalB.history ).not.to.be.undefined;
  779. expect( insertDeltaFinalB.history.length ).to.equal( 2 );
  780. expect( insertDeltaFinalB.history[ 0 ] ).to.deep.equal( {
  781. before: JSON.stringify( insertDeltaA ),
  782. transformedBy: JSON.stringify( insertDeltaB ),
  783. wasImportant: true,
  784. resultIndex: 0,
  785. resultsTotal: 1
  786. } );
  787. expect( insertDeltaFinalB.history[ 1 ] ).to.deep.equal( {
  788. before: insertDeltaFinalAJsonWithoutHistory,
  789. transformedBy: JSON.stringify( insertDeltaC ),
  790. wasImportant: false,
  791. resultIndex: 1,
  792. resultsTotal: 3
  793. } );
  794. } );
  795. it( 'save delta history on deltaTransform.transform', () => {
  796. const deltaA = new MoveDelta();
  797. const opA = new MoveOperation( ModelPosition.createAt( root, 4 ), 4, ModelPosition.createAt( otherRoot, 4 ), 0 );
  798. deltaA.addOperation( opA );
  799. const deltaB = new InsertDelta();
  800. const opB = new InsertOperation( ModelPosition.createAt( root, 0 ), new ModelText( 'a' ), 0 );
  801. deltaB.addOperation( opB );
  802. const deltaC = new MoveDelta();
  803. const opC = new MoveOperation( ModelPosition.createAt( root, 4 ), 2, ModelPosition.createAt( root, 0 ), 1 );
  804. deltaC.addOperation( opC );
  805. let result = deltaTransform.transform( deltaA, deltaB, { document, wasAffected: new Map() } );
  806. expect( result[ 0 ].history ).not.to.be.undefined;
  807. expect( result[ 0 ].history.length ).to.equal( 1 );
  808. expect( result[ 0 ].history[ 0 ] ).to.deep.equal( {
  809. before: JSON.stringify( deltaA ),
  810. transformedBy: JSON.stringify( deltaB ),
  811. wasImportant: false,
  812. resultIndex: 0,
  813. resultsTotal: 1
  814. } );
  815. const firstResultWithoutHistory = result[ 0 ].clone();
  816. delete firstResultWithoutHistory.history;
  817. result = deltaTransform.transform( result[ 0 ], deltaC, {
  818. isStrong: true,
  819. document,
  820. wasAffected: new Map()
  821. } );
  822. expect( result[ 0 ].history ).not.to.be.undefined;
  823. expect( result[ 0 ].history.length ).to.equal( 2 );
  824. expect( result[ 0 ].history[ 0 ] ).to.deep.equal( {
  825. before: JSON.stringify( deltaA ),
  826. transformedBy: JSON.stringify( deltaB ),
  827. wasImportant: false,
  828. resultIndex: 0,
  829. resultsTotal: 1
  830. } );
  831. expect( result[ 0 ].history[ 1 ] ).to.deep.equal( {
  832. before: JSON.stringify( firstResultWithoutHistory ),
  833. transformedBy: JSON.stringify( deltaC ),
  834. wasImportant: true,
  835. resultIndex: 0,
  836. resultsTotal: 2
  837. } );
  838. } );
  839. it( 'recreate delta using Delta#history', () => {
  840. const deltaA = new MoveDelta();
  841. const opA = new MoveOperation( ModelPosition.createAt( root, 4 ), 4, ModelPosition.createAt( otherRoot, 4 ), 0 );
  842. deltaA.addOperation( opA );
  843. const deltaB = new InsertDelta();
  844. const opB = new InsertOperation( ModelPosition.createAt( root, 0 ), new ModelText( 'a' ), 0 );
  845. deltaB.addOperation( opB );
  846. const deltaC = new MoveDelta();
  847. const opC = new MoveOperation( ModelPosition.createAt( root, 4 ), 2, ModelPosition.createAt( root, 0 ), 1 );
  848. deltaC.addOperation( opC );
  849. let original = deltaTransform.transform( deltaA, deltaB, { document, wasAffected: new Map() } );
  850. original = deltaTransform.transform( original[ 0 ], deltaC, {
  851. isStrong: true,
  852. document,
  853. wasAffected: new Map()
  854. } )[ 0 ];
  855. const history = original.history;
  856. let recreated = deltaTransform.transform(
  857. DeltaFactory.fromJSON( JSON.parse( history[ 0 ].before ), document ),
  858. DeltaFactory.fromJSON( JSON.parse( history[ 0 ].transformedBy ), document ),
  859. { isStrong: history[ 0 ].wasImportant, document, wasAffected: new Map() }
  860. );
  861. recreated = recreated[ history[ 0 ].resultIndex ];
  862. recreated = deltaTransform.transform(
  863. recreated,
  864. DeltaFactory.fromJSON( JSON.parse( history[ 1 ].transformedBy ), document ),
  865. { isStrong: history[ 1 ].wasImportant, document, wasAffected: new Map() }
  866. );
  867. recreated = recreated[ history[ 1 ].resultIndex ];
  868. delete original.history;
  869. delete recreated.history;
  870. expect( JSON.stringify( recreated ) ).to.equal( JSON.stringify( original ) );
  871. } );
  872. describe( 'provide additional logging when transformation crashes', () => {
  873. it( 'with more important delta A', () => {
  874. const deltaA = new MoveDelta();
  875. const opA = new MoveOperation( ModelPosition.createAt( root, 4 ), 4, ModelPosition.createAt( otherRoot, 4 ), 0 );
  876. deltaA.addOperation( opA );
  877. const deltaB = new InsertDelta();
  878. const opB = new InsertOperation( ModelPosition.createAt( root, 0 ), new ModelText( 'a' ), 0 );
  879. deltaB.addOperation( opB );
  880. testUtils.sinon.stub( deltaTransform, 'defaultTransform' ).throws( new Error() );
  881. expect( () => deltaTransform.transform( deltaA, deltaB, { isStrong: true } ) ).to.throw( Error );
  882. expect( error.calledWith( deltaA.toString() + ' (important)' ) ).to.be.true;
  883. expect( error.calledWith( deltaB.toString() ) ).to.be.true;
  884. } );
  885. it( 'with more important delta B', () => {
  886. const deltaA = new MoveDelta();
  887. const opA = new MoveOperation( ModelPosition.createAt( root, 4 ), 4, ModelPosition.createAt( otherRoot, 4 ), 0 );
  888. deltaA.addOperation( opA );
  889. const deltaB = new InsertDelta();
  890. const opB = new InsertOperation( ModelPosition.createAt( root, 0 ), new ModelText( 'a' ), 0 );
  891. deltaB.addOperation( opB );
  892. testUtils.sinon.stub( deltaTransform, 'defaultTransform' ).throws( new Error() );
  893. expect( () => deltaTransform.transform( deltaA, deltaB, { isStrong: false } ) ).to.throw( Error );
  894. expect( error.calledWith( deltaA.toString() ) ).to.be.true;
  895. expect( error.calledWith( deltaB.toString() + ' (important)' ) ).to.be.true;
  896. } );
  897. } );
  898. } );
  899. function expectLog( expectedLogMsg ) {
  900. expect( log.calledWithExactly( expectedLogMsg ) ).to.be.true;
  901. log.reset();
  902. }
  903. function wrapInDelta( op ) {
  904. const delta = new Delta();
  905. delta.addOperation( op );
  906. return op;
  907. }
  908. } );