Procházet zdrojové kódy

Added: Introduced Delta#history (debug tools).

Szymon Cofalik před 8 roky
rodič
revize
e239cbe9f6

+ 34 - 1
packages/ckeditor5-engine/src/dev-utils/enableenginedebug.js

@@ -9,6 +9,8 @@
 
 /* global console */
 
+import DeltaReplayer from './deltareplayer';
+
 import ModelPosition from '../model/position';
 import ModelRange from '../model/range';
 import ModelText from '../model/text';
@@ -33,6 +35,7 @@ import RenameDelta from '../model/delta/renamedelta';
 import SplitDelta from '../model/delta/splitdelta';
 import UnwrapDelta from '../model/delta/unwrapdelta';
 import WrapDelta from '../model/delta/wrapdelta';
+import deltaTransform from '../model/delta/transform';
 import ModelDocument from '../model/document';
 import ModelDocumentFragment from '../model/documentfragment';
 import ModelRootElement from '../model/rootelement';
@@ -46,7 +49,7 @@ import ViewDocumentFragment from '../view/documentfragment';
 import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
 import Editor from '@ckeditor/ckeditor5-core/src/editor/editor';
 
-import DeltaReplayer from './deltareplayer';
+import clone from '@ckeditor/ckeditor5-utils/src/lib/lodash/clone';
 
 const treeDump = Symbol( '_treeDump' );
 
@@ -317,6 +320,36 @@ function enableLoggingTools() {
 		}
 	};
 
+	Delta.prototype.saveHistory = function( before, transformedBy, wasImportant, resultIndex, resultsTotal ) {
+		const history = before.history ? before.history : [];
+
+		const beforeClone = clone( before );
+		delete beforeClone.history;
+
+		const transformedByClone = clone( transformedBy );
+		delete transformedByClone.history;
+
+		this.history = history.concat( {
+			before: JSON.stringify( beforeClone ),
+			transformedBy: JSON.stringify( transformedByClone ),
+			wasImportant: wasImportant,
+			resultIndex: resultIndex,
+			resultsTotal: resultsTotal
+		} );
+	};
+
+	const _deltaTransformTransform = deltaTransform.transform;
+
+	deltaTransform.transform = function( a, b, isAMoreImportantThanB ) {
+		const results = _deltaTransformTransform( a, b, isAMoreImportantThanB );
+
+		for ( let i = 0; i < results.length; i++ ) {
+			results[ i ].saveHistory( a, b, isAMoreImportantThanB, i, results.length );
+		}
+
+		return results;
+	};
+
 	AttributeDelta.prototype.toString = function() {
 		return getClassName( this ) + `( ${ this.baseVersion } ): ` +
 			`"${ this.key }": -> ${ JSON.stringify( this.value ) }, ${ this.range }, ${ this.operations.length } ops`;

+ 7 - 0
packages/ckeditor5-engine/src/model/delta/deltafactory.js

@@ -51,6 +51,13 @@ export default class DeltaFactory {
 			delta.addOperation( OperationFactory.fromJSON( operation, doc ) );
 		}
 
+		// Rewrite all other properties.
+		for ( let prop in json ) {
+			if ( prop != '__className' && delta[ prop ] === undefined ) {
+				delta[ prop ] = json[ prop ];
+			}
+		}
+
 		return delta;
 	}
 

+ 157 - 0
packages/ckeditor5-engine/tests/dev-utils/enableenginedebug.js

@@ -20,6 +20,7 @@ import NoOperation from '../../src/model/operation/nooperation';
 import RenameOperation from '../../src/model/operation/renameoperation';
 import RootAttributeOperation from '../../src/model/operation/rootattributeoperation';
 import RemoveOperation from '../../src/model/operation/removeoperation';
+import DeltaFactory from '../../src/model/delta/deltafactory';
 import Delta from '../../src/model/delta/delta';
 import AttributeDelta from '../../src/model/delta/attributedelta';
 import { RootAttributeDelta } from '../../src/model/delta/attributedelta';
