8
0

enableenginedebug.js 34 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045
  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( 'MoveDelta', () => {
  294. const delta = new MoveDelta();
  295. const move1 = new MoveOperation( ModelPosition.createAt( modelRoot, 0 ), 1, ModelPosition.createAt( modelRoot, 3 ), 0 );
  296. const move2 = new MoveOperation( ModelPosition.createAt( modelRoot, 1 ), 1, ModelPosition.createAt( modelRoot, 6 ), 0 );
  297. delta.addOperation( move1 );
  298. delta.addOperation( move2 );
  299. expect( delta.toString() ).to.equal( 'MoveDelta( 0 ): main [ 0 ] - [ 1 ] -> main [ 3 ]; main [ 1 ] - [ 2 ] -> main [ 6 ]' );
  300. delta.log();
  301. expect( log.calledWithExactly( delta.toString() ) ).to.be.true;
  302. } );
  303. it( 'RenameDelta', () => {
  304. const delta = new RenameDelta();
  305. const op = new RenameOperation( ModelPosition.createAt( modelRoot, 1 ), 'old', 'new', 0 );
  306. delta.addOperation( op );
  307. expect( delta.toString() ).to.equal( 'RenameDelta( 0 ): main [ 1 ]: "old" -> "new"' );
  308. delta.log();
  309. expect( log.calledWithExactly( delta.toString() ) ).to.be.true;
  310. } );
  311. it( 'RootAttributeDelta', () => {
  312. const delta = new RootAttributeDelta();
  313. const op = new RootAttributeOperation( modelRoot, 'key', 'old', null, 0 );
  314. delta.addOperation( op );
  315. expect( delta.toString() ).to.equal( 'RootAttributeDelta( 0 ): "key": "old" -> null, main' );
  316. delta.log();
  317. expect( log.calledWithExactly( delta.toString() ) ).to.be.true;
  318. } );
  319. it( 'SplitDelta', () => {
  320. const otherRoot = modelDoc.createRoot( 'main', 'otherRoot' );
  321. const splitEle = new ModelElement( 'paragraph', null, [ new ModelText( 'foo' ) ] );
  322. otherRoot.appendChildren( [ splitEle ] );
  323. const delta = new SplitDelta();
  324. const insert = new InsertOperation( ModelPosition.createAt( otherRoot, 1 ), [ new ModelElement( 'paragraph' ) ], 0 );
  325. const move = new MoveOperation( ModelPosition.createAt( splitEle, 1 ), 2, new ModelPosition( otherRoot, [ 1, 0 ] ), 1 );
  326. delta.addOperation( insert );
  327. delta.addOperation( move );
  328. expect( delta.toString() ).to.equal( 'SplitDelta( 0 ): otherRoot [ 0, 1 ]' );
  329. delta.log();
  330. expect( log.calledWithExactly( delta.toString() ) ).to.be.true;
  331. } );
  332. it( 'UnwrapDelta', () => {
  333. const otherRoot = modelDoc.createRoot( 'main', 'otherRoot' );
  334. const unwrapEle = new ModelElement( 'paragraph', null, [ new ModelText( 'foo' ) ] );
  335. otherRoot.appendChildren( [ unwrapEle ] );
  336. const graveyard = modelDoc.graveyard;
  337. const delta = new UnwrapDelta();
  338. const move = new MoveOperation( ModelPosition.createAt( unwrapEle, 0 ), 3, ModelPosition.createAt( otherRoot, 0 ), 0 );
  339. const remove = new RemoveOperation( ModelPosition.createAt( otherRoot, 3 ), 1, ModelPosition.createAt( graveyard, 0 ), 1 );
  340. delta.addOperation( move );
  341. delta.addOperation( remove );
  342. expect( delta.toString() ).to.equal( 'UnwrapDelta( 0 ): otherRoot [ 0 ]' );
  343. delta.log();
  344. expect( log.calledWithExactly( delta.toString() ) ).to.be.true;
  345. } );
  346. it( 'WrapDelta', () => {
  347. const delta = new WrapDelta();
  348. const insert = new InsertOperation( ModelPosition.createAt( modelRoot, 6 ), new ModelElement( 'paragraph' ), 0 );
  349. const move = new MoveOperation( ModelPosition.createAt( modelRoot, 0 ), 6, new ModelPosition( modelRoot, [ 1, 0 ] ), 1 );
  350. delta.addOperation( insert );
  351. delta.addOperation( move );
  352. expect( delta.toString() ).to.equal( 'WrapDelta( 0 ): main [ 0 ] - [ 6 ] -> <paragraph>' );
  353. delta.log();
  354. expect( log.calledWithExactly( delta.toString() ) ).to.be.true;
  355. } );
  356. } );
  357. it( 'for applied operations', () => {
  358. const delta = new InsertDelta();
  359. const op = new InsertOperation( ModelPosition.createAt( modelRoot, 0 ), [ new ModelText( 'foo' ) ], 0 );
  360. delta.addOperation( op );
  361. modelDoc.applyOperation( op );
  362. expect( log.calledWithExactly( 'Applying InsertOperation( 0 ): #foo -> main [ 0 ]' ) ).to.be.true;
  363. } );
  364. } );
  365. describe( 'should provide tree printing tools', () => {
  366. it( 'for model', () => {
  367. const modelDoc = new ModelDocument();
  368. const modelRoot = modelDoc.createRoot();
  369. modelRoot.appendChildren( [
  370. new ModelElement( 'paragraph', { foo: 'bar' }, [
  371. new ModelText( 'This is ' ), new ModelText( 'bold', { bold: true } ), new ModelText( '.' )
  372. ] ),
  373. new ModelElement( 'listItem', { type: 'numbered', indent: 0 }, new ModelText( 'One.' ) ),
  374. ] );
  375. const modelRootTree = modelRoot.printTree();
  376. expect( modelRootTree ).to.equal(
  377. '<main>' +
  378. '\n\t<paragraph foo="bar">' +
  379. '\n\t\tThis is ' +
  380. '\n\t\t<$text bold=true>bold</$text>' +
  381. '\n\t\t.' +
  382. '\n\t</paragraph>' +
  383. '\n\t<listItem type="numbered" indent=0>' +
  384. '\n\t\tOne.' +
  385. '\n\t</listItem>' +
  386. '\n</main>'
  387. );
  388. modelRoot.logTree();
  389. expect( log.calledWithExactly( modelRootTree ) ).to.be.true;
  390. const modelParagraph = modelRoot.getChild( 0 );
  391. const modelParagraphTree = modelParagraph.printTree();
  392. expect( modelParagraphTree ).to.equal(
  393. '<paragraph foo="bar">' +
  394. '\n\tThis is ' +
  395. '\n\t<$text bold=true>bold</$text>' +
  396. '\n\t.' +
  397. '\n</paragraph>'
  398. );
  399. log.reset();
  400. modelParagraph.logTree();
  401. expect( log.calledWithExactly( modelParagraphTree ) ).to.be.true;
  402. const modelDocFrag = new ModelDocumentFragment( [
  403. new ModelText( 'This is ' ), new ModelText( 'bold', { bold: true } ), new ModelText( '.' ),
  404. new ModelElement( 'paragraph', { foo: 'bar' }, [
  405. new ModelText( 'This is ' ), new ModelText( 'bold', { bold: true } ), new ModelText( '.' )
  406. ] )
  407. ] );
  408. const modelDocFragTree = modelDocFrag.printTree();
  409. expect( modelDocFragTree ).to.equal(
  410. 'ModelDocumentFragment: [' +
  411. '\n\tThis is ' +
  412. '\n\t<$text bold=true>bold</$text>' +
  413. '\n\t.' +
  414. '\n\t<paragraph foo="bar">' +
  415. '\n\t\tThis is ' +
  416. '\n\t\t<$text bold=true>bold</$text>' +
  417. '\n\t\t.' +
  418. '\n\t</paragraph>' +
  419. '\n]'
  420. );
  421. log.reset();
  422. modelDocFrag.logTree();
  423. expect( log.calledWithExactly( modelDocFragTree ) ).to.be.true;
  424. } );
  425. it( 'for view', () => {
  426. const viewDoc = new ViewDocument();
  427. const viewRoot = viewDoc.createRoot( 'div' );
  428. viewRoot.appendChildren( [
  429. new ViewContainerElement( 'p', { foo: 'bar' }, [
  430. new ViewText( 'This is ' ), new ViewAttributeElement( 'b', null, new ViewText( 'bold' ) ), new ViewText( '.' )
  431. ] ),
  432. new ViewContainerElement( 'ol', null, [
  433. new ViewContainerElement( 'li', null, new ViewText( 'One.' ) )
  434. ] )
  435. ] );
  436. const viewRootTree = viewRoot.printTree();
  437. expect( viewRootTree ).to.equal(
  438. '<div>' +
  439. '\n\t<p foo="bar">' +
  440. '\n\t\tThis is ' +
  441. '\n\t\t<b>' +
  442. '\n\t\t\tbold' +
  443. '\n\t\t</b>' +
  444. '\n\t\t.' +
  445. '\n\t</p>' +
  446. '\n\t<ol>' +
  447. '\n\t\t<li>' +
  448. '\n\t\t\tOne.' +
  449. '\n\t\t</li>' +
  450. '\n\t</ol>' +
  451. '\n</div>'
  452. );
  453. viewRoot.logTree();
  454. expect( log.calledWithExactly( viewRootTree ) ).to.be.true;
  455. const viewParagraph = viewRoot.getChild( 0 );
  456. const viewParagraphTree = viewParagraph.printTree();
  457. expect( viewParagraphTree ).to.equal(
  458. '<p foo="bar">' +
  459. '\n\tThis is ' +
  460. '\n\t<b>' +
  461. '\n\t\tbold' +
  462. '\n\t</b>' +
  463. '\n\t.' +
  464. '\n</p>'
  465. );
  466. log.reset();
  467. viewParagraph.logTree();
  468. expect( log.calledWithExactly( viewParagraphTree ) ).to.be.true;
  469. const viewDocFrag = new ViewDocumentFragment( [
  470. new ViewText( 'Text.' ),
  471. new ViewContainerElement( 'p', { foo: 'bar' }, [
  472. new ViewText( 'This is ' ), new ViewAttributeElement( 'b', null, new ViewText( 'bold' ) ), new ViewText( '.' )
  473. ] )
  474. ] );
  475. const viewDocFragTree = viewDocFrag.printTree();
  476. expect( viewDocFragTree ).to.equal(
  477. 'ViewDocumentFragment: [' +
  478. '\n\tText.' +
  479. '\n\t<p foo="bar">' +
  480. '\n\t\tThis is ' +
  481. '\n\t\t<b>' +
  482. '\n\t\t\tbold' +
  483. '\n\t\t</b>' +
  484. '\n\t\t.' +
  485. '\n\t</p>' +
  486. '\n]'
  487. );
  488. log.reset();
  489. viewDocFrag.logTree();
  490. expect( log.calledWithExactly( viewDocFragTree ) ).to.be.true;
  491. } );
  492. } );
  493. describe( 'should store model and view trees state', () => {
  494. let editor;
  495. beforeEach( () => {
  496. const div = document.createElement( 'div' );
  497. return TestEditor.create( div, {
  498. plugins: [ DebugPlugin ]
  499. } ).then( _editor => {
  500. editor = _editor;
  501. } );
  502. } );
  503. it( 'should store model and view state after each applied operation', () => {
  504. const model = editor.document;
  505. const modelRoot = model.getRoot();
  506. const view = editor.editing.view;
  507. const insert = new InsertOperation( ModelPosition.createAt( modelRoot, 0 ), new ModelText( 'foobar' ), 0 );
  508. model.applyOperation( wrapInDelta( insert ) );
  509. const graveyard = model.graveyard;
  510. const remove = new RemoveOperation( ModelPosition.createAt( modelRoot, 1 ), 2, ModelPosition.createAt( graveyard, 0 ), 1 );
  511. model.applyOperation( wrapInDelta( remove ) );
  512. log.reset();
  513. model.log( 0 );
  514. expectLog(
  515. '<$graveyard></$graveyard>' +
  516. '\n<main></main>'
  517. );
  518. model.log( 1 );
  519. expectLog(
  520. '<$graveyard></$graveyard>' +
  521. '\n<main>' +
  522. '\n\tfoobar' +
  523. '\n</main>'
  524. );
  525. model.log( 2 );
  526. expectLog(
  527. '<$graveyard>' +
  528. '\n\too' +
  529. '\n</$graveyard>' +
  530. '\n<main>' +
  531. '\n\tfbar' +
  532. '\n</main>'
  533. );
  534. model.log();
  535. expectLog(
  536. '<$graveyard>' +
  537. '\n\too' +
  538. '\n</$graveyard>' +
  539. '\n<main>' +
  540. '\n\tfbar' +
  541. '\n</main>'
  542. );
  543. view.log( 0 );
  544. expectLog(
  545. '<div></div>'
  546. );
  547. view.log( 1 );
  548. expectLog(
  549. '<div>' +
  550. '\n\tfoobar' +
  551. '\n</div>'
  552. );
  553. view.log( 2 );
  554. expectLog(
  555. '<div>' +
  556. '\n\tfbar' +
  557. '\n</div>'
  558. );
  559. sinon.spy( model, 'log' );
  560. sinon.spy( view, 'log' );
  561. editor.logModel( 1 );
  562. expect( model.log.calledWithExactly( 1 ) ).to.be.true;
  563. editor.logView( 2 );
  564. expect( view.log.calledWithExactly( 2 ) ).to.be.true;
  565. model.log.reset();
  566. view.log.reset();
  567. editor.logModel();
  568. expect( model.log.calledWithExactly( 2 ) ).to.be.true;
  569. model.log.reset();
  570. view.log.reset();
  571. editor.logDocuments();
  572. expect( model.log.calledWithExactly( 2 ) ).to.be.true;
  573. expect( view.log.calledWithExactly( 2 ) ).to.be.true;
  574. model.log.reset();
  575. view.log.reset();
  576. editor.logDocuments( 1 );
  577. expect( model.log.calledWithExactly( 1 ) ).to.be.true;
  578. expect( view.log.calledWithExactly( 1 ) ).to.be.true;
  579. } );
  580. it( 'should remove old states', () => {
  581. const model = editor.document;
  582. const modelRoot = model.getRoot();
  583. for ( let i = 0; i < 25; i++ ) {
  584. const insert = new InsertOperation( ModelPosition.createAt( modelRoot, 0 ), new ModelText( 'foobar' ), model.version );
  585. model.applyOperation( wrapInDelta( insert ) );
  586. }
  587. model.log( 0 );
  588. expectLog( 'Tree log unavailable for given version: 0' );
  589. } );
  590. } );
  591. describe( 'should provide methods for delta replayer', () => {
  592. it( 'getAppliedDeltas()', () => {
  593. const modelDoc = new ModelDocument();
  594. expect( modelDoc.getAppliedDeltas() ).to.equal( '' );
  595. const otherRoot = modelDoc.createRoot( '$root', 'otherRoot' );
  596. const firstEle = new ModelElement( 'paragraph' );
  597. const removedEle = new ModelElement( 'paragraph', null, [ new ModelText( 'foo' ) ] );
  598. otherRoot.appendChildren( [ firstEle, removedEle ] );
  599. const delta = new MergeDelta();
  600. const graveyard = modelDoc.graveyard;
  601. const move = new MoveOperation( ModelPosition.createAt( removedEle, 0 ), 3, ModelPosition.createAt( firstEle, 0 ), 0 );
  602. const remove = new RemoveOperation( ModelPosition.createBefore( removedEle ), 1, ModelPosition.createAt( graveyard, 0 ), 1 );
  603. delta.addOperation( move );
  604. delta.addOperation( remove );
  605. modelDoc.applyOperation( move );
  606. modelDoc.applyOperation( remove );
  607. const stringifiedDeltas = modelDoc.getAppliedDeltas();
  608. expect( stringifiedDeltas ).to.equal( JSON.stringify( delta.toJSON() ) );
  609. } );
  610. it( 'createReplayer()', () => {
  611. const modelDoc = new ModelDocument();
  612. const otherRoot = modelDoc.createRoot( '$root', 'otherRoot' );
  613. const firstEle = new ModelElement( 'paragraph' );
  614. const removedEle = new ModelElement( 'paragraph', null, [ new ModelText( 'foo' ) ] );
  615. otherRoot.appendChildren( [ firstEle, removedEle ] );
  616. const delta = new MergeDelta();
  617. const graveyard = modelDoc.graveyard;
  618. const move = new MoveOperation( ModelPosition.createAt( removedEle, 0 ), 3, ModelPosition.createAt( firstEle, 0 ), 0 );
  619. const remove = new RemoveOperation( ModelPosition.createBefore( removedEle ), 1, ModelPosition.createAt( graveyard, 0 ), 1 );
  620. delta.addOperation( move );
  621. delta.addOperation( remove );
  622. const stringifiedDeltas = JSON.stringify( delta.toJSON() );
  623. const deltaReplayer = modelDoc.createReplayer( stringifiedDeltas );
  624. expect( deltaReplayer.getDeltasToReplay() ).to.deep.equal( [ JSON.parse( stringifiedDeltas ) ] );
  625. } );
  626. } );
  627. describe( 'should provide debug tools for delta transformation', () => {
  628. let document, root, otherRoot;
  629. beforeEach( () => {
  630. document = new ModelDocument();
  631. root = document.createRoot();
  632. otherRoot = document.createRoot( 'other', 'other' );
  633. } );
  634. it( 'Delta#_saveHistory()', () => {
  635. const insertDeltaA = new InsertDelta();
  636. const insertOpA = new InsertOperation( ModelPosition.createAt( root, 0 ), new ModelText( 'a' ), 0 );
  637. insertDeltaA.addOperation( insertOpA );
  638. const insertDeltaB = new InsertDelta();
  639. const insertOpB = new InsertOperation( ModelPosition.createAt( root, 0 ), new ModelText( 'a' ), 0 );
  640. insertDeltaB.addOperation( insertOpB );
  641. const insertDeltaFinalA = new InsertDelta();
  642. const insertOpFinalA = new InsertOperation( ModelPosition.createAt( root, 0 ), new ModelText( 'a' ), 1 );
  643. insertDeltaFinalA.addOperation( insertOpFinalA );
  644. const insertDeltaFinalAJsonWithoutHistory = JSON.stringify( insertDeltaFinalA );
  645. insertDeltaFinalA._saveHistory( {
  646. before: insertDeltaA,
  647. transformedBy: insertDeltaB,
  648. wasImportant: true,
  649. resultIndex: 0,
  650. resultsTotal: 1
  651. } );
  652. const insertDeltaC = new InsertDelta();
  653. const insertOpC = new InsertOperation( ModelPosition.createAt( root, 0 ), new ModelText( 'a' ), 1 );
  654. insertDeltaC.addOperation( insertOpC );
  655. const insertDeltaFinalB = new InsertDelta();
  656. const insertOpFinalB = new InsertOperation( ModelPosition.createAt( root, 0 ), new ModelText( 'a' ), 2 );
  657. insertDeltaFinalB.addOperation( insertOpFinalB );
  658. insertDeltaFinalB._saveHistory( {
  659. before: insertDeltaFinalA,
  660. transformedBy: insertDeltaC,
  661. wasImportant: false,
  662. resultIndex: 1,
  663. resultsTotal: 3
  664. } );
  665. expect( insertDeltaA.history ).to.be.undefined;
  666. expect( insertDeltaB.history ).to.be.undefined;
  667. expect( insertDeltaFinalA.history ).not.to.be.undefined;
  668. expect( insertDeltaFinalA.history.length ).to.equal( 1 );
  669. expect( insertDeltaFinalA.history[ 0 ] ).to.deep.equal( {
  670. before: JSON.stringify( insertDeltaA ),
  671. transformedBy: JSON.stringify( insertDeltaB ),
  672. wasImportant: true,
  673. resultIndex: 0,
  674. resultsTotal: 1
  675. } );
  676. expect( insertDeltaFinalB.history ).not.to.be.undefined;
  677. expect( insertDeltaFinalB.history.length ).to.equal( 2 );
  678. expect( insertDeltaFinalB.history[ 0 ] ).to.deep.equal( {
  679. before: JSON.stringify( insertDeltaA ),
  680. transformedBy: JSON.stringify( insertDeltaB ),
  681. wasImportant: true,
  682. resultIndex: 0,
  683. resultsTotal: 1
  684. } );
  685. expect( insertDeltaFinalB.history[ 1 ] ).to.deep.equal( {
  686. before: insertDeltaFinalAJsonWithoutHistory,
  687. transformedBy: JSON.stringify( insertDeltaC ),
  688. wasImportant: false,
  689. resultIndex: 1,
  690. resultsTotal: 3
  691. } );
  692. } );
  693. it( 'save delta history on deltaTransform.transform', () => {
  694. const deltaA = new MoveDelta();
  695. const opA = new MoveOperation( ModelPosition.createAt( root, 4 ), 4, ModelPosition.createAt( otherRoot, 4 ), 0 );
  696. deltaA.addOperation( opA );
  697. const deltaB = new InsertDelta();
  698. const opB = new InsertOperation( ModelPosition.createAt( root, 0 ), new ModelText( 'a' ), 0 );
  699. deltaB.addOperation( opB );
  700. const deltaC = new MoveDelta();
  701. const opC = new MoveOperation( ModelPosition.createAt( root, 4 ), 2, ModelPosition.createAt( root, 0 ), 1 );
  702. deltaC.addOperation( opC );
  703. let result = deltaTransform.transform( deltaA, deltaB, { document, wasAffected: new Map() } );
  704. expect( result[ 0 ].history ).not.to.be.undefined;
  705. expect( result[ 0 ].history.length ).to.equal( 1 );
  706. expect( result[ 0 ].history[ 0 ] ).to.deep.equal( {
  707. before: JSON.stringify( deltaA ),
  708. transformedBy: JSON.stringify( deltaB ),
  709. wasImportant: false,
  710. resultIndex: 0,
  711. resultsTotal: 1
  712. } );
  713. const firstResultWithoutHistory = result[ 0 ].clone();
  714. delete firstResultWithoutHistory.history;
  715. result = deltaTransform.transform( result[ 0 ], deltaC, { isStrong: true, document, wasAffected: new Map() } );
  716. expect( result[ 0 ].history ).not.to.be.undefined;
  717. expect( result[ 0 ].history.length ).to.equal( 2 );
  718. expect( result[ 0 ].history[ 0 ] ).to.deep.equal( {
  719. before: JSON.stringify( deltaA ),
  720. transformedBy: JSON.stringify( deltaB ),
  721. wasImportant: false,
  722. resultIndex: 0,
  723. resultsTotal: 1
  724. } );
  725. expect( result[ 0 ].history[ 1 ] ).to.deep.equal( {
  726. before: JSON.stringify( firstResultWithoutHistory ),
  727. transformedBy: JSON.stringify( deltaC ),
  728. wasImportant: true,
  729. resultIndex: 0,
  730. resultsTotal: 2
  731. } );
  732. } );
  733. it( 'recreate delta using Delta#history', () => {
  734. const deltaA = new MoveDelta();
  735. const opA = new MoveOperation( ModelPosition.createAt( root, 4 ), 4, ModelPosition.createAt( otherRoot, 4 ), 0 );
  736. deltaA.addOperation( opA );
  737. const deltaB = new InsertDelta();
  738. const opB = new InsertOperation( ModelPosition.createAt( root, 0 ), new ModelText( 'a' ), 0 );
  739. deltaB.addOperation( opB );
  740. const deltaC = new MoveDelta();
  741. const opC = new MoveOperation( ModelPosition.createAt( root, 4 ), 2, ModelPosition.createAt( root, 0 ), 1 );
  742. deltaC.addOperation( opC );
  743. let original = deltaTransform.transform( deltaA, deltaB, { document, wasAffected: new Map() } );
  744. original = deltaTransform.transform( original[ 0 ], deltaC, { isStrong: true, document, wasAffected: new Map() } )[ 0 ];
  745. const history = original.history;
  746. let recreated = deltaTransform.transform(
  747. DeltaFactory.fromJSON( JSON.parse( history[ 0 ].before ), document ),
  748. DeltaFactory.fromJSON( JSON.parse( history[ 0 ].transformedBy ), document ),
  749. { isStrong: history[ 0 ].wasImportant, document, wasAffected: new Map() }
  750. );
  751. recreated = recreated[ history[ 0 ].resultIndex ];
  752. recreated = deltaTransform.transform(
  753. recreated,
  754. DeltaFactory.fromJSON( JSON.parse( history[ 1 ].transformedBy ), document ),
  755. { isStrong: history[ 1 ].wasImportant, document, wasAffected: new Map() }
  756. );
  757. recreated = recreated[ history[ 1 ].resultIndex ];
  758. delete original.history;
  759. delete recreated.history;
  760. expect( JSON.stringify( recreated ) ).to.equal( JSON.stringify( original ) );
  761. } );
  762. describe( 'provide additional logging when transformation crashes', () => {
  763. it( 'with more important delta A', () => {
  764. const deltaA = new MoveDelta();
  765. const opA = new MoveOperation( ModelPosition.createAt( root, 4 ), 4, ModelPosition.createAt( otherRoot, 4 ), 0 );
  766. deltaA.addOperation( opA );
  767. const deltaB = new InsertDelta();
  768. const opB = new InsertOperation( ModelPosition.createAt( root, 0 ), new ModelText( 'a' ), 0 );
  769. deltaB.addOperation( opB );
  770. deltaTransform.defaultTransform = () => {
  771. throw new Error();
  772. };
  773. expect( () => deltaTransform.transform( deltaA, deltaB, { isStrong: true } ) ).to.throw( Error );
  774. expect( error.calledWith( deltaA.toString() + ' (important)' ) ).to.be.true;
  775. expect( error.calledWith( deltaB.toString() ) ).to.be.true;
  776. } );
  777. it( 'with more important delta B', () => {
  778. const deltaA = new MoveDelta();
  779. const opA = new MoveOperation( ModelPosition.createAt( root, 4 ), 4, ModelPosition.createAt( otherRoot, 4 ), 0 );
  780. deltaA.addOperation( opA );
  781. const deltaB = new InsertDelta();
  782. const opB = new InsertOperation( ModelPosition.createAt( root, 0 ), new ModelText( 'a' ), 0 );
  783. deltaB.addOperation( opB );
  784. deltaTransform.defaultTransform = () => {
  785. throw new Error();
  786. };
  787. expect( () => deltaTransform.transform( deltaA, deltaB, { isStrong: false } ) ).to.throw( Error );
  788. expect( error.calledWith( deltaA.toString() ) ).to.be.true;
  789. expect( error.calledWith( deltaB.toString() + ' (important)' ) ).to.be.true;
  790. } );
  791. } );
  792. } );
  793. function expectLog( expectedLogMsg ) {
  794. expect( log.calledWithExactly( expectedLogMsg ) ).to.be.true;
  795. log.reset();
  796. }
  797. function wrapInDelta( op ) {
  798. const delta = new Delta();
  799. delta.addOperation( op );
  800. return op;
  801. }
  802. } );