From edf6166a07ca89fd645383e773ccbfd7afa66e91 Mon Sep 17 00:00:00 2001 From: Maciej Bukowski Date: Fri, 31 Mar 2017 15:25:23 +0200 Subject: [PATCH 01/14] Added replayer tool. --- src/dev-utils/enableenginedebug.js | 69 +++++++++++++++++++++++++++++- 1 file changed, 67 insertions(+), 2 deletions(-) diff --git a/src/dev-utils/enableenginedebug.js b/src/dev-utils/enableenginedebug.js index 703e54bf3..f89573554 100644 --- a/src/dev-utils/enableenginedebug.js +++ b/src/dev-utils/enableenginedebug.js @@ -43,11 +43,16 @@ import ViewDocumentFragment from '../view/documentfragment'; import Plugin from '@ckeditor/ckeditor5-core/src/plugin'; import Editor from '@ckeditor/ckeditor5-core/src/editor/editor'; +import DeltaFactory from '../model/delta/deltafactory'; + const treeDump = Symbol( '_treeDump' ); // Maximum number of stored states of model and view document. const maxTreeDumpLength = 20; +// Separator used to separate stringified deltas +const LOG_SEPARATOR = '\n----------------\n'; + // Specified whether debug tools were already enabled. let enabled = false; @@ -93,8 +98,10 @@ let log = console.log; * @param {Function} [logger] Function used to log messages. By default messages are logged to console. * @returns {module:engine/dev-utils/enableenginedebug~DebugPlugin} Plugin to be loaded in the editor. */ -export default function enableEngineDebug( logger = console.log ) { - log = logger; +export default function enableEngineDebug( logger ) { + if ( logger ) { + log = logger; + } if ( !enabled ) { enabled = true; @@ -415,6 +422,12 @@ function enableDocumentTools() { ModelDocument.prototype.applyOperation = function( operation ) { log( 'Applying ' + operation ); + if ( !this._operationLogs ) { + this._operationLogs = []; + } + + this._operationLogs.push( JSON.stringify( operation.toJSON() ) ); + _modelDocumentApplyOperation.call( this, operation ); }; @@ -424,6 +437,58 @@ function enableDocumentTools() { logDocument( this, version ); }; + + ModelDocument.prototype.logDeltas = function() { + return ( this._deltaLogs || [] ).map( JSON.stringify ).join( LOG_SEPARATOR ); + }; + + ModelDocument.prototype.logAppliedDeltas = function() { + return ( this._appliedDeltas || [] ).map( JSON.stringify ).join( LOG_SEPARATOR ); + }; + + ModelDocument.prototype.applyStringifiedDeltas = function( stringifiedDeltas ) { + this.enqueueChanges( () => { + for ( const stringifiedDelta of stringifiedDeltas.split( LOG_SEPARATOR ) ) { + const jsonDelta = JSON.parse( stringifiedDelta ); + const delta = DeltaFactory.fromJSON( jsonDelta, this ); + + const batch = this.batch(); + + batch.addDelta( delta ); + + for ( const operation of delta.operations ) { + this.applyOperation( operation ); + } + } + } ); + }; + + ModelDocument.prototype.createReplayer = function( stringifiedDeltas ) { + this._deltaToReplay = stringifiedDeltas + .split( LOG_SEPARATOR ) + .map( stringifiedDelta => JSON.parse( stringifiedDelta ) ) + .map( jsonDelta => DeltaFactory.fromJSON( jsonDelta, this ) ); + }; + + ModelDocument.prototype.nextDelta = function() { + this.enqueueChanges( () => { + const delta = this._deltaToReplay.shift(); + + if ( !delta ) { + console.warn( 'No deltas to replay' ); + + return; + } + + const batch = this.batch(); + batch.addDelta( delta ); + + for ( const operation of delta.operations ) { + this.applyOperation( operation ); + } + } ); + }; + ViewDocument.prototype.log = function( version ) { logDocument( this, version ); }; From 84d64cef35c69d195d32aa6581a77ce7e79b1d73 Mon Sep 17 00:00:00 2001 From: Maciej Bukowski Date: Fri, 31 Mar 2017 17:45:41 +0200 Subject: [PATCH 02/14] Moved replayer to separate file. --- src/dev-utils/deltareplayer.js | 87 ++++++++++++++++++++++++++++++ src/dev-utils/enableenginedebug.js | 50 +++-------------- 2 files changed, 94 insertions(+), 43 deletions(-) create mode 100644 src/dev-utils/deltareplayer.js diff --git a/src/dev-utils/deltareplayer.js b/src/dev-utils/deltareplayer.js new file mode 100644 index 000000000..fda94185a --- /dev/null +++ b/src/dev-utils/deltareplayer.js @@ -0,0 +1,87 @@ +/** + * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md. + */ + +/** + * @module engine/dev-utils/deltareplayer + */ + +/* global setTimeout, console */ + +import DeltaFactory from '../model/delta/deltafactory'; + +/** + * DeltaReplayer is a dev-tool created for easily replaying operations on the document from stringified deltas + */ +export default class DeltaReplayer { + /** + * @param {module:engine/model/document~Document} document + * @param {String} logSeparator Separator between deltas. + * @param {String} stringifiedDeltas Deltas to replay. + */ + constructor( document, logSeparator, stringifiedDeltas ) { + this._document = document; + this._deltaToReplay = []; + this._logSeparator = logSeparator; + this.setStringifiedDeltas( stringifiedDeltas ); + } + + /** + * @param {String} stringifiedDeltas Deltas to replay. + */ + setStringifiedDeltas( stringifiedDeltas ) { + this._deltaToReplay = stringifiedDeltas + .split( this._logSeparator ) + .map( stringifiedDelta => JSON.parse( stringifiedDelta ) ) + .map( jsonDelta => DeltaFactory.fromJSON( jsonDelta, this._document ) ); + } + + /** + * @param {Number} timeInterval + */ + play( timeInterval = 1000 ) { + if ( this._deltaToReplay.length === 0 ) { + return; + } + + this.applyNextDelta().then( () => { + setTimeout( () => this.play(), timeInterval ); + } ); + } + + /** + * @returns {Promise} + */ + applyNextDelta() { + const document = this._document; + + return new Promise( ( res, rej ) => { + document.enqueueChanges( () => { + const delta = this._deltaToReplay.shift(); + + if ( !delta ) { + return rej( new Error('No deltas to replay') ); + } + + const batch = document.batch(); + batch.addDelta( delta ); + + for ( const operation of delta.operations ) { + document.applyOperation( operation ); + } + + res(); + } ); + } ); + } + + /** + * @returns {Promise} + */ + applyAllDeltas() { + return this.applyNextDelta() + .then( () => this.applyAllDeltas() ) + .catch( err => console.warn( err ) ); + } +} \ No newline at end of file diff --git a/src/dev-utils/enableenginedebug.js b/src/dev-utils/enableenginedebug.js index a06f2d530..e45b322cc 100644 --- a/src/dev-utils/enableenginedebug.js +++ b/src/dev-utils/enableenginedebug.js @@ -46,7 +46,7 @@ import ViewDocumentFragment from '../view/documentfragment'; import Plugin from '@ckeditor/ckeditor5-core/src/plugin'; import Editor from '@ckeditor/ckeditor5-core/src/editor/editor'; -import DeltaFactory from '../model/delta/deltafactory'; +import DeltaReplayer from './deltareplayer'; const treeDump = Symbol( '_treeDump' ); @@ -476,56 +476,20 @@ function enableDocumentTools() { logDocument( this, version ); }; + ModelDocument.prototype.initializeDebugging = function() { + this._appliedDeltas = []; + }; - ModelDocument.prototype.logDeltas = function() { - return ( this._deltaLogs || [] ).map( JSON.stringify ).join( LOG_SEPARATOR ); + ModelDocument.prototype.addAppliedDelta = function( delta ) { + this._appliedDeltas.push( delta.toJSON() ); }; ModelDocument.prototype.logAppliedDeltas = function() { return ( this._appliedDeltas || [] ).map( JSON.stringify ).join( LOG_SEPARATOR ); }; - ModelDocument.prototype.applyStringifiedDeltas = function( stringifiedDeltas ) { - this.enqueueChanges( () => { - for ( const stringifiedDelta of stringifiedDeltas.split( LOG_SEPARATOR ) ) { - const jsonDelta = JSON.parse( stringifiedDelta ); - const delta = DeltaFactory.fromJSON( jsonDelta, this ); - - const batch = this.batch(); - - batch.addDelta( delta ); - - for ( const operation of delta.operations ) { - this.applyOperation( operation ); - } - } - } ); - }; - ModelDocument.prototype.createReplayer = function( stringifiedDeltas ) { - this._deltaToReplay = stringifiedDeltas - .split( LOG_SEPARATOR ) - .map( stringifiedDelta => JSON.parse( stringifiedDelta ) ) - .map( jsonDelta => DeltaFactory.fromJSON( jsonDelta, this ) ); - }; - - ModelDocument.prototype.nextDelta = function() { - this.enqueueChanges( () => { - const delta = this._deltaToReplay.shift(); - - if ( !delta ) { - console.warn( 'No deltas to replay' ); - - return; - } - - const batch = this.batch(); - batch.addDelta( delta ); - - for ( const operation of delta.operations ) { - this.applyOperation( operation ); - } - } ); + return new DeltaReplayer( this, LOG_SEPARATOR, stringifiedDeltas ); }; ViewDocument.prototype.log = function( version ) { From 5d64f293a463add6b659389ce74e2b76300fedfc Mon Sep 17 00:00:00 2001 From: Maciej Bukowski Date: Fri, 31 Mar 2017 18:23:36 +0200 Subject: [PATCH 03/14] Added simple test. --- src/dev-utils/deltareplayer.js | 141 ++++++++++++++++--------------- tests/dev-utils/deltareplayer.js | 20 +++++ 2 files changed, 93 insertions(+), 68 deletions(-) create mode 100644 tests/dev-utils/deltareplayer.js diff --git a/src/dev-utils/deltareplayer.js b/src/dev-utils/deltareplayer.js index fda94185a..64b3d01da 100644 --- a/src/dev-utils/deltareplayer.js +++ b/src/dev-utils/deltareplayer.js @@ -12,76 +12,81 @@ import DeltaFactory from '../model/delta/deltafactory'; /** - * DeltaReplayer is a dev-tool created for easily replaying operations on the document from stringified deltas + * DeltaReplayer is a dev-tool created for easily replaying operations on the document from stringified deltas. */ export default class DeltaReplayer { - /** - * @param {module:engine/model/document~Document} document - * @param {String} logSeparator Separator between deltas. - * @param {String} stringifiedDeltas Deltas to replay. - */ - constructor( document, logSeparator, stringifiedDeltas ) { - this._document = document; - this._deltaToReplay = []; - this._logSeparator = logSeparator; - this.setStringifiedDeltas( stringifiedDeltas ); - } - - /** - * @param {String} stringifiedDeltas Deltas to replay. - */ - setStringifiedDeltas( stringifiedDeltas ) { - this._deltaToReplay = stringifiedDeltas + /** + * @param {module:engine/model/document~Document} document. + * @param {String} logSeparator Separator between deltas. + * @param {String} stringifiedDeltas Deltas to replay. + */ + constructor( document, logSeparator, stringifiedDeltas ) { + this._document = document; + this._logSeparator = logSeparator; + this.setStringifiedDeltas( stringifiedDeltas ); + } + + /** + * @param {String} stringifiedDeltas Deltas to replay. + */ + setStringifiedDeltas( stringifiedDeltas ) { + if ( stringifiedDeltas === '' ) { + this._deltaToReplay = []; + + return; + } + + this._deltaToReplay = stringifiedDeltas .split( this._logSeparator ) .map( stringifiedDelta => JSON.parse( stringifiedDelta ) ) .map( jsonDelta => DeltaFactory.fromJSON( jsonDelta, this._document ) ); - } - - /** - * @param {Number} timeInterval - */ - play( timeInterval = 1000 ) { - if ( this._deltaToReplay.length === 0 ) { - return; - } - - this.applyNextDelta().then( () => { - setTimeout( () => this.play(), timeInterval ); - } ); - } - - /** - * @returns {Promise} - */ - applyNextDelta() { - const document = this._document; - - return new Promise( ( res, rej ) => { - document.enqueueChanges( () => { - const delta = this._deltaToReplay.shift(); - - if ( !delta ) { - return rej( new Error('No deltas to replay') ); - } - - const batch = document.batch(); - batch.addDelta( delta ); - - for ( const operation of delta.operations ) { - document.applyOperation( operation ); - } - - res(); - } ); - } ); - } - - /** - * @returns {Promise} - */ - applyAllDeltas() { - return this.applyNextDelta() - .then( () => this.applyAllDeltas() ) - .catch( err => console.warn( err ) ); - } -} \ No newline at end of file + } + + /** + * @param {Number} timeInterval + */ + play( timeInterval = 1000 ) { + if ( this._deltaToReplay.length === 0 ) { + return; + } + + this.applyNextDelta().then( () => { + setTimeout( () => this.play(), timeInterval ); + } ); + } + + /** + * @returns {Promise} + */ + applyNextDelta() { + const document = this._document; + + return new Promise( ( res, rej ) => { + document.enqueueChanges( () => { + const delta = this._deltaToReplay.shift(); + + if ( !delta ) { + return rej( new Error( 'No deltas to replay' ) ); + } + + const batch = document.batch(); + batch.addDelta( delta ); + + for ( const operation of delta.operations ) { + document.applyOperation( operation ); + } + + res(); + } ); + } ); + } + + /** + * @returns {Promise} + */ + applyAllDeltas() { + return this.applyNextDelta() + .then( () => this.applyAllDeltas() ) + .catch( err => console.warn( err ) ); + } +} diff --git a/tests/dev-utils/deltareplayer.js b/tests/dev-utils/deltareplayer.js new file mode 100644 index 000000000..9298ed4b2 --- /dev/null +++ b/tests/dev-utils/deltareplayer.js @@ -0,0 +1,20 @@ +/** + * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md. + */ + +import DeltaReplayer from '../../src/dev-utils/deltareplayer'; + +describe( 'DeltaReplayer', () => { + describe( 'constructor()', () => { + it( 'should be able to initialize replayer with no deltas', () => { + const fakeDocument = {}; + const stringifiedDeltas = ''; + const deltaReplayer = new DeltaReplayer( fakeDocument, '---', stringifiedDeltas ); + + expect( deltaReplayer._deltaToReplay ).to.deep.equal( [] ); + expect( deltaReplayer._document ).to.deep.equal( fakeDocument ); + expect( deltaReplayer._logSeparator ).to.deep.equal( '---' ); + } ); + } ); +} ); From d7959311f3b369f472afe0538e3ea3a72ca66d5d Mon Sep 17 00:00:00 2001 From: Maciej Bukowski Date: Fri, 31 Mar 2017 20:04:21 +0200 Subject: [PATCH 04/14] Added applyDeltas method to the DeltaReplayer. --- src/dev-utils/deltareplayer.js | 34 +++++++++++++++++++++++--------- tests/dev-utils/deltareplayer.js | 2 +- 2 files changed, 26 insertions(+), 10 deletions(-) diff --git a/src/dev-utils/deltareplayer.js b/src/dev-utils/deltareplayer.js index 64b3d01da..da3fe1042 100644 --- a/src/dev-utils/deltareplayer.js +++ b/src/dev-utils/deltareplayer.js @@ -43,6 +43,8 @@ export default class DeltaReplayer { } /** + * Applies all deltas with delay between actions. + * * @param {Number} timeInterval */ play( timeInterval = 1000 ) { @@ -55,6 +57,29 @@ export default class DeltaReplayer { } ); } + /** + * @param {Number} numberOfDeltas Number of deltas to apply. + * @returns {Promise} + */ + applyDeltas( numberOfDeltas ) { + if ( numberOfDeltas <= 0 ) { + return; + } + + return this.applyNextDelta() + .then( () => this.applyDeltas( numberOfDeltas - 1 ) ) + .catch( err => console.warn( err ) ); + } + + /** + * @returns {Promise} + */ + applyAllDeltas() { + return this.applyNextDelta() + .then( () => this.applyAllDeltas() ) + .catch( err => console.warn( err ) ); + } + /** * @returns {Promise} */ @@ -80,13 +105,4 @@ export default class DeltaReplayer { } ); } ); } - - /** - * @returns {Promise} - */ - applyAllDeltas() { - return this.applyNextDelta() - .then( () => this.applyAllDeltas() ) - .catch( err => console.warn( err ) ); - } } diff --git a/tests/dev-utils/deltareplayer.js b/tests/dev-utils/deltareplayer.js index 9298ed4b2..ae1a54dab 100644 --- a/tests/dev-utils/deltareplayer.js +++ b/tests/dev-utils/deltareplayer.js @@ -7,7 +7,7 @@ import DeltaReplayer from '../../src/dev-utils/deltareplayer'; describe( 'DeltaReplayer', () => { describe( 'constructor()', () => { - it( 'should be able to initialize replayer with no deltas', () => { + it( 'should be able to initialize replayer without deltas', () => { const fakeDocument = {}; const stringifiedDeltas = ''; const deltaReplayer = new DeltaReplayer( fakeDocument, '---', stringifiedDeltas ); From a5b9237647a67f8d0fdacad0111b964c7223ffd4 Mon Sep 17 00:00:00 2001 From: Maciej Bukowski Date: Fri, 31 Mar 2017 20:34:22 +0200 Subject: [PATCH 05/14] Added test for the DeltaReplayer constructor. --- src/dev-utils/deltareplayer.js | 12 ++++++------ tests/dev-utils/deltareplayer.js | 30 +++++++++++++++++++++++++++++- 2 files changed, 35 insertions(+), 7 deletions(-) diff --git a/src/dev-utils/deltareplayer.js b/src/dev-utils/deltareplayer.js index da3fe1042..792a3d779 100644 --- a/src/dev-utils/deltareplayer.js +++ b/src/dev-utils/deltareplayer.js @@ -31,15 +31,14 @@ export default class DeltaReplayer { */ setStringifiedDeltas( stringifiedDeltas ) { if ( stringifiedDeltas === '' ) { - this._deltaToReplay = []; + this._deltasToReplay = []; return; } - this._deltaToReplay = stringifiedDeltas + this._deltasToReplay = stringifiedDeltas .split( this._logSeparator ) - .map( stringifiedDelta => JSON.parse( stringifiedDelta ) ) - .map( jsonDelta => DeltaFactory.fromJSON( jsonDelta, this._document ) ); + .map( stringifiedDelta => JSON.parse( stringifiedDelta ) ); } /** @@ -48,7 +47,7 @@ export default class DeltaReplayer { * @param {Number} timeInterval */ play( timeInterval = 1000 ) { - if ( this._deltaToReplay.length === 0 ) { + if ( this._deltasToReplay.length === 0 ) { return; } @@ -88,7 +87,8 @@ export default class DeltaReplayer { return new Promise( ( res, rej ) => { document.enqueueChanges( () => { - const delta = this._deltaToReplay.shift(); + const jsonDelta = this._deltasToReplay.shift(); + const delta = DeltaFactory.fromJSON( jsonDelta, this._document ); if ( !delta ) { return rej( new Error( 'No deltas to replay' ) ); diff --git a/tests/dev-utils/deltareplayer.js b/tests/dev-utils/deltareplayer.js index ae1a54dab..f7245545c 100644 --- a/tests/dev-utils/deltareplayer.js +++ b/tests/dev-utils/deltareplayer.js @@ -4,6 +4,7 @@ */ import DeltaReplayer from '../../src/dev-utils/deltareplayer'; +import Document from '../../src/model/document'; describe( 'DeltaReplayer', () => { describe( 'constructor()', () => { @@ -12,9 +13,36 @@ describe( 'DeltaReplayer', () => { const stringifiedDeltas = ''; const deltaReplayer = new DeltaReplayer( fakeDocument, '---', stringifiedDeltas ); - expect( deltaReplayer._deltaToReplay ).to.deep.equal( [] ); + expect( deltaReplayer._deltasToReplay ).to.deep.equal( [] ); expect( deltaReplayer._document ).to.deep.equal( fakeDocument ); expect( deltaReplayer._logSeparator ).to.deep.equal( '---' ); } ); + + it( 'should be able to initialize replayer with deltas', () => { + const doc = new Document(); + doc.createRoot( 'main' ); + + const delta = { + operations: [ { + baseVersion: 0, + position: { + root: 'main', + path: [ 0 ] + }, + nodes: [ { + name: 'heading1', + children: [ { + data: 'The great world of open Web standards' + } ] + } ], + __className: 'engine.model.operation.InsertOperation' + } ], + __className: 'engine.model.delta.InsertDelta' + }; + + const deltaReplayer = new DeltaReplayer( doc, '---', JSON.stringify( delta ) ); + + expect( deltaReplayer._deltasToReplay ).to.deep.equal( [ delta ] ); + } ); } ); } ); From e0d02de1fc7e58e2b7f704efb0c7c2f965fd6305 Mon Sep 17 00:00:00 2001 From: Maciej Bukowski Date: Fri, 31 Mar 2017 21:40:07 +0200 Subject: [PATCH 06/14] Added test for the DeltaReplayer applyNextDelta and applyAllDeltas. --- src/dev-utils/deltareplayer.js | 9 ++- tests/dev-utils/deltareplayer.js | 132 +++++++++++++++++++++++++------ 2 files changed, 113 insertions(+), 28 deletions(-) diff --git a/src/dev-utils/deltareplayer.js b/src/dev-utils/deltareplayer.js index 792a3d779..e00ad9c45 100644 --- a/src/dev-utils/deltareplayer.js +++ b/src/dev-utils/deltareplayer.js @@ -41,6 +41,10 @@ export default class DeltaReplayer { .map( stringifiedDelta => JSON.parse( stringifiedDelta ) ); } + getDeltasToReplay() { + return this._deltasToReplay; + } + /** * Applies all deltas with delay between actions. * @@ -88,12 +92,13 @@ export default class DeltaReplayer { return new Promise( ( res, rej ) => { document.enqueueChanges( () => { const jsonDelta = this._deltasToReplay.shift(); - const delta = DeltaFactory.fromJSON( jsonDelta, this._document ); - if ( !delta ) { + if ( !jsonDelta ) { return rej( new Error( 'No deltas to replay' ) ); } + const delta = DeltaFactory.fromJSON( jsonDelta, this._document ); + const batch = document.batch(); batch.addDelta( delta ); diff --git a/tests/dev-utils/deltareplayer.js b/tests/dev-utils/deltareplayer.js index f7245545c..5be46928c 100644 --- a/tests/dev-utils/deltareplayer.js +++ b/tests/dev-utils/deltareplayer.js @@ -9,40 +9,120 @@ import Document from '../../src/model/document'; describe( 'DeltaReplayer', () => { describe( 'constructor()', () => { it( 'should be able to initialize replayer without deltas', () => { - const fakeDocument = {}; + const doc = getDocument(); const stringifiedDeltas = ''; - const deltaReplayer = new DeltaReplayer( fakeDocument, '---', stringifiedDeltas ); + const deltaReplayer = new DeltaReplayer( doc, '---', stringifiedDeltas ); - expect( deltaReplayer._deltasToReplay ).to.deep.equal( [] ); - expect( deltaReplayer._document ).to.deep.equal( fakeDocument ); - expect( deltaReplayer._logSeparator ).to.deep.equal( '---' ); + expect( deltaReplayer.getDeltasToReplay() ).to.deep.equal( [] ); } ); it( 'should be able to initialize replayer with deltas', () => { - const doc = new Document(); - doc.createRoot( 'main' ); - - const delta = { - operations: [ { - baseVersion: 0, - position: { - root: 'main', - path: [ 0 ] - }, - nodes: [ { - name: 'heading1', - children: [ { - data: 'The great world of open Web standards' - } ] - } ], - __className: 'engine.model.operation.InsertOperation' - } ], - __className: 'engine.model.delta.InsertDelta' - }; + const doc = getDocument(); + const delta = getFirstDelta(); const deltaReplayer = new DeltaReplayer( doc, '---', JSON.stringify( delta ) ); - expect( deltaReplayer._deltasToReplay ).to.deep.equal( [ delta ] ); + expect( deltaReplayer.getDeltasToReplay() ).to.deep.equal( [ delta ] ); + } ); + } ); + + describe( 'applyNextDelta()', () => { + it( 'should remove first delta from stack', () => { + const doc = getDocument(); + const delta = getFirstDelta(); + + const deltaReplayer = new DeltaReplayer( doc, '---', JSON.stringify( delta ) ); + + return deltaReplayer.applyNextDelta().then( () => { + expect( deltaReplayer.getDeltasToReplay() ).to.deep.equal( [] ); + } ); + } ); + + it( 'should apply first delta on the document', () => { + const doc = getDocument(); + const delta = getFirstDelta(); + + const deltaReplayer = new DeltaReplayer( doc, '---', JSON.stringify( delta ) ); + + return deltaReplayer.applyNextDelta().then( () => { + expect( Array.from( doc.getRoot().getChildren() ).length ).to.equal( 1 ); + } ); + } ); + + it( 'should throw an error if 0 deltas are provided', () => { + const doc = getDocument(); + const deltaReplayer = new DeltaReplayer( doc, '---', '' ); + + return deltaReplayer.applyNextDelta().then( () => { + throw new Error( 'This should throw an error' ); + }, ( err ) => { + expect( err instanceof Error ).to.equal( true ); + expect( err.message ).to.equal( 'No deltas to replay' ); + } ); + } ); + } ); + + describe( 'applyAllDeltas', () => { + it( 'should apply all deltas on the document', () => { + const doc = getDocument(); + + const stringifiedDeltas = [ getFirstDelta(), getSecondDelta() ] + .map( d => JSON.stringify( d ) ) + .join( '---' ); + + const deltaReplayer = new DeltaReplayer( doc, '---', stringifiedDeltas ); + + return deltaReplayer.applyAllDeltas().then( () => { + expect( Array.from( doc.getRoot().getChildren() ).length ).to.equal( 2 ); + expect( deltaReplayer.getDeltasToReplay().length ).to.equal( 0 ); + } ); } ); } ); } ); + +function getDocument() { + const doc = new Document(); + doc.createRoot( 'main' ); + + return doc; +} + +function getFirstDelta() { + return { + operations: [ { + baseVersion: 0, + position: { + root: 'main', + path: [ 0 ] + }, + nodes: [ { + name: 'heading1', + children: [ { + data: 'The great world of open Web standards' + } ] + } ], + __className: 'engine.model.operation.InsertOperation' + } ], + __className: 'engine.model.delta.InsertDelta' + }; +} + +function getSecondDelta() { + return { + operations: [ { + baseVersion: 1, + position: { + root: 'main', + path: [ 1 ] + }, + nodes: [ { + name: 'heading1', + children: [ { + data: 'The great world of open Web standards' + } ] + } ], + __className: 'engine.model.operation.InsertOperation' + } ], + __className: 'engine.model.delta.InsertDelta' + }; +} From 115d88fcfaad2c660d2ebf21002a4d28977ce3a6 Mon Sep 17 00:00:00 2001 From: Maciej Bukowski Date: Fri, 31 Mar 2017 21:45:48 +0200 Subject: [PATCH 07/14] Added test for the DeltaReplayer applyDeltas. --- tests/dev-utils/deltareplayer.js | 34 +++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/tests/dev-utils/deltareplayer.js b/tests/dev-utils/deltareplayer.js index 5be46928c..e46377722 100644 --- a/tests/dev-utils/deltareplayer.js +++ b/tests/dev-utils/deltareplayer.js @@ -78,6 +78,38 @@ describe( 'DeltaReplayer', () => { } ); } ); } ); + + describe( 'applyDeltas', () => { + it( 'should apply certain number of deltas on the document', () => { + const doc = getDocument(); + + const stringifiedDeltas = [ getFirstDelta(), getSecondDelta() ] + .map( d => JSON.stringify( d ) ) + .join( '---' ); + + const deltaReplayer = new DeltaReplayer( doc, '---', stringifiedDeltas ); + + return deltaReplayer.applyDeltas( 1 ).then( () => { + expect( Array.from( doc.getRoot().getChildren() ).length ).to.equal( 1 ); + expect( deltaReplayer.getDeltasToReplay().length ).to.equal( 1 ); + } ); + } ); + + it( 'should not throw an error if the number of deltas is lower than number of expected deltas to replay', () => { + const doc = getDocument(); + + const stringifiedDeltas = [ getFirstDelta(), getSecondDelta() ] + .map( d => JSON.stringify( d ) ) + .join( '---' ); + + const deltaReplayer = new DeltaReplayer( doc, '---', stringifiedDeltas ); + + return deltaReplayer.applyDeltas( 3 ).then( () => { + expect( Array.from( doc.getRoot().getChildren() ).length ).to.equal( 2 ); + expect( deltaReplayer.getDeltasToReplay().length ).to.equal( 0 ); + } ); + } ); + } ); } ); function getDocument() { @@ -116,7 +148,7 @@ function getSecondDelta() { path: [ 1 ] }, nodes: [ { - name: 'heading1', + name: 'heading2', children: [ { data: 'The great world of open Web standards' } ] From ab2c04fec588cb125fe40e0e0522fa449561c85a Mon Sep 17 00:00:00 2001 From: Maciej Bukowski Date: Mon, 3 Apr 2017 12:01:19 +0200 Subject: [PATCH 08/14] Added missing test. --- src/dev-utils/enableenginedebug.js | 2 +- tests/dev-utils/enableenginedebug.js | 49 ++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+), 1 deletion(-) diff --git a/src/dev-utils/enableenginedebug.js b/src/dev-utils/enableenginedebug.js index e45b322cc..6cbb63082 100644 --- a/src/dev-utils/enableenginedebug.js +++ b/src/dev-utils/enableenginedebug.js @@ -484,7 +484,7 @@ function enableDocumentTools() { this._appliedDeltas.push( delta.toJSON() ); }; - ModelDocument.prototype.logAppliedDeltas = function() { + ModelDocument.prototype.getAppliedDeltas = function() { return ( this._appliedDeltas || [] ).map( JSON.stringify ).join( LOG_SEPARATOR ); }; diff --git a/tests/dev-utils/enableenginedebug.js b/tests/dev-utils/enableenginedebug.js index 5fd2af235..ed3b54880 100644 --- a/tests/dev-utils/enableenginedebug.js +++ b/tests/dev-utils/enableenginedebug.js @@ -713,6 +713,55 @@ describe( 'debug tools', () => { } ); } ); + describe( 'should provide methods for delta replayer', () => { + it( 'getAppliedDeltas()', () => { + const modelDoc = new ModelDocument(); + + const otherRoot = modelDoc.createRoot( '$root', 'otherRoot' ); + const firstEle = new ModelElement( 'paragraph' ); + const removedEle = new ModelElement( 'paragraph', null, [ new ModelText( 'foo' ) ] ); + + otherRoot.appendChildren( [ firstEle, removedEle ] ); + + const delta = new MergeDelta(); + const move = new MoveOperation( ModelPosition.createAt( removedEle, 0 ), 3, ModelPosition.createAt( firstEle, 0 ), 0 ); + const remove = new RemoveOperation( ModelPosition.createBefore( removedEle ), 1, 1 ); + + delta.addOperation( move ); + delta.addOperation( remove ); + + modelDoc.initializeDebugging(); + modelDoc.addAppliedDelta( delta ); + + const stringifiedDeltas = modelDoc.getAppliedDeltas(); + + expect( stringifiedDeltas ).to.equal( JSON.stringify( delta.toJSON() ) ); + } ); + + it( 'createReplayer()', () => { + const modelDoc = new ModelDocument(); + + const otherRoot = modelDoc.createRoot( '$root', 'otherRoot' ); + const firstEle = new ModelElement( 'paragraph' ); + const removedEle = new ModelElement( 'paragraph', null, [ new ModelText( 'foo' ) ] ); + + otherRoot.appendChildren( [ firstEle, removedEle ] ); + + const delta = new MergeDelta(); + const move = new MoveOperation( ModelPosition.createAt( removedEle, 0 ), 3, ModelPosition.createAt( firstEle, 0 ), 0 ); + const remove = new RemoveOperation( ModelPosition.createBefore( removedEle ), 1, 1 ); + + delta.addOperation( move ); + delta.addOperation( remove ); + + const stringifiedDeltas = JSON.stringify( delta.toJSON() ); + + const deltaReplayer = modelDoc.createReplayer( stringifiedDeltas ); + + expect( deltaReplayer.getDeltasToReplay() ).to.deep.equal( [ JSON.parse( stringifiedDeltas ) ] ); + } ); + } ); + function expectLog( expectedLogMsg ) { expect( log.calledWithExactly( expectedLogMsg ) ).to.be.true; log.reset(); From c72a240314e20a11e63d4536d6b915f1561a400b Mon Sep 17 00:00:00 2001 From: Maciej Bukowski Date: Mon, 3 Apr 2017 12:58:49 +0200 Subject: [PATCH 09/14] Added test for the play method. --- src/dev-utils/deltareplayer.js | 23 +++++++++++++++-------- tests/dev-utils/deltareplayer.js | 18 ++++++++++++++++++ 2 files changed, 33 insertions(+), 8 deletions(-) diff --git a/src/dev-utils/deltareplayer.js b/src/dev-utils/deltareplayer.js index e00ad9c45..03d30d06f 100644 --- a/src/dev-utils/deltareplayer.js +++ b/src/dev-utils/deltareplayer.js @@ -48,16 +48,23 @@ export default class DeltaReplayer { /** * Applies all deltas with delay between actions. * - * @param {Number} timeInterval + * @param {Number} timeInterval Time between applying deltas. + * @param {Function} [cb] Callback. */ - play( timeInterval = 1000 ) { - if ( this._deltasToReplay.length === 0 ) { - return; - } + play( timeInterval = 1000, cb = () => {} ) { + const deltaReplayer = this; - this.applyNextDelta().then( () => { - setTimeout( () => this.play(), timeInterval ); - } ); + play(); + + function play() { + if ( deltaReplayer._deltasToReplay.length === 0 ) { + return cb(); + } + + deltaReplayer.applyNextDelta().then( () => { + setTimeout( play, timeInterval ); + }, cb ); + } } /** diff --git a/tests/dev-utils/deltareplayer.js b/tests/dev-utils/deltareplayer.js index e46377722..84cc8f9f0 100644 --- a/tests/dev-utils/deltareplayer.js +++ b/tests/dev-utils/deltareplayer.js @@ -110,6 +110,24 @@ describe( 'DeltaReplayer', () => { } ); } ); } ); + + describe( 'play', () => { + it( 'should play deltas with time interval', ( done ) => { + const doc = getDocument(); + + const stringifiedDeltas = [ getFirstDelta(), getSecondDelta() ] + .map( d => JSON.stringify( d ) ) + .join( '---' ); + + const deltaReplayer = new DeltaReplayer( doc, '---', stringifiedDeltas ); + + deltaReplayer.play( 0, () => { + expect( deltaReplayer.getDeltasToReplay().length ).to.equal( 0 ); + + done(); + } ); + } ); + } ); } ); function getDocument() { From ec194428c65cff01b1217fd71e12c97ae3ea902a Mon Sep 17 00:00:00 2001 From: Maciej Bukowski Date: Mon, 3 Apr 2017 14:06:50 +0200 Subject: [PATCH 10/14] Removed warinings from karma logs. --- src/dev-utils/deltareplayer.js | 2 +- tests/dev-utils/deltareplayer.js | 16 ++++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/src/dev-utils/deltareplayer.js b/src/dev-utils/deltareplayer.js index 03d30d06f..f54bcb297 100644 --- a/src/dev-utils/deltareplayer.js +++ b/src/dev-utils/deltareplayer.js @@ -87,7 +87,7 @@ export default class DeltaReplayer { applyAllDeltas() { return this.applyNextDelta() .then( () => this.applyAllDeltas() ) - .catch( err => console.warn( err ) ); + .catch( () => {} ); } /** diff --git a/tests/dev-utils/deltareplayer.js b/tests/dev-utils/deltareplayer.js index 84cc8f9f0..4aefbf6d8 100644 --- a/tests/dev-utils/deltareplayer.js +++ b/tests/dev-utils/deltareplayer.js @@ -3,10 +3,25 @@ * For licensing, see LICENSE.md. */ +/* global console */ + import DeltaReplayer from '../../src/dev-utils/deltareplayer'; import Document from '../../src/model/document'; describe( 'DeltaReplayer', () => { + const sandbox = sinon.sandbox.create(); + let stubs; + + beforeEach( () => { + stubs = { + consoleWarn: sandbox.stub( console, 'warn' ), + }; + } ); + + afterEach( () => { + sandbox.restore(); + } ); + describe( 'constructor()', () => { it( 'should be able to initialize replayer without deltas', () => { const doc = getDocument(); @@ -107,6 +122,7 @@ describe( 'DeltaReplayer', () => { return deltaReplayer.applyDeltas( 3 ).then( () => { expect( Array.from( doc.getRoot().getChildren() ).length ).to.equal( 2 ); expect( deltaReplayer.getDeltasToReplay().length ).to.equal( 0 ); + sinon.assert.calledWithExactly( stubs.consoleWarn, new Error( 'No deltas to replay' ) ); } ); } ); } ); From 204ea68c709d1932bd13742e4ce08105e239f6d1 Mon Sep 17 00:00:00 2001 From: Maciej Bukowski Date: Mon, 3 Apr 2017 14:54:24 +0200 Subject: [PATCH 11/14] Fixed coverage. --- src/dev-utils/deltareplayer.js | 24 +++++++++++++----------- src/dev-utils/enableenginedebug.js | 2 +- tests/dev-utils/deltareplayer.js | 14 ++++++++++---- 3 files changed, 24 insertions(+), 16 deletions(-) diff --git a/src/dev-utils/deltareplayer.js b/src/dev-utils/deltareplayer.js index f54bcb297..23bc174d1 100644 --- a/src/dev-utils/deltareplayer.js +++ b/src/dev-utils/deltareplayer.js @@ -49,22 +49,24 @@ export default class DeltaReplayer { * Applies all deltas with delay between actions. * * @param {Number} timeInterval Time between applying deltas. - * @param {Function} [cb] Callback. + * @returns {Promise} */ - play( timeInterval = 1000, cb = () => {} ) { + play( timeInterval = 1000 ) { const deltaReplayer = this; - play(); + return new Promise( ( res ) => { + play(); - function play() { - if ( deltaReplayer._deltasToReplay.length === 0 ) { - return cb(); - } + function play() { + if ( deltaReplayer._deltasToReplay.length === 0 ) { + return res(); + } - deltaReplayer.applyNextDelta().then( () => { - setTimeout( play, timeInterval ); - }, cb ); - } + deltaReplayer.applyNextDelta().then( () => { + setTimeout( play, timeInterval ); + }, res ); + } + } ); } /** diff --git a/src/dev-utils/enableenginedebug.js b/src/dev-utils/enableenginedebug.js index 6cbb63082..344665ded 100644 --- a/src/dev-utils/enableenginedebug.js +++ b/src/dev-utils/enableenginedebug.js @@ -485,7 +485,7 @@ function enableDocumentTools() { }; ModelDocument.prototype.getAppliedDeltas = function() { - return ( this._appliedDeltas || [] ).map( JSON.stringify ).join( LOG_SEPARATOR ); + return this._appliedDeltas.map( JSON.stringify ).join( LOG_SEPARATOR ); }; ModelDocument.prototype.createReplayer = function( stringifiedDeltas ) { diff --git a/tests/dev-utils/deltareplayer.js b/tests/dev-utils/deltareplayer.js index 4aefbf6d8..a56d3f67d 100644 --- a/tests/dev-utils/deltareplayer.js +++ b/tests/dev-utils/deltareplayer.js @@ -128,7 +128,7 @@ describe( 'DeltaReplayer', () => { } ); describe( 'play', () => { - it( 'should play deltas with time interval', ( done ) => { + it( 'should play deltas with time interval', () => { const doc = getDocument(); const stringifiedDeltas = [ getFirstDelta(), getSecondDelta() ] @@ -137,12 +137,18 @@ describe( 'DeltaReplayer', () => { const deltaReplayer = new DeltaReplayer( doc, '---', stringifiedDeltas ); - deltaReplayer.play( 0, () => { + return deltaReplayer.play( 0 ).then( () => { expect( deltaReplayer.getDeltasToReplay().length ).to.equal( 0 ); - - done(); } ); } ); + + it( 'should work with default time interval', () => { + const doc = getDocument(); + + const deltaReplayer = new DeltaReplayer( doc, '---', '' ); + + return deltaReplayer.play(); + } ); } ); } ); From 55197d0b0992ce954d333246db0ca3a146ae42c4 Mon Sep 17 00:00:00 2001 From: Szymon Cofalik Date: Tue, 4 Apr 2017 09:06:32 +0200 Subject: [PATCH 12/14] Docs: added missing methods descriptions. Tests: added "()" in tests names. --- src/dev-utils/deltareplayer.js | 17 +++++++++++++++-- tests/dev-utils/deltareplayer.js | 6 +++--- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/src/dev-utils/deltareplayer.js b/src/dev-utils/deltareplayer.js index 23bc174d1..0cc7574a7 100644 --- a/src/dev-utils/deltareplayer.js +++ b/src/dev-utils/deltareplayer.js @@ -16,7 +16,7 @@ import DeltaFactory from '../model/delta/deltafactory'; */ export default class DeltaReplayer { /** - * @param {module:engine/model/document~Document} document. + * @param {module:engine/model/document~Document} document Document to reply deltas on. * @param {String} logSeparator Separator between deltas. * @param {String} stringifiedDeltas Deltas to replay. */ @@ -27,7 +27,9 @@ export default class DeltaReplayer { } /** - * @param {String} stringifiedDeltas Deltas to replay. + * Parses given string containing stringified deltas and sets parsed deltas as deltas to reply. + * + * @param {String} stringifiedDeltas Stringified deltas to replay. */ setStringifiedDeltas( stringifiedDeltas ) { if ( stringifiedDeltas === '' ) { @@ -41,6 +43,11 @@ export default class DeltaReplayer { .map( stringifiedDelta => JSON.parse( stringifiedDelta ) ); } + /** + * Returns deltas to reply. + * + * @returns {Array.} + */ getDeltasToReplay() { return this._deltasToReplay; } @@ -70,6 +77,8 @@ export default class DeltaReplayer { } /** + * Applies `numberOfDeltas` deltas, beginning after the last applied delta (or first delta, if no deltas were applied). + * * @param {Number} numberOfDeltas Number of deltas to apply. * @returns {Promise} */ @@ -84,6 +93,8 @@ export default class DeltaReplayer { } /** + * Applies all deltas to replay at once. + * * @returns {Promise} */ applyAllDeltas() { @@ -93,6 +104,8 @@ export default class DeltaReplayer { } /** + * Applies the next delta to replay. + * * @returns {Promise} */ applyNextDelta() { diff --git a/tests/dev-utils/deltareplayer.js b/tests/dev-utils/deltareplayer.js index a56d3f67d..df5a3b95d 100644 --- a/tests/dev-utils/deltareplayer.js +++ b/tests/dev-utils/deltareplayer.js @@ -77,7 +77,7 @@ describe( 'DeltaReplayer', () => { } ); } ); - describe( 'applyAllDeltas', () => { + describe( 'applyAllDeltas()', () => { it( 'should apply all deltas on the document', () => { const doc = getDocument(); @@ -94,7 +94,7 @@ describe( 'DeltaReplayer', () => { } ); } ); - describe( 'applyDeltas', () => { + describe( 'applyDeltas()', () => { it( 'should apply certain number of deltas on the document', () => { const doc = getDocument(); @@ -127,7 +127,7 @@ describe( 'DeltaReplayer', () => { } ); } ); - describe( 'play', () => { + describe( 'play()', () => { it( 'should play deltas with time interval', () => { const doc = getDocument(); From e0145f2288c91b214775ab2796e4c99ab3ee9f43 Mon Sep 17 00:00:00 2001 From: Maciej Bukowski Date: Tue, 4 Apr 2017 10:49:15 +0200 Subject: [PATCH 13/14] Added hook for the `Document#applyOperation`. --- src/dev-utils/enableenginedebug.js | 43 +++++++++++++++++----------- tests/dev-utils/enableenginedebug.js | 4 +-- 2 files changed, 29 insertions(+), 18 deletions(-) diff --git a/src/dev-utils/enableenginedebug.js b/src/dev-utils/enableenginedebug.js index 344665ded..dea8575c7 100644 --- a/src/dev-utils/enableenginedebug.js +++ b/src/dev-utils/enableenginedebug.js @@ -111,6 +111,7 @@ export default function enableEngineDebug( logger ) { enableLoggingTools(); enableDocumentTools(); + enableReplayerTools(); } return DebugPlugin; @@ -455,6 +456,32 @@ function enableLoggingTools() { }; } +function enableReplayerTools() { + const _modelDocumentApplyOperation = ModelDocument.prototype.applyOperation; + + ModelDocument.prototype.applyOperation = function( operation ) { + if ( !this._lastDelta ) { + this._appliedDeltas = []; + this._lastDelta = operation.delta; + } else if ( this._lastDelta !== operation.delta ) { + this._appliedDeltas.push( this._lastDelta.toJSON() ); + this._lastDelta = operation.delta; + } + + _modelDocumentApplyOperation.call( this, operation ); + }; + + ModelDocument.prototype.getAppliedDeltas = function() { + const appliedDeltas = this._appliedDeltas.concat( this._lastDelta.toJSON() ); + + return appliedDeltas.map( JSON.stringify ).join( LOG_SEPARATOR ); + }; + + ModelDocument.prototype.createReplayer = function( stringifiedDeltas ) { + return new DeltaReplayer( this, LOG_SEPARATOR, stringifiedDeltas ); + }; +} + function enableDocumentTools() { const _modelDocumentApplyOperation = ModelDocument.prototype.applyOperation; @@ -476,22 +503,6 @@ function enableDocumentTools() { logDocument( this, version ); }; - ModelDocument.prototype.initializeDebugging = function() { - this._appliedDeltas = []; - }; - - ModelDocument.prototype.addAppliedDelta = function( delta ) { - this._appliedDeltas.push( delta.toJSON() ); - }; - - ModelDocument.prototype.getAppliedDeltas = function() { - return this._appliedDeltas.map( JSON.stringify ).join( LOG_SEPARATOR ); - }; - - ModelDocument.prototype.createReplayer = function( stringifiedDeltas ) { - return new DeltaReplayer( this, LOG_SEPARATOR, stringifiedDeltas ); - }; - ViewDocument.prototype.log = function( version ) { logDocument( this, version ); }; diff --git a/tests/dev-utils/enableenginedebug.js b/tests/dev-utils/enableenginedebug.js index ed3b54880..2c07db0a3 100644 --- a/tests/dev-utils/enableenginedebug.js +++ b/tests/dev-utils/enableenginedebug.js @@ -730,8 +730,8 @@ describe( 'debug tools', () => { delta.addOperation( move ); delta.addOperation( remove ); - modelDoc.initializeDebugging(); - modelDoc.addAppliedDelta( delta ); + modelDoc.applyOperation( move ); + modelDoc.applyOperation( remove ); const stringifiedDeltas = modelDoc.getAppliedDeltas(); From dd3015b82786b648e0c77f949c79d8a0cace4ca3 Mon Sep 17 00:00:00 2001 From: Szymon Cofalik Date: Tue, 4 Apr 2017 13:21:35 +0200 Subject: [PATCH 14/14] Changed: `model.Document#getAppliedDeltas` should return empty string if no deltas were applied. --- src/dev-utils/enableenginedebug.js | 9 +++++++-- tests/dev-utils/enableenginedebug.js | 2 ++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/dev-utils/enableenginedebug.js b/src/dev-utils/enableenginedebug.js index dea8575c7..17e2aa02f 100644 --- a/src/dev-utils/enableenginedebug.js +++ b/src/dev-utils/enableenginedebug.js @@ -462,16 +462,21 @@ function enableReplayerTools() { ModelDocument.prototype.applyOperation = function( operation ) { if ( !this._lastDelta ) { this._appliedDeltas = []; - this._lastDelta = operation.delta; } else if ( this._lastDelta !== operation.delta ) { this._appliedDeltas.push( this._lastDelta.toJSON() ); - this._lastDelta = operation.delta; } + this._lastDelta = operation.delta; + _modelDocumentApplyOperation.call( this, operation ); }; ModelDocument.prototype.getAppliedDeltas = function() { + // No deltas has been applied yet, return empty string. + if ( !this._lastDelta ) { + return ''; + } + const appliedDeltas = this._appliedDeltas.concat( this._lastDelta.toJSON() ); return appliedDeltas.map( JSON.stringify ).join( LOG_SEPARATOR ); diff --git a/tests/dev-utils/enableenginedebug.js b/tests/dev-utils/enableenginedebug.js index 2c07db0a3..93ca306db 100644 --- a/tests/dev-utils/enableenginedebug.js +++ b/tests/dev-utils/enableenginedebug.js @@ -717,6 +717,8 @@ describe( 'debug tools', () => { it( 'getAppliedDeltas()', () => { const modelDoc = new ModelDocument(); + expect( modelDoc.getAppliedDeltas() ).to.equal( '' ); + const otherRoot = modelDoc.createRoot( '$root', 'otherRoot' ); const firstEle = new ModelElement( 'paragraph' ); const removedEle = new ModelElement( 'paragraph', null, [ new ModelText( 'foo' ) ] );