@@ -31,6 +32,7 @@ import RenameDelta from '../../src/model/delta/renamedelta';
 import SplitDelta from '../../src/model/delta/splitdelta';
 import UnwrapDelta from '../../src/model/delta/unwrapdelta';
 import WrapDelta from '../../src/model/delta/wrapdelta';
+import deltaTransform from '../../src/model/delta/transform';
 import ModelDocument from '../../src/model/document';
 import ModelDocumentFragment from '../../src/model/documentfragment';
 
@@ -764,6 +766,161 @@ describe( 'debug tools', () => {
 		} );
 	} );
 
+	describe( 'should provide means for saving delta history transformation', () => {
+		let document, root, otherRoot;
+
+		beforeEach( () => {
+			document = new ModelDocument();
+			root = document.createRoot();
+			otherRoot = document.createRoot( 'other', 'other' );
+		} );
+
+		it( 'Delta#saveHistory()', () => {
+			const insertDeltaA = new InsertDelta();
+			const insertOpA = new InsertOperation( ModelPosition.createAt( root, 0 ), new ModelText( 'a' ), 0 );
+			insertDeltaA.addOperation( insertOpA );
+
+			const insertDeltaB = new InsertDelta();
+			const insertOpB = new InsertOperation( ModelPosition.createAt( root, 0 ), new ModelText( 'a' ), 0 );
+			insertDeltaB.addOperation( insertOpB );
+
+			const insertDeltaFinalA = new InsertDelta();
+			const insertOpFinalA = new InsertOperation( ModelPosition.createAt( root, 0 ), new ModelText( 'a' ), 1 );
+			insertDeltaFinalA.addOperation( insertOpFinalA );
+			const insertDeltaFinalAJsonWithoutHistory = JSON.stringify( insertDeltaFinalA );
+			insertDeltaFinalA.saveHistory( insertDeltaA, insertDeltaB, true, 0, 1 );
+
+			const insertDeltaC = new InsertDelta();
+			const insertOpC = new InsertOperation( ModelPosition.createAt( root, 0 ), new ModelText( 'a' ), 1 );
+			insertDeltaC.addOperation( insertOpC );
+
+			const insertDeltaFinalB = new InsertDelta();
+			const insertOpFinalB = new InsertOperation( ModelPosition.createAt( root, 0 ), new ModelText( 'a' ), 2 );
+			insertDeltaFinalB.addOperation( insertOpFinalB );
+			insertDeltaFinalB.saveHistory( insertDeltaFinalA, insertDeltaC, false, 1, 3 );
+
+			expect( insertDeltaA.history ).to.be.undefined;
+			expect( insertDeltaB.history ).to.be.undefined;
+
+			expect( insertDeltaFinalA.history ).not.to.be.undefined;
+			expect( insertDeltaFinalA.history.length ).to.equal( 1 );
+			expect( insertDeltaFinalA.history[ 0 ] ).to.deep.equal( {
+				before: JSON.stringify( insertDeltaA ),
+				transformedBy: JSON.stringify( insertDeltaB ),
+				wasImportant: true,
+				resultIndex: 0,
+				resultsTotal: 1
+			} );
+
+			expect( insertDeltaFinalB.history ).not.to.be.undefined;
+			expect( insertDeltaFinalB.history.length ).to.equal( 2 );
+			expect( insertDeltaFinalB.history[ 0 ] ).to.deep.equal( {
+				before: JSON.stringify( insertDeltaA ),
+				transformedBy: JSON.stringify( insertDeltaB ),
+				wasImportant: true,
+				resultIndex: 0,
+				resultsTotal: 1
+			} );
+			expect( insertDeltaFinalB.history[ 1 ] ).to.deep.equal( {
+				before: insertDeltaFinalAJsonWithoutHistory,
+				transformedBy: JSON.stringify( insertDeltaC ),
+				wasImportant: false,
+				resultIndex: 1,
+				resultsTotal: 3
+			} );
+		} );
+
+		it( 'save delta history on deltaTransform.transform', () => {
+			const deltaA = new MoveDelta();
+			const opA = new MoveOperation( ModelPosition.createAt( root, 4 ), 4, ModelPosition.createAt( otherRoot, 4 ), 0 );
+			deltaA.addOperation( opA );
+
+			const deltaB = new InsertDelta();
+			const opB = new InsertOperation( ModelPosition.createAt( root, 0 ), new ModelText( 'a' ), 0 );
+			deltaB.addOperation( opB );
+
+			const deltaC = new MoveDelta();
+			const opC = new MoveOperation( ModelPosition.createAt( root, 4 ), 2, ModelPosition.createAt( root, 0 ), 1 );
+			deltaC.addOperation( opC );
+
+			let result = deltaTransform.transform( deltaA, deltaB, false );
+
+			expect( result[ 0 ].history ).not.to.be.undefined;
+			expect( result[ 0 ].history.length ).to.equal( 1 );
+
+			expect( result[ 0 ].history[ 0 ] ).to.deep.equal( {
+				before: JSON.stringify( deltaA ),
+				transformedBy: JSON.stringify( deltaB ),
+				wasImportant: false,
+				resultIndex: 0,
+				resultsTotal: 1
+			} );
+
+			const firstResultWithoutHistory = result[ 0 ].clone();
+			delete firstResultWithoutHistory.history;
+
+			result = deltaTransform.transform( result[ 0 ], deltaC, true );
+			expect( result[ 0 ].history ).not.to.be.undefined;
+			expect( result[ 0 ].history.length ).to.equal( 2 );
+
+			expect( result[ 0 ].history[ 0 ] ).to.deep.equal( {
+				before: JSON.stringify( deltaA ),
+				transformedBy: JSON.stringify( deltaB ),
+				wasImportant: false,
+				resultIndex: 0,
+				resultsTotal: 1
+			} );
+
+			expect( result[ 0 ].history[ 1 ] ).to.deep.equal( {
+				before: JSON.stringify( firstResultWithoutHistory ),
+				transformedBy: JSON.stringify( deltaC ),
+				wasImportant: true,
+				resultIndex: 0,
+				resultsTotal: 1
+			} );
+		} );
+
+		it( 'recreate delta using history', () => {
+			const deltaA = new MoveDelta();
+			const opA = new MoveOperation( ModelPosition.createAt( root, 4 ), 4, ModelPosition.createAt( otherRoot, 4 ), 0 );
+			deltaA.addOperation( opA );
+
+			const deltaB = new InsertDelta();
+			const opB = new InsertOperation( ModelPosition.createAt( root, 0 ), new ModelText( 'a' ), 0 );
+			deltaB.addOperation( opB );
+
+			const deltaC = new MoveDelta();
+			const opC = new MoveOperation( ModelPosition.createAt( root, 4 ), 2, ModelPosition.createAt( root, 0 ), 1 );
+			deltaC.addOperation( opC );
+
+			let original = deltaTransform.transform( deltaA, deltaB, false );
+			original = deltaTransform.transform( original[ 0 ], deltaC, true )[ 0 ];
+
+			const history = original.history;
+
+			let recreated = deltaTransform.transform(
+				DeltaFactory.fromJSON( JSON.parse( history[ 0 ].before ), document ),
+				DeltaFactory.fromJSON( JSON.parse( history[ 0 ].transformedBy ), document ),
+				history[ 0 ].wasImportant
+			);
+
+			recreated = recreated[ history[ 0 ].resultIndex ];
+
+			recreated = deltaTransform.transform(
+				recreated,
+				DeltaFactory.fromJSON( JSON.parse( history[ 1 ].transformedBy ), document ),
+				history[ 1 ].wasImportant
+			);
+
+			recreated = recreated[ history[ 1 ].resultIndex ];
+
+			delete original.history;
+			delete recreated.history;
+
+			expect( JSON.stringify( recreated ) ).to.equal( JSON.stringify( original ) );
+		} );
+	} );
+
 	function expectLog( expectedLogMsg ) {
 		expect( log.calledWithExactly( expectedLogMsg ) ).to.be.true;
 		log.reset();