enableenginedebug.js 36 KB

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