8
0

enableenginedebug.js 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709
  1. /**
  2. * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
  3. * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
  4. */
  5. import { default as enableEngineDebug, disableEngineDebug } from '../../src/dev-utils/enableenginedebug';
  6. import Editor from '@ckeditor/ckeditor5-core/src/editor/editor';
  7. import VirtualTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/virtualtesteditor';
  8. import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
  9. import ModelPosition from '../../src/model/position';
  10. import ModelRange from '../../src/model/range';
  11. import ModelText from '../../src/model/text';
  12. import ModelTextProxy from '../../src/model/textproxy';
  13. import ModelElement from '../../src/model/element';
  14. import AttributeOperation from '../../src/model/operation/attributeoperation';
  15. import DetachOperation from '../../src/model/operation/detachoperation';
  16. import InsertOperation from '../../src/model/operation/insertoperation';
  17. import MarkerOperation from '../../src/model/operation/markeroperation';
  18. import MoveOperation from '../../src/model/operation/moveoperation';
  19. import NoOperation from '../../src/model/operation/nooperation';
  20. import RenameOperation from '../../src/model/operation/renameoperation';
  21. import RootAttributeOperation from '../../src/model/operation/rootattributeoperation';
  22. import MergeOperation from '../../src/model/operation/mergeoperation';
  23. import SplitOperation from '../../src/model/operation/splitoperation';
  24. import Model from '../../src/model/model';
  25. import ModelDocumentFragment from '../../src/model/documentfragment';
  26. import ViewDocument from '../../src/view/document';
  27. import ViewAttributeElement from '../../src/view/attributeelement';
  28. import ViewContainerElement from '../../src/view/containerelement';
  29. import ViewText from '../../src/view/text';
  30. import ViewTextProxy from '../../src/view/textproxy';
  31. import ViewDocumentFragment from '../../src/view/documentfragment';
  32. import ViewElement from '../../src/view/element';
  33. import createViewRoot from '../view/_utils/createroot';
  34. import testUtils from '@ckeditor/ckeditor5-core/tests/_utils/utils';
  35. describe( 'enableEngineDebug', () => {
  36. testUtils.createSinonSandbox();
  37. afterEach( () => {
  38. disableEngineDebug();
  39. } );
  40. it( 'should return plugin class', () => {
  41. const result = enableEngineDebug();
  42. expect( result.prototype ).to.be.instanceof( Plugin );
  43. } );
  44. it( 'should not throw when called multiple times', () => {
  45. enableEngineDebug();
  46. enableEngineDebug();
  47. const result = enableEngineDebug();
  48. expect( result.prototype ).to.be.instanceof( Plugin );
  49. } );
  50. } );
  51. describe( 'disableEngineDebug', () => {
  52. testUtils.createSinonSandbox();
  53. it( 'restores modified stubs', () => {
  54. expect( ModelPosition.prototype.log ).to.equal( undefined, 'Initial value (model/position)' );
  55. expect( ModelElement.prototype.printTree ).to.equal( undefined, 'Initial value (model/element)' );
  56. expect( ViewElement.prototype.printTree ).to.equal( undefined, 'Initial value (view/element)' );
  57. expect( Model.prototype.createReplayer ).to.equal( undefined, 'Initial value (model/document)' );
  58. expect( Editor.prototype.logDocuments ).to.equal( undefined, 'Initial value (core~editor/editor)' );
  59. enableEngineDebug();
  60. expect( ModelPosition.prototype.log ).to.be.a( 'function', 'After enabling engine debug (model/position)' );
  61. expect( ModelElement.prototype.printTree ).to.be.a( 'function', 'After enabling engine debug (model/element)' );
  62. expect( ViewElement.prototype.printTree ).to.be.a( 'function', 'After enabling engine debug (view/element)' );
  63. expect( Model.prototype.createReplayer ).to.be.a( 'function', 'After enabling engine debug (model/document)' );
  64. expect( Editor.prototype.logDocuments ).to.be.a( 'function', 'After enabling engine debug (core~editor/editor)' );
  65. disableEngineDebug();
  66. expect( ModelPosition.prototype.log ).to.equal( undefined, 'After disabling engine debug (model/position)' );
  67. expect( ModelElement.prototype.printTree ).to.equal( undefined, 'After disabling engine debug (model/element)' );
  68. expect( ViewElement.prototype.printTree ).to.equal( undefined, 'After disabling engine debug (view/element)' );
  69. expect( Model.prototype.createReplayer ).to.equal( undefined, 'After disabling engine debug (model/document)' );
  70. expect( Editor.prototype.logDocuments ).to.equal( undefined, 'After disabling engine debug (core~editor/editor)' );
  71. } );
  72. } );
  73. describe( 'debug tools', () => {
  74. let DebugPlugin, log, error;
  75. testUtils.createSinonSandbox();
  76. before( () => {
  77. log = sinon.spy();
  78. error = sinon.spy();
  79. DebugPlugin = enableEngineDebug( { log, error } );
  80. } );
  81. after( () => {
  82. disableEngineDebug();
  83. } );
  84. afterEach( () => {
  85. log.resetHistory();
  86. } );
  87. describe( 'should provide logging tools', () => {
  88. let model, modelDoc, modelRoot, modelElement, modelDocFrag;
  89. beforeEach( () => {
  90. model = new Model();
  91. modelDoc = model.document;
  92. modelRoot = modelDoc.createRoot();
  93. modelElement = new ModelElement( 'paragraph', null, new ModelText( 'foo' ) );
  94. modelDocFrag = new ModelDocumentFragment( [ new ModelText( 'bar' ) ] );
  95. } );
  96. it( 'for ModelText', () => {
  97. const foo = new ModelText( 'foo', { foo: 'bar' } );
  98. expect( foo.toString() ).to.equal( '#foo' );
  99. foo.log();
  100. expect( log.calledWithExactly( 'ModelText: #foo' ) ).to.be.true;
  101. foo.logExtended();
  102. expect( log.calledWithExactly( 'ModelText: #foo, attrs: {"foo":"bar"}' ) ).to.be.true;
  103. } );
  104. it( 'for ModelTextProxy', () => {
  105. const foo = new ModelText( 'foo', { foo: 'bar' } );
  106. const proxy = new ModelTextProxy( foo, 1, 1 );
  107. expect( proxy.toString() ).to.equal( '#o' );
  108. proxy.log();
  109. expect( log.calledWithExactly( 'ModelTextProxy: #o' ) ).to.be.true;
  110. proxy.logExtended();
  111. expect( log.calledWithExactly( 'ModelTextProxy: #o, attrs: {"foo":"bar"}' ) ).to.be.true;
  112. } );
  113. it( 'for ModelElement', () => {
  114. const paragraph = new ModelElement( 'paragraph', { foo: 'bar' }, new ModelText( 'foo' ) );
  115. expect( paragraph.toString() ).to.equal( '<paragraph>' );
  116. paragraph.log();
  117. expectLog( 'ModelElement: <paragraph>' );
  118. paragraph.logExtended();
  119. expectLog( 'ModelElement: <paragraph>, 1 children, attrs: {"foo":"bar"}' );
  120. sinon.spy( paragraph, 'logExtended' );
  121. paragraph.logAll();
  122. expect( paragraph.logExtended.called ).to.be.true;
  123. expectLog( 'ModelText: #foo' );
  124. } );
  125. it( 'for ModelRootElement', () => {
  126. modelRoot.log();
  127. expectLog( 'ModelRootElement: main' );
  128. } );
  129. it( 'for ModelDocumentFragment', () => {
  130. modelDocFrag.log();
  131. expectLog( 'ModelDocumentFragment: documentFragment' );
  132. } );
  133. it( 'for ModelPosition', () => {
  134. const posInRoot = new ModelPosition( modelRoot, [ 0, 1, 0 ] );
  135. const posInElement = new ModelPosition( modelElement, [ 0 ] );
  136. const posInDocFrag = new ModelPosition( modelDocFrag, [ 2, 3 ] );
  137. expect( posInRoot.toString() ).to.equal( 'main [ 0, 1, 0 ]' );
  138. expect( posInElement.toString() ).to.equal( '<paragraph> [ 0 ]' );
  139. expect( posInDocFrag.toString() ).to.equal( 'documentFragment [ 2, 3 ]' );
  140. posInRoot.log();
  141. expectLog( 'ModelPosition: main [ 0, 1, 0 ]' );
  142. } );
  143. it( 'for ModelRange', () => {
  144. const rangeInRoot = ModelRange._createIn( modelRoot );
  145. const rangeInElement = ModelRange._createIn( modelElement );
  146. const rangeInDocFrag = ModelRange._createIn( modelDocFrag );
  147. expect( rangeInRoot.toString() ).to.equal( 'main [ 0 ] - [ 0 ]' );
  148. expect( rangeInElement.toString() ).to.equal( '<paragraph> [ 0 ] - [ 3 ]' );
  149. expect( rangeInDocFrag.toString() ).to.equal( 'documentFragment [ 0 ] - [ 3 ]' );
  150. rangeInRoot.log();
  151. expectLog( 'ModelRange: main [ 0 ] - [ 0 ]' );
  152. } );
  153. it( 'for ViewText', () => {
  154. const foo = new ViewText( 'foo' );
  155. expect( foo.toString() ).to.equal( '#foo' );
  156. foo.log();
  157. expect( log.calledWithExactly( 'ViewText: #foo' ) ).to.be.true;
  158. foo.logExtended();
  159. expect( log.calledWithExactly( 'ViewText: #foo' ) ).to.be.true;
  160. } );
  161. it( 'for ViewTextProxy', () => {
  162. const foo = new ViewText( 'foo', { foo: 'bar' } );
  163. const proxy = new ViewTextProxy( foo, 1, 1 );
  164. expect( proxy.toString() ).to.equal( '#o' );
  165. proxy.log();
  166. expect( log.calledWithExactly( 'ViewTextProxy: #o' ) ).to.be.true;
  167. proxy.logExtended();
  168. expect( log.calledWithExactly( 'ViewTextProxy: #o' ) ).to.be.true;
  169. } );
  170. describe( 'for operations', () => {
  171. beforeEach( () => {
  172. modelRoot._appendChild( [ new ModelText( 'foobar' ) ] );
  173. } );
  174. it( 'AttributeOperation', () => {
  175. const op = new AttributeOperation( ModelRange._createIn( modelRoot ), 'key', null, { foo: 'bar' }, 0 );
  176. expect( op.toString() ).to.equal( 'AttributeOperation( 0 ): "key": null -> {"foo":"bar"}, main [ 0 ] - [ 6 ]' );
  177. op.log();
  178. expect( log.calledWithExactly( op.toString() ) ).to.be.true;
  179. } );
  180. it( 'DetachOperation (text node)', () => {
  181. const op = new DetachOperation( ModelPosition._createAt( modelRoot, 0 ), 3 );
  182. expect( op.toString() ).to.equal( 'DetachOperation( null ): #foo -> main [ 0 ] - [ 3 ]' );
  183. op.log();
  184. expect( log.calledWithExactly( op.toString() ) ).to.be.true;
  185. } );
  186. it( 'DetachOperation (element)', () => {
  187. const element = new ModelElement( 'element' );
  188. modelRoot._insertChild( 0, element );
  189. const op = new DetachOperation( ModelPosition._createBefore( element ), 1 );
  190. expect( op.toString() ).to.equal( 'DetachOperation( null ): <element> -> main [ 0 ] - [ 1 ]' );
  191. op.log();
  192. expect( log.calledWithExactly( op.toString() ) ).to.be.true;
  193. } );
  194. it( 'DetachOperation (multiple nodes)', () => {
  195. const element = new ModelElement( 'element' );
  196. modelRoot._insertChild( 0, element );
  197. const op = new DetachOperation( ModelPosition._createBefore( element ), 2 );
  198. expect( op.toString() ).to.equal( 'DetachOperation( null ): [ 2 ] -> main [ 0 ] - [ 2 ]' );
  199. op.log();
  200. expect( log.calledWithExactly( op.toString() ) ).to.be.true;
  201. } );
  202. it( 'InsertOperation (text node)', () => {
  203. const op = new InsertOperation( ModelPosition._createAt( modelRoot, 3 ), [ new ModelText( 'abc' ) ], 0 );
  204. expect( op.toString() ).to.equal( 'InsertOperation( 0 ): #abc -> main [ 3 ]' );
  205. op.log();
  206. expect( log.calledWithExactly( op.toString() ) ).to.be.true;
  207. } );
  208. it( 'InsertOperation (element)', () => {
  209. const op = new InsertOperation( ModelPosition._createAt( modelRoot, 3 ), [ new ModelElement( 'paragraph' ) ], 0 );
  210. expect( op.toString() ).to.equal( 'InsertOperation( 0 ): <paragraph> -> main [ 3 ]' );
  211. op.log();
  212. expect( log.calledWithExactly( op.toString() ) ).to.be.true;
  213. } );
  214. it( 'InsertOperation (multiple nodes)', () => {
  215. const nodes = [ new ModelText( 'x' ), new ModelElement( 'y' ), new ModelText( 'z' ) ];
  216. const op = new InsertOperation( ModelPosition._createAt( modelRoot, 3 ), nodes, 0 );
  217. expect( op.toString() ).to.equal( 'InsertOperation( 0 ): [ 3 ] -> main [ 3 ]' );
  218. op.log();
  219. expect( log.calledWithExactly( op.toString() ) ).to.be.true;
  220. } );
  221. it( 'MarkerOperation', () => {
  222. const op = new MarkerOperation( 'marker', null, ModelRange._createIn( modelRoot ), modelDoc.markers, false, 0 );
  223. expect( op.toString() ).to.equal( 'MarkerOperation( 0 ): "marker": null -> main [ 0 ] - [ 6 ]' );
  224. op.log();
  225. expect( log.calledWithExactly( op.toString() ) ).to.be.true;
  226. } );
  227. it( 'MoveOperation', () => {
  228. const op = new MoveOperation( ModelPosition._createAt( modelRoot, 1 ), 2, ModelPosition._createAt( modelRoot, 6 ), 0 );
  229. expect( op.toString() ).to.equal( 'MoveOperation( 0 ): main [ 1 ] - [ 3 ] -> main [ 6 ]' );
  230. op.log();
  231. expect( log.calledWithExactly( op.toString() ) ).to.be.true;
  232. } );
  233. it( 'NoOperation', () => {
  234. const op = new NoOperation( 0 );
  235. expect( op.toString() ).to.equal( 'NoOperation( 0 )' );
  236. op.log();
  237. expect( log.calledWithExactly( op.toString() ) ).to.be.true;
  238. } );
  239. it( 'RenameOperation', () => {
  240. const op = new RenameOperation( ModelPosition._createAt( modelRoot, 1 ), 'old', 'new', 0 );
  241. expect( op.toString() ).to.equal( 'RenameOperation( 0 ): main [ 1 ]: "old" -> "new"' );
  242. op.log();
  243. expect( log.calledWithExactly( op.toString() ) ).to.be.true;
  244. } );
  245. it( 'RootAttributeOperation', () => {
  246. const op = new RootAttributeOperation( modelRoot, 'key', 'old', null, 0 );
  247. expect( op.toString() ).to.equal( 'RootAttributeOperation( 0 ): "key": "old" -> null, main' );
  248. op.log();
  249. expect( log.calledWithExactly( op.toString() ) ).to.be.true;
  250. } );
  251. it( 'MergeOperation', () => {
  252. const op = new MergeOperation(
  253. new ModelPosition( modelRoot, [ 1, 0 ] ),
  254. 2,
  255. new ModelPosition( modelRoot, [ 0, 2 ] ),
  256. new ModelPosition( modelDoc.graveyard, [ 0 ] ),
  257. 0
  258. );
  259. expect( op.toString() ).to.equal(
  260. 'MergeOperation( 0 ): main [ 1, 0 ] -> main [ 0, 2 ] ( 2 ), $graveyard [ 0 ]'
  261. );
  262. op.log();
  263. expect( log.calledWithExactly( op.toString() ) ).to.be.true;
  264. } );
  265. it( 'SplitOperation without graveyard position', () => {
  266. const position = new ModelPosition( modelRoot, [ 1, 4 ] );
  267. const op = new SplitOperation( position, 6, null, 0 );
  268. expect( op.toString() ).to.equal(
  269. 'SplitOperation( 0 ): main [ 1, 4 ] ( 6 ) -> main [ 2 ]'
  270. );
  271. op.log();
  272. expect( log.calledWithExactly( op.toString() ) ).to.be.true;
  273. } );
  274. it( 'SplitOperation with graveyard position', () => {
  275. const position = new ModelPosition( modelRoot, [ 1, 4 ] );
  276. const op = new SplitOperation( position, 6, new ModelPosition( modelDoc.graveyard, [ 0 ] ), 0 );
  277. expect( op.toString() ).to.equal(
  278. 'SplitOperation( 0 ): main [ 1, 4 ] ( 6 ) -> main [ 2 ] with $graveyard [ 0 ]'
  279. );
  280. op.log();
  281. expect( log.calledWithExactly( op.toString() ) ).to.be.true;
  282. } );
  283. } );
  284. it( 'for applied operations', () => {
  285. const op = new InsertOperation( ModelPosition._createAt( modelRoot, 0 ), [ new ModelText( 'foo' ) ], 0 );
  286. model.applyOperation( op );
  287. expect( log.calledWithExactly( 'Applying InsertOperation( 0 ): #foo -> main [ 0 ]' ) ).to.be.true;
  288. } );
  289. } );
  290. describe( 'should provide tree printing tools', () => {
  291. it( 'for model', () => {
  292. const model = new Model();
  293. const modelDoc = model.document;
  294. const modelRoot = modelDoc.createRoot();
  295. modelRoot._appendChild( [
  296. new ModelElement( 'paragraph', { foo: 'bar' }, [
  297. new ModelText( 'This is ' ), new ModelText( 'bold', { bold: true } ), new ModelText( '.' )
  298. ] ),
  299. new ModelElement( 'listItem', { listType: 'numbered', listIndent: 0 }, new ModelText( 'One.' ) ),
  300. ] );
  301. const modelRootTree = modelRoot.printTree();
  302. expect( modelRootTree ).to.equal(
  303. '<main>' +
  304. '\n\t<paragraph foo="bar">' +
  305. '\n\t\tThis is ' +
  306. '\n\t\t<$text bold=true>bold</$text>' +
  307. '\n\t\t.' +
  308. '\n\t</paragraph>' +
  309. '\n\t<listItem listType="numbered" listIndent=0>' +
  310. '\n\t\tOne.' +
  311. '\n\t</listItem>' +
  312. '\n</main>'
  313. );
  314. modelRoot.logTree();
  315. expect( log.calledWithExactly( modelRootTree ) ).to.be.true;
  316. const modelParagraph = modelRoot.getChild( 0 );
  317. const modelParagraphTree = modelParagraph.printTree();
  318. expect( modelParagraphTree ).to.equal(
  319. '<paragraph foo="bar">' +
  320. '\n\tThis is ' +
  321. '\n\t<$text bold=true>bold</$text>' +
  322. '\n\t.' +
  323. '\n</paragraph>'
  324. );
  325. log.resetHistory();
  326. modelParagraph.logTree();
  327. expect( log.calledWithExactly( modelParagraphTree ) ).to.be.true;
  328. const modelDocFrag = new ModelDocumentFragment( [
  329. new ModelText( 'This is ' ), new ModelText( 'bold', { bold: true } ), new ModelText( '.' ),
  330. new ModelElement( 'paragraph', { foo: 'bar' }, [
  331. new ModelText( 'This is ' ), new ModelText( 'bold', { bold: true } ), new ModelText( '.' )
  332. ] )
  333. ] );
  334. const modelDocFragTree = modelDocFrag.printTree();
  335. expect( modelDocFragTree ).to.equal(
  336. 'ModelDocumentFragment: [' +
  337. '\n\tThis is ' +
  338. '\n\t<$text bold=true>bold</$text>' +
  339. '\n\t.' +
  340. '\n\t<paragraph foo="bar">' +
  341. '\n\t\tThis is ' +
  342. '\n\t\t<$text bold=true>bold</$text>' +
  343. '\n\t\t.' +
  344. '\n\t</paragraph>' +
  345. '\n]'
  346. );
  347. log.resetHistory();
  348. modelDocFrag.logTree();
  349. expect( log.calledWithExactly( modelDocFragTree ) ).to.be.true;
  350. } );
  351. it( 'for view', () => {
  352. const viewDoc = new ViewDocument();
  353. const viewRoot = createViewRoot( viewDoc );
  354. viewRoot._appendChild( [
  355. new ViewContainerElement( 'p', { foo: 'bar' }, [
  356. new ViewText( 'This is ' ), new ViewAttributeElement( 'b', null, new ViewText( 'bold' ) ), new ViewText( '.' )
  357. ] ),
  358. new ViewContainerElement( 'ol', null, [
  359. new ViewContainerElement( 'li', null, new ViewText( 'One.' ) )
  360. ] )
  361. ] );
  362. const viewRootTree = viewRoot.printTree();
  363. expect( viewRootTree ).to.equal(
  364. '<div>' +
  365. '\n\t<p foo="bar">' +
  366. '\n\t\tThis is ' +
  367. '\n\t\t<b>' +
  368. '\n\t\t\tbold' +
  369. '\n\t\t</b>' +
  370. '\n\t\t.' +
  371. '\n\t</p>' +
  372. '\n\t<ol>' +
  373. '\n\t\t<li>' +
  374. '\n\t\t\tOne.' +
  375. '\n\t\t</li>' +
  376. '\n\t</ol>' +
  377. '\n</div>'
  378. );
  379. viewRoot.logTree();
  380. expect( log.calledWithExactly( viewRootTree ) ).to.be.true;
  381. const viewParagraph = viewRoot.getChild( 0 );
  382. const viewParagraphTree = viewParagraph.printTree();
  383. expect( viewParagraphTree ).to.equal(
  384. '<p foo="bar">' +
  385. '\n\tThis is ' +
  386. '\n\t<b>' +
  387. '\n\t\tbold' +
  388. '\n\t</b>' +
  389. '\n\t.' +
  390. '\n</p>'
  391. );
  392. log.resetHistory();
  393. viewParagraph.logTree();
  394. expect( log.calledWithExactly( viewParagraphTree ) ).to.be.true;
  395. const viewDocFrag = new ViewDocumentFragment( [
  396. new ViewText( 'Text.' ),
  397. new ViewContainerElement( 'p', { foo: 'bar' }, [
  398. new ViewText( 'This is ' ), new ViewAttributeElement( 'b', null, new ViewText( 'bold' ) ), new ViewText( '.' )
  399. ] )
  400. ] );
  401. const viewDocFragTree = viewDocFrag.printTree();
  402. expect( viewDocFragTree ).to.equal(
  403. 'ViewDocumentFragment: [' +
  404. '\n\tText.' +
  405. '\n\t<p foo="bar">' +
  406. '\n\t\tThis is ' +
  407. '\n\t\t<b>' +
  408. '\n\t\t\tbold' +
  409. '\n\t\t</b>' +
  410. '\n\t\t.' +
  411. '\n\t</p>' +
  412. '\n]'
  413. );
  414. log.resetHistory();
  415. viewDocFrag.logTree();
  416. expect( log.calledWithExactly( viewDocFragTree ) ).to.be.true;
  417. } );
  418. } );
  419. describe( 'should store model and view trees state', () => {
  420. let editor;
  421. beforeEach( () => {
  422. return VirtualTestEditor.create( {
  423. plugins: [ DebugPlugin ]
  424. } ).then( _editor => {
  425. editor = _editor;
  426. } );
  427. } );
  428. it( 'should store model and view state after each applied operation', () => {
  429. const model = editor.model;
  430. const modelDoc = model.document;
  431. const modelRoot = modelDoc.getRoot();
  432. const view = editor.editing.view;
  433. const viewDoc = view.document;
  434. // Reset model document version to ensure it will start at 0.
  435. model.document.version = 0;
  436. model.change( () => {
  437. const insert = new InsertOperation( ModelPosition._createAt( modelRoot, 0 ), new ModelText( 'foobar' ), 0 );
  438. model.applyOperation( insert );
  439. const graveyard = modelDoc.graveyard;
  440. const move = new MoveOperation( ModelPosition._createAt( modelRoot, 1 ), 2, ModelPosition._createAt( graveyard, 0 ), 1 );
  441. model.applyOperation( move );
  442. } );
  443. log.resetHistory();
  444. modelDoc.log( 0 );
  445. expectLog(
  446. '<$graveyard></$graveyard>' +
  447. '\n<main></main>'
  448. );
  449. modelDoc.log( 1 );
  450. expectLog(
  451. '<$graveyard></$graveyard>' +
  452. '\n<main>' +
  453. '\n\tfoobar' +
  454. '\n</main>'
  455. );
  456. modelDoc.log( 2 );
  457. expectLog(
  458. '<$graveyard>' +
  459. '\n\too' +
  460. '\n</$graveyard>' +
  461. '\n<main>' +
  462. '\n\tfbar' +
  463. '\n</main>'
  464. );
  465. modelDoc.log();
  466. expectLog(
  467. '<$graveyard>' +
  468. '\n\too' +
  469. '\n</$graveyard>' +
  470. '\n<main>' +
  471. '\n\tfbar' +
  472. '\n</main>'
  473. );
  474. viewDoc.log( 0 );
  475. expectLog(
  476. '<$root></$root>'
  477. );
  478. viewDoc.log( 2 );
  479. expectLog(
  480. '<$root>' +
  481. '\n\tfbar' +
  482. '\n</$root>'
  483. );
  484. sinon.spy( modelDoc, 'log' );
  485. sinon.spy( viewDoc, 'log' );
  486. editor.logModel( 1 );
  487. expect( modelDoc.log.calledWithExactly( 1 ), 1 ).to.be.true;
  488. editor.logView( 2 );
  489. expect( viewDoc.log.calledWithExactly( 2 ), 2 ).to.be.true;
  490. modelDoc.log.resetHistory();
  491. viewDoc.log.resetHistory();
  492. editor.logModel();
  493. expect( modelDoc.log.calledWithExactly( 2 ), 3 ).to.be.true;
  494. modelDoc.log.resetHistory();
  495. viewDoc.log.resetHistory();
  496. editor.logDocuments();
  497. expect( modelDoc.log.calledWithExactly( 2 ), 4 ).to.be.true;
  498. expect( viewDoc.log.calledWithExactly( 2 ), 5 ).to.be.true;
  499. modelDoc.log.resetHistory();
  500. viewDoc.log.resetHistory();
  501. editor.logDocuments( 1 );
  502. expect( modelDoc.log.calledWithExactly( 1 ), 6 ).to.be.true;
  503. expect( viewDoc.log.calledWithExactly( 1 ), 7 ).to.be.true;
  504. } );
  505. it( 'should remove old states', () => {
  506. const model = editor.model;
  507. const modelDoc = model.document;
  508. const modelRoot = model.document.getRoot();
  509. for ( let i = 0; i < 25; i++ ) {
  510. const insert = new InsertOperation( ModelPosition._createAt( modelRoot, 0 ), new ModelText( 'foobar' ), modelDoc.version );
  511. model.applyOperation( insert );
  512. }
  513. modelDoc.log( 0 );
  514. expectLog( 'Tree log unavailable for given version: 0' );
  515. } );
  516. } );
  517. describe( 'should provide methods for operation replayer', () => {
  518. it( 'getAppliedOperations()', () => {
  519. const model = new Model();
  520. const modelDoc = model.document;
  521. expect( model.getAppliedOperations() ).to.equal( '' );
  522. const otherRoot = modelDoc.createRoot( '$root', 'otherRoot' );
  523. const element = new ModelElement( 'paragraph' );
  524. otherRoot._appendChild( element );
  525. const insert = new InsertOperation( ModelPosition._createAt( element, 0 ), new ModelText( 'foo' ), 0 );
  526. model.applyOperation( insert );
  527. const stringifiedOperations = model.getAppliedOperations();
  528. expect( stringifiedOperations ).to.equal( JSON.stringify( insert ) );
  529. } );
  530. it( 'createReplayer()', () => {
  531. const model = new Model();
  532. const modelDoc = model.document;
  533. const otherRoot = modelDoc.createRoot( '$root', 'otherRoot' );
  534. const element = new ModelElement( 'paragraph' );
  535. otherRoot._appendChild( element );
  536. const insert = new InsertOperation( ModelPosition._createAt( element, 0 ), new ModelText( 'foo' ), 0 );
  537. model.applyOperation( insert );
  538. const stringifiedOperations = model.getAppliedOperations();
  539. const operationReplayer = model.createReplayer( stringifiedOperations );
  540. expect( operationReplayer.getOperationsToReplay() ).to.deep.equal( [ JSON.parse( stringifiedOperations ) ] );
  541. } );
  542. } );
  543. function expectLog( expectedLogMsg ) {
  544. expect( log.calledWithExactly( expectedLogMsg ) ).to.be.true;
  545. log.resetHistory();
  546. }
  547. } );