浏览代码

Added: OT for RenameOperation.

Szymon Cofalik 9 年之前
父节点
当前提交
3e2a4f7e60

+ 53 - 0
packages/ckeditor5-engine/src/model/operation/transform.js

@@ -6,6 +6,7 @@
 import InsertOperation from './insertoperation.js';
 import AttributeOperation from './attributeoperation.js';
 import RootAttributeOperation from './rootattributeoperation.js';
+import RenameOperation from './renameoperation.js';
 import MoveOperation from './moveoperation.js';
 import RemoveOperation from './removeoperation.js';
 import NoOperation from './nooperation.js';
@@ -72,6 +73,8 @@ const ot = {
 
 		RootAttributeOperation: doNotUpdate,
 
+		RenameOperation: doNotUpdate,
+
 		// Transforms InsertOperation `a` by MoveOperation `b`. Accepts a flag stating whether `a` is more important
 		// than `b` when it comes to resolving conflicts. Returns results as an array of operations.
 		MoveOperation( a, b, isStrong ) {
@@ -134,6 +137,8 @@ const ot = {
 
 		RootAttributeOperation: doNotUpdate,
 
+		RenameOperation: doNotUpdate,
+
 		// Transforms AttributeOperation `a` by MoveOperation `b`. Returns results as an array of operations.
 		MoveOperation( a, b ) {
 			// Convert MoveOperation properties into a range.
@@ -212,9 +217,51 @@ const ot = {
 			return [ a.clone() ];
 		},
 
+		RenameOperation: doNotUpdate,
+
 		MoveOperation: doNotUpdate
 	},
 
+	RenameOperation: {
+		// Transforms RenameOperation `a` by InsertOperation `b`. Returns results as an array of operations.
+		InsertOperation( a, b ) {
+			// Clone the operation, we don't want to alter the original operation.
+			const clone = a.clone();
+
+			// Transform this operation's position.
+			clone.position = clone.position._getTransformedByInsertion( b.position, b.nodes.maxOffset, true );
+
+			return [ clone ];
+		},
+
+		AttributeOperation: doNotUpdate,
+
+		RootAttributeOperation: doNotUpdate,
+
+		// Transforms RenameOperation `a` by RenameOperation `b`. Accepts a flag stating whether `a` is more important
+		// than `b` when it comes to resolving conflicts. Returns results as an array of operations.
+		RenameOperation( a, b, isStrong ) {
+			// Clone the operation, we don't want to alter the original operation.
+			const clone = a.clone();
+
+			if ( a.position.isEqual( b.position ) && !isStrong ) {
+				return [ new NoOperation( a.baseVersion ) ];
+			}
+
+			return [ clone ];
+		},
+
+		// Transforms RenameOperation `a` by MoveOperation `b`. Returns results as an array of operations.
+		MoveOperation( a, b ) {
+			const clone = a.clone();
+			const isSticky = clone.position.isEqual( b.sourcePosition );
+
+			clone.position = clone.position._getTransformedByMove( b.sourcePosition, b.targetPosition, b.howMany, true, isSticky );
+
+			return [ clone ];
+		}
+	},
+
 	MoveOperation: {
 		// Transforms MoveOperation `a` by InsertOperation `b`. Accepts a flag stating whether `a` is more important
 		// than `b` when it comes to resolving conflicts. Returns results as an array of operations.
@@ -241,6 +288,8 @@ const ot = {
 
 		RootAttributeOperation: doNotUpdate,
 
+		RenameOperation: doNotUpdate,
+
 		// Transforms MoveOperation `a` by MoveOperation `b`. Accepts a flag stating whether `a` is more important
 		// than `b` when it comes to resolving conflicts. Returns results as an array of operations.
 		MoveOperation( a, b, isStrong ) {
@@ -379,6 +428,8 @@ function transform( a, b, isStrong ) {
 		group = ot.AttributeOperation;
 	} else if ( a instanceof RootAttributeOperation ) {
 		group = ot.RootAttributeOperation;
+	} else if ( a instanceof RenameOperation ) {
+		group = ot.RenameOperation;
 	} else if ( a instanceof MoveOperation ) {
 		group = ot.MoveOperation;
 	} else {
@@ -392,6 +443,8 @@ function transform( a, b, isStrong ) {
 			algorithm = group.AttributeOperation;
 		} else if ( b instanceof RootAttributeOperation ) {
 			algorithm = group.RootAttributeOperation;
+		} else if ( b instanceof RenameOperation ) {
+			algorithm = group.RenameOperation;
 		} else if ( b instanceof MoveOperation ) {
 			algorithm = group.MoveOperation;
 		} else {

+ 319 - 0
packages/ckeditor5-engine/tests/model/operation/transform.js

@@ -18,6 +18,7 @@ import AttributeOperation from '/ckeditor5/engine/model/operation/attributeopera
 import RootAttributeOperation from '/ckeditor5/engine/model/operation/rootattributeoperation.js';
 import MoveOperation from '/ckeditor5/engine/model/operation/moveoperation.js';
 import RemoveOperation from '/ckeditor5/engine/model/operation/removeoperation.js';
+import RenameOperation from '/ckeditor5/engine/model/operation/renameoperation.js';
 import NoOperation from '/ckeditor5/engine/model/operation/nooperation.js';
 
 describe( 'transform', () => {
@@ -403,6 +404,17 @@ describe( 'transform', () => {
 				expectOperation( transOp[ 0 ], expected );
 			} );
 		} );
+
+		describe( 'by RenameOperation', () => {
+			it( 'no position update', () => {
+				let transformBy = new RenameOperation( new Position( root, [ 0, 2, 0 ] ), 'oldName', 'newName', baseVersion );
+
+				let transOp = transform( op, transformBy );
+
+				expect( transOp.length ).to.equal( 1 );
+				expectOperation( transOp[ 0 ], expected );
+			} );
+		} );
 	} );
 
 	describe( 'AttributeOperation', () => {
@@ -1087,6 +1099,17 @@ describe( 'transform', () => {
 					expectOperation( transOp[ 0 ], expected );
 				} );
 			} );
+
+			describe( 'by RenameOperation', () => {
+				it( 'no position update', () => {
+					let transformBy = new RenameOperation( new Position( root, [ 1, 0 ] ), 'oldName', 'newName', baseVersion );
+
+					let transOp = transform( op, transformBy );
+
+					expect( transOp.length ).to.equal( 1 );
+					expectOperation( transOp[ 0 ], expected );
+				} );
+			} );
 		} );
 
 		// Some extra cases for a AttributeOperation that operates on single tree level range.
@@ -1544,6 +1567,17 @@ describe( 'transform', () => {
 				expectOperation( transOp[ 0 ], expected );
 			} );
 		} );
+
+		describe( 'by RenameOperation', () => {
+			it( 'no position update', () => {
+				let transformBy = new RenameOperation( new Position( root, [ 0 ] ), 'oldName', 'newName', baseVersion );
+
+				let transOp = transform( op, transformBy );
+
+				expect( transOp.length ).to.equal( 1 );
+				expectOperation( transOp[ 0 ], expected );
+			} );
+		} );
 	} );
 
 	describe( 'MoveOperation', () => {
@@ -2647,6 +2681,17 @@ describe( 'transform', () => {
 				expectOperation( transOp[ 0 ], expected );
 			} );
 		} );
