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