enableenginedebug.js 23 KB

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