+
+		describe( 'by RenameOperation', () => {
+			it( 'no position update', () => {
+				let transformBy = new RenameOperation( new Position( root, [ 2, 2, 4 ] ), 'oldName', 'newName', baseVersion );
+
+				let transOp = transform( op, transformBy );
+
+				expect( transOp.length ).to.equal( 1 );
+				expectOperation( transOp[ 0 ], expected );
+			} );
+		} );
 	} );
 
 	describe( 'NoOperation', () => {
@@ -2737,5 +2782,279 @@ describe( 'transform', () => {
 				expectOperation( transOp[ 0 ], expected );
 			} );
 		} );
+
+		describe( 'by RenameOperation', () => {
+			it( 'no position update', () => {
+				let transformBy = new RenameOperation( new Position( root, [ 0, 2, 0 ] ), 'oldName', 'newName', baseVersion );
+
+				let transOp = transform( op, transformBy );
+
+				expect( transOp.length ).to.equal( 1 );
+				expectOperation( transOp[ 0 ], expected );
+			} );
+		} );
+	} );
+
+	describe( 'RenameOperation', () => {
+		beforeEach( () => {
+			const position = new Position( root, [ 0, 2, 2 ] );
+
+			op = new RenameOperation( position, 'oldName', 'newName', baseVersion );
+
+			expected = {
+				position: position,
+				oldName: 'oldName',
+				newName: 'newName',
+				baseVersion: baseVersion + 1
+			};
+		} );
+
+		describe( 'by InsertOperation', () => {
+			it( 'target before renamed element: offset update', () => {
+				let transformBy = new InsertOperation(
+					new Position( root, [ 0, 2, 1 ] ),
+					[ nodeA, nodeB ],
+					baseVersion
+				);
+
+				let transOp = transform( op, transformBy );
+
+				expect( transOp.length ).to.equal( 1 );
+
+				expected.position.offset = 4;
+
+				expectOperation( transOp[ 0 ], expected );
+			} );
+
+			it( 'target after renamed element: no change', () => {
+				let transformBy = new InsertOperation(
+					new Position( root, [ 0, 2, 3 ] ),
+					[ nodeA, nodeB ],
+					baseVersion
+				);
+
+				let transOp = transform( op, transformBy );
+
+				expect( transOp.length ).to.equal( 1 );
+				expectOperation( transOp[ 0 ], expected );
+			} );
+
+			it( 'target before a node on path to renamed element: path update', () => {
+				let transformBy = new InsertOperation(
+					new Position( root, [ 0, 1 ] ),
+					[ nodeA, nodeB ],
+					baseVersion
+				);
+
+				let transOp = transform( op, transformBy );
+
+				expect( transOp.length ).to.equal( 1 );
+
+				expected.position.path = [ 0, 4, 2 ];
+
+				expectOperation( transOp[ 0 ], expected );
+			} );
+
+			it( 'target after a node on path to renamed element: no change', () => {
+				let transformBy = new InsertOperation(
+					new Position( root, [ 0, 3 ] ),
+					[ nodeA, nodeB ],
+					baseVersion
+				);
+
+				let transOp = transform( op, transformBy );
+
+				expect( transOp.length ).to.equal( 1 );
+				expectOperation( transOp[ 0 ], expected );
+			} );
+		} );
+
+		describe( 'by AttributeOperation', () => {
+			it( 'no operation update', () => {
+				let transformBy = new AttributeOperation(
+					new Range(
+						new Position( root, [ 0, 2, 1 ] ),
+						new Position( root, [ 1, 3 ] )
+					),
+					'foo',
+					'bar',
+					'xyz',
+					baseVersion
+				);
+
+				let transOp = transform( op, transformBy );
+
+				expect( transOp.length ).to.equal( 1 );
+				expectOperation( transOp[ 0 ], expected );
+			} );
+		} );
+
+		describe( 'by RootAttributeOperation', () => {
+			it( 'no operation update', () => {
+				let transformBy = new RootAttributeOperation(
+					root,
+					'foo',
+					null,
+					'bar',
+					baseVersion
+				);
+
+				let transOp = transform( op, transformBy );
+
+				expect( transOp.length ).to.equal( 1 );
+				expectOperation( transOp[ 0 ], expected );
+			} );
+		} );
+
+		describe( 'by RenameOperation', () => {
+			it( 'different element: no change', () => {
+				let transformBy = new RenameOperation(
+					new Position( root, [ 0, 2, 1 ] ),
+					'foo',
+					'bar',
+					baseVersion
+				);
+
+				let transOp = transform( op, transformBy );
+
+				expect( transOp.length ).to.equal( 1 );
+				expectOperation( transOp[ 0 ], expected );
+			} );
+
+			it( 'same element and is important: convert to NoOperation', () => {
+				let transformBy = new RenameOperation(
+					new Position( root, [ 0, 2, 2 ] ),
+					'oldName',
+					'otherName',
+					baseVersion
+				);
+
+				let transOp = transform( op, transformBy, false );
+
+				expect( transOp.length ).to.equal( 1 );
+				expectOperation( transOp[ 0 ], {
+					type: NoOperation,
+					baseVersion: baseVersion + 1
+				} );
+			} );
+
+			it( 'same element and is not important: no change', () => {
+				let transformBy = new RenameOperation(
+					new Position( root, [ 0, 2, 2 ] ),
+					'oldName',
+					'otherName',
+					baseVersion
+				);
+
+				let transOp = transform( op, transformBy, true );
+
+				expect( transOp.length ).to.equal( 1 );
+				expectOperation( transOp[ 0 ], expected );
+			} );
+		} );
+
+		describe( 'by MoveOperation', () => {
+			it( 'moved range before renamed element: update offset', () => {
+				let transformBy = new MoveOperation(
+					new Position( root, [ 0, 2, 0 ] ),
+					2,
+					new Position( root, [ 2, 5 ] ),
+					baseVersion
+				);
+
+				let transOp = transform( op, transformBy );
+
+				expect( transOp.length ).to.equal( 1 );
+
+				expected.position.offset = 0;
+
+				expectOperation( transOp[ 0 ], expected );
+			} );
+
+			it( 'moved range before an element on path to renamed element: update path', () => {
+				let transformBy = new MoveOperation(
+					new Position( root, [ 0, 0 ] ),
+					2,
+					new Position( root, [ 2, 5 ] ),
+					baseVersion
+				);
+
+				let transOp = transform( op, transformBy );
+
+				expect( transOp.length ).to.equal( 1 );
+
+				expected.position.path = [ 0, 0, 2 ];
+
+				expectOperation( transOp[ 0 ], expected );
+			} );
+
+			it( 'moved range contains renamed element: update path', () => {
+				let transformBy = new MoveOperation(
+					new Position( root, [ 0, 2, 1 ] ),
+					3,
+					new Position( root, [ 2, 5 ] ),
+					baseVersion
+				);
+
+				let transOp = transform( op, transformBy );
+
+				expect( transOp.length ).to.equal( 1 );
+
+				expected.position.path = [ 2, 6 ];
+
+				expectOperation( transOp[ 0 ], expected );
+			} );
+
+			it( 'moved range contains renamed element parent: updated path', () => {
+				let transformBy = new MoveOperation(
+					new Position( root, [ 0, 1 ] ),
+					3,
+					new Position( root, [ 2, 5 ] ),
+					baseVersion
+				);
+
+				let transOp = transform( op, transformBy );
+
+				expect( transOp.length ).to.equal( 1 );
+
+				expected.position.path = [ 2, 6, 2 ];
+
+				expectOperation( transOp[ 0 ], expected );
+			} );
+
+			it( 'move target before renamed element: update offset', () => {
+				let transformBy = new MoveOperation(
+					new Position( root, [ 2, 5 ] ),
+					2,
+					new Position( root, [ 0, 2, 1 ] ),
+					baseVersion
+				);
+
+				let transOp = transform( op, transformBy );
+
+				expect( transOp.length ).to.equal( 1 );
+
+				expected.position.offset = 4;
+
+				expectOperation( transOp[ 0 ], expected );
+			} );
+
+			it( 'move target before an element on path to renamed element: update path', () => {
+				let transformBy = new MoveOperation(
+					new Position( root, [ 0, 2, 0 ] ),
+					2,
+					new Position( root, [ 2, 5 ] ),
+					baseVersion
+				);
+
+				let transOp = transform( op, transformBy );
+
+				expect( transOp.length ).to.equal( 1 );
+
+				expected.position.offset = 0;
+
+				expectOperation( transOp[ 0 ], expected );
+			} );
+		} );
 	} );
 